// src/main.js import Vue from "vue"; import Cookies from "js-cookie"; import "babel-polyfill"; import Element from "element-ui"; import "./assets/styles/element-variables.scss"; import "@/assets/styles/index.scss"; import "@/assets/styles/ruoyi.scss"; import App from "./App"; import store from "./store"; import router from "./router"; import directive from "./directive"; import plugins from "./plugins"; import { download } from "@/utils/request"; import Print from "vue-print-nb"; import JsonExcel from "vue-json-excel"; import "./assets/icons"; import "./permission"; import { getDicts } from "@/api/system/dict/data"; import { getConfigKey, yidu } from "@/api/system/config"; import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree, } from "@/utils/ruoyi"; import Pagination from "@/components/Pagination"; import Editor from "@/components/Editor"; import FileUpload from "@/components/FileUpload"; import ImageUpload from "@/components/ImageUpload"; import ImagePreview from "@/components/ImagePreview"; import DictTag from "@/components/DictTag"; import VueMeta from "vue-meta"; import DictData from "@/components/DictData"; import * as echarts from "echarts"; import VueBarcode from "vue-barcode"; import { initWebSocket, closeWebSocket } from "@/utils/websocket"; import RightToolbar from "@/components/RightToolbar" // 注册全局组件 Vue.component("downloadExcel", JsonExcel); Vue.component("barcode", VueBarcode); Vue.component("DictTag", DictTag); Vue.component("Pagination", Pagination); Vue.component("Editor", Editor); Vue.component("FileUpload", FileUpload); Vue.component("ImageUpload", ImageUpload); Vue.component("ImagePreview", ImagePreview); Vue.component("RightToolbar", RightToolbar); // 注册全局方法 Vue.prototype.getDicts = getDicts; Vue.prototype.getConfigKey = getConfigKey; Vue.prototype.parseTime = parseTime; Vue.prototype.resetForm = resetForm; Vue.prototype.addDateRange = addDateRange; Vue.prototype.selectDictLabel = selectDictLabel; Vue.prototype.selectDictLabels = selectDictLabels; Vue.prototype.download = download; Vue.prototype.handleTree = handleTree; Vue.prototype.$echarts = echarts; // 通知管理:跟踪当前通知和偏移量 const notificationManager = { notifications: [], // 存储当前显示的通知实例 baseOffset: 50, // 基础偏移量 notificationHeight: 80, // 每个通知的估计高度(包括间距) maxNotifications: 5, // 最大同时显示的通知数量 addNotification(notification) { if (this.notifications.length >= this.maxNotifications) { // 关闭最早的通知 const oldest = this.notifications.shift(); oldest.close(); } this.notifications.push(notification); // 设置动态 offset 和 z-index notification.offset = this.baseOffset + this.notifications.length * this.notificationHeight; notification.customClass += ` notification-${this.notifications.length}`; // 为 z-index 添加唯一类 // 监听通知关闭 notification.onClose = () => { const index = this.notifications.indexOf(notification); if (index > -1) { this.notifications.splice(index, 1); // 更新后续通知的 offset this.updateOffsets(); } }; }, updateOffsets() { this.notifications.forEach((notification, index) => { notification.offset = this.baseOffset + (index + 1) * this.notificationHeight; notification.customClass = notification.customClass.replace(/notification-\d+/, `notification-${index + 1}`); }); } }; // 全局通知方法,添加“已读”按钮 Vue.prototype.$showNotification = function (type, title, message, onClick, noticeId) { console.log('触发通知:', { type, title, message, noticeId, noticeIdType: typeof noticeId }); // 调试:记录 noticeId 和类型 const h = this.$createElement; const notification = this.$notify({ title: title, message: h('div', { class: 'notification-content' }, [ h('div', { class: 'notification-message' }, message), noticeId ? h('el-button', { class: 'read-button', style: { marginLeft: '10px', float: 'right', cursor: 'pointer' }, props: { type: 'primary', size: 'mini' }, on: { click: async () => { console.log('点击“已读”按钮,noticeId:', noticeId, 'type:', typeof noticeId); // 调试:记录点击时的 noticeId try { await yidu({ noticeId: String(noticeId) }); console.log(`通知 ${noticeId} 已标记为已读`); this.$message.success('标记为已读成功'); notification.close(); } catch (error) { console.error('标记已读失败:', error, 'noticeId:', noticeId); this.$message.error('标记已读失败'); } } } }, '已读') : null ]), type, duration: 5000, position: 'top-right', offset: notificationManager.baseOffset, // 初始 offset onClick: null, // 不跳转 customClass: 'global-notification', dangerouslyUseHTMLString: false, appendTo: document.body }); notificationManager.addNotification(notification); // 添加到通知管理 }; // 监听路由变化 router.afterEach(() => { console.log('路由切换完成,当前路径:', router.currentRoute.path); }); // 定义 WebSocket 初始化标志,防止重复连接 let isWebSocketInitialized = false; const app = new Vue({ el: "#app", router, store, render: (h) => h(App), mounted() { const token = store.state.user.token || Cookies.get('token') || ''; if (token && !isWebSocketInitialized) { console.log('初始化 WebSocket,Token:', token); isWebSocketInitialized = true; initWebSocket(token, (type, data) => { console.log('WebSocket 收到消息:', { type, data }); // 调试:记录原始数据 if (type === 'error') { Vue.prototype.$showNotification.call(this, 'error', '错误', data); return; } try { if (typeof data === 'string' && data.trim().startsWith('{')) { // 替换大整数字段,防止精度丢失 const normalizedData = data.replace(/"(noticeId|notice_id|id)":\s*(\d+)/g, '"$1":"$2"'); const message = JSON.parse(normalizedData); console.log('WebSocket 解析后消息:', message); // 调试:记录解析后的消息 if (message.noticeId || message.notice_id || message.id) { const noticeTypeLabel = message.noticeType === '1' ? '通知' : '公告'; const noticeTitle = message.noticeTitle ? message.noticeTitle.replace(/<[^>]+>/g, '') : '无标题'; const contentPreview = message.noticeContent ? message.noticeContent.replace(/<[^>]+>/g, '').substring(0, 20) + '...' : '无内容'; const noticeId = String(message.noticeId || message.notice_id || message.id); console.log('准备触发通知,noticeId:', noticeId, 'type:', typeof noticeId); // 调试:记录传递的 noticeId Vue.prototype.$showNotification.call( this, 'success', `新${noticeTypeLabel}`, `${noticeTitle} - ${contentPreview}`, null, noticeId ); } else { console.log('未知消息类型:', message); Vue.prototype.$showNotification.call(this, 'info', '消息', '收到未知格式的消息'); } } else { console.log('WebSocket 非 JSON 消息:', data); } } catch (error) { console.error('消息解析失败:', error, '原始数据:', data); Vue.prototype.$showNotification.call(this, 'error', '消息解析失败', `服务器回应字符串: ${data}`); } }); } else if (!token) { console.error('未找到 token,无法初始化 WebSocket'); } else { console.log('WebSocket 已初始化,跳过重复连接'); } }, beforeDestroy() { if (isWebSocketInitialized) { closeWebSocket(); isWebSocketInitialized = false; console.log('Vue 实例销毁,WebSocket 已清理'); } } }); // 注册插件 Vue.use(directive); Vue.use(plugins); Vue.use(VueMeta); Vue.use(Print); Vue.use(Element, { size: Cookies.get("size") || "medium", }); DictData.install(); Vue.config.productionTip = false;