12th_fix error

This commit is contained in:
beabigegg
2025-09-04 09:44:13 +08:00
parent d638d682b7
commit 5662fcc039
19 changed files with 1735 additions and 50 deletions

View File

@@ -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)

View 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')
}
}

View 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'
}
}
})

View File

@@ -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