"use strict"; const common_vendor = require("../../common/vendor.js"); const sheep_index = require("../../sheep/index.js"); function useChatWebSocket(socketConfig) { let SocketIo = null; const state = common_vendor.reactive({ chatDotNum: 0, //总状态红点 chatList: [], //会话信息 customerUserInfo: {}, //用户信息 customerServerInfo: { //客服信息 title: "连接中...", state: "connecting", avatar: null, nickname: "" }, socketState: { isConnect: true, //是否连接成功 isConnecting: false, //重连中,不允许新的socket开启。 tip: "" }, chatHistoryPagination: { page: 0, //当前页 list_rows: 10, //每页条数 last_id: 0, //最后条ID lastPage: 0, //总共多少页 loadStatus: "loadmore" //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态 }, templateChatList: [], //猜你想问 chatConfig: {}, // 配置信息 isSendSucces: -1 // 是否发送成功 -1=发送中|0=发送成功|1发送失败 }); const socketInit = (config, callBack) => { state.chatConfig = config; if (SocketIo && SocketIo.connected) return; if (state.socketState.isConnecting) return; SocketIo = common_vendor.io(config.chat_domain, { reconnection: true, // 默认 true 是否断线重连 reconnectionAttempts: 5, // 默认无限次 断线尝试次数 reconnectionDelay: 1e3, // 默认 1000,进行下一次重连的间隔。 reconnectionDelayMax: 5e3, // 默认 5000, 重新连接等待的最长时间 默认 5000 randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间 timeout: 2e4, // 默认 20s transports: ["websocket", "polling"], // websocket | polling, ...config }); SocketIo.on("connect", async (res) => { socketReset(callBack); console.log("socket:connect"); }); SocketIo.on("message", (res) => { if (res.error === 0) { res.data; state.chatList.push(formatMessage(res.data.message)); callBack && callBack(); } }); SocketIo.on("customer_service_access", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: res.data.customer_service.name, state: "online", avatar: res.data.customer_service.avatar }); state.chatList.push(formatMessage(res.data.message)); } }); SocketIo.on("waiting_queue", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: res.data.title, state: "waiting", avatar: "" }); } }); SocketIo.on("no_customer_service", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: "暂无客服在线...", state: "waiting", avatar: "" }); } state.chatList.push(formatMessage(res.data.message)); }); SocketIo.on("customer_service_online", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: res.data.customer_service.name, state: "online", avatar: res.data.customer_service.avatar }); } }); SocketIo.on("customer_service_offline", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: res.data.customer_service.name, state: "offline", avatar: res.data.customer_service.avatar }); } }); SocketIo.on("customer_service_busy", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: res.data.customer_service.name, state: "busy", avatar: res.data.customer_service.avatar }); } }); SocketIo.on("customer_service_break", (res) => { if (res.error === 0) { editCustomerServerInfo({ title: "客服服务结束", state: "offline", avatar: "" }); state.socketState.isConnect = false; state.socketState.tip = "当前服务已结束"; } state.chatList.push(formatMessage(res.data.message)); }); SocketIo.on("custom_error", (error) => { editCustomerServerInfo({ title: error.msg, state: "offline", avatar: "" }); console.log("custom_error:", error); }); SocketIo.on("error", (error) => { console.log("error:", error); }); SocketIo.on("connect_error", (error) => { console.log("connect_error"); }); SocketIo.on("connect_timeout", (error) => { console.log(error, "connect_timeout"); }); SocketIo.on("disconnect", (error) => { console.log(error, "disconnect"); }); SocketIo.on("reconnect", (error) => { console.log(error, "reconnect"); }); SocketIo.on("reconnect_attempt", (error) => { state.socketState.isConnect = false; state.socketState.isConnecting = true; editCustomerServerInfo({ title: `重连中,第${error}次尝试...`, state: "waiting", avatar: "" }); console.log(error, "reconnect_attempt"); }); SocketIo.on("reconnecting", (error) => { console.log(error, "reconnecting"); }); SocketIo.on("reconnect_error", (error) => { console.log("reconnect_error"); }); SocketIo.on("reconnect_failed", (error) => { state.socketState.isConnecting = false; editCustomerServerInfo({ title: `重连失败,请刷新重试~`, state: "waiting", avatar: "" }); console.log(error, "reconnect_failed"); state.isSendSucces = 1; }); }; const socketReset = (callBack) => { state.chatList = []; state.chatHistoryList = []; state.chatHistoryPagination = { page: 0, per_page: 10, last_id: 0, totalPage: 0 }; socketConnection(callBack); }; const socketClose = () => { SocketIo.emit("customer_logout", {}, (res) => { console.log("socket:退出", res); }); }; const socketTest = () => { SocketIo.emit("test", {}, (res) => { console.log("test:test", res); }); }; const socketSendMsg = (data, sendMsgCallBack) => { state.isSendSucces = -1; state.chatList.push(data); sendMsgCallBack && sendMsgCallBack(); SocketIo.emit( "message", { message: formatInput(data), ...data.customData }, (res) => { state.isSendSucces = res.error; } ); }; const socketConnection = (callBack) => { SocketIo.emit( "connection", { auth: "user", token: common_vendor.index.getStorageSync("socketUserToken") || "", session_id: common_vendor.index.getStorageSync("socketSessionId") || "" }, (res) => { if (res.error === 0) { socketCustomerLogin(callBack); common_vendor.index.setStorageSync("socketSessionId", res.data.session_id); state.customerUserInfo = res.data.chat_user; state.socketState.isConnect = true; } else { editCustomerServerInfo({ title: `服务器异常!`, state: "waiting", avatar: "" }); state.socketState.isConnect = false; } } ); }; const getUserToken = async (id) => { const res = await chat.unifiedToken(); if (res.error === 0) { common_vendor.index.setStorageSync("socketUserToken", res.data.token); } return res; }; const socketCustomerLogin = (callBack) => { SocketIo.emit( "customer_login", { room_id: state.chatConfig.room_id }, (res) => { state.templateChatList = res.data.questions.length ? res.data.questions : []; state.chatList.push({ from: "customer_service", // 用户customer右 | 顾客customer_service左 | 系统system中间 mode: "template", // goods,order,image,text,system date: (/* @__PURE__ */ new Date()).getTime(), //时间 content: { //内容 list: state.templateChatList } }); res.error === 0 && socketHistoryList(callBack); } ); }; const socketHistoryList = (historyCallBack) => { state.chatHistoryPagination.loadStatus = "loading"; state.chatHistoryPagination.page += 1; SocketIo.emit("messages", state.chatHistoryPagination, (res) => { if (res.error === 0) { state.chatHistoryPagination.total = res.data.messages.total; state.chatHistoryPagination.lastPage = res.data.messages.last_page; state.chatHistoryPagination.page = res.data.messages.current_page; res.data.messages.data.forEach((item) => { item.message_type && state.chatList.unshift(formatMessage(item)); }); state.chatHistoryPagination.loadStatus = state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage ? "loadmore" : "nomore"; if (state.chatHistoryPagination.last_id == 0) { state.chatHistoryPagination.last_id = res.data.messages.data.length ? res.data.messages.data[0].id : 0; } state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack(); } }); }; const editCustomerServerInfo = (data) => { state.customerServerInfo = { ...state.customerServerInfo, ...data }; }; const showTime = (item, index) => { if (common_vendor.unref(state.chatList)[index + 1]) { let dateString = common_vendor.dayjs(common_vendor.unref(state.chatList)[index + 1].date).fromNow(); if (dateString === common_vendor.dayjs(common_vendor.unref(item).date).fromNow()) { return false; } else { dateString = common_vendor.dayjs(common_vendor.unref(item).date).fromNow(); return true; } } return false; }; const formatTime = (time) => { let diffTime = (/* @__PURE__ */ new Date()).getTime() - time; if (diffTime > 28 * 24 * 60 * 1e3) { return common_vendor.dayjs(time).format("MM/DD HH:mm"); } if (diffTime > 360 * 28 * 24 * 60 * 1e3) { return common_vendor.dayjs(time).format("YYYY/MM/DD HH:mm"); } return common_vendor.dayjs(time).fromNow(); }; const getFocus = (virtualNode) => { if (window.getSelection) { let chatInput2 = common_vendor.unref(virtualNode); chatInput2.focus(); let range = window.getSelection(); range.selectAllChildren(chatInput2); range.collapseToEnd(); } else if (document.selection) { let range = document.selection.createRange(); range.moveToElementText(chatInput); range.collapse(false); range.select(); } }; const upload = (name, file) => { return new Promise((resolve, reject) => { let data = new FormData(); data.append("file", file, name); data.append("group", "chat"); ajax({ url: "/upload", method: "post", headers: { "Content-Type": "multipart/form-data" }, data, success: function(res) { resolve(res); }, error: function(err) { reject(err); } }); }); }; const onPaste = async (e) => { let paste = e.clipboardData || window.clipboardData; let filesArr = Array.from(paste.files); filesArr.forEach(async (child) => { if (child && child.type.includes("image")) { e.preventDefault(); let file = child; const img = await readImg(file); const blob = await compressImg(img, file.type); const { data } = await upload(file.name, blob); let image = ``; document.execCommand("insertHTML", false, image); } else { document.execCommand("insertHTML", false, paste.getData("text")); } }); }; const onDrop = async (e) => { e.preventDefault(); let filesArr = Array.from(e.dataTransfer.files); filesArr.forEach(async (child) => { if (child && child.type.includes("image")) { let file = child; const img = await readImg(file); const blob = await compressImg(img, file.type); const { data } = await upload(file.name, blob); let image = ``; document.execCommand("insertHTML", false, image); } else { ElMessage({ message: "禁止拖拽非图片资源", type: "warning" }); } }); }; const formatChatInput = (virtualNode, formatInputCallBack) => { let res = ""; let elemArr = Array.from(virtualNode.childNodes); elemArr.forEach((child, index) => { if (child.nodeName === "#text") { res += child.nodeValue; if ( //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。 elemArr[index + 1] && elemArr[index + 1].nodeName === "IMG" && elemArr[index + 1] && elemArr[index + 1].name !== "emoji" ) { const data = { from: "customer", mode: "text", date: (/* @__PURE__ */ new Date()).getTime(), content: { text: filterXSS(res) } }; formatInputCallBack && formatInputCallBack(data); res = ""; } } else if (child.nodeName === "BR") { res += "
"; } else if (child.nodeName === "IMG") { if (child.name !== "emoji") { let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i; let src = child.outerHTML.match(srcReg); const data = { from: "customer", mode: "image", date: (/* @__PURE__ */ new Date()).getTime(), content: { url: src[1], path: src[1].replace(/http:\/\/[^\/]*/, "") } }; formatInputCallBack && formatInputCallBack(data); } else { res += child.outerHTML; } } else if (child.nodeName === "DIV") { res += `
${child.outerHTML}
`; } }); if (res) { const data = { from: "customer", mode: "text", date: (/* @__PURE__ */ new Date()).getTime(), content: { text: filterXSS(res) } }; formatInputCallBack && formatInputCallBack(data); } common_vendor.unref(virtualNode).innerHTML = ""; }; const callBackNotice = (res) => { ElNotification({ title: "socket", message: res.msg, showClose: true, type: res.error === 0 ? "success" : "warning", duration: 1200 }); }; const formatInput = (message) => { let obj = {}; switch (message.mode) { case "text": obj = { message_type: "text", message: message.content.text }; break; case "image": obj = { message_type: "image", message: message.content.path }; break; case "goods": obj = { message_type: "goods", message: message.content.item }; break; case "order": obj = { message_type: "order", message: message.content.item }; break; } return obj; }; const formatMessage = (message) => { let obj = {}; switch (message.message_type) { case "system": obj = { from: "system", // 用户customer左 | 顾客customer_service右 | 系统system中间 mode: "system", // goods,order,image,text,system date: message.create_time * 1e3, //时间 content: { //内容 text: message.message } }; break; case "text": obj = { from: message.sender_identify, mode: message.message_type, date: message.create_time * 1e3, //时间 sender: message.sender, content: { text: message.message, messageId: message.id } }; break; case "image": obj = { from: message.sender_identify, mode: message.message_type, date: message.create_time * 1e3, //时间 sender: message.sender, content: { url: sheep_index.sheep.$url.cdn(message.message), messageId: message.id } }; break; case "goods": obj = { from: message.sender_identify, mode: message.message_type, date: message.create_time * 1e3, //时间 sender: message.sender, content: { item: message.message, messageId: message.id } }; break; case "order": obj = { from: message.sender_identify, mode: message.message_type, date: message.create_time * 1e3, //时间 sender: message.sender, content: { item: message.message, messageId: message.id } }; break; } return obj; }; const readImg = (file) => { return new Promise((resolve, reject) => { const img = new Image(); const reader = new FileReader(); reader.onload = function(e) { img.src = e.target.result; }; reader.onerror = function(e) { reject(e); }; reader.readAsDataURL(file); img.onload = function() { resolve(img); }; img.onerror = function(e) { reject(e); }; }); }; const compressImg = (img, type = "image/jpeg", mx = 1e3, mh = 1e3, quality = 1) => { return new Promise((resolve, reject) => { const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); const { width: originWidth, height: originHeight } = img; const maxWidth = mx; const maxHeight = mh; let targetWidth = originWidth; let targetHeight = originHeight; if (originWidth > maxWidth || originHeight > maxHeight) { if (originWidth / originHeight > 1) { targetWidth = maxWidth; targetHeight = Math.round(maxWidth * (originHeight / originWidth)); } else { targetHeight = maxHeight; targetWidth = Math.round(maxHeight * (originWidth / originHeight)); } } canvas.width = targetWidth; canvas.height = targetHeight; context.clearRect(0, 0, targetWidth, targetHeight); context.drawImage(img, 0, 0, targetWidth, targetHeight); canvas.toBlob( function(blob) { resolve(blob); }, type, quality ); }); }; return { compressImg, readImg, formatMessage, formatInput, callBackNotice, socketInit, socketSendMsg, socketClose, socketHistoryList, getFocus, formatChatInput, onDrop, onPaste, upload, getUserToken, state, socketTest, showTime, formatTime }; } exports.useChatWebSocket = useChatWebSocket;