优化: 所有剩余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
+58 -6
View File
@@ -72,7 +72,7 @@
>
<span v-if="processingId === tc.id" class="popup-btn-spinner"></span>
<svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
{{ $t('aiChat.dirAuthOnce') }}
{{ $t('aiChat.dirAuthOnce') }}<span class="popup-btn-key">1</span>
</button>
<button
class="popup-btn popup-btn--session"
@@ -88,7 +88,7 @@
@click.stop="onApproveAlways(tc)"
>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
{{ $t('aiChat.dirAuthAlways') }}
{{ $t('aiChat.dirAuthAlways') }}<span class="popup-btn-key">2</span>
</button>
<button
class="popup-btn popup-btn--reject"
@@ -96,7 +96,7 @@
@click.stop="onReject(tc)"
>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
{{ $t('aiChat.dirAuthDeny') }}
{{ $t('aiChat.dirAuthDeny') }}<span class="popup-btn-key">3</span>
</button>
</div>
<div v-else class="popup-item-actions">
@@ -107,7 +107,7 @@
>
<span v-if="processingId === tc.id" class="popup-btn-spinner"></span>
<svg v-else width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
{{ $t('aiTool.approve') }}
{{ $t('aiTool.approve') }}<span class="popup-btn-key">1</span>
</button>
<button
class="popup-btn popup-btn--reject"
@@ -115,7 +115,7 @@
@click.stop="onReject(tc)"
>
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
{{ $t('aiTool.reject') }}
{{ $t('aiTool.reject') }}<span class="popup-btn-key">3</span>
</button>
</div>
</div>
@@ -244,11 +244,45 @@ let _closeFallbackTimer: ReturnType<typeof setTimeout> | null = null
const CLOSE_TIMEOUT_MS = 1500
/** Esc 键关浮窗(键盘可达性)。模块级命名函数,onBeforeUnmount 据此精确移除同一引用。
* 仅响应 Esc(KeyDown),其他键透传(不拦截 Tab/Enter 等原生 button 导航)。 */
* 仅响应 Esc(KeyDown),其他键透传(不拦截 Tab/Enter 等原生 button 导航)。
*
* 7c:数字键快捷审批(对列表顶部第一条生效,多审批场景用户应逐条点或用方向键导航)。
* - path 类:1=once / 2=always / 3=deny(deny 仍走二次确认,与按钮同款不可逆守卫)
* - risk 类:1=approve / 3=reject(2 仅 path 类有 always,risk 类忽略 '2')
* 守卫:
* - 确认对话框打开时(confirmState.visible)禁用快捷键,避免与确认按钮的 Enter/Esc 冲突
* - 处理中(processingId)时忽略,防重复触发
* - 输入元素聚焦时不拦截(input/textarea/contenteditable 场景,本浮窗暂无 input 但防御)
* - Ctrl/Meta/Alt 组合不拦截(避免劫持浏览器/系统快捷键) */
function _onKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
e.preventDefault()
void onClose()
return
}
// 7c:数字键快捷审批
if (e.key === '1' || e.key === '2' || e.key === '3') {
// 组合键(Ctrl+1 等)不拦截;确认对话框打开时不拦截;处理中不拦截
if (e.ctrlKey || e.metaKey || e.altKey) return
if (confirmState.visible) return
if (processingId.value) return
// 输入元素聚焦时不拦截(防御:本浮窗无 input,但 ConfirmDialog 未来或有)
const tgt = e.target as HTMLElement | null
if (tgt && (tgt.tagName === 'INPUT' || tgt.tagName === 'TEXTAREA' || tgt.isContentEditable)) return
const first = approvals.value[0]
if (!first) return
e.preventDefault()
if (e.key === '1') {
// 1 = once(path) / approve(risk)
if (first.kind === 'path') void onApproveOnce(first)
else void onApprove(first)
} else if (e.key === '2') {
// 2 = always(仅 path 类;risk 类无 always,忽略)
if (first.kind === 'path') void onApproveAlways(first)
} else {
// 3 = deny(path) / reject(risk)
void onReject(first)
}
}
}
@@ -683,6 +717,24 @@ function onDragEnd() { isDragging.value = false }
opacity: 0.6;
cursor: not-allowed;
}
/* 7c:数字键快捷键标记(1/2/3 对应 once/approve、always、deny/reject)。
小尺寸单字符徽章,提示键盘可达性;继承父按钮对比色但降透明度作辅助信息层。 */
.popup-btn-key {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 13px;
height: 13px;
padding: 0 3px;
margin-left: 1px;
border-radius: var(--df-radius-xs, 3px);
font-family: var(--df-font-mono);
font-size: 9px;
font-weight: 700;
line-height: 1;
background: rgba(0, 0, 0, 0.18);
opacity: 0.7;
}
.popup-btn--approve {
background: var(--df-accent);
color: #fff;