优化: 所有剩余UI/UX待办一批完成(持久化+AuditLog+解耦+total+原12大改+P2)

持久化(P1-c):新建 usePersistedRef composable,Tasks/AuditLog/ProjectDetail 等接入 localStorage

AuditLog(P1-d):后端 list_tool_executions 加 WHERE 筛选+返 {items,total,has_more},前端对接+长列折叠+筛选持久化

数据源解耦(P1-g):ProjectDetail projectTasks 按 project_id 独立加载 + ChatInput @项目联想独立加载(不读 store.tasks 当前页)

GitChanges(12a):后端 get_module_commits 加 git rev-list --count 返 total,前端显真实总数

原12大改:Dashboard 统计卡压底行(1)/Projects 列表卡片视图(2)/project_event 埋点排序(3)/TaskDetail 重设计(4)/IdeaDetail 重设计(5)/KnowledgeDetail 重设计(6)/界面持久化+侧栏Ctrl+B+审批数字键(7)/ProjectDetail 三栏改两栏(10)

P2打磨:文件浏览器(FileTree去重/FilePreview行号.md Diff/selectedFilePath归位)/settings反馈(假保存/端口校验)/AI会话(try-catch/scrollIntoView)/后端计数(move_queue事件/timeline total/workflow分页/import_batch分块)/杂项(TopBar/ConfirmDialog键盘/CIStatus i18n/ToolResultBody/ModuleNode/ApprovalDialog全选)
This commit is contained in:
lxy
2026-08-02 13:11:06 +08:00
parent caaabf0c15
commit f736f435bc
70 changed files with 2645 additions and 576 deletions
+13 -4
View File
@@ -87,7 +87,8 @@
* 子组件只负责 emit('toggle-dir', path, entries) / emit('load-children', path),
* 实际的网络请求与状态更新统一在 FileExplorer 中处理。
* - 本组件自身的 loading/entries 仅用于"首次挂载时拉取自己 sub_path 的根条目";
* 递归子树复用同一份 props/事件,不重复拉取。
* 递归子树复用同一份 props/事件,且 loadEntries 会先查 loadedChildren 缓存命中即复用,
* 避免父级 toggleDir 已拉取后子树 onMounted 再次重复请求同目录。
*
* 这样设计的好处:刷新(refresh)只需 FileExplorer 清空 loadedChildren 重拉根,
* 所有展开的子树因 expandedPaths 被清而卸载,下次展开重新拉取,无脏数据。
@@ -130,13 +131,21 @@ const error = ref<string | null>(null)
async function loadEntries() {
// moduleId 为空时不发起请求(工程列表还在加载中)
if (!props.moduleId) return
// 去重:递归子树实例挂载时,父级 toggleDir 通常已为本目录拉取并缓存。
// 命中缓存则直接复用,不再发同样的请求(避免 onMounted 与 toggleDir 双重拉取)。
const cacheKey = props.subPath || ''
const cached = props.loadedChildren.get(cacheKey)
if (cached) {
entries.value = cached
return
}
loading.value = true
error.value = null
try {
const res = await moduleApi.getModuleFileTree(props.moduleId, props.subPath || undefined)
entries.value = res.entries
// 缓存到顶层 loadedChildren(供 toggle 判断是否已有数据,避免重复请求)。
props.loadedChildren.set(props.subPath || '', res.entries)
props.loadedChildren.set(cacheKey, res.entries)
} catch (e) {
error.value = e instanceof Error ? e.message : String(e)
} finally {
@@ -153,10 +162,10 @@ async function toggleDir(entry: FileTreeEntry) {
// 收起:仅移除展开标记(loadedChildren 缓存保留,下次展开秒开)。
emit('toggle-dir', entry.path, [])
} else {
// 展开:若未加载过则先拉取(由顶层处理缓存命中),再 emit 通知展开。
// 展开:若未加载过则先拉取并缓存(去重——只在这里拉一次;
// 递归子树实例 onMounted 时会复用此缓存,不重复请求同目录)。
if (!props.loadedChildren.has(entry.path)) {
emit('load-children', entry.path)
// 立即拉取本目录(子组件实例挂载后会自取 loadedChildren;此处同步拉保响应即时)。
try {
loading.value = true
const res = await moduleApi.getModuleFileTree(props.moduleId, entry.path)