优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)

- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod

- AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation

- 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n

- 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
lxy
2026-08-05 22:15:34 +08:00
parent 28de5d6143
commit e68d283651
49 changed files with 1257 additions and 138 deletions
+135 -16
View File
@@ -180,9 +180,16 @@
</template>
<!-- 编辑态 -->
<template v-else>
<div v-if="candidateIdeas.length > 0" class="related-edit-list">
<div class="related-search" v-if="candidateIdeas.length > 0">
<input
v-model="relatedSearch"
class="related-search-input"
:placeholder="$t('ideas.relatedSearchPlaceholder')"
/>
</div>
<div v-if="filteredCandidates.length > 0" class="related-edit-list">
<label
v-for="idea in candidateIdeas"
v-for="idea in filteredCandidates"
:key="idea.id"
class="related-edit-item"
>
@@ -202,10 +209,15 @@
</template>
</div>
<!-- 传统评分条(score-bar) -->
<!-- 传统评分条(score-bar,从旧评估体系保留,默认折叠以降低信息密度) -->
<div class="detail-section">
<h3>{{ $t('ideas.multiScoreTitle') }}</h3>
<div class="score-bar-chart" v-if="parseScores(idea.scores).length > 0">
<h3>
<button class="section-toggle-btn" @click="scoreBarExpanded = !scoreBarExpanded">
{{ $t('ideas.multiScoreTitle') }}
<span class="section-toggle-arrow" :class="{ 'is-open': scoreBarExpanded }"></span>
</button>
</h3>
<div v-show="scoreBarExpanded" class="score-bar-chart" v-if="parseScores(idea.scores).length > 0">
<div class="score-bar-row" v-for="dim in parseScores(idea.scores)" :key="dim.name">
<span class="score-bar-label">{{ dim.name }}</span>
<div class="score-bar-track">
@@ -220,10 +232,30 @@
<!-- 标签 -->
<div class="detail-section">
<h3>{{ $t('ideas.tagsTitle') }}</h3>
<div class="tag-list" v-if="parseTags(idea.tags).length > 0">
<span class="tag" v-for="tag in parseTags(idea.tags)" :key="tag">{{ tag }}</span>
</div>
<div v-else class="eval-report eval-report--muted">{{ $t('ideas.noTags') }}</div>
<template v-if="tagsEditing">
<input
v-model="tagsEditInput"
class="detail-tags-edit-input"
:placeholder="$t('ideas.tagsPlaceholder')"
@keyup.enter="saveTagsEdit"
/>
<div class="tags-edit-actions">
<button class="btn btn-primary btn-sm" :disabled="savingTags" @click="saveTagsEdit">
{{ savingTags ? $t('common.loading') : $t('common.save') }}
</button>
<button class="btn btn-ghost btn-sm" :disabled="savingTags" @click="cancelTagsEdit">{{ $t('common.cancel') }}</button>
</div>
</template>
<template v-else>
<div class="tag-list" v-if="parseTags(idea.tags).length > 0">
<span class="tag" v-for="tag in parseTags(idea.tags)" :key="tag">{{ tag }}</span>
<button class="btn btn-ghost btn-sm" @click="startTagsEdit">{{ $t('ideas.editTags') }}</button>
</div>
<div v-else class="eval-report eval-report--muted">
{{ $t('ideas.noTags') }}
<button class="btn btn-ghost btn-sm" @click="startTagsEdit">{{ $t('ideas.editTags') }}</button>
</div>
</template>
</div>
<!-- 状态管理:P1- select badge 点击(当前态显色 badge,点击展开下拉选项) -->
@@ -292,6 +324,7 @@ const emit = defineEmits<{
(e: 'status-change', value: IdeaStatus): void
(e: 'update-desc', value: string): void
(e: 'update-related', ids: string[]): void
(e: 'update-tags', tags: string): void
}>()
const { t } = useI18n()
@@ -336,6 +369,8 @@ const descExpanded = ref(false)
// P0-① 详细论据(正反方/行动建议)折叠:默认收起,降低首屏信息密度,结论前置后用户按需展开
const debateExpanded = ref(false)
// score-bar 默认折叠(旧评估体系,降低信息密度)
const scoreBarExpanded = ref(false)
function measureDesc() {
const el = descRef.value
@@ -348,6 +383,7 @@ function measureDesc() {
watch(() => props.idea.id, () => {
descExpanded.value = false
debateExpanded.value = false
scoreBarExpanded.value = false
void nextTick(measureDesc)
})
watch(() => props.idea.description, () => void nextTick(measureDesc))
@@ -386,6 +422,15 @@ const vClickOutside: Directive<HTMLElement, () => void> = {
}
// ===== 关联灵感(related_ids JSON 数组字符串)=====
// 关联编辑搜索
const relatedSearch = ref('')
const filteredCandidates = computed(() => {
const q = relatedSearch.value.trim().toLowerCase()
if (!q) return candidateIdeas.value
return candidateIdeas.value.filter(
i => (i.title ?? '').toLowerCase().includes(q) || (i.description ?? '').toLowerCase().includes(q),
)
})
// 解析 props.idea.related_ids(JSON 字符串数组,null/空/非法 → [])
const relatedIds = computed<string[]>(() => {
const raw = props.idea.related_ids
@@ -426,6 +471,7 @@ function confirmRelate() {
function cancelRelate() {
relatedEditing.value = false
relatedSearch.value = ''
}
function toggleSelect(id: string) {
@@ -437,6 +483,33 @@ function toggleSelect(id: string) {
}
}
// ===== 标签编辑(show + edit 双态)=====
const tagsEditing = ref(false)
const tagsEditInput = ref('')
const savingTags = ref(false)
function startTagsEdit() {
tagsEditInput.value = parseTags(props.idea.tags).join(', ')
tagsEditing.value = true
}
async function saveTagsEdit() {
if (savingTags.value) return
savingTags.value = true
try {
const tags = tagsEditInput.value.split(',').map(t => t.trim()).filter(Boolean)
emit('update-tags', JSON.stringify(tags))
tagsEditing.value = false
await nextTick()
} finally {
savingTags.value = false
}
}
function cancelTagsEdit() {
tagsEditing.value = false
}
// 任意 status 字符串 → 对应 i18n key;未知状态回退到原值显示
function statusLabelKey(status: IdeaStatus): string {
return props.statusOptions.find(s => s.value === status)?.labelKey ?? status
@@ -639,7 +712,7 @@ watch(() => props.idea.id, loadHistory)
color: var(--df-text-secondary);
line-height: 1.6;
padding: 14px 16px;
background: rgba(108, 99, 255, 0.06);
background: var(--df-accent-bg);
border-left: 3px solid var(--df-accent);
border-radius: 0 8px 8px 0;
}
@@ -884,7 +957,7 @@ watch(() => props.idea.id, loadHistory)
font-weight: 500;
padding: 2px 8px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.12);
background: var(--df-accent-soft);
color: var(--df-accent);
}
.latest-tag {
@@ -937,7 +1010,7 @@ watch(() => props.idea.id, loadHistory)
.tag {
font-size: 12px; padding: 4px 10px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.1);
background: var(--df-accent-bg);
color: var(--df-accent);
}
@@ -946,13 +1019,13 @@ watch(() => props.idea.id, loadHistory)
.related-chip {
font-size: 12px; padding: 4px 10px;
border-radius: var(--df-radius-xs);
background: rgba(108, 99, 255, 0.1);
background: var(--df-accent-bg);
color: var(--df-accent);
text-decoration: none;
cursor: pointer;
transition: background 0.15s;
}
.related-chip:hover { background: rgba(108, 99, 255, 0.2); }
.related-chip:hover { background: var(--df-accent-soft); }
.related-chip-missing { cursor: default; opacity: 0.6; }
.related-edit-list {
@@ -971,6 +1044,52 @@ watch(() => props.idea.id, loadHistory)
}
.related-edit-item input { cursor: pointer; }
.related-edit-actions { display: flex; gap: 8px; }
/* 关联编辑搜索框 */
.related-search { margin-bottom: 8px; }
.related-search-input {
width: 100%;
box-sizing: border-box;
padding: 6px 10px;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-sm);
background: var(--df-bg);
color: var(--df-text);
font-size: 13px;
outline: none;
}
.related-search-input:focus {
border-color: var(--df-accent);
}
/* ===== 标签编辑 ===== */
.detail-tags-edit-input {
width: 100%;
box-sizing: border-box;
padding: 6px 10px;
border: 0.5px solid var(--df-border);
border-radius: var(--df-radius-sm);
background: var(--df-bg);
color: var(--df-text);
font-size: 13px;
outline: none;
}
.detail-tags-edit-input:focus {
border-color: var(--df-accent);
}
.tags-edit-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.tag-list .btn-ghost {
font-size: 11px;
padding: 2px 8px;
}
.eval-report .btn-ghost {
font-size: 11px;
padding: 2px 8px;
margin-left: 6px;
}
/* ===== 按钮已提取到 global.css 全局(.btn/.btn-primary/.btn-ghost/.btn-sm) ===== */
/* IdeaDetail 特有按钮定位 */
@@ -1057,8 +1176,8 @@ watch(() => props.idea.id, loadHistory)
font-size: 13px;
transition: background 0.15s;
}
.status-menu-item:hover { background: rgba(108, 99, 255, 0.08); }
.status-menu-item.active { background: rgba(108, 99, 255, 0.12); font-weight: 500; }
.status-menu-item:hover { background: var(--df-accent-bg); }
.status-menu-item.active { background: var(--df-accent-soft); font-weight: 500; }
/* menu 内 badge 取消 emoji 之外的全局 hover 等副作用,仅作色标 */
.status-menu-item .status-tag { cursor: pointer; }