From 90321a5ca49d3b0483e8efb9176e8a8e4adeec99 Mon Sep 17 00:00:00 2001 From: Orion Date: Fri, 5 Jun 2026 15:33:14 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E5=8F=96=E4=BB=B6=E6=AC=A1=E6=95=B0?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/composables/useAdminFiles.ts | 99 +++++++++++++++++++++++++++----- src/i18n/locales/en-US.ts | 4 ++ src/i18n/locales/zh-CN.ts | 4 ++ 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/composables/useAdminFiles.ts b/src/composables/useAdminFiles.ts index 8b6cdb9..d4e3f23 100644 --- a/src/composables/useAdminFiles.ts +++ b/src/composables/useAdminFiles.ts @@ -83,6 +83,7 @@ const hourInMilliseconds = 60 * 60 * 1000 const dayInMilliseconds = 24 * 60 * 60 * 1000 const adminPageSizeOptions = [5, 10, 20, 50, 100] as const const defaultAdminPageSize = 10 +const legacyUsedCountTimelineKeys = new Set(['legacy-used-count', 'legacy_used_count']) type DetailPolicyActionOption = { action: AdminFilePolicyAction @@ -595,45 +596,109 @@ export function useAdminFiles() { } } + const normalizeTimelineNumber = (value: unknown) => { + if (typeof value === 'number' && Number.isFinite(value)) return value + if (typeof value === 'string' && value.trim() !== '') { + const numberValue = Number(value) + if (Number.isFinite(numberValue)) return numberValue + } + return null + } + + const hasNumericTimelineValue = (item: AdminFileDetailTimelineItem) => + normalizeTimelineNumber(item.value) !== null + + const isLegacyUsedCountTimelineValue = (value: unknown) => + typeof value === 'string' && legacyUsedCountTimelineKeys.has(value.trim()) + + const normalizeTimelineItem = ( + item: AdminFileDetailTimelineItem, + fallbackUsedCount: number + ) => { + const normalizedItem = { ...item } + + if (legacyUsedCountTimelineKeys.has(normalizedItem.key)) { + normalizedItem.key = 'retrieved' + } + + if (isLegacyUsedCountTimelineValue(normalizedItem.value)) { + normalizedItem.value = fallbackUsedCount > 0 ? fallbackUsedCount : null + } + + return normalizedItem + } + + const getTimelineTranslationKey = (item: AdminFileDetailTimelineItem) => { + if ( + (item.key === 'retrieved' || + item.key === 'retrieved_event' || + legacyUsedCountTimelineKeys.has(item.key)) && + !hasNumericTimelineValue(item) && + !item.detail + ) { + return 'retrieved_event_unknown' + } + + return item.key + } + const formatTimelineValue = (item: AdminFileDetailTimelineItem) => { if (item.key === 'download_limit') { if (item.status === 'unlimited') return t('fileManage.unlimited') - if (typeof item.value === 'number') return t('fileManage.remaining', { count: item.value }) + const numericValue = normalizeTimelineNumber(item.value) + if (numericValue !== null) return t('fileManage.remaining', { count: numericValue }) } - if (item.key === 'retrieved' && typeof item.value === 'number') { - return `${item.value} ${t('common.times')}` + if ((item.key === 'retrieved' || item.key === 'retrieved_event') && hasNumericTimelineValue(item)) { + return `${normalizeTimelineNumber(item.value)} ${t('common.times')}` } if (item.key === 'expiration_policy') { if (item.status === 'unlimited') return t('send.expiration.units.forever') - if (typeof item.value === 'number') { - const duration = formatDuration(Math.abs(item.value), (key) => t(key)) - return item.value <= 0 + const numericValue = normalizeTimelineNumber(item.value) + if (numericValue !== null) { + const duration = formatDuration(Math.abs(numericValue), (key) => t(key)) + return numericValue <= 0 ? t('fileManage.timeline.expiration_policy.overdue', { time: duration }) : t('fileManage.timeline.expiration_policy.remaining', { time: duration }) } } if (item.detail) return item.detail - if (item.value !== null && item.value !== undefined) return String(item.value) + const numericValue = normalizeTimelineNumber(item.value) + if (numericValue !== null) return String(numericValue) return '' } - const createTimelineViewItems = (items: AdminFileDetailTimelineItem[] = []) => - items.map((item) => { + const formatTimelineTitleValue = (item: AdminFileDetailTimelineItem, valueText: string) => { + if (item.key === 'retrieved_event') { + const numericValue = normalizeTimelineNumber(item.value) + return numericValue === null ? '' : String(numericValue) + } + + return valueText + } + + const createTimelineViewItems = ( + items: AdminFileDetailTimelineItem[] = [], + fallbackUsedCount = 0 + ) => + items.map((rawItem) => { + const item = normalizeTimelineItem(rawItem, fallbackUsedCount) + const timelineKey = getTimelineTranslationKey(item) const timestampText = item.timestamp ? formatTimestamp(item.timestamp) : '' const valueText = formatTimelineValue(item) + const titleValue = formatTimelineTitleValue(item, valueText) const metaParts = [timestampText, valueText].filter(Boolean) return { ...item, severity: normalizeInsightSeverity(item.severity), - displayTitle: t(`fileManage.timeline.${item.key}.title`, { + displayTitle: t(`fileManage.timeline.${timelineKey}.title`, { status: t(`fileManage.timeline.status.${item.status || 'pending'}`), detail: item.detail || '', - value: valueText || item.value || '' + value: titleValue }), - displayDescription: t(`fileManage.timeline.${item.key}.description`, { + displayDescription: t(`fileManage.timeline.${timelineKey}.description`, { status: t(`fileManage.timeline.status.${item.status || 'pending'}`), detail: item.detail || '', value: valueText || '' @@ -701,6 +766,14 @@ export function useAdminFiles() { (viewItem.isExpiredFile ? 'expired' : isPermanentFile ? 'permanent' : 'available') const statusInsightNextAction = statusInsights?.nextAction ?? statusInsights?.next_action ?? 'monitor' + const usedCountValue = normalizeCount( + detail.usedCount ?? + detail.used_count ?? + statusInsights?.metrics?.usedCount ?? + statusInsights?.metrics?.used_count ?? + file.usedCount ?? + file.used_count + ) const metadata = detail.metadata ?? detail.meta const metadataNote = detail.note ?? metadata?.note ?? '' const metadataTags = normalizeMetadataTags(detail.tags ?? metadata?.tags) @@ -742,7 +815,7 @@ export function useAdminFiles() { statusInsightNextAction, statusInsightReasons: statusInsights?.reasons || [], statusInsightMetrics: statusInsights?.metrics, - detailTimeline: createTimelineViewItems(detail.timeline || []) + detailTimeline: createTimelineViewItems(detail.timeline || [], usedCountValue) } } diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 67859b6..584e374 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -618,6 +618,10 @@ export default { retrieved_event: { title: 'Retrieval #{value}', description: 'This retrieval is complete; current status is {status}.' + }, + retrieved_event_unknown: { + title: 'Retrieval History', + description: 'This retrieval is complete; current status is {status}.' } }, headers: { diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 0b46a10..db11836 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -615,6 +615,10 @@ export default { retrieved_event: { title: '第 {value} 次取件', description: '本次取件已完成,当前状态为 {status}。' + }, + retrieved_event_unknown: { + title: '取件记录', + description: '本次取件已完成,当前状态为 {status}。' } }, headers: {