优化: 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:
@@ -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; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user