03-1008946-知识库文件解析状态不更新-6轮迭代.md 6.91 KB

缺陷 1008946 - 上传到知识库的文件解析完之后,不更新前端的状态

原始需求(一字不差)

缺陷标题:上传到知识库的文件解析完之后,不更新前端的状态

状态:进行中 | 负责人:尹帮会 | 严重程度:fatal | 优先级:high | 创建者:Ryan章桦

创建时间:2026-03-20 16:22:59 | 迭代:—

描述:上传到知识库的文件解析完之后,不更新前端的状态,前端一直显示转圈,要前端人工刷新之后,才能完成更新

评论:无


根因分析

上传流程梳理

  1. 用户上传文件 → 后端存储文件并创建知识处理任务
  2. 前端通过 /files/upload/progress/{uploadId} SSE 监听上传进度
  3. SSE 在文件"提交给知识服务"后即关闭(此时文件 knowledgeStatus = processing
  4. Node.js 知识服务开始异步处理(文本抽取 → 分块 → 向量化),可能需要数十秒至数分钟
  5. 处理完成后 knowledgeStatus 变为 completed——但前端无感知

关键差异

页面 轮询机制
KnowledgeBase/FileList.vue ✅ 有 10 秒轮询(第 1096 行)
Workspace.vue(工作台文件树) ❌ 无,SSE 关闭后不再更新状态

迭代记录

第 1 轮:新增全量轮询

commit96b92e82a

新增 hasProcessingFiles computed + 每 10 秒 loadKnowledgeFiles(true) 轮询。

问题:轮询代码块插入在 knowledgeFiles = ref([]) 之前,导致运行时报错:

Uncaught (in promise) ReferenceError: Cannot access 'knowledgeFiles' before initialization

第 2 轮:修正初始化顺序

commit550ca3d2d

将轮询代码块移至 knowledgeFiles 定义之后,消除暂时性死区错误。

新问题loadKnowledgeFiles(true) 内部调用 treeRefreshKey.value++ 会强制重挂载整棵文件树。重挂载后,所有曾经展开的目录(从 localStorage 读到展开状态)重新触发 load-children,每 10 秒产生一轮目录级联查询。用户切换文档预览时能明显观察到左侧目录的冗余请求。


第 3 轮:改为精准节点更新

commit0d679c4a1

不再调用 loadKnowledgeFiles(true),改为 pollProcessingFileStatuses

  • 递归收集树中所有 processing 节点
  • 对每个节点单独调用 fetchFileKnowledgeStatusgetFileMeta
  • 直接修改节点的 knowledgeStatus 属性,不重建树,不触发 treeRefreshKey++

遗留问题(用户反馈)

  1. 切换到已解析完成的文档时,setActiveTab 仍无条件调用 fetchFileKnowledgeStatus,产生冗余请求
  2. 轮询状态更新仅同步激活 tab,其他打开中的同文件 tab 状态滞后

第 4 轮:终态跳过 + 全 tab 同步

commit3c52c3bb6

两处修复:

1. setActiveTab 跳过终态重查

终态(completed / not_supported / failed)不可逆,切换 tab 时不再重查,消除频繁切换时的冗余请求。

2. pollProcessingFileStatuses 同步所有打开的同文件 tab

改为遍历 tabs.value,覆盖所有与节点同 fileId 的 tab(包括非激活 tab)。用户后续切换到该文档时,状态已是最新值,不再需要重查。

遗留问题(用户反馈):切换 tab 时左侧目录仍出现"已展开的菜单关闭了然后又展开"并伴随冗余请求。


第 5 轮:路由 watcher 提前退出,阻断目录重载链路

commit1ce450e08

根因setActiveTab 在切换 tab 时调用 router.replace 更新 URL 中的 fileId,路由 watcher 检测到 newFileId !== oldFileId 后触发 openFileFromRoute → openKnowledgeFileInWorkspace → expandAndLocateFile → handleLoadChildrenhandleLoadChildrentargetFolder.children 替换为新 API 数据(含 children: undefined 的子节点),导致已展开的子目录视觉上先折叠后展开,并产生级联 API 请求。

修复:路由 watcher 中,若新 fileId 已匹配当前激活 tab(activeTabDatasetActiveTab 中已同步赋值),直接 return,跳过 openFileFromRoute

if (activeTabData.value?.fileId?.toString() === newFileId.toString()) {
  return;
}

新问题expandAndLocateFile 被完全跳过,切换 tab 后左侧目录不再滚动/展开到当前文件位置(回归)。


第 6 轮:setActiveTab 补充轻量定位,修复回归

commit7987cb618

setActiveTabupdateCurrentFileFolderPath 完成后,补充两步轻量操作,恢复定位功能:

updateCurrentFileFolderPath().then(() => {
  if (!activeTabData.value?.fileId || activeTabData.value?.isChatAnswer) return;
  const fileId = activeTabData.value.fileId;
  const path = currentFileFolderPath.value;
  if (path.length > 0) {
    // 展开路径中未展开的父目录(纯事件,无 API 请求)
    window.dispatchEvent(new CustomEvent("expand-folders-in-path", { detail: { path } }));
  }
  // 滚动到文件节点并高亮
  setTimeout(() => locateFileInTree(fileId), 300);
});

不调用 expandAndLocateFile,不触发 handleLoadChildren,彻底避免折叠闪烁和冗余请求,同时恢复目录定位和滚动。


最终修复方案

文件src/pages/Workspace.vue,关键改动:

  1. knowledgeFiles 之后插入轮询逻辑(pollProcessingFileStatuses + hasProcessingFiles + startKnowledgePolling
  2. setActiveTab 对终态文件跳过 fetchFileKnowledgeStatus
  3. pollProcessingFileStatuses 遍历 tabs.value 同步所有同文件 tab
  4. 路由 watcher:newFileId 已是激活 tab 时提前 return
  5. setActiveTabupdateCurrentFileFolderPath 后轻量 dispatch + locateFileInTree

onBeforeUnmount 清理:stopKnowledgePolling()


修改的文件

  • src/pages/Workspace.vue

测试结果

  • npm run type-check 通过,无 TypeScript 错误
  • npm run test:unit 通过,19/19 用例全部通过
  • npx playwright test e2e/tests/2026-03-24/1008946-knowledge-status-polling.spec.ts:1/1 通过

验收条件

  1. 上传文件后,知识库处理完成时前端图标自动从转圈变为正常 ✅
  2. 无 processing 文件时不发送轮询请求 ✅
  3. 切换 tab 时左侧目录不产生冗余 API 请求,不出现折叠闪烁 ✅
  4. 切换 tab 后左侧目录自动滚动并高亮到当前文件位置 ✅
  5. 已解析完成的文件切换 tab 时不再重查状态接口 ✅
  6. 轮询到状态变更时,所有打开的同文件 tab 同步更新 ✅
  7. 组件卸载时定时器正确清理,无内存泄漏 ✅

测试覆盖

E2E 测试(Playwright)

文件e2e/tests/2026-03-24/1008946-knowledge-status-polling.spec.ts(1 个用例)

用例 验证内容
hasProcessingFiles 逻辑:递归扫描树中 processing 文件 空树/顶层/深层嵌套/已完成各场景均验证正确