| New file |
| | |
| | | import { request, type ApiResponse } from './request' |
| | | export interface TodoDto { id:number; content:string; ownerId:number; owner:string; status:'待处理'|'已处理'; creator:string; createdAt:number; operatedAt:number; handleRemark:string } |
| | | export function listTodos(){ return request<ApiResponse & {rows:TodoDto[]}>('/system/todo/list').then(r=>r.rows || []) } |
| | | export function createTodo(body:{content:string;ownerId:number;ownerName:string}){ return request<ApiResponse & {data:TodoDto}>('/system/todo',{method:'POST',body:JSON.stringify(body)}).then(r=>r.data) } |
| | | export function handleTodo(id:number,remark:string){ return request<ApiResponse>(`/system/todo/${id}/handle`,{method:'PUT',body:JSON.stringify({remark})}) } |
| | | export function removeTodo(id:number){ return request<ApiResponse>(`/system/todo/${id}`,{method:'DELETE'}) } |
| | |
| | | import { ref } from 'vue' |
| | | |
| | | import { listTodos, createTodo as createTodoApi, handleTodo as handleTodoApi, removeTodo as removeTodoApi } from '@/api/todos' |
| | | import { formatBusinessTime } from '@/utils/businessTime' |
| | | export type TodoStatus = '待处理' | '已处理' |
| | | |
| | | export interface TodoRecord { |
| | | id: number |
| | | content: string |
| | | owner: string |
| | | status: TodoStatus |
| | | creator: string |
| | | createdAt: string |
| | | operatedAt: string |
| | | handleRemark: string |
| | | } |
| | | |
| | | export interface TodoPayload { |
| | | content: string |
| | | owner: string |
| | | creator: string |
| | | } |
| | | |
| | | const STORAGE_KEY = 'battery_detection_todos_v1' |
| | | |
| | | const seedRecords: TodoRecord[] = [ |
| | | { id: 7012, content: '复核高温循环测试的壳体温度异常', owner: '张工', status: '待处理', creator: 'Admin', createdAt: '2026-08-06 09:18', operatedAt: '2026-08-06 09:18', handleRemark: '' }, |
| | | { id: 7011, content: '确认检测柜 A-03 通道校准结果', owner: '李工', status: '待处理', creator: 'Admin', createdAt: '2026-08-06 08:52', operatedAt: '2026-08-06 08:52', handleRemark: '' }, |
| | | { id: 7010, content: '补充 SP2 化成报表测试方法说明', owner: '王工', status: '已处理', creator: '李工', createdAt: '2026-08-05 16:44', operatedAt: '2026-08-06 08:36', handleRemark: '测试方法已补充并提交复核。' }, |
| | | { id: 7009, content: '复核低温充放电测试修订数据', owner: '周工', status: '待处理', creator: '王工', createdAt: '2026-08-05 15:20', operatedAt: '2026-08-05 15:20', handleRemark: '' }, |
| | | { id: 7008, content: '处理采集器 COL-08 离线告警', owner: '陈工', status: '已处理', creator: 'Admin', createdAt: '2026-08-05 13:08', operatedAt: '2026-08-05 14:16', handleRemark: '重新连接通信线并恢复采集。' }, |
| | | { id: 7007, content: '确认容量一致性测试的复测样品', owner: '赵工', status: '待处理', creator: '李工', createdAt: '2026-08-05 11:42', operatedAt: '2026-08-05 11:42', handleRemark: '' }, |
| | | { id: 7006, content: '整理首充容量标定拼接报表', owner: '王工', status: '已处理', creator: 'Admin', createdAt: '2026-08-04 17:35', operatedAt: '2026-08-05 10:12', handleRemark: '拼接报表已保存。' }, |
| | | { id: 7005, content: '检查 3C 倍率放电测试温升数据', owner: '张工', status: '待处理', creator: 'Admin', createdAt: '2026-08-04 15:26', operatedAt: '2026-08-04 15:26', handleRemark: '' }, |
| | | { id: 7004, content: '更新高温 A 批电池组字段模板', owner: '李工', status: '已处理', creator: '周工', createdAt: '2026-08-04 11:10', operatedAt: '2026-08-04 14:28', handleRemark: '字段模板已更新。' }, |
| | | { id: 7003, content: '复核过充安全边界测试结论', owner: '陈工', status: '已处理', creator: 'Admin', createdAt: '2026-08-03 16:48', operatedAt: '2026-08-04 09:05', handleRemark: '保护响应符合预期。' }, |
| | | { id: 7002, content: '补录低温启动测试环境参数', owner: '赵工', status: '待处理', creator: '王工', createdAt: '2026-08-03 13:24', operatedAt: '2026-08-03 13:24', handleRemark: '' }, |
| | | { id: 7001, content: '归档七月检测柜维护记录', owner: '周工', status: '已处理', creator: 'Admin', createdAt: '2026-08-01 09:30', operatedAt: '2026-08-02 16:40', handleRemark: '维护记录已归档。' }, |
| | | ] |
| | | |
| | | function loadRecords() { |
| | | try { |
| | | const stored = localStorage.getItem(STORAGE_KEY) |
| | | if (stored) return JSON.parse(stored) as TodoRecord[] |
| | | } catch { |
| | | localStorage.removeItem(STORAGE_KEY) |
| | | } |
| | | return seedRecords |
| | | } |
| | | |
| | | const records = ref<TodoRecord[]>(loadRecords()) |
| | | |
| | | function persist() { |
| | | localStorage.setItem(STORAGE_KEY, JSON.stringify(records.value)) |
| | | } |
| | | |
| | | function nowString() { |
| | | const date = new Date() |
| | | const pad = (value: number) => String(value).padStart(2, '0') |
| | | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}` |
| | | } |
| | | |
| | | export interface TodoRecord { id:number; content:string; ownerId:number; owner:string; status:TodoStatus; creator:string; createdAt:string; operatedAt:string; handleRemark:string } |
| | | export interface TodoPayload { content:string; ownerId:number; owner:string } |
| | | const records = ref<TodoRecord[]>([]) |
| | | const loaded = ref(false) |
| | | function map(item: any): TodoRecord { return {...item, createdAt: formatBusinessTime(item.createdAt), operatedAt: formatBusinessTime(item.operatedAt)} } |
| | | export function useTodos() { |
| | | function createTodo(payload: TodoPayload) { |
| | | const id = Math.max(7000, ...records.value.map(item => item.id)) + 1 |
| | | const timestamp = nowString() |
| | | records.value.unshift({ ...payload, id, status: '待处理', createdAt: timestamp, operatedAt: timestamp, handleRemark: '' }) |
| | | persist() |
| | | } |
| | | |
| | | function handleTodo(id: number, remark: string) { |
| | | const target = records.value.find(item => item.id === id) |
| | | if (!target || target.status === '已处理') return false |
| | | target.status = '已处理' |
| | | target.handleRemark = remark |
| | | target.operatedAt = nowString() |
| | | persist() |
| | | return true |
| | | } |
| | | |
| | | function removeTodo(id: number) { |
| | | records.value = records.value.filter(item => item.id !== id) |
| | | persist() |
| | | } |
| | | |
| | | return { records, createTodo, handleTodo, removeTodo } |
| | | async function load(){ records.value=(await listTodos()).map(map); loaded.value=true } |
| | | async function createTodo(payload:TodoPayload){ const item=await createTodoApi({content:payload.content,ownerId:payload.ownerId,ownerName:payload.owner}); records.value.unshift(map(item)) } |
| | | async function handleTodo(id:number,remark:string){ await handleTodoApi(id,remark); await load() } |
| | | async function removeTodo(id:number){ await removeTodoApi(id); records.value=records.value.filter(item=>item.id!==id) } |
| | | return {records,loaded,load,createTodo,handleTodo,removeTodo} |
| | | } |
| | |
| | | <script setup lang="ts"> |
| | | import { computed, reactive, ref } from 'vue' |
| | | import { computed, onMounted, reactive, ref } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { getCurrentUser } from '@/utils/auth' |
| | | import { useTodos, type TodoRecord, type TodoStatus } from '@/stores/todos' |
| | | import { getEnabledUserOptions } from '@/api/users' |
| | | |
| | | interface FilterState { |
| | | owner: string |
| | |
| | | dateRange: [string, string] | null |
| | | } |
| | | |
| | | const owners = ['张工', '李工', '王工', '周工', '赵工', '陈工', 'Admin'] |
| | | const owners = ref<{ id:number; name:string }[]>([]) |
| | | const statuses: TodoStatus[] = ['待处理', '已处理'] |
| | | const { records, createTodo, handleTodo, removeTodo } = useTodos() |
| | | const { records, createTodo, handleTodo, removeTodo, load } = useTodos() |
| | | const currentOwner = computed(() => getCurrentUser()?.displayName || getCurrentUser()?.username || 'Admin') |
| | | const filters = reactive<FilterState>({ owner: '', status: '', dateRange: null }) |
| | | const appliedFilters = ref<FilterState>({ owner: '', status: '', dateRange: null }) |
| | |
| | | const pageSize = ref(10) |
| | | const createDialogVisible = ref(false) |
| | | const createForm = reactive({ content: '', owner: '' }) |
| | | onMounted(async () => { await load(); owners.value = await getEnabledUserOptions() }) |
| | | |
| | | const filteredRecords = computed(() => { |
| | | const query = appliedFilters.value |
| | |
| | | const content = createForm.content.trim() |
| | | if (!content) { ElMessage.warning('请输入待办内容'); return } |
| | | if (!createForm.owner) { ElMessage.warning('请选择负责人'); return } |
| | | createTodo({ content, owner: createForm.owner, creator: getCurrentUser()?.displayName || 'Admin' }) |
| | | const owner = owners.value.find(item => item.name === createForm.owner) |
| | | if (!owner) { ElMessage.warning('负责人不存在'); return } |
| | | createTodo({ content, ownerId: owner.id, owner: owner.name }) |
| | | createDialogVisible.value = false |
| | | currentPage.value = 1 |
| | | ElMessage.success('待办已新增') |
| | |
| | | <section class="filter-panel" aria-label="待办查询条件"> |
| | | <header class="section-heading"><div><i></i><strong>查询条件</strong><span>TODO FILTER CONDITIONS</span></div><small>共 {{ records.length }} 条待办</small></header> |
| | | <div class="filter-grid"> |
| | | <label><span>负责人</span><el-select v-model="filters.owner" clearable placeholder="全部"><el-option label="全部" value="全部" /><el-option v-for="item in owners" :key="item" :label="item" :value="item" /></el-select></label> |
| | | <label><span>负责人</span><el-select v-model="filters.owner" clearable placeholder="全部"><el-option label="全部" value="全部" /><el-option v-for="item in owners" :key="item.id" :label="item.name" :value="item.name" /></el-select></label> |
| | | <label><span>状态</span><el-select v-model="filters.status" clearable placeholder="全部"><el-option label="全部" value="全部" /><el-option v-for="item in statuses" :key="item" :label="item" :value="item" /></el-select></label> |
| | | <label class="date-filter"><span>操作时间</span><el-date-picker v-model="filters.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" unlink-panels /></label> |
| | | <div class="filter-actions"><button class="primary-button" type="button" @click="applySearch"><el-icon><Search /></el-icon>查询</button><button class="secondary-button" type="button" @click="resetSearch"><el-icon><RefreshLeft /></el-icon>重置</button></div> |
| | |
| | | </section> |
| | | |
| | | <el-dialog v-model="createDialogVisible" title="新增待办" width="600px" align-center destroy-on-close> |
| | | <div class="create-form"><label><span>待办内容</span><el-input v-model="createForm.content" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="请输入待办内容" /></label><label><span>负责人</span><el-select v-model="createForm.owner" filterable placeholder="请选择负责人"><el-option v-for="item in owners" :key="item" :label="item" :value="item" /></el-select></label></div> |
| | | <div class="create-form"><label><span>待办内容</span><el-input v-model="createForm.content" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="请输入待办内容" /></label><label><span>负责人</span><el-select v-model="createForm.owner" filterable placeholder="请选择负责人"><el-option v-for="item in owners" :key="item.id" :label="item.name" :value="item.name" /></el-select></label></div> |
| | | <template #footer><button class="secondary-button" type="button" @click="createDialogVisible = false">取消</button><button class="primary-button" type="button" @click="submitCreate"><el-icon><Check /></el-icon>确定新增</button></template> |
| | | </el-dialog> |
| | | </div> |