diff --git a/web/src/components/layout/TopBar.vue b/web/src/components/layout/TopBar.vue index 5ad4387..3a30cca 100644 --- a/web/src/components/layout/TopBar.vue +++ b/web/src/components/layout/TopBar.vue @@ -204,18 +204,22 @@ - - - {{ item.title }} - {{ item.badge }} + + + + {{ item.title }} + {{ item.badge }} + + - {{ item.description }} - - {{ item.category || '系统通知' }} - + {{ item.description }} + + {{ item.category || '系统通知' }} + -
@@ -516,12 +520,28 @@ function normalizeNotificationId(value) { return String(value || '').trim() } -function formatNotificationTime(value) { - const date = new Date(value) - if (!Number.isFinite(date.getTime())) { +function formatNotificationTimeLabel(value) { + const raw = String(value || '').trim() + if (!raw) { return '最近更新' } + const normalized = raw.replace('T', ' ') + const isoMatched = normalized.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})/) + if (isoMatched) { + return `${isoMatched[2]}-${isoMatched[3]} ${isoMatched[4]}:${isoMatched[5]}` + } + + const shortMatched = normalized.match(/^(\d{2})-(\d{2})\s+(\d{2}):(\d{2})/) + if (shortMatched) { + return `${shortMatched[1]}-${shortMatched[2]} ${shortMatched[3]}:${shortMatched[4]}` + } + + const date = new Date(raw) + if (!Number.isFinite(date.getTime())) { + return raw.length > 16 ? `${raw.slice(0, 16)}...` : raw + } + const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hour = String(date.getHours()).padStart(2, '0') @@ -560,13 +580,14 @@ const documentNotificationItems = computed(() => return { id, - kind: 'document', - title: `${row.documentTypeLabel || '单据'} ${row.documentNo || row.claimId || '待生成'}`, - description: resolveDocumentNotificationDescription(row), - time: formatNotificationTime(row.updatedAt || row.createdAt), - category: row.sourceLabel || '单据中心', - tone: resolveDocumentNotificationTone({ ...row, isUnread: unread }), - unread, + kind: 'document', + title: `${row.documentTypeLabel || '单据'} ${row.documentNo || row.claimId || '待生成'}`, + description: resolveDocumentNotificationDescription(row), + time: row.updatedAt || row.createdAt, + timeLabel: formatNotificationTimeLabel(row.updatedAt || row.createdAt), + category: row.sourceLabel || '单据中心', + tone: resolveDocumentNotificationTone({ ...row, isUnread: unread }), + unread, icon: row.source === 'approval' ? 'mdi mdi-clipboard-text-clock-outline' : 'mdi mdi-file-document-outline', badge: unread ? '新' : '', target: { @@ -587,12 +608,15 @@ const workbenchNotificationItems = computed(() => ( if (!id || isNotificationHidden(id)) { return null } + const notificationTime = item.time || item.updatedAt || item.due return { ...item, id, kind: 'workbench', category: item.category || '个人工作台', + time: notificationTime, + timeLabel: formatNotificationTimeLabel(item.time || item.updatedAt || item.due), unread: Boolean(item.unread) && !readNotificationIds.value.has(id), icon: item.icon || resolveNotificationIcon(item) } diff --git a/web/tests/sidebar-document-unread-dot.test.mjs b/web/tests/sidebar-document-unread-dot.test.mjs index 93e9de1..0eee735 100644 --- a/web/tests/sidebar-document-unread-dot.test.mjs +++ b/web/tests/sidebar-document-unread-dot.test.mjs @@ -96,6 +96,25 @@ test('topbar bell owns document center unread notifications', () => { assert.doesNotMatch(topbarStyles, /\.notification-dot/) }) +test('topbar notification popover uses inbox-style rows with formatted time labels', () => { + assert.match(topbar, /class="notification-row-main"/) + assert.match(topbar, /class="notification-row-head"/) + assert.match(topbar, /class="notification-row-title"/) + assert.match(topbar, /class="notification-context"/) + assert.match(topbar, /class="notification-row-foot"/) + assert.match(topbar, /class="notification-category-pill"/) + assert.match(topbar, /class="notification-time"/) + assert.match(topbar, /class="notification-row-action"/) + assert.match(topbar, /timeLabel:\s*formatNotificationTimeLabel\(row\.updatedAt \|\| row\.createdAt\)/) + assert.match(topbar, /timeLabel:\s*formatNotificationTimeLabel\(item\.time \|\| item\.updatedAt \|\| item\.due\)/) + assert.doesNotMatch(topbar, /