From 46db04e43ede8a12b77fc3035e77164a96e261b1 Mon Sep 17 00:00:00 2001 From: ShaoHua <345265198@qqcom> Date: Sun, 14 Jun 2026 05:17:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(TaskItem,TaskList):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=AD=90=E4=BB=BB=E5=8A=A1=E5=B5=8C=E5=A5=97=E5=B1=82=E7=BA=A7?= =?UTF-8?q?=E4=B8=A2=E5=A4=B1=E5=92=8C=E6=B8=B2=E6=9F=93=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复API返回平面子任务DTO时,直接替换任务丢失后代子任务的问题 2. 重构子任务创建逻辑,改用重新拉取全量任务树的方式解决深层嵌套任务的响应式渲染问题 3. 修改子任务添加方式为新数组引用赋值,确保Vue3可以正确检测嵌套层级的变更 4. 添加缺失的父任务未找到的警告日志和异常捕获处理 --- src/Hua.Todo.Web/src/components/TaskItem.vue | 5 ++++ src/Hua.Todo.Web/src/components/TaskList.vue | 26 +++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Hua.Todo.Web/src/components/TaskItem.vue b/src/Hua.Todo.Web/src/components/TaskItem.vue index 4e3b7bb..8d1f900 100644 --- a/src/Hua.Todo.Web/src/components/TaskItem.vue +++ b/src/Hua.Todo.Web/src/components/TaskItem.vue @@ -201,6 +201,11 @@ const handleSubTaskUpdated = (updatedTask: Task) => { if (props.task.subTasks) { const index = props.task.subTasks.findIndex(t => t.id === updatedTask.id); if (index !== -1) { + // 保留现有子任务:API 返回的 DTO 是平面的(subTasks=[]),直接替换会丢失该任务已有的所有后代 + const existing = props.task.subTasks[index]; + if (existing.subTasks?.length && !updatedTask.subTasks?.length) { + updatedTask.subTasks = existing.subTasks; + } props.task.subTasks[index] = updatedTask; emit('updated', props.task); } diff --git a/src/Hua.Todo.Web/src/components/TaskList.vue b/src/Hua.Todo.Web/src/components/TaskList.vue index 8f464c2..8997ff7 100644 --- a/src/Hua.Todo.Web/src/components/TaskList.vue +++ b/src/Hua.Todo.Web/src/components/TaskList.vue @@ -402,6 +402,10 @@ const handleTaskUpdated = (updatedTask: Task) => { const updateTaskInTree = (tasks: Task[], updatedTask: Task): boolean => { for (let i = 0; i < tasks.length; i++) { if (tasks[i].id === updatedTask.id) { + // 保留现有子任务:API 返回的 DTO 是平面的(subTasks=[]),直接替换会丢失该任务已有的所有后代 + if (tasks[i].subTasks?.length && !updatedTask.subTasks?.length) { + updatedTask.subTasks = tasks[i].subTasks; + } tasks[i] = updatedTask; return true; } @@ -434,8 +438,22 @@ const deleteTaskFromTree = (tasks: Task[], taskId: string): boolean => { return false; }; -const handleSubTaskCreated = (subTask: Task) => { - addSubTaskToTree(tasks.value, subTask); +const handleSubTaskCreated = async (subTask: Task) => { + try { + // 直接从 API 重新加载完整任务树,保证所有层级的嵌套关系正确且响应式链路完整。 + // 深层嵌套(3+层)时,原地修改树(addSubTaskToTree)无法可靠触发 TaskItem 的 re-render, + // 因为 filteredTasks 计算属性中的浅拷贝({ ...task })在替换 subTasks 引用时会断开追踪。 + const response = await taskApi.getTasks(); + if (response.success && response.data) { + tasks.value = response.data; + } else { + console.warn('[handleSubTaskCreated] API 返回异常,尝试就地添加'); + addSubTaskToTree(tasks.value, subTask); + } + } catch (error) { + console.error('[handleSubTaskCreated] 加载任务失败,使用就地添加:', error); + addSubTaskToTree(tasks.value, subTask); + } lastSyncTime.value = Date.now(); }; @@ -445,7 +463,8 @@ const addSubTaskToTree = (tasks: Task[], subTask: Task): boolean => { if (!tasks[i].subTasks) { tasks[i].subTasks = []; } - tasks[i].subTasks.push(subTask); + // 使用新数组引用替换而非 push,确保 Vue 3 响应式系统无论嵌套多深都能检测到变更 + tasks[i].subTasks = [...tasks[i].subTasks, subTask]; return true; } if (tasks[i].subTasks && tasks[i].subTasks.length > 0) { @@ -454,6 +473,7 @@ const addSubTaskToTree = (tasks: Task[], subTask: Task): boolean => { } } } + console.warn(`[addSubTaskToTree] 未找到父任务 id="${subTask.parentTaskId}",子任务 id="${subTask.id}" 未能加入树中`); return false; };