修复: 全栈审查 FR-C1~C5 五项正确性 bug

- FR-C1 formattedEvents 时间漂移:事件入数组固 _ts 时间戳(project.ts push + ProjectDetail 用 _ts),computed 重算不再刷历史时间为当前时刻
- FR-C2 sentiment 三档统一:模板 >0/<0/===0 + sentimentClass 同源 + i18n neutral(zh/en),原模板>0 与 class>=0 矛盾
- FR-C3 Settings timer 泄漏:onUnmounted 清全部三个(原仅 _concurrencyTimer)
- FR-C4 MIGRATION_VERSION 死常量:删(零代码引用,run() if 链自管版本)
- FR-C5 AiConversationDetail 缺 readonly:加 readonly?: boolean(后端 generating 时返回)
来源 fullstack-review §3(已核实);cargo/vue-tsc 0 err
This commit is contained in:
2026-06-14 17:00:33 +08:00
parent 3e1f119003
commit cf18678634
8 changed files with 18 additions and 11 deletions

View File

@@ -100,7 +100,7 @@
</div>
<p class="final-score">{{ $t('ideas.finalScore', { score: adversarialEval.final_score.toFixed(1) }) }}</p>
<div class="net-sentiment" :class="sentimentClass(adversarialEval.net_sentiment)">
{{ $t('ideas.netSentiment', { tone: adversarialEval.net_sentiment > 0 ? $t('ideas.sentimentPositive') : $t('ideas.sentimentNegative'), n: (adversarialEval.net_sentiment * 100).toFixed(0) }) }}
{{ $t('ideas.netSentiment', { tone: adversarialEval.net_sentiment > 0 ? $t('ideas.sentimentPositive') : (adversarialEval.net_sentiment < 0 ? $t('ideas.sentimentNegative') : $t('ideas.sentimentNeutral')), n: (adversarialEval.net_sentiment * 100).toFixed(0) }) }}
</div>
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
</div>
@@ -366,7 +366,10 @@ function assessmentLabel(recommendation: string) {
}
function sentimentClass(sentiment: number) {
return sentiment >= 0 ? 'positive' : 'negative'
// 统一三档(FR-C2: 原 >=0 与模板 >0 矛盾net_sentiment=0 时文案负面样式 positive)
if (sentiment > 0) return 'positive'
if (sentiment < 0) return 'negative'
return 'neutral'
}
function scoreClass(score: number | null) {

View File

@@ -212,7 +212,6 @@ import { formatDate } from '../utils/time'
import { parseStack } from '../utils/project'
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import type { WorkflowEventPayload } from '../api/types'
const route = useRoute()
const router = useRouter()
@@ -296,12 +295,13 @@ interface LogEntry {
}
const formattedEvents = computed<LogEntry[]>(() => {
return store.liveEvents.map((evt: WorkflowEventPayload) => {
return store.liveEvents.map((evt) => {
const ev = evt.event
const type = ev.type ?? 'unknown'
const level = type.includes('error') || type.includes('fail') ? 'error'
: type.includes('warn') ? 'warn' : 'info'
const time = new Date().toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' })
// 用事件入数组时固化的时间戳(FR-C1),非 computed 重算的当前时刻
const time = new Date(evt._ts).toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' })
const message = ev.label
? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`
: `${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`

View File

@@ -745,9 +745,11 @@ onMounted(() => {
syncConcurrencyConfig()
})
// 组件卸载清 debounce timer,防 unmount 后旧 timer 仍 fire 发无效 IPC(timer 泄漏)
// 组件卸载清全部 debounce timer,防 unmount 后旧 timer 仍 fire 写已销毁 reactive(timer 泄漏 FR-C3)
onUnmounted(() => {
if (_concurrencyTimer) clearTimeout(_concurrencyTimer)
if (_knowledgeSaveTimer) clearTimeout(_knowledgeSaveTimer)
if (_toastTimer) clearTimeout(_toastTimer)
})
</script>