feat(workbench): persist topbar notification state

This commit is contained in:
caoxiaozhu
2026-06-03 21:43:35 +08:00
parent b9826a1985
commit 75d5c178e1
15 changed files with 799 additions and 59 deletions

View File

@@ -316,6 +316,7 @@
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useDocumentCenterInbox } from '../../composables/useDocumentCenterInbox.js'
import { useTopBarNotificationStates } from '../../composables/useTopBarNotificationStates.js'
import EnterpriseSelect from '../shared/EnterpriseSelect.vue'
const props = defineProps({
@@ -400,8 +401,6 @@ const eyebrowLabel = computed(() => (
|| (isChat.value ? 'Smart Finance Q&A' : 'Smart Expense Operations')
))
const displayCompanyName = computed(() => String(props.companyName || '远光软件股份有限公司').trim() || '远光软件股份有限公司')
const NOTIFICATION_READ_STORAGE_KEY = 'x-financial.topbar.notifications.read'
const NOTIFICATION_HIDDEN_STORAGE_KEY = 'x-financial.topbar.notifications.hidden'
const MAX_NOTIFICATION_ITEMS = 30
const {
markDocumentInboxRowRead,
@@ -412,46 +411,21 @@ const {
stopDocumentInboxPolling
} = useDocumentCenterInbox()
let documentInboxInitialRefreshTimer = null
const readNotificationIds = ref(readNotificationIdSet(NOTIFICATION_READ_STORAGE_KEY))
const hiddenNotificationIds = ref(readNotificationIdSet(NOTIFICATION_HIDDEN_STORAGE_KEY))
const notificationOpen = ref(false)
const {
readNotificationIds,
hideNotificationStates,
isNotificationHidden,
isNotificationRead,
loadNotificationStates,
markNotificationStateRead
} = useTopBarNotificationStates()
const notificationTab = ref('unread')
function readNotificationIdSet(storageKey) {
if (typeof window === 'undefined') {
return new Set()
}
try {
const parsed = JSON.parse(window.localStorage.getItem(storageKey) || '[]')
return new Set(Array.isArray(parsed) ? parsed.map((item) => String(item || '').trim()).filter(Boolean) : [])
} catch {
return new Set()
}
}
function writeNotificationIdSet(storageKey, values) {
if (typeof window === 'undefined') {
return
}
window.localStorage.setItem(storageKey, JSON.stringify(Array.from(values).filter(Boolean)))
}
function updateNotificationIdSet(targetRef, storageKey, updater) {
const next = updater(new Set(targetRef.value))
targetRef.value = next
writeNotificationIdSet(storageKey, next)
}
function normalizeNotificationId(value) {
return String(value || '').trim()
}
function isNotificationHidden(id) {
return hiddenNotificationIds.value.has(normalizeNotificationId(id))
}
function formatNotificationTime(value) {
const date = new Date(value)
if (!Number.isFinite(date.getTime())) {
@@ -492,6 +466,7 @@ const documentNotificationItems = computed(() =>
if (!id || isNotificationHidden(id)) {
return null
}
const unread = Boolean(row.isUnread) && !isNotificationRead(id)
return {
id,
@@ -500,10 +475,10 @@ const documentNotificationItems = computed(() =>
description: resolveDocumentNotificationDescription(row),
time: formatNotificationTime(row.updatedAt || row.createdAt),
category: row.sourceLabel || '单据中心',
tone: resolveDocumentNotificationTone(row),
unread: Boolean(row.isUnread),
tone: resolveDocumentNotificationTone({ ...row, isUnread: unread }),
unread,
icon: row.source === 'approval' ? 'mdi mdi-clipboard-text-clock-outline' : 'mdi mdi-file-document-outline',
badge: row.isUnread ? '新' : '',
badge: unread ? '新' : '',
target: {
type: 'document',
id: row.claimId,
@@ -593,13 +568,9 @@ function markNotificationRead(item) {
if (item.kind === 'document' && item.documentRow) {
markDocumentInboxRowRead(item.documentRow)
return
}
updateNotificationIdSet(readNotificationIds, NOTIFICATION_READ_STORAGE_KEY, (next) => {
next.add(item.id)
return next
})
void markNotificationStateRead(item)
}
function clearAllNotifications() {
@@ -616,10 +587,7 @@ function clearAllNotifications() {
markDocumentInboxRowsRead(documentRows)
}
updateNotificationIdSet(hiddenNotificationIds, NOTIFICATION_HIDDEN_STORAGE_KEY, (next) => {
currentItems.forEach((item) => next.add(item.id))
return next
})
void hideNotificationStates(currentItems)
notificationTab.value = 'unread'
}
@@ -827,12 +795,20 @@ watch(
(activeView, previousView) => {
if (activeView === 'workbench' && previousView !== 'workbench') {
clearDocumentInboxInitialRefreshTimer()
void loadNotificationStates()
void refreshDocumentInbox({ force: true })
}
}
)
watch(notificationOpen, (open) => {
if (open) {
void loadNotificationStates()
}
})
onMounted(() => {
void loadNotificationStates()
scheduleDocumentInboxInitialRefresh()
startDocumentInboxPolling()
})