优化: 前端DRY收敛与命名对齐

审批等待格式化与阈值沉 utils/time 正典
相对时间阶梯三套收敛 formatRelative 参数化
灵感状态表沉 constants,透传通道退役
aggText/clearOwn 等命名对齐,IPC 参数名统一 conversation_id
This commit is contained in:
lxy
2026-10-01 19:35:20 +08:00
parent 6ef15a0940
commit 0560137840
16 changed files with 111 additions and 97 deletions
+2 -2
View File
@@ -52,7 +52,7 @@ import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import ToolCard from './ToolCard.vue'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { aggregateCalls, aggText } from '@/composables/ai/useTurnTrace'
import { aggregateCalls, turnSummaryText } from '@/composables/ai/useTurnTrace'
import type { AiToolCallInfo } from '@/api/types'
// useI18n needed for $t() in template (collapseAll / expandAll) + groupDisplayName(i18n key)
@@ -230,7 +230,7 @@ function groupDisplayName(group: ToolCallGroup): string {
/** 分组聚合行文案:「N 次调用 · 成功 x 失败 y」(零值项省略;与单卡状态点同源 isToolFailure 判定) */
function groupAggText(group: ToolCallGroup): string {
return aggText(aggregateCalls(group.calls))
return turnSummaryText(aggregateCalls(group.calls))
}
/**
+3 -3
View File
@@ -21,7 +21,7 @@ import { useMarkdown } from '../../composables/useMarkdown'
import { useMessageScroll } from '../../composables/ai/useMessageScroll'
import { useStreamRenderer } from '../../composables/ai/useStreamRenderer'
import { loadMoreHistory, getLoadMoreState } from '../../composables/ai/useAiConversations'
import { collectTurnStats, aggText, type TurnCallStats } from '../../composables/ai/useTurnTrace'
import { collectTurnStats, turnSummaryText, type TurnCallStats } from '../../composables/ai/useTurnTrace'
import ToolCardList from '../ToolCardList.vue'
import MessageItem from './MessageItem.vue'
import EmptyState from './EmptyState.vue'
@@ -741,9 +741,9 @@ function toggleTrace(key: string): void {
expandedTraceIds.value = next
}
/** 轨迹卡聚合行:「N 次调用 · 成功 x 失败 y · 总耗时 T」(拼装统一走 useTurnTrace.aggText) */
/** 轨迹卡聚合行:「N 次调用 · 成功 x 失败 y · 总耗时 T」(拼装统一走 useTurnTrace.turnSummaryText) */
function traceAggText(stats: TurnCallStats, startTs: number, endTs: number): string {
return aggText(stats, endTs - startTs)
return turnSummaryText(stats, endTs - startTs)
}
/** 轨迹卡 hover:绝对起止时刻(相对时长之外可溯源具体时间) */
+7 -10
View File
@@ -19,7 +19,7 @@
>
🚀 {{ $t('ideas.promotedProject') }} →
</router-link>
<span class="status-tag" :class="'status-' + idea.status">{{ $t(statusLabelKey(idea.status)) }}</span>
<span class="status-tag" :class="'status-' + idea.status">{{ $t(ideaStatusLabelKey(idea.status)) }}</span>
</div>
</div>
<!-- B-260615-25:灵感描述 Markdown 渲染,复用 useMarkdown composable(同 B-24 TaskDetail),空值回退 — -->
@@ -268,7 +268,7 @@
:class="'status-' + idea.status"
@click="statusMenuOpen = !statusMenuOpen"
>
{{ $t(statusLabelKey(idea.status)) }}
{{ $t(ideaStatusLabelKey(idea.status)) }}
<span class="status-caret" :class="{ 'is-open': statusMenuOpen }">▾</span>
</button>
<ul v-show="statusMenuOpen" class="status-menu">
@@ -306,6 +306,7 @@ import { useRendered } from '../../composables/useMarkdown'
import { ideaApi } from '../../api'
import { formatDate } from '../../utils/time'
import { parseScores, assessmentClass, assessmentLabel, scoreTier } from '../../utils/ideaEval'
import { IDEA_STATUS_OPTIONS, ideaStatusLabelKey } from '../../constants/project'
import type { IdeaRecord, IdeaStatus, IdeaEvaluationRecord } from '../../api/types'
const props = defineProps<{
@@ -314,7 +315,6 @@ const props = defineProps<{
evalError: string
promoting: boolean
deleting: boolean
statusOptions: { value: IdeaStatus; labelKey: string }[]
}>()
const emit = defineEmits<{
@@ -390,11 +390,11 @@ watch(() => props.idea.id, () => {
watch(() => props.idea.description, () => void nextTick(measureDesc))
// P1-④ 状态切换 select → badge 点击:点击当前 badge 展开/收起下拉菜单,
// 选中项后 emit('status-change') 交父组件确认+落库(沿用既有契约,不破坏 statusOptions 来源)。
// 选中项后 emit('status-change') 交父组件确认+落库(沿用既有契约)。
const statusMenuOpen = ref(false)
// 状态下拉排除 promoted(立项走独立 promoteToProject 流程,列表状态菜单同排除,
// 见 Ideas.vue onStatusChange 兜底);statusOptions 仍含 promoted 供 label 映射(已转立项的灵感 badge 正常显示)。
const menuStatusOptions = computed(() => props.statusOptions.filter(s => s.value !== 'promoted'))
// 见 Ideas.vue onStatusChange 兜底);状态表(含 promoted 供 badge label 映射)单一数据源在 @/constants/project。
const menuStatusOptions = computed(() => IDEA_STATUS_OPTIONS.filter(s => s.value !== 'promoted'))
function closeStatusMenu() {
statusMenuOpen.value = false
}
@@ -515,10 +515,7 @@ function cancelTagsEdit() {
tagsEditing.value = false
}
// 任意 status 字符串 → 对应 i18n key;未知状态回退到原值显示
function statusLabelKey(status: IdeaStatus): string {
return props.statusOptions.find(s => s.value === status)?.labelKey ?? status
}
// 任意 status → i18n key 的映射走 @/constants/project ideaStatusLabelKey(与列表同源)
// 灵感描述 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例),
// useRendered 封装 computed(读 mdReady 触发响应式 + renderMd)+ ensureLoaded(幂等预热)
+4 -11
View File
@@ -147,6 +147,7 @@ import {
knowledgeConfidenceLabel as confidenceLabel,
} from '@/stores/knowledge'
import { useRendered } from '@/composables/useMarkdown'
import { formatRelative } from '@/utils/time'
import type { KnowledgeDetailPayload, KnowledgeEventRecord } from '@/api/types'
// 详情面板子组件(Knowledge.vue 拆分,SW-260618-20):
@@ -317,20 +318,12 @@ const timelineNodes = computed(() => {
return nodes
})
// ===== 标签/时间辅助(kind*/status/confidence label 由 @/stores/knowledge 共用,相对时间本组件自留 30 天截止)=====
// 相对时间:复用 common.* 既有 key(justNow/minutesAgo/hoursAgo/dayAgo),30 天以上回退绝对值
// ===== 标签/时间辅助(kind*/status/confidence label 由 @/stores/knowledge 共用)=====
// 相对时间:走 @/utils/time formatRelative(30 天以上回退绝对日期,档位与全局同源)
function relativeTime(millisStr: string): string {
const ms = Number(millisStr)
if (!ms) return ''
const diff = Date.now() - ms
const min = Math.floor(diff / 60000)
if (min < 1) return t('common.justNow')
if (min < 60) return t('common.minutesAgo', { n: min })
const hr = Math.floor(min / 60)
if (hr < 24) return t('common.hoursAgo', { n: hr })
const day = Math.floor(hr / 24)
if (day < 30) return t('common.dayAgo', { n: day })
return t('common.ago', { time: new Date(ms).toLocaleDateString() })
return formatRelative(ms, { absAfterDays: 30 })
}
// 切换知识时重置引用列表「加载更多」上限 + 折叠态(对齐原 Knowledge.vue selectKnowledge 内 refLimit=10 行为)
+3 -11
View File
@@ -165,6 +165,7 @@
*/
import { ref, computed, watch } from 'vue'
import { moduleApi, type GitStatusResult } from '@/api/module'
import { formatRelative } from '@/utils/time'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
@@ -274,19 +275,10 @@ function statusLabel(s: string): string {
return x.charAt(0) || '?'
}
/** 格式化 Unix 时间戳为相对时间或具体时间 */
/** 格式化 Unix 时间戳为相对时间或具体时间(阶梯走 @/utils/time formatRelative:昨天档 + 30 天截断 ISO 日期) */
function formatTime(ts: number): string {
if (!ts) return ''
const now = Math.floor(Date.now() / 1000)
const diffSec = now - ts
if (diffSec < 60) return t('common.justNow')
if (diffSec < 3600) return t('common.minutesAgo', { n: Math.floor(diffSec / 60) })
if (diffSec < 86400) return t('common.hoursAgo', { n: Math.floor(diffSec / 3600) })
if (diffSec < 172800) return t('common.yesterday')
if (diffSec < 2592000) return t('common.dayAgo', { n: Math.floor(diffSec / 86400) })
// 超过 30 天显示具体日期
const d = new Date(ts * 1000)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
return formatRelative(ts, { yesterday: true, absAfterDays: 30, absStyle: 'iso' })
}
/** 计算文件路径的目录深度(用于缩进)。root 级文件返回 0。 */