12th_fix error
This commit is contained in:
@@ -167,11 +167,12 @@
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Document, House, Upload, List, Clock, User, Setting, Bell, Menu,
|
||||
Fold, Expand, SwitchButton, SuccessFilled, WarningFilled,
|
||||
CircleCloseFilled, InfoFilled
|
||||
CircleCloseFilled, InfoFilled, Refresh
|
||||
} from '@element-plus/icons-vue'
|
||||
import { initWebSocket, cleanupWebSocket } from '@/utils/websocket'
|
||||
|
||||
@@ -179,13 +180,16 @@ import { initWebSocket, cleanupWebSocket } from '@/utils/websocket'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
// 響應式數據
|
||||
const sidebarCollapsed = ref(false)
|
||||
const mobileSidebarVisible = ref(false)
|
||||
const notificationDrawerVisible = ref(false)
|
||||
const notifications = ref([])
|
||||
const unreadCount = ref(0)
|
||||
|
||||
// 從 store 獲取通知相關數據
|
||||
const notifications = computed(() => notificationStore.notifications)
|
||||
const unreadCount = computed(() => notificationStore.unreadCount)
|
||||
|
||||
// 計算屬性
|
||||
const currentRoute = computed(() => route)
|
||||
@@ -239,10 +243,10 @@ const handleMenuClick = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const showNotifications = () => {
|
||||
const showNotifications = async () => {
|
||||
notificationDrawerVisible.value = true
|
||||
// 可以在這裡載入最新通知
|
||||
loadNotifications()
|
||||
// 載入最新通知
|
||||
await notificationStore.fetchNotifications()
|
||||
}
|
||||
|
||||
const handleUserMenuCommand = async (command) => {
|
||||
@@ -270,50 +274,22 @@ const handleUserMenuCommand = async (command) => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotifications = async () => {
|
||||
const markAsRead = async (notificationId) => {
|
||||
try {
|
||||
// 這裡應該從 API 載入通知,目前使用模擬數據
|
||||
notifications.value = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'success',
|
||||
title: '翻譯完成',
|
||||
message: '檔案「文件.docx」翻譯完成',
|
||||
created_at: new Date().toISOString(),
|
||||
read: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'warning',
|
||||
title: '系統維護通知',
|
||||
message: '系統將於今晚 23:00 進行維護',
|
||||
created_at: new Date(Date.now() - 3600000).toISOString(),
|
||||
read: true
|
||||
}
|
||||
]
|
||||
|
||||
unreadCount.value = notifications.value.filter(n => !n.read).length
|
||||
await notificationStore.markAsRead(notificationId)
|
||||
} catch (error) {
|
||||
console.error('載入通知失敗:', error)
|
||||
console.error('標記已讀失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const markAsRead = (notificationId) => {
|
||||
const notification = notifications.value.find(n => n.id === notificationId)
|
||||
if (notification) {
|
||||
notification.read = true
|
||||
unreadCount.value = notifications.value.filter(n => !n.read).length
|
||||
const markAllAsRead = async () => {
|
||||
try {
|
||||
await notificationStore.markAllAsRead()
|
||||
} catch (error) {
|
||||
console.error('標記全部已讀失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const markAllAsRead = () => {
|
||||
notifications.value.forEach(notification => {
|
||||
notification.read = true
|
||||
})
|
||||
unreadCount.value = 0
|
||||
ElMessage.success('所有通知已標記為已讀')
|
||||
}
|
||||
|
||||
const getNotificationIcon = (type) => {
|
||||
const iconMap = {
|
||||
success: 'SuccessFilled',
|
||||
@@ -355,7 +331,7 @@ onMounted(() => {
|
||||
// initWebSocket()
|
||||
|
||||
// 載入通知
|
||||
loadNotifications()
|
||||
notificationStore.fetchNotifications()
|
||||
|
||||
// 監聽窗口大小變化
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
63
frontend/src/services/notification.js
Normal file
63
frontend/src/services/notification.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 通知相關 API 服務
|
||||
*/
|
||||
export const notificationAPI = {
|
||||
/**
|
||||
* 獲取通知列表
|
||||
* @param {Object} params - 查詢參數
|
||||
* @param {number} params.page - 頁碼
|
||||
* @param {number} params.per_page - 每頁數量
|
||||
* @param {string} params.status - 狀態過濾 ('all', 'unread', 'read')
|
||||
* @param {string} params.type - 類型過濾
|
||||
*/
|
||||
getNotifications(params = {}) {
|
||||
return request.get('/notifications', { params })
|
||||
},
|
||||
|
||||
/**
|
||||
* 獲取單個通知詳情
|
||||
* @param {string} notificationId - 通知ID
|
||||
*/
|
||||
getNotification(notificationId) {
|
||||
return request.get(`/notifications/${notificationId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 標記通知為已讀
|
||||
* @param {string} notificationId - 通知ID
|
||||
*/
|
||||
markAsRead(notificationId) {
|
||||
return request.put(`/notifications/${notificationId}/read`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 標記所有通知為已讀
|
||||
*/
|
||||
markAllAsRead() {
|
||||
return request.put('/notifications/mark-all-read')
|
||||
},
|
||||
|
||||
/**
|
||||
* 刪除通知
|
||||
* @param {string} notificationId - 通知ID
|
||||
*/
|
||||
deleteNotification(notificationId) {
|
||||
return request.delete(`/notifications/${notificationId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空所有已讀通知
|
||||
*/
|
||||
clearNotifications() {
|
||||
return request.delete('/notifications/clear')
|
||||
},
|
||||
|
||||
/**
|
||||
* 創建測試通知(開發用)
|
||||
*/
|
||||
createTestNotification() {
|
||||
return request.post('/notifications/test')
|
||||
}
|
||||
}
|
310
frontend/src/stores/notification.js
Normal file
310
frontend/src/stores/notification.js
Normal file
@@ -0,0 +1,310 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { notificationAPI } from '@/services/notification'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
export const useNotificationStore = defineStore('notification', {
|
||||
state: () => ({
|
||||
notifications: [],
|
||||
unreadCount: 0,
|
||||
loading: false,
|
||||
pagination: {
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
pages: 0
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
unreadNotifications: (state) => {
|
||||
return state.notifications.filter(notification => !notification.is_read)
|
||||
},
|
||||
|
||||
readNotifications: (state) => {
|
||||
return state.notifications.filter(notification => notification.is_read)
|
||||
},
|
||||
|
||||
hasUnreadNotifications: (state) => {
|
||||
return state.unreadCount > 0
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 獲取通知列表
|
||||
* @param {Object} params - 查詢參數
|
||||
*/
|
||||
async fetchNotifications(params = {}) {
|
||||
try {
|
||||
this.loading = true
|
||||
|
||||
const response = await notificationAPI.getNotifications({
|
||||
page: this.pagination.page,
|
||||
per_page: this.pagination.per_page,
|
||||
...params
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
this.notifications = response.data.notifications
|
||||
this.unreadCount = response.data.unread_count
|
||||
this.pagination = {
|
||||
...this.pagination,
|
||||
...response.data.pagination
|
||||
}
|
||||
|
||||
console.log('📮 [Notification] 通知列表已更新', {
|
||||
total: this.pagination.total,
|
||||
unread: this.unreadCount
|
||||
})
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 獲取通知列表失敗:', error)
|
||||
ElMessage.error('獲取通知失敗')
|
||||
throw error
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 標記通知為已讀
|
||||
* @param {string} notificationId - 通知ID
|
||||
*/
|
||||
async markAsRead(notificationId) {
|
||||
try {
|
||||
const response = await notificationAPI.markAsRead(notificationId)
|
||||
|
||||
if (response.success) {
|
||||
// 更新本地狀態
|
||||
const notification = this.notifications.find(n => n.id === notificationId)
|
||||
if (notification && !notification.is_read) {
|
||||
notification.is_read = true
|
||||
notification.read = true
|
||||
notification.read_at = new Date().toISOString()
|
||||
this.unreadCount = Math.max(0, this.unreadCount - 1)
|
||||
}
|
||||
|
||||
console.log('✅ [Notification] 通知已標記為已讀:', notificationId)
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 標記已讀失敗:', error)
|
||||
ElMessage.error('標記已讀失敗')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 標記所有通知為已讀
|
||||
*/
|
||||
async markAllAsRead() {
|
||||
try {
|
||||
const response = await notificationAPI.markAllAsRead()
|
||||
|
||||
if (response.success) {
|
||||
// 更新本地狀態
|
||||
this.notifications.forEach(notification => {
|
||||
if (!notification.is_read) {
|
||||
notification.is_read = true
|
||||
notification.read = true
|
||||
notification.read_at = new Date().toISOString()
|
||||
}
|
||||
})
|
||||
this.unreadCount = 0
|
||||
|
||||
console.log('✅ [Notification] 所有通知已標記為已讀')
|
||||
ElMessage.success(response.message || '所有通知已標記為已讀')
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 標記全部已讀失敗:', error)
|
||||
ElMessage.error('標記全部已讀失敗')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 刪除通知
|
||||
* @param {string} notificationId - 通知ID
|
||||
*/
|
||||
async deleteNotification(notificationId) {
|
||||
try {
|
||||
const response = await notificationAPI.deleteNotification(notificationId)
|
||||
|
||||
if (response.success) {
|
||||
// 從本地狀態移除
|
||||
const index = this.notifications.findIndex(n => n.id === notificationId)
|
||||
if (index !== -1) {
|
||||
const notification = this.notifications[index]
|
||||
if (!notification.is_read) {
|
||||
this.unreadCount = Math.max(0, this.unreadCount - 1)
|
||||
}
|
||||
this.notifications.splice(index, 1)
|
||||
this.pagination.total = Math.max(0, this.pagination.total - 1)
|
||||
}
|
||||
|
||||
console.log('🗑️ [Notification] 通知已刪除:', notificationId)
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 刪除通知失敗:', error)
|
||||
ElMessage.error('刪除通知失敗')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清空所有已讀通知
|
||||
*/
|
||||
async clearNotifications() {
|
||||
try {
|
||||
const response = await notificationAPI.clearNotifications()
|
||||
|
||||
if (response.success) {
|
||||
// 從本地狀態移除已讀通知
|
||||
this.notifications = this.notifications.filter(n => !n.is_read)
|
||||
this.pagination.total = this.notifications.length
|
||||
|
||||
console.log('🧹 [Notification] 已讀通知已清除')
|
||||
ElMessage.success(response.message || '已讀通知已清除')
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 清除通知失敗:', error)
|
||||
ElMessage.error('清除通知失敗')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 添加新通知(用於 WebSocket 推送)
|
||||
* @param {Object} notification - 通知數據
|
||||
*/
|
||||
addNotification(notification) {
|
||||
// 檢查是否已存在
|
||||
const exists = this.notifications.find(n => n.id === notification.id)
|
||||
if (!exists) {
|
||||
// 添加到列表開頭
|
||||
this.notifications.unshift(notification)
|
||||
|
||||
// 更新未讀數量
|
||||
if (!notification.is_read) {
|
||||
this.unreadCount += 1
|
||||
}
|
||||
|
||||
// 更新總數
|
||||
this.pagination.total += 1
|
||||
|
||||
console.log('📩 [Notification] 新通知已添加:', notification.title)
|
||||
|
||||
// 顯示通知
|
||||
ElMessage({
|
||||
type: this.getMessageType(notification.type),
|
||||
title: notification.title,
|
||||
message: notification.message,
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新通知
|
||||
* @param {Object} notification - 通知數據
|
||||
*/
|
||||
updateNotification(notification) {
|
||||
const index = this.notifications.findIndex(n => n.id === notification.id)
|
||||
if (index !== -1) {
|
||||
const oldNotification = this.notifications[index]
|
||||
|
||||
// 更新未讀數量
|
||||
if (oldNotification.is_read !== notification.is_read) {
|
||||
if (notification.is_read) {
|
||||
this.unreadCount = Math.max(0, this.unreadCount - 1)
|
||||
} else {
|
||||
this.unreadCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
// 更新通知
|
||||
this.notifications[index] = { ...oldNotification, ...notification }
|
||||
|
||||
console.log('📝 [Notification] 通知已更新:', notification.id)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 創建測試通知(開發用)
|
||||
*/
|
||||
async createTestNotification() {
|
||||
try {
|
||||
const response = await notificationAPI.createTestNotification()
|
||||
|
||||
if (response.success) {
|
||||
// 重新獲取通知列表
|
||||
await this.fetchNotifications()
|
||||
ElMessage.success('測試通知已創建')
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [Notification] 創建測試通知失敗:', error)
|
||||
ElMessage.error('創建測試通知失敗')
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 設置分頁
|
||||
* @param {number} page - 頁碼
|
||||
* @param {number} per_page - 每頁數量
|
||||
*/
|
||||
setPagination(page, per_page) {
|
||||
this.pagination.page = page
|
||||
if (per_page) {
|
||||
this.pagination.per_page = per_page
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置狀態
|
||||
*/
|
||||
reset() {
|
||||
this.notifications = []
|
||||
this.unreadCount = 0
|
||||
this.loading = false
|
||||
this.pagination = {
|
||||
total: 0,
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
pages: 0
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 獲取 ElMessage 類型
|
||||
* @param {string} type - 通知類型
|
||||
*/
|
||||
getMessageType(type) {
|
||||
const typeMap = {
|
||||
'success': 'success',
|
||||
'error': 'error',
|
||||
'warning': 'warning',
|
||||
'info': 'info',
|
||||
'system': 'info'
|
||||
}
|
||||
return typeMap[type] || 'info'
|
||||
}
|
||||
}
|
||||
})
|
@@ -1,5 +1,6 @@
|
||||
import { io } from 'socket.io-client'
|
||||
import { useJobsStore } from '@/stores/jobs'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
|
||||
/**
|
||||
@@ -93,6 +94,16 @@ class WebSocketService {
|
||||
this.socket.on('system_notification', (data) => {
|
||||
this.handleSystemNotification(data)
|
||||
})
|
||||
|
||||
// 新通知推送
|
||||
this.socket.on('new_notification', (data) => {
|
||||
this.handleNewNotification(data)
|
||||
})
|
||||
|
||||
// 系統消息
|
||||
this.socket.on('system_message', (data) => {
|
||||
this.handleSystemMessage(data)
|
||||
})
|
||||
|
||||
// 連接狀態回應
|
||||
this.socket.on('connected', (data) => {
|
||||
@@ -177,6 +188,89 @@ class WebSocketService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理新通知推送
|
||||
* @param {Object} data - 通知資料
|
||||
*/
|
||||
handleNewNotification(data) {
|
||||
try {
|
||||
console.log('📩 [WebSocket] 收到新通知:', data)
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
// 添加通知到 store
|
||||
notificationStore.addNotification(data)
|
||||
|
||||
// 顯示桌面通知
|
||||
this.showDesktopNotification(data)
|
||||
|
||||
} catch (error) {
|
||||
console.error('處理新通知失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理系統消息
|
||||
* @param {Object} data - 系統消息資料
|
||||
*/
|
||||
handleSystemMessage(data) {
|
||||
try {
|
||||
console.log('📢 [WebSocket] 收到系統消息:', data)
|
||||
|
||||
const { message, type } = data
|
||||
|
||||
// 顯示系統消息
|
||||
const messageType = type || 'info'
|
||||
ElMessage({
|
||||
type: messageType === 'system' ? 'info' : messageType,
|
||||
message: message,
|
||||
duration: 5000,
|
||||
showClose: true
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('處理系統消息失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示桌面通知
|
||||
* @param {Object} notification - 通知資料
|
||||
*/
|
||||
showDesktopNotification(notification) {
|
||||
try {
|
||||
// 檢查瀏覽器是否支援通知
|
||||
if (!('Notification' in window)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 檢查通知權限
|
||||
if (Notification.permission === 'granted') {
|
||||
new Notification(notification.title, {
|
||||
body: notification.message,
|
||||
icon: '/panjit-logo.png',
|
||||
tag: notification.id,
|
||||
requireInteraction: false
|
||||
})
|
||||
} else if (Notification.permission !== 'denied') {
|
||||
// 請求通知權限
|
||||
Notification.requestPermission().then(permission => {
|
||||
if (permission === 'granted') {
|
||||
new Notification(notification.title, {
|
||||
body: notification.message,
|
||||
icon: '/panjit-logo.png',
|
||||
tag: notification.id,
|
||||
requireInteraction: false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('顯示桌面通知失敗:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 訂閱任務狀態更新
|
||||
* @param {string} jobUuid - 任務 UUID
|
||||
|
Reference in New Issue
Block a user