diff --git a/locales/en.json b/locales/en.json index 10dcae4..90ac87d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -120,5 +120,10 @@ "keywords": "China National Heavy Duty Truck Group (CNHTC) HOWO, HOWO Trucks, Heavy Truck Products, Heavy duty Trucks, Commercial Vehicles, HOWO Models, CNHTC Products, HOWO Truck Series, Large Trucks, Commercial Vehicle Products", "description": "China National Heavy Duty Truck Group Co., Ltd. provides a rich range of HOWO truck products, including various models and configurations. Understand the performance, technical specifications, and industry advantages of our heavy-duty trucks to meet your procurement needs." } + }, + "chat": { + "title": "Online Service", + "description": "Please enter your question... Press Enter to send.", + "littleTitle": "Hello! It's a pleasure to assist you. How may I help you?" } } \ No newline at end of file diff --git a/locales/zh.json b/locales/zh.json index 62f0a87..cc2c794 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -120,5 +120,10 @@ "keywords": "中国重汽豪沃, 豪沃卡车, 重卡产品, 重型卡车, 商用车, 豪沃车型, 重汽产品, 豪沃卡车系列, 大型卡车, 商用车产品", "description": "中国重汽豪沃销售有限公司提供丰富的豪沃卡车产品系列,包括各种车型和配置。了解我们的重型卡车性能、技术参数及行业优势,满足您的采购需求。" } + }, + "chat": { + "title": "在线客服", + "description": "请输入您的问题...按Enter发送", + "littleTitle": "您好!很高兴为您服务,请问有什么可以帮您?" } } \ No newline at end of file diff --git a/pages/products/chatForm.vue b/pages/products/chatForm.vue index 6529f5e..2e366b4 100644 --- a/pages/products/chatForm.vue +++ b/pages/products/chatForm.vue @@ -1,54 +1,54 @@ @@ -59,179 +59,237 @@ export default { name: 'chatForm', data() { return { - //设备唯一码 - deviceCode:null, + // 设备唯一码 + deviceCode: null, open: false, - title: null, - //消息集合 + isInputDisabled: false, + // 消息集合 message: [], - //会话ID - sessionId:null, - //客服ID - serviceId:null, - //发送消息内容 - text: null, - //产品id - productId:'', - //设备类型 - equipment:null, + // 会话ID + sessionId: null, + // 客服ID + serviceId: null, + // 发送消息内容 + text: '', + // 产品id + productId: '', + // 设备类型 + equipment: null, } }, beforeDestroy() { // 离开页面生命周期函数 - this.$store.dispatch('modules/websocket/websocket_close') + this.closeWebSocket(); }, -watch:{ - message(val) { - this.scrollBottom() - } -}, - mounted() { + watch: { + message: { + handler(newVal) { + // 特别处理最后一个消息 type=3 的情况 + if (newVal && newVal.length > 0) { + const lastMessage = newVal[newVal.length - 1]; + this.isInputDisabled = lastMessage.type && lastMessage.type === 3; + } + this.scrollBottom(); + }, + deep: true, + immediate: false + } + }, + + mounted() { + // 可以在这里添加初始化逻辑 }, methods: { + /** + * 处理键盘回车事件 + * @param {Event} event - 键盘事件对象 + */ + handleEnterKey(event) { + // 如果按下 Shift+Enter,则允许换行 + if (event.shiftKey) { + return; // 不阻止默认行为,允许换行 + } - /**用户列表*/ - getUserList() { - + // 只按下 Enter,发送消息 + event.preventDefault(); + this.sendToServer(); + }, + /** + * 显示聊天对话框组件 + * @param {string} id - 产品ID + */ + show(id) { + this.open = true; + this.productId = id; + this.getChatMain(); }, /** - * 组件显示 + * 获取聊天主信息,包括设备指纹、WebSocket连接和聊天会话 */ - show(id) { - this.open = true - this.productId = id - this.getChatMain() - }, + async getChatMain() { + try { + // 获取当前浏览器唯一id + const components = await new Promise((resolve) => { + Fingerprint2.get((components) => { + resolve(components); + }); + }); - /**获取原有聊天记录*/ - getChatMain() { - //获取当前浏览器唯一id - Fingerprint2.get((components) => { const values = components.map((component) => component.value); this.deviceCode = Fingerprint2.x64hash128(values.join(''), 31); + const userAgent = navigator.userAgent || navigator.vendor || window.opera; // 简单判断是否为手机端 - if (/android|webos|iphone|ipod|BlackBerry|iemobile|opera mini/i.test(userAgent.toLowerCase())) { - this.equipment = '手机端'; - } else { - this.equipment = 'pc端'; - } - const websocketUrl = process.env.NUXT_ENV.VUE_APP_WEBSOCKET+`${this.deviceCode}`; - // 调用Vuex dispatch,初始化WebSocket - this.$store.dispatch('modules/websocket/websocket_init', websocketUrl) - .then(() => { - console.log('WebSocket 初始化成功'); - }) - .catch((err) => { - console.error('WebSocket 初始化失败:', err); - }); - // 这里使用箭头函数,确保 `this` 指向当前 Vue 实例 - this.$axios.$get('/chat/active?deviceCode='+this.deviceCode+'&prodId='+this.productId).then((res) => { - if (null!=res && res.id != null) { - //有活跃的聊天 - this.sessionId = res.id - this.serviceId = res.userId - //加载消息内容 - this.loadMessages() + this.equipment = /android|webos|iphone|ipod|BlackBerry|iemobile|opera mini/i + .test(userAgent.toLowerCase()) ? '手机端' : 'pc端'; - // if (this.chatMain.jsonArray != null){ - // this.message = this.chatMain.jsonArray - // this.$store.dispatch('modules/websocket/set_message',this.chatMain.jsonArray); - // } - } else { - //没有,创建新的消息对话 - this.createNewSession() - } - }).catch(error => { - console.error('请求错误:', error); - }); - }); - }, - //加载消息内容 - loadMessages(){ - this.$axios.$get('/chat/session/'+this.sessionId).then((res) => { - this.message = res - this.$store.dispatch('modules/websocket/set_message',res); - this.scrollBottom() - }).catch(error => { - console.error('连接客服失败,请稍后再试:', error); - }); - }, - createNewSession() { - const session = { - cusCode: this.deviceCode, - equipment: this.equipment, - prodId: this.productId + const websocketUrl = `${process.env.NUXT_ENV.VUE_APP_WEBSOCKET}${this.deviceCode}`; + + // 调用Vuex dispatch,初始化WebSocket + await this.$store.dispatch('modules/websocket/websocket_init', websocketUrl); + console.log('WebSocket 初始化成功'); + + // 获取活跃聊天 + const res = await this.$axios.$get(`/chat/active?deviceCode=${this.deviceCode}&prodId=${this.productId}`); + + if (res && res.id != null) { + // 有活跃的聊天 + this.sessionId = res.id; + this.serviceId = res.userId; + // 加载消息内容 + await this.loadMessages(); + } else { + // 没有,创建新的消息对话 + await this.createNewSession(); + } + } catch (error) { + console.error('初始化聊天失败:', error); } - this.$axios.$post('/chat/newChat', session).then((res) => { - this.sessionId = res.id - this.serviceId = res.userId - //发个消息,通知客服建立会话关系 + }, + + /** + * 加载指定会话的消息内容 + */ + async loadMessages() { + try { + const res = await this.$axios.$get(`/chat/session/${this.sessionId}`); + this.message = res; + this.$store.dispatch('modules/websocket/set_message', res); + this.scrollBottom(); + } catch (error) { + console.error('加载消息失败:', error); + } + }, + /** + * 创建新的聊天会话 + */ + async createNewSession() { + try { + const session = { + cusCode: this.deviceCode, + equipment: this.equipment, + prodId: this.productId + }; + + const res = await this.$axios.$post('/chat/newChat', session); + this.sessionId = res.id; + this.serviceId = res.userId; + + // 发个消息,通知客服建立会话关系 const wsMsg = { - type: 2, + type: 2,// 2-建立会话关系 toUserId: this.serviceId, sessionId: this.sessionId, fromUserId: this.deviceCode - } - this.$store.dispatch('modules/websocket/websocket_send',JSON.stringify(wsMsg)); - }).catch(error => { - console.error('连接客服失败,请稍后再试:', error); - }); - }, + }; - /**弹窗关闭方法*/ - close() { - this.$store.dispatch('modules/websocket/websocket_close') - // let data = { - // id:this.chatMain.id, - // jsonArray:this.message - // } - //关闭时将消息保存到数据库 - this.$axios.$post('/web/saveMessage', data).then((res)=>{ - console.log(res) - }) - }, - - /**发送消息*/ - sendToServer() { - // 构造消息对象 - const message = { - mainId: this.sessionId, - dataFrom: "customer", // 用户发送 - senderId: this.deviceCode, // 用设备编码作为用户标识 - receiverId: this.serviceId, - content: this.text, + this.$store.dispatch('modules/websocket/websocket_send', JSON.stringify(wsMsg)); + } catch (error) { + console.error('创建新会话失败:', error); } - this.$axios.$post('/chat/newMes', message).then((res) => { + }, + + /** + * 关闭聊天对话框 + */ + close() { + this.resetChat(); + this.open = false; + }, + + /** + * 重置聊天状态,清空所有聊天数据 + */ + resetChat() { + this.message = []; + this.$store.dispatch('modules/websocket/set_message', []); + this.closeWebSocket(); + this.isInputDisabled = false; + this.text = ''; + this.sessionId = null; + this.serviceId = null; + }, + + /** + * 关闭WebSocket连接 + */ + closeWebSocket() { + this.$store.dispatch('modules/websocket/websocket_close'); + }, + + /** + * 发送消息到服务器 + */ + async sendToServer() { + // 如果没有输入内容或输入被禁用,则不发送 + if (!this.text || !this.text.trim() || this.isInputDisabled) { + return; + } + try { + // 构造消息对象 + const message = { + mainId: this.sessionId, + dataFrom: "customer", + senderId: this.deviceCode, + receiverId: this.serviceId, + content: this.text, + }; + + await this.$axios.$post('/chat/newMes', message); + // 成功后再通过WebSocket实时发送给客服 const wsMsg = { - type: 1, + type: 1,// 1-普通消息 toUserId: this.serviceId, content: this.text, sessionId: this.sessionId, fromUserId: this.deviceCode - } - this.$store.dispatch('modules/websocket/websocket_send',JSON.stringify(wsMsg)); - this.addMyMsg() - this.scrollBottom() - }).catch(error => { - console.error('消息发送失败,请关闭聊天窗口稍后再试:', error); - }); + }; + + this.$store.dispatch('modules/websocket/websocket_send', JSON.stringify(wsMsg)); + this.addMyMsg(); + this.scrollBottom(); + } catch (error) { + console.error('消息发送失败:', error); + } }, - //把自己发的消息添加的消息列表中 - addMyMsg(){ - let newMsg = { - id: new Date().getTime(), // 临时ID,实际应该从消息中获取 + + /** + * 将自己发送的消息添加到消息列表中 + */ + addMyMsg() { + const newMsg = { + id: new Date().getTime(), mainId: this.sessionId, - dataFrom: "customer", // 用户发送 + dataFrom: "customer", senderId: this.deviceCode, receiverId: this.serviceId, content: this.text, - createTime: new Date().toLocaleString('zh-CN', { + createTime: new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', @@ -240,8 +298,9 @@ watch:{ second: '2-digit', hour12: false }).replace(/\//g, '-'), - isRead: 1 // 已读 - } + isRead: 1 + }; + // 创建新的数组而不是直接修改原数组 const updatedMessages = [...this.message, newMsg]; this.$store.dispatch('modules/websocket/set_message', updatedMessages); @@ -250,90 +309,358 @@ watch:{ this.message = updatedMessages; this.text = ''; }, - scrollBottom(){ - // 确保在对话框打开后滚动到底部 + /** + * 滚动消息列表到底部 + */ + scrollBottom() { this.$nextTick(() => { - const chatBox = this.$el.querySelector('.AddressBook-main'); - if (chatBox) { - chatBox.scrollTop = chatBox.scrollHeight; + const messageList = this.$refs.messageList; + if (messageList) { + messageList.scrollTop = messageList.scrollHeight; } }); }, + /** + * 格式化时间显示 + * @param {string|number} time - 时间戳或时间字符串 + * @returns {string} 格式化后的时间字符串 HH:mm + */ + formatTime(time) { + if (!time) return ''; + try { + const date = new Date(time); + if (isNaN(date.getTime())) return ''; // 检查日期是否有效 + + return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; + } catch (error) { + console.error('时间格式化错误:', error); + return ''; + } + } } } + + + \ No newline at end of file diff --git a/store/modules/websocket.js b/store/modules/websocket.js index 52b74a8..ba353ac 100644 --- a/store/modules/websocket.js +++ b/store/modules/websocket.js @@ -9,90 +9,57 @@ export const state = () => ({ }) export const mutations = { - WEBSOCKET_INIT(state, url) { + WEBSOCKET_INIT(state,url){ state.socket = new WebSocket(url); - state.socket.onopen = function() { + state.socket.onopen=function () { console.log("WebSocket连接成功"); }; - // 注意:这里不再直接处理 onmessage,而是在 action 中处理 - state.socket.onerror = function() { + state.socket.onmessage = function (e) { + console.log(e,'接收到的消息') + if (e.data.startsWith("C")) { + state.count = e.data; + } + else if (e.data.startsWith("系统通知")){ + state.notices.push(e.data); + }else if (e.data.startsWith("close")){ + console.log(e.data) + } else { + state.messages.push(JSON.parse(e.data)); + console.log(state.messages); + } + }; + state.socket.onerror= function () { console.log("WebSocket连接发生错误"); }; - state.socket.onclose = function(e) { + state.socket.onclose = function (e) { console.log("connection closed (" + e.code + ")"); }; }, - - SET_SOCKET_ONMESSAGE(state, handler) { - // 设置 WebSocket 的消息处理函数 - if (state.socket) { - state.socket.onmessage = handler; - } + WEBSOCKET_SEND(state,msg){ + state.socket.send(msg); }, - - WEBSOCKET_SEND(state, msg) { - if (state.socket) { - state.socket.send(msg); - } - }, - - WEBSOCKET_CLOSE(state) { + WEBSOCKET_CLOSE(state){ if (state.socket) { state.socket.close(); state.socket = null; } }, - - SET_MESSAGE(state, msg) { - state.messages = msg; - }, - - ADD_MESSAGE(state, message) { - // 使用展开运算符创建新数组而不是直接 push - state.messages = [...state.messages, message]; - }, - - ADD_NOTICE(state, notice) { - // 使用展开运算符创建新数组而不是直接 push - state.notices = [...state.notices, notice]; - }, - - SET_COUNT(state, count) { - state.count = count; + SET_MESSAGE(state,msg){ + state.messages = msg } } export const actions = { - websocket_init({ commit, state }, url) { - commit('WEBSOCKET_INIT', url); - - // 在 action 中设置 WebSocket 的 onmessage 回调 - if (state.socket) { - state.socket.onmessage = function(e) { - console.log(e, '接收到的消息'); - if (e.data.startsWith("C")) { - commit('SET_COUNT', e.data); - } else if (e.data.startsWith("系统通知")) { - commit('ADD_NOTICE', e.data); - } else if (e.data.startsWith("close")) { - console.log(e.data); - } else { - commit('ADD_MESSAGE', JSON.parse(e.data)); - console.log(state.messages); - } - }; - } + websocket_init({commit}, url) { + commit('WEBSOCKET_INIT', url) }, - - websocket_send({ commit }, msg) { - commit('WEBSOCKET_SEND', msg); + websocket_send({commit}, msg) { + commit('WEBSOCKET_SEND', msg) }, - - websocket_close({ commit }) { - commit('WEBSOCKET_CLOSE'); + websocket_close({commit}){ + commit('WEBSOCKET_CLOSE') }, - - set_message({ commit }, msg) { - commit('SET_MESSAGE', msg); + set_message({commit},msg){ + commit('SET_MESSAGE',msg) } } \ No newline at end of file