缺陷 1008946 - 上传到知识库的文件解析完之后,不更新前端的状态
原始需求(一字不差)
缺陷标题:上传到知识库的文件解析完之后,不更新前端的状态
状态:进行中 | 负责人:尹帮会 | 严重程度:fatal | 优先级:high | 创建者:Ryan章桦
创建时间:2026-03-20 16:22:59 | 迭代:—
描述:上传到知识库的文件解析完之后,不更新前端的状态,前端一直显示转圈,要前端人工刷新之后,才能完成更新
评论:无
根因分析
上传流程梳理
- 用户上传文件 → 后端存储文件并创建知识处理任务
- 前端通过
/files/upload/progress/{uploadId}SSE 监听上传进度 -
SSE 在文件"提交给知识服务"后即关闭(此时文件
knowledgeStatus = processing) - Node.js 知识服务开始异步处理(文本抽取 → 分块 → 向量化),可能需要数十秒至数分钟
- 处理完成后
knowledgeStatus变为completed——但前端无感知
关键差异
| 页面 | 轮询机制 |
|---|---|
KnowledgeBase/FileList.vue |
✅ 有 10 秒轮询(第 1096 行) |
Workspace.vue(工作台文件树) |
❌ 无,SSE 关闭后不再更新状态 |
迭代记录
第 1 轮:新增全量轮询
commit:96b92e82a
新增 hasProcessingFiles computed + 每 10 秒 loadKnowledgeFiles(true) 轮询。
问题:轮询代码块插入在 knowledgeFiles = ref([]) 之前,导致运行时报错:
Uncaught (in promise) ReferenceError: Cannot access 'knowledgeFiles' before initialization
第 2 轮:修正初始化顺序
commit:550ca3d2d
将轮询代码块移至 knowledgeFiles 定义之后,消除暂时性死区错误。
新问题:loadKnowledgeFiles(true) 内部调用 treeRefreshKey.value++ 会强制重挂载整棵文件树。重挂载后,所有曾经展开的目录(从 localStorage 读到展开状态)重新触发 load-children,每 10 秒产生一轮目录级联查询。用户切换文档预览时能明显观察到左侧目录的冗余请求。
第 3 轮:改为精准节点更新
commit:0d679c4a1
不再调用 loadKnowledgeFiles(true),改为 pollProcessingFileStatuses:
- 递归收集树中所有
processing节点 - 对每个节点单独调用
fetchFileKnowledgeStatus(getFileMeta) -
直接修改节点的
knowledgeStatus属性,不重建树,不触发treeRefreshKey++
遗留问题(用户反馈):
- 切换到已解析完成的文档时,
setActiveTab仍无条件调用fetchFileKnowledgeStatus,产生冗余请求 - 轮询状态更新仅同步激活 tab,其他打开中的同文件 tab 状态滞后
第 4 轮:终态跳过 + 全 tab 同步
commit:3c52c3bb6
两处修复:
1. setActiveTab 跳过终态重查
终态(completed / not_supported / failed)不可逆,切换 tab 时不再重查,消除频繁切换时的冗余请求。
2. pollProcessingFileStatuses 同步所有打开的同文件 tab
改为遍历 tabs.value,覆盖所有与节点同 fileId 的 tab(包括非激活 tab)。用户后续切换到该文档时,状态已是最新值,不再需要重查。
遗留问题(用户反馈):切换 tab 时左侧目录仍出现"已展开的菜单关闭了然后又展开"并伴随冗余请求。
第 5 轮:路由 watcher 提前退出,阻断目录重载链路
commit:1ce450e08
根因:setActiveTab 在切换 tab 时调用 router.replace 更新 URL 中的 fileId,路由 watcher 检测到 newFileId !== oldFileId 后触发 openFileFromRoute → openKnowledgeFileInWorkspace → expandAndLocateFile → handleLoadChildren。handleLoadChildren 将 targetFolder.children 替换为新 API 数据(含 children: undefined 的子节点),导致已展开的子目录视觉上先折叠后展开,并产生级联 API 请求。
修复:路由 watcher 中,若新 fileId 已匹配当前激活 tab(activeTabData 在 setActiveTab 中已同步赋值),直接 return,跳过 openFileFromRoute:
if (activeTabData.value?.fileId?.toString() === newFileId.toString()) {
return;
}
新问题:expandAndLocateFile 被完全跳过,切换 tab 后左侧目录不再滚动/展开到当前文件位置(回归)。
第 6 轮:setActiveTab 补充轻量定位,修复回归
commit:7987cb618
在 setActiveTab 的 updateCurrentFileFolderPath 完成后,补充两步轻量操作,恢复定位功能:
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,关键改动:
-
knowledgeFiles之后插入轮询逻辑(pollProcessingFileStatuses+hasProcessingFiles+startKnowledgePolling) -
setActiveTab对终态文件跳过fetchFileKnowledgeStatus -
pollProcessingFileStatuses遍历tabs.value同步所有同文件 tab - 路由 watcher:
newFileId已是激活 tab 时提前 return -
setActiveTab:updateCurrentFileFolderPath后轻量 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 通过
验收条件
- 上传文件后,知识库处理完成时前端图标自动从转圈变为正常 ✅
- 无 processing 文件时不发送轮询请求 ✅
- 切换 tab 时左侧目录不产生冗余 API 请求,不出现折叠闪烁 ✅
- 切换 tab 后左侧目录自动滚动并高亮到当前文件位置 ✅
- 已解析完成的文件切换 tab 时不再重查状态接口 ✅
- 轮询到状态变更时,所有打开的同文件 tab 同步更新 ✅
- 组件卸载时定时器正确清理,无内存泄漏 ✅
测试覆盖
E2E 测试(Playwright)
文件:e2e/tests/2026-03-24/1008946-knowledge-status-polling.spec.ts(1 个用例)
| 用例 | 验证内容 |
|---|---|
| hasProcessingFiles 逻辑:递归扫描树中 processing 文件 | 空树/顶层/深层嵌套/已完成各场景均验证正确 |