Industrial-image-management.../src/services/notificationService.ts

696 lines
21 KiB
TypeScript
Raw Normal View History

import { ref, computed } from 'vue'
export interface NotificationItem {
id: string
type: 'APPROVAL' | 'PENDING' | 'SYSTEM' | 'PROCUREMENT' | 'EQUIPMENT_BORROW' | 'EQUIPMENT_RETURN' | 'EQUIPMENT_MAINTENANCE' | 'EQUIPMENT_ALERT' | 'WORKFLOW'
title: string
content: string
createTime: string
read: boolean
targetUrl?: string
priority?: 'LOW' | 'NORMAL' | 'HIGH' | 'URGENT'
category?: string
metadata?: Record<string, any>
// 新增字段
source?: string // 消息来源
actionRequired?: boolean // 是否需要操作
expiresAt?: string // 过期时间
relatedId?: string // 关联的业务ID
// 新增智能提醒字段
reminderTime?: string // 提醒时间
reminderType?: 'IMMEDIATE' | 'DELAYED' | 'RECURRING' // 提醒类型
reminderInterval?: number // 重复提醒间隔(分钟)
lastReminderTime?: string // 上次提醒时间
}
class NotificationService {
private notifications = ref<NotificationItem[]>([])
// 计算属性
public readonly unreadCount = computed(() =>
this.notifications.value.filter(n => !n.read).length
)
public readonly pendingCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'PENDING' && !n.read
).length
)
public readonly approvalCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'APPROVAL' && !n.read
).length
)
public readonly procurementCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'PROCUREMENT' && !n.read
).length
)
// 新增:设备借用通知数量
public readonly equipmentBorrowCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'EQUIPMENT_BORROW' && !n.read
).length
)
// 新增:设备归还通知数量
public readonly equipmentReturnCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'EQUIPMENT_RETURN' && !n.read
).length
)
// 新增:设备维护通知数量
public readonly equipmentMaintenanceCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'EQUIPMENT_MAINTENANCE' && !n.read
).length
)
// 新增:设备告警通知数量
public readonly equipmentAlertCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'EQUIPMENT_ALERT' && !n.read
).length
)
// 新增:工作流通知数量
public readonly workflowCount = computed(() =>
this.notifications.value.filter(n =>
n.type === 'WORKFLOW' && !n.read
).length
)
// 新增:需要操作的通知数量
public readonly actionRequiredCount = computed(() =>
this.notifications.value.filter(n =>
n.actionRequired && !n.read
).length
)
// 新增:紧急通知数量
public readonly urgentCount = computed(() =>
this.notifications.value.filter(n =>
n.priority === 'URGENT' && !n.read
).length
)
// 新增:今日通知数量
public readonly todayCount = computed(() => {
const today = new Date()
today.setHours(0, 0, 0, 0)
return this.notifications.value.filter(n => {
const createDate = new Date(n.createTime)
return createDate >= today
}).length
})
// 获取所有通知
public getAllNotifications() {
return this.notifications.value
}
// 获取未读通知
public getUnreadNotifications() {
return this.notifications.value.filter(n => !n.read)
}
// 获取特定类型的通知
public getNotificationsByType(type: NotificationItem['type']) {
return this.notifications.value.filter(n => n.type === type)
}
// 新增:获取需要操作的通知
public getActionRequiredNotifications() {
return this.notifications.value.filter(n => n.actionRequired && !n.read)
}
// 新增:获取紧急通知
public getUrgentNotifications() {
return this.notifications.value.filter(n => n.priority === 'URGENT' && !n.read)
}
// 新增:获取今日通知
public getTodayNotifications() {
const today = new Date()
today.setHours(0, 0, 0, 0)
return this.notifications.value.filter(n => {
const createDate = new Date(n.createTime)
return createDate >= today
})
}
// 新增:获取相关通知
public getRelatedNotifications(relatedId: string) {
return this.notifications.value.filter(n => n.relatedId === relatedId)
}
// 新增:获取需要提醒的通知
public getReminderNotifications() {
const now = new Date()
return this.notifications.value.filter(n => {
if (!n.reminderTime) return false
const reminderDate = new Date(n.reminderTime)
if (reminderDate <= now) {
// 检查重复提醒
if (n.reminderType === 'RECURRING' && n.reminderInterval) {
const lastReminder = n.lastReminderTime ? new Date(n.lastReminderTime) : reminderDate
const nextReminder = new Date(lastReminder.getTime() + n.reminderInterval * 60 * 1000)
return nextReminder <= now
}
return true
}
return false
})
}
// 新增:设置通知提醒
public setNotificationReminder(notificationId: string, reminderTime: string, reminderType: 'IMMEDIATE' | 'DELAYED' | 'RECURRING' = 'IMMEDIATE', reminderInterval?: number) {
const notification = this.notifications.value.find(n => n.id === notificationId)
if (notification) {
notification.reminderTime = reminderTime
notification.reminderType = reminderType
notification.reminderInterval = reminderInterval
this.saveToStorage()
}
}
// 新增:取消通知提醒
public cancelNotificationReminder(notificationId: string) {
const notification = this.notifications.value.find(n => n.id === notificationId)
if (notification) {
notification.reminderTime = undefined
notification.reminderType = undefined
notification.reminderInterval = undefined
notification.lastReminderTime = undefined
this.saveToStorage()
}
}
// 新增:标记提醒已处理
public markReminderProcessed(notificationId: string) {
const notification = this.notifications.value.find(n => n.id === notificationId)
if (notification && notification.reminderType === 'RECURRING') {
notification.lastReminderTime = new Date().toISOString()
this.saveToStorage()
}
}
// 添加通知
public addNotification(notification: Omit<NotificationItem, 'id' | 'createTime' | 'read'>) {
const newNotification: NotificationItem = {
...notification,
id: this.generateId(),
createTime: new Date().toISOString(),
read: false,
// 设置默认值
priority: notification.priority || 'NORMAL',
actionRequired: notification.actionRequired || false,
source: notification.source || 'SYSTEM'
}
this.notifications.value.unshift(newNotification)
// 限制通知数量,避免内存泄漏
if (this.notifications.value.length > 200) {
this.notifications.value = this.notifications.value.slice(0, 200)
}
// 触发事件
this.emitNotificationEvent('add', newNotification)
// 自动保存
this.saveToStorage()
// 检查是否需要播放提示音
if (newNotification.priority === 'URGENT' || newNotification.priority === 'HIGH') {
this.playNotificationSound()
}
return newNotification
}
// 标记通知为已读
public markAsRead(id: string) {
const notification = this.notifications.value.find(n => n.id === id)
if (notification) {
notification.read = true
this.emitNotificationEvent('read', notification)
this.saveToStorage()
}
}
// 标记所有通知为已读
public markAllAsRead() {
this.notifications.value.forEach(n => n.read = true)
this.emitNotificationEvent('readAll', null)
this.saveToStorage()
}
// 标记特定类型通知为已读
public markTypeAsRead(type: NotificationItem['type']) {
this.notifications.value
.filter(n => n.type === type)
.forEach(n => n.read = true)
this.emitNotificationEvent('readType', { type })
this.saveToStorage()
}
// 新增:标记相关通知为已读
public markRelatedAsRead(relatedId: string) {
this.notifications.value
.filter(n => n.relatedId === relatedId)
.forEach(n => n.read = true)
this.emitNotificationEvent('readRelated', { relatedId })
this.saveToStorage()
}
// 删除通知
public removeNotification(id: string) {
const index = this.notifications.value.findIndex(n => n.id === id)
if (index > -1) {
const notification = this.notifications.value[index]
this.notifications.value.splice(index, 1)
this.emitNotificationEvent('remove', notification)
this.saveToStorage()
}
}
// 清空所有通知
public clearAll() {
this.notifications.value = []
this.emitNotificationEvent('clear', null)
this.saveToStorage()
}
// 清空已读通知
public clearRead() {
this.notifications.value = this.notifications.value.filter(n => !n.read)
this.emitNotificationEvent('clearRead', null)
this.saveToStorage()
}
// 新增:清空过期通知
public clearExpired() {
const now = new Date()
const beforeCount = this.notifications.value.length
this.notifications.value = this.notifications.value.filter(n => {
if (!n.expiresAt) return true
const expireDate = new Date(n.expiresAt)
return expireDate > now
})
const afterCount = this.notifications.value.length
if (beforeCount !== afterCount) {
this.emitNotificationEvent('clearExpired', { clearedCount: beforeCount - afterCount })
this.saveToStorage()
}
}
// 获取通知统计
public getStats() {
const total = this.notifications.value.length
const unread = this.unreadCount.value
const pending = this.pendingCount.value
const approval = this.approvalCount.value
const procurement = this.procurementCount.value
const actionRequired = this.actionRequiredCount.value
const urgent = this.urgentCount.value
const today = this.todayCount.value
return {
total,
unread,
pending,
approval,
procurement,
actionRequired,
urgent,
today,
read: total - unread
}
}
// 搜索通知
public searchNotifications(keyword: string) {
if (!keyword.trim()) {
return this.notifications.value
}
const lowerKeyword = keyword.toLowerCase()
return this.notifications.value.filter(n =>
n.title.toLowerCase().includes(lowerKeyword) ||
n.content.toLowerCase().includes(lowerKeyword) ||
n.category?.toLowerCase().includes(lowerKeyword) ||
n.source?.toLowerCase().includes(lowerKeyword)
)
}
// 按优先级排序
public sortByPriority() {
const priorityOrder = { 'URGENT': 4, 'HIGH': 3, 'NORMAL': 2, 'LOW': 1 }
this.notifications.value.sort((a, b) => {
const aPriority = priorityOrder[a.priority || 'NORMAL'] || 2
const bPriority = priorityOrder[b.priority || 'NORMAL'] || 2
if (aPriority !== bPriority) {
return bPriority - aPriority
}
// 优先级相同时,按时间倒序
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
})
}
// 新增:按时间排序
public sortByTime() {
this.notifications.value.sort((a, b) =>
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
)
}
// 新增:按类型分组
public groupByType() {
const groups: Record<string, NotificationItem[]> = {}
this.notifications.value.forEach(notification => {
const type = notification.type
if (!groups[type]) {
groups[type] = []
}
groups[type].push(notification)
})
return groups
}
// 添加采购申请通知
public addProcurementNotification(equipmentName: string, applicantName: string) {
return this.addNotification({
type: 'PROCUREMENT',
title: '新的设备采购申请',
content: `收到来自 ${applicantName} 的设备采购申请:${equipmentName}`,
targetUrl: '/asset-management/device-management/approval',
priority: 'HIGH',
category: '设备采购',
actionRequired: true,
source: 'PROCUREMENT_SYSTEM',
metadata: {
equipmentName,
applicantName,
timestamp: Date.now()
}
})
}
// 添加审批结果通知
public addApprovalNotification(equipmentName: string, result: 'APPROVED' | 'REJECTED', approverName: string) {
const isApproved = result === 'APPROVED'
return this.addNotification({
type: 'APPROVAL',
title: `采购申请${isApproved ? '已通过' : '已拒绝'}`,
content: `您的设备采购申请"${equipmentName}"${isApproved ? '已通过' : '被拒绝'},审批人:${approverName}`,
targetUrl: '/asset-management/device-management/procurement',
priority: isApproved ? 'NORMAL' : 'HIGH',
category: '审批结果',
actionRequired: !isApproved, // 被拒绝时需要操作
source: 'APPROVAL_SYSTEM',
metadata: {
equipmentName,
result,
approverName,
timestamp: Date.now()
}
})
}
// 添加系统通知
public addSystemNotification(title: string, content: string, priority: NotificationItem['priority'] = 'NORMAL') {
return this.addNotification({
type: 'SYSTEM',
title,
content,
priority,
category: '系统通知',
source: 'SYSTEM',
actionRequired: false
})
}
// 新增:添加待审批通知
public addPendingApprovalNotification(equipmentName: string, applicantName: string, businessType: string) {
return this.addNotification({
type: 'PENDING',
title: '新的审批申请',
content: `收到来自 ${applicantName}${businessType}申请:${equipmentName}`,
targetUrl: '/asset-management/device-management/approval',
priority: 'HIGH',
category: '审批申请',
actionRequired: true,
source: 'APPROVAL_SYSTEM',
metadata: {
equipmentName,
applicantName,
businessType,
timestamp: Date.now()
}
})
}
// 新增:添加设备借用通知
public addEquipmentBorrowNotification(equipmentName: string, borrowerName: string, borrowReason: string) {
return this.addNotification({
type: 'EQUIPMENT_BORROW',
title: '新的设备借用申请',
content: `收到来自 ${borrowerName} 的设备借用申请:${equipmentName},借用原因:${borrowReason}`,
targetUrl: '/asset-management/device-management/device-center',
priority: 'HIGH',
category: '设备借用',
actionRequired: true,
source: 'EQUIPMENT_SYSTEM',
metadata: {
equipmentName,
borrowerName,
borrowReason,
timestamp: Date.now()
}
})
}
// 新增:添加设备归还通知
public addEquipmentReturnNotification(equipmentName: string, returnerName: string, returnCondition: string) {
return this.addNotification({
type: 'EQUIPMENT_RETURN',
title: '设备归还申请',
content: `收到来自 ${returnerName} 的设备归还申请:${equipmentName},归还状态:${returnCondition}`,
targetUrl: '/asset-management/device-management/device-center',
priority: 'NORMAL',
category: '设备归还',
actionRequired: false,
source: 'EQUIPMENT_SYSTEM',
metadata: {
equipmentName,
returnerName,
returnCondition,
timestamp: Date.now()
}
})
}
// 新增:添加设备维护通知
public addEquipmentMaintenanceNotification(equipmentName: string, maintenanceType: string, estimatedDuration: string) {
return this.addNotification({
type: 'EQUIPMENT_MAINTENANCE',
title: '设备维护申请',
content: `设备 ${equipmentName} 申请${maintenanceType}维护,预计时长:${estimatedDuration}`,
targetUrl: '/asset-management/device-management/device-center',
priority: 'NORMAL',
category: '设备维护',
actionRequired: true,
source: 'EQUIPMENT_SYSTEM',
metadata: {
equipmentName,
maintenanceType,
estimatedDuration,
timestamp: Date.now()
}
})
}
// 新增:添加工作流通知
public addWorkflowNotification(workflowName: string, currentNode: string, actionRequired: string) {
return this.addNotification({
type: 'WORKFLOW',
title: '工作流待处理',
content: `工作流"${workflowName}"当前节点:${currentNode},需要操作:${actionRequired}`,
targetUrl: '/asset-management/device-management/approval',
priority: 'HIGH',
category: '工作流',
actionRequired: true,
source: 'WORKFLOW_SYSTEM',
metadata: {
workflowName,
currentNode,
actionRequired,
timestamp: Date.now()
}
})
}
// 新增:播放通知提示音
private playNotificationSound() {
try {
// 创建音频上下文
const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
const oscillator = audioContext.createOscillator()
const gainNode = audioContext.createGain()
oscillator.connect(gainNode)
gainNode.connect(audioContext.destination)
oscillator.frequency.setValueAtTime(800, audioContext.currentTime)
oscillator.frequency.setValueAtTime(600, audioContext.currentTime + 0.1)
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime)
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3)
oscillator.start(audioContext.currentTime)
oscillator.stop(audioContext.currentTime + 0.3)
} catch (error) {
console.warn('无法播放通知提示音:', error)
}
}
// 事件系统
private eventListeners: Map<string, Function[]> = new Map()
public on(event: string, callback: Function) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, [])
}
this.eventListeners.get(event)!.push(callback)
}
public off(event: string, callback: Function) {
const listeners = this.eventListeners.get(event)
if (listeners) {
const index = listeners.indexOf(callback)
if (index > -1) {
listeners.splice(index, 1)
}
}
}
private emitNotificationEvent(event: string, data: any) {
const listeners = this.eventListeners.get(event)
if (listeners) {
listeners.forEach(callback => {
try {
callback(data)
} catch (error) {
console.error('通知事件回调执行失败:', error)
}
})
}
}
// 生成唯一ID
private generateId(): string {
return `notification_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
// 持久化存储
public saveToStorage() {
try {
localStorage.setItem('notifications', JSON.stringify(this.notifications.value))
} catch (error) {
console.error('保存通知到本地存储失败:', error)
}
}
public loadFromStorage() {
try {
const stored = localStorage.getItem('notifications')
if (stored) {
this.notifications.value = JSON.parse(stored)
// 清理过期通知
this.clearExpired()
// 重新排序
this.sortByPriority()
}
} catch (error) {
console.error('从本地存储加载通知失败:', error)
}
}
// 自动保存
public enableAutoSave() {
// 监听通知变化,自动保存
this.on('add', () => this.saveToStorage())
this.on('read', () => this.saveToStorage())
this.on('remove', () => this.saveToStorage())
this.on('clear', () => this.saveToStorage())
}
// 新增:定期清理过期通知
public enableAutoCleanup() {
// 每小时清理一次过期通知
setInterval(() => {
this.clearExpired()
}, 60 * 60 * 1000)
}
// 新增:导出通知数据
public exportNotifications() {
try {
const data = JSON.stringify(this.notifications.value, null, 2)
const blob = new Blob([data], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `notifications_${new Date().toISOString().split('T')[0]}.json`
a.click()
URL.revokeObjectURL(url)
} catch (error) {
console.error('导出通知失败:', error)
}
}
// 新增:导入通知数据
public importNotifications(data: string) {
try {
const notifications = JSON.parse(data)
if (Array.isArray(notifications)) {
this.notifications.value = [...this.notifications.value, ...notifications]
this.saveToStorage()
return true
}
return false
} catch (error) {
console.error('导入通知失败:', error)
return false
}
}
}
// 创建单例实例
const notificationService = new NotificationService()
// 启用自动保存
notificationService.enableAutoSave()
// 启用自动清理
notificationService.enableAutoCleanup()
// 从本地存储加载通知
notificationService.loadFromStorage()
export default notificationService