vue2项目集成websocket

在utils文件下新建一个websocket.js文件

javascript 复制代码
import { updateMcCenter } from "@/api/mes/mc/mcCenter";
import { Message, Notification } from "element-ui";

// 读取环境变量
const envWsUrl = process.env.VUE_APP_WS_URL;

// 全局配置
const WS_CONFIG = {
  URL_PREFIX: envWsUrl || "",
  HEARTBEAT_INTERVAL: 30000,
  RECONNECT_DELAY: 5000,
};

// 全局通知容器配置
const NOTIFICATION_CONFIG = {
  containerId: 'global-notification-container',
  maxHeight: '500px',
  position: 'bottom-right',
  notificationQueue: [] // 存储Notification实例
};

let socket = null;
let lockReconnect = false;
let heartTimer = null;
let routerInstance = null;
let storeInstance = null;
let currentUserId = null;

/**
 * 创建全局通知容器
 */
const createNotificationContainer = () => {
  // 如果容器已存在,先移除
  const existingContainer = document.getElementById(NOTIFICATION_CONFIG.containerId);
  if (existingContainer) {
    existingContainer.remove();
  }

  // 创建容器
  const container = document.createElement('div');
  container.id = NOTIFICATION_CONFIG.containerId;
  container.className = 'global-notification-container';

  // 设置样式
  container.style.cssText = `
    position: fixed;
    ${NOTIFICATION_CONFIG.position.includes('right') ? 'right: 16px;' : 'left: 16px;'}
    ${NOTIFICATION_CONFIG.position.includes('bottom') ? 'bottom: 16px;' : 'top: 16px;'}
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-height: ${NOTIFICATION_CONFIG.maxHeight};
    overflow-y: auto;
    width: 380px;
  `;

  document.body.appendChild(container);
  return container;
};

/**
 * 创建自定义通知元素(替换Element UI的Notification)
 */
const createCustomNotification = (msg, notificationId) => {
  const container = document.getElementById(NOTIFICATION_CONFIG.containerId);
  if (!container) return null;

  // 创建通知元素
  const notificationEl = document.createElement('div');
  notificationEl.id = notificationId;
  notificationEl.className = 'custom-notification';
  notificationEl.dataset.msgId = msg.msgId;
  // console.log("创建通知:", msg);
  // 设置样式
  notificationEl.style.cssText = `
    background: white;
    border-radius: 8px;
    padding: 16px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  `;

  // 构建HTML内容
  notificationEl.innerHTML = `
    <div class="notification-header" style="position: relative; margin-bottom: 8px;">
      <h4 style="margin: 0 0 8px 0; color: #303133; font-size: 14px;">
        ${msg.msgType || '系统通知'}
      </h4>
      <button class="close-btn" style="
        background: none;
        border: none;
        color: #909399;
        cursor: pointer;
        font-size: 16px;
        font-weight: bold;
        line-height: 1;
        position: absolute;
        top: 0px;
        right: 0px;
      ">×</button>
    </div>
    <div class="notification-body">
      <div style="color: #606266; font-size: 13px; line-height: 1.5; margin-bottom: 12px;">
        ${msg.msgContent || '新消息'}
      </div>
      <div style="text-align: right;">
        <button class="view-btn" style="
          padding: 5px 10px;
          background: #409EFF;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 12px;
        ">
          <i style="margin-right: 4px;">👁️</i>去查看
        </button>
      </div>
    </div>
  `;

  // 添加到容器顶部
  container.insertBefore(notificationEl, container.firstChild);

  // 添加事件监听
  const closeBtn = notificationEl.querySelector('.close-btn');
  const viewBtn = notificationEl.querySelector('.view-btn');

  const notificationObj = {
    element: notificationEl,
    msgId: msg.msgId,
    notificationId: notificationId,
    close: () => {
      // notificationEl.style.animation = 'slideOut 0.3s ease';
      setTimeout(() => {
        if (notificationEl.parentNode) {
          notificationEl.remove();
        }
        removeFromNotificationQueue(msg.msgId);
      }, 300);
    }
  };

  // 关闭按钮点击事件
  closeBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    notificationObj.close();
    // markAsRead(msg);
  });

  // 查看按钮点击事件
  viewBtn.addEventListener('click', (e) => {
    e.stopPropagation();
    handleNotificationJump(msg);
    // notificationObj.close();
  });

  // 整个通知点击事件(点击空白处)
  notificationEl.addEventListener('click', (e) => {
    if (e.target === notificationEl || e.target.classList.contains('notification-body')) {
      handleNotificationJump(msg);
      // notificationObj.close();
    }
  });

  // 添加到队列
  NOTIFICATION_CONFIG.notificationQueue.push(notificationObj);

  return notificationObj;
};

/**
 * 从通知队列中移除
 */
const removeFromNotificationQueue = (msgId) => {
  const index = NOTIFICATION_CONFIG.notificationQueue.findIndex(item => item.msgId === msgId);
  if (index !== -1) {
    NOTIFICATION_CONFIG.notificationQueue.splice(index, 1);
  }
};

/**
 * 初始化 WebSocket
 */
export const initWebSocket = (router, store) => {
  routerInstance = router;
  storeInstance = store;

  if (!WS_CONFIG.URL_PREFIX) {
    console.error("[WebSocket] 未配置 VUE_APP_WS_URL,无法连接");
    return;
  }

  const user = store.state.user;
  if (!user || !user.id) {
    console.warn("[WebSocket] 用户未登录,跳过连接");
    return;
  }

  if (socket && currentUserId === user.id) {
    return;
  }

  if (socket) {
    closeWebSocket();
  }

  currentUserId = user.id;

  // 创建通知容器
  createNotificationContainer();

  createWebSocket();
};

/**
 * 创建 WebSocket 连接
 */
const createWebSocket = () => {
  try {
    const wsUrl = WS_CONFIG.URL_PREFIX + currentUserId;
    console.log("[WebSocket] 正在连接:", wsUrl);

    socket = new WebSocket(wsUrl);
    initEventHandle();
  } catch (e) {
    console.error("[WebSocket] 连接创建失败:", e);
    reconnect();
  }
};

/**
 * 初始化事件监听
 */
const initEventHandle = () => {
  socket.onopen = () => {
    console.log("[WebSocket] 连接成功");
    startHeartbeat();
  };

  socket.onmessage = (e) => {
    startHeartbeat();

    if (e.data === "PONG") {
      return;
    }

    try {
      const msg = JSON.parse(e.data);
      console.log("[WebSocket] 收到消息:", msg);
      handleMessage(msg);
    } catch (err) {
      console.error("[WebSocket] 消息解析失败:", err);
    }
  };

  socket.onclose = (e) => {
    console.log("[WebSocket] 连接关闭", e);
    reconnect();
  };

  socket.onerror = (e) => {
    console.log("[WebSocket] 连接错误", e);
    reconnect();
  };
};

/**
 * 断线重连
 */
const reconnect = () => {
  if (lockReconnect) return;
  lockReconnect = true;

  clearTimeout(heartTimer);

  if (!storeInstance || !storeInstance.state.user || !storeInstance.state.user.id) {
    lockReconnect = false;
    return;
  }

  console.log(`[WebSocket] ${WS_CONFIG.RECONNECT_DELAY / 1000}秒后尝试重连...`);
  setTimeout(() => {
    createWebSocket();
    lockReconnect = false;
  }, WS_CONFIG.RECONNECT_DELAY);
};

/**
 * 心跳检测
 */
const startHeartbeat = () => {
  clearTimeout(heartTimer);
  heartTimer = setTimeout(() => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send("PING");
    }
  }, WS_CONFIG.HEARTBEAT_INTERVAL);
};

/**
 * 主动关闭连接
 */
export const closeWebSocket = () => {
  if (socket) {
    socket.close();
    socket = null;
  }
  clearTimeout(heartTimer);
  lockReconnect = false;
  currentUserId = null;

  // 清空所有通知
  clearAllNotifications();
  console.log("[WebSocket] 主动关闭连接");
};

/**
 * 清空所有通知
 */
const clearAllNotifications = () => {
  NOTIFICATION_CONFIG.notificationQueue.forEach(item => {
    if (item.close) {
      item.close();
    }
  });
  NOTIFICATION_CONFIG.notificationQueue = [];

  // 移除容器
  const container = document.getElementById(NOTIFICATION_CONFIG.containerId);
  if (container) {
    container.remove();
  }
};

/**
 * 标记消息为已读
 */
const markAsRead = (msg) => {
  return updateMcCenter({
    msgType: msg.msgType,
    msgStatus: 1,
    msgId: msg.msgId,
  });
};

/**
 * 核心业务逻辑:处理消息弹窗
 */
const handleMessage = (msg) => {
  const user = storeInstance.state.user;
  // 逻辑判断:是否应该显示通知
  // const isTargetUser =
  //   (msg.msgOrigin === "ERP下发" &&
  //     (msg.msgType === "下单通知" || msg.msgType === "结构报价单通知")) ||
  //   user.id === msg.recipientId;

  // if (!isTargetUser) return;

  // 生成唯一ID
  const notificationId = `notify-${Date.now()}`;

  // 创建自定义通知(替换Element UI Notification)
  createCustomNotification(msg, notificationId);
};

/**
 * 跳转逻辑封装
 */
const handleNotificationJump = (row) => {
  // console.log(row)
  if (!row.msgId) {
    Message.warning("消息数据不完整");
    return;
  }
  routerInstance.push(`/index?msgId=${row.msgId}`);
};

在main.js引入

javascript 复制代码
// 引入刚才创建的 socket 工具
import { initWebSocket } from "@/utils/websocket";
new Vue({
  el: "#app",
  router,
  store,
  i18n,
  created() {
    // 应用启动时尝试连接
    initWebSocket(router, store);
  },
  render: (h) => h(App),
});

还可以加入全局弹窗组件

在websocket.js文件中引入组件

javascript 复制代码
// 引入刚才创建的 socket 工具
import MessageDetailDialog from '@/components/msgCenter/MessageDialog.vue';
在Notification的点击事件中判断什么时候需要弹窗如:
onClick: () => {
// 打开 MessageDetailDialog 组件
	if (msg.msgType === "订单未接单提醒") {
		openMessageDetailDialog(msg.msgId);
	}
})


// 创建 MessageDetailDialog 组件的实例并挂载到 body 上
function openMessageDetailDialog(msgId) {
  const MessageDetailConstructor = Vue.extend(MessageDetailDialog);
  const instance = new MessageDetailConstructor({
    propsData: {
      visible: true,
      messageId: msgId
    }
  });

  instance.$mount(); // 挂载组件
  document.body.appendChild(instance.$el);

  instance.$on('update:visible', (val) => {
    if (!val) {
      document.body.removeChild(instance.$el);
      instance.$destroy();
    }
  });
}

弹窗组件

javascript 复制代码
<template>
    <el-dialog
      title="消息详情"
      :visible.sync="dialogVisible"
      width="50%"
      :before-close="handleClose"
      :close-on-click-modal="false"
    >
    <div v-if="messageDetail" class="message-content">

      </div>
      <div v-else>
        <p>加载中...</p>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="handleClose">关闭</el-button>
      </span>
    </el-dialog>
  </template>

  <script>
  import { getMessageDetail } from '@/api/msg/msgCenter';

  export default {
  dicts: ['vehicle_type'],

    props: {
      visible: {
        type: Boolean,
        default: false,
      },
      messageId: {
        type: Number,
        required: true,
      },
    },
    data() {
      return {
        dialogVisible: this.visible,
        messageDetail: null,
      };
    },
    created() {
    this.$watch('visible', (newVal) => {
      console.log('watch 监听到 visible 变化:', newVal);
      this.dialogVisible = newVal;
      if (newVal) {
        this.fetchMessageDetail();
      }
    }, { immediate: true });
  },
    methods: {
      handleClose() {
        this.$emit('update:visible', false);
      },
      fetchMessageDetail() {
        console.log("654645");

        if (!this.messageId) {
          console.error('messageId 为空,无法获取消息详情');
          return;
        }
        console.log("666");

        getMessageDetail(this.messageId).then(response => {
            this.messageDetail = response.data;
          })
          .catch(error => {
            console.error('获取消息详情失败', error);
            this.$message.error('获取消息详情失败');
          });
      },
    },
  };
  </script>

以此记录

相关推荐
相思难忘成疾4 小时前
华为HCIP:MPLS实验
网络·华为·智能路由器·hcip
CS创新实验室4 小时前
《计算机网络》深入学:IPv4 协议架构与演进
网络·计算机网络·架构·ipv4
坐怀不乱杯魂5 小时前
Linux网络 - UDP/TCP底层
linux·服务器·网络·c++·tcp/ip·udp
DevilSeagull5 小时前
HTTP/HTTPS数据包拓展
网络·网络协议·http·https·web渗透·we
Hello.Reader5 小时前
Flink 弹性伸缩(Elastic Scaling)Adaptive Scheduler、Reactive Mode 与外部资源声明
服务器·网络·flink
REDcker5 小时前
HTTP 状态码清单大全
网络·网络协议·http
小白电脑技术5 小时前
如何修改电脑名称及其实际作用
运维·网络·电脑
酣大智5 小时前
TCP与UDP协议
运维·网络·网络协议·tcp/ip
汤愈韬5 小时前
DHCP Server服务器拒绝攻击、DHCP Server服务器拒绝攻击防范、端口安全
网络协议·网络安全·security