优化: 审批弹窗全局化与体验打磨

This commit is contained in:
lxy
2026-08-10 08:04:30 +08:00
parent dedb9a8862
commit 6141a67b0e
10 changed files with 159 additions and 47 deletions
+43 -12
View File
@@ -322,9 +322,11 @@ const emit = defineEmits<{
(e: 'promote'): void
(e: 'delete'): void
(e: 'status-change', value: IdeaStatus): void
(e: 'update-desc', value: string): void
(e: 'update-related', ids: string[]): void
(e: 'update-tags', tags: string): void
// WK-16:返 Promise<boolean> 让子组件 await 知成功/失败。
// true=落库成功(关编辑态);false=失败(保持编辑态,toast 由父组件处理)。
(e: 'update-desc', value: string): Promise<boolean>
(e: 'update-related', ids: string[]): Promise<void>
(e: 'update-tags', tags: string): Promise<boolean>
}>()
const { t } = useI18n()
@@ -341,16 +343,15 @@ function startEdit() {
editing.value = true
}
// P1-⑥ saveEdit 改异步:emit 父组件 store.updateIdea 是 Promise,但 emit 不回传结果;
// 这里用本地 savingDesc 包裹一个最小延迟窗口(等待父组件 patch 同步 props.idea.description
// 完成),保证 UI 反馈一致。父组件 updateIdea 失败会 throw 但不冒泡回子组件(沿用既有契约)。
// P1-⑥ saveEdit 改异步:emit 返回 Promise<boolean>(父组件 store.updateIdea 落库结果),
// 仅成功时关编辑态;失败保持编辑态 + toast 由父组件处理(WK-16)。
async function saveEdit() {
if (savingDesc.value) return
savingDesc.value = true
try {
emit('update-desc', editDesc.value)
editing.value = false
// 等下一帧让父组件 patch 生效再释放(微观反馈,失败也释放不卡死)
const ok = await emit('update-desc', editDesc.value)
if (ok) editing.value = false
// 成功后等下一帧让父组件 patch 生效再释放(微观反馈,失败也释放不卡死)
await nextTick()
} finally {
savingDesc.value = false
@@ -501,8 +502,9 @@ async function saveTagsEdit() {
savingTags.value = true
try {
const tags = tagsEditInput.value.split(',').map(t => t.trim()).filter(Boolean)
emit('update-tags', JSON.stringify(tags))
tagsEditing.value = false
// WK-16:emit 返回落库结果,失败保持编辑态(成功才关),toast 由父组件处理
const ok = await emit('update-tags', JSON.stringify(tags))
if (ok) tagsEditing.value = false
await nextTick()
} finally {
savingTags.value = false
@@ -545,13 +547,42 @@ interface AdversarialEval {
}
// 对抗式评估数据持久化在 ai_analysis JSON 字段中,前端解析渲染
//
// WK-15:旧实现直接 JSON.parse 后强转 AdversarialEval,字段缺失/非数值时
// 模板对 undefined*100 调 .toFixed 显 NaN,assessmentClass 对 undefined 调
// .toLowerCase() 崩。这里在解析层补齐数值默认值(防御式归一化),模板直读不二次判空:
// - 数值字段缺失/非数值 → 0
// - recommendation/thesis 缺失 → ''(assessmentClass/assessmentLabel 已在源头对空串防御)
// - 数组字段缺失 → []
const adversarialEval = computed<AdversarialEval | null>(() => {
if (!props.idea.ai_analysis) return null
let raw: any
try {
return JSON.parse(props.idea.ai_analysis) as AdversarialEval
raw = JSON.parse(props.idea.ai_analysis)
} catch {
return null
}
if (typeof raw !== 'object' || raw === null) return null
const num = (v: unknown): number => typeof v === 'number' && !isNaN(v) ? v : 0
const str = (v: unknown): string => typeof v === 'string' ? v : ''
const strArr = (v: unknown): string[] => Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []
const thesis = (v: unknown): { thesis: string; evidence: string[] } => {
const o = (typeof v === 'object' && v !== null) ? v as Record<string, unknown> : {}
return { thesis: str(o.thesis), evidence: strArr(o.evidence) }
}
return {
positive_strength: num(raw.positive_strength),
negative_strength: num(raw.negative_strength),
net_sentiment: num(raw.net_sentiment),
recommendation: str(raw.recommendation),
final_score: num(raw.final_score),
summary: str(raw.summary),
action_items: strArr(raw.action_items),
positive: thesis(raw.positive),
negative: thesis(raw.negative),
analyst: { summary: str((typeof raw.analyst === 'object' && raw.analyst !== null ? (raw.analyst as Record<string, unknown>).summary : undefined)) },
evaluated_by: typeof raw.evaluated_by === 'string' ? raw.evaluated_by : undefined,
}
})
// 评估深度标签动态化(P0):根据 evaluated_by 字段映射 i18n key