优化: 所有剩余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
+63 -2
View File
@@ -61,7 +61,8 @@
<section class="panel" v-if="providerForm.visible">
<div class="panel-header">
<h2>{{ providerForm.editId ? $t('settings.editProviderTitle') : $t('settings.addProviderTitle') }}</h2>
<button class="btn btn-ghost btn-sm" @click="providerForm.visible = false">{{ $t('common.cancel') }}</button>
<!-- L19: 取消走 closeProviderForm,脏表单二次确认后再关(防误弃改动) -->
<button class="btn btn-ghost btn-sm" @click="closeProviderForm">{{ $t('common.cancel') }}</button>
</div>
<div class="form-grid">
<div class="form-field">
@@ -139,6 +140,8 @@
</div>
</div>
<div class="model-list-hint">{{ $t('settings.modelListHint') }}</div>
<!-- M32: 模型列表 enabled/weight 改了未保存时显提示(关闭/切走将丢弃) -->
<div v-if="isFormDirty" class="model-list-unsaved-hint">{{ $t('settings.modelListUnsavedHint') }}</div>
</div>
</div>
</section>
@@ -200,8 +203,51 @@ const providerForm = reactive({
// toast 3s 自隐易被忽略,保存失败时表单内常驻错误条 + 可操作建议,
// 直到下次保存尝试/重开表单才清除。null=无错误。
saveError: null as string | null,
// L19/M32: 表单打开/保存成功时的「已落库快照」JSON 串,用于 isFormDirty 比对。
// openProviderForm 写入初始快照,saveProvider 成功后刷新;关闭表单清空。
savedSnapshot: '' as string,
})
/**
* L19/M32: 表单脏判定——当前表单状态与已落库快照不一致即脏。
* 触发场景:① 取消/关闭有改动的表单 → 二次确认放弃(L19);
* ② 模型列表 enabled/weight 改了未保存 → 表单内提示(M32)。
* 比对用 JSON.stringify(整体快照语义,简单可靠;表单字段少,性能无虞)。
* 不含 saving/fetching/saveError/visible 等 UI 态字段(只比业务字段)。
*/
const isFormDirty = computed(() => {
if (!providerForm.visible) return false
return formBusinessSnapshot() !== providerForm.savedSnapshot
})
function formBusinessSnapshot(): string {
return JSON.stringify({
name: providerForm.name,
providerType: providerForm.providerType,
baseUrl: providerForm.baseUrl,
apiKey: providerForm.apiKey,
defaultModel: providerForm.defaultModel,
models: providerForm.models.map(m => ({
model_id: m.model_id,
enabled: m.enabled,
weight: m.weight,
})),
})
}
/**
* L19: 关闭表单入口——脏表单二次确认,清表单后关闭。
* 用于模板「取消」按钮 + saveProvider 成功后(无脏直接关)。
*/
async function closeProviderForm() {
if (isFormDirty.value) {
const ok = await props.confirmDialog(t('settings.confirmDiscardUnsaved'))
if (!ok) return // 用户取消关闭,继续编辑
}
providerForm.visible = false
providerForm.savedSnapshot = ''
}
async function loadProviders() {
try {
aiProviders.value = await aiApi.listProviders()
@@ -241,6 +287,8 @@ function openProviderForm(p?: AiProviderConfig) {
providerForm.fetching = false
providerForm.saveError = null // P0-2: 重开表单清旧错误横幅
providerForm.visible = true
// L19/M32: 记初始快照(在 visible=true、字段回填完成后),isFormDirty 据此比对。
providerForm.savedSnapshot = formBusinessSnapshot()
}
/**
@@ -354,6 +402,7 @@ async function saveProvider() {
// P0-2: 成功明确反馈——清错误横幅 + 关表单 + 绿色「已保存」toast
providerForm.saveError = null
providerForm.visible = false
providerForm.savedSnapshot = '' // L19: 关表单清快照
await loadProviders()
emit('toast', t('settings.toastSaved'), 'success')
} catch (e) {
@@ -445,10 +494,14 @@ function maskKey(key: string): string {
return key.slice(0, 4) + '••••••••' + key.slice(-4)
}
/** 暴露给 shell:loadProviders 供外部主动刷新 / Knowledge 读 aiProviders / 卸载清 timer */
/** 暴露给 shell:loadProviders 供外部主动刷新 / Knowledge 读 aiProviders / 卸载清 timer。
* L19: isFormDirty + closeProviderForm 暴露,供外部(如导航切走前)查询/触发拦截。
* 当前 Settings.vue 未接入 nav 拦截(不在本次改文件集),接口先行供后续 shell 接入。 */
defineExpose({
aiProviders,
loadProviders,
isFormDirty,
closeProviderForm,
clearPoolTimer() { if (_poolTimer) clearTimeout(_poolTimer) },
})
@@ -563,4 +616,12 @@ onMounted(loadProviders)
.toggle--sm { width: 32px; height: 18px; }
.toggle--sm .toggle-slider::before { width: 12px; height: 12px; left: 3px; bottom: 3px; }
.toggle--sm input:checked + .toggle-slider::before { transform: translateX(14px); }
/* M32: 模型列表有未保存调整时的提示(弱 warning 色,贴合 model-list-hint 之下) */
.model-list-unsaved-hint {
margin-top: 4px;
font-size: 11px;
line-height: 1.5;
color: var(--df-warning);
}
</style>