yeg
2026-09-09 c52ac07b45015b16aa0bf8b959f559cec6f5a38f
接入待办真实接口与负责人权限
1个文件已添加
2个文件已修改
115 ■■■■■ 已修改文件
src/api/todos.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/todos.ts 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/TodoList.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/todos.ts
New file
@@ -0,0 +1,6 @@
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'}) }
src/stores/todos.ts
@@ -1,85 +1,16 @@
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}
}
src/views/TodoList.vue
@@ -1,8 +1,9 @@
<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
@@ -10,9 +11,9 @@
  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 })
@@ -20,6 +21,7 @@
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
@@ -57,7 +59,9 @@
  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('待办已新增')
@@ -97,7 +101,7 @@
    <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>
@@ -119,7 +123,7 @@
    </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>