优化: 所有剩余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:
+32
-4
@@ -125,12 +125,40 @@ function onResize() {
|
||||
// ------------------------------------------------------------
|
||||
// SettingsNav 搜索命中首匹配项时上抛 scroll-target(key)。
|
||||
// 此处据 key 查 SETTINGS_INDEX 取 labelKey → t() 得当前 locale 文本,
|
||||
// 在右侧内容 DOM 中找匹配文本的 .setting-label(表单型 Section)或
|
||||
// 在右侧内容 DOM 中找匹配的 .setting-label(表单型 Section)或
|
||||
// .panel-header h2(列表型面板如 ProviderPanel),scrollIntoView 平滑定位。
|
||||
// 用文本匹配而非 data-* 属性:避免改动各 Section(严守"仅改 plan 列出文件")。
|
||||
//
|
||||
// 匹配策略(2026-08-02 加固):
|
||||
// 旧实现用 textContent.trim() === labelText 严格相等,SettingRow 的 .setting-label
|
||||
// 常带 next-round-badge 子 span(「下次对话生效」) → textContent 拼接出
|
||||
// 「主题 下次对话生效」≠「主题」,严格相等失效,搜索定位静默无命中。
|
||||
// 现改读 .setting-label 的"首子文本节点"(纯 label,排除 badge/desc 等子元素),
|
||||
// 与 labelText trim 后 includes 匹配(双向 includes 兜底首尾空白/标点)。
|
||||
// h2 同理改 includes 提升容错。理想方案是 .setting-label 加 data-setting-key,
|
||||
// 但那需改 SettingRow.vue + 6 个 Section(越"仅改指定文件"范围),故此处加固文本匹配。
|
||||
// ============================================================
|
||||
const contentRef = ref<HTMLElement | null>(null)
|
||||
|
||||
/** 取元素"首文本节点"内容(跳过子元素,排除 SettingRow 的 next-round-badge 拼接干扰) */
|
||||
function firstTextNodeText(el: HTMLElement | null): string {
|
||||
if (!el) return ''
|
||||
// 优先 childNodes 中的首个 text node;退而 textContent(无子元素场景)
|
||||
for (const node of Array.from(el.childNodes)) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent || ''
|
||||
}
|
||||
}
|
||||
return el.textContent || ''
|
||||
}
|
||||
|
||||
/** 双向 includes 匹配(容错首尾空白/标点差异;空串不命中) */
|
||||
function fuzzyMatch(a: string, b: string): boolean {
|
||||
const sa = a.trim()
|
||||
const sb = b.trim()
|
||||
if (!sa || !sb) return false
|
||||
return sa.includes(sb) || sb.includes(sa)
|
||||
}
|
||||
|
||||
function scrollToSettingItem(itemKey: string) {
|
||||
const entry = SETTINGS_INDEX.find((e) => e.key === itemKey)
|
||||
if (!entry) return
|
||||
@@ -145,7 +173,7 @@ function scrollToSettingItem(itemKey: string) {
|
||||
const labels = root.querySelectorAll<HTMLElement>('.setting-label')
|
||||
let target: HTMLElement | null = null
|
||||
for (const el of labels) {
|
||||
if (el.textContent?.trim() === labelText.trim()) {
|
||||
if (fuzzyMatch(firstTextNodeText(el), labelText)) {
|
||||
target = el.closest<HTMLElement>('.setting-row') ?? el
|
||||
break
|
||||
}
|
||||
@@ -153,7 +181,7 @@ function scrollToSettingItem(itemKey: string) {
|
||||
if (!target) {
|
||||
const headers = root.querySelectorAll<HTMLElement>('.panel-header h2')
|
||||
for (const el of headers) {
|
||||
if (el.textContent?.trim() === labelText.trim()) {
|
||||
if (fuzzyMatch(firstTextNodeText(el), labelText)) {
|
||||
target = el
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user