2
This commit is contained in:
parent
ad4672e49c
commit
c231ed0832
@ -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",
|
"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."
|
"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?"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -120,5 +120,10 @@
|
|||||||
"keywords": "中国重汽豪沃, 豪沃卡车, 重卡产品, 重型卡车, 商用车, 豪沃车型, 重汽产品, 豪沃卡车系列, 大型卡车, 商用车产品",
|
"keywords": "中国重汽豪沃, 豪沃卡车, 重卡产品, 重型卡车, 商用车, 豪沃车型, 重汽产品, 豪沃卡车系列, 大型卡车, 商用车产品",
|
||||||
"description": "中国重汽豪沃销售有限公司提供丰富的豪沃卡车产品系列,包括各种车型和配置。了解我们的重型卡车性能、技术参数及行业优势,满足您的采购需求。"
|
"description": "中国重汽豪沃销售有限公司提供丰富的豪沃卡车产品系列,包括各种车型和配置。了解我们的重型卡车性能、技术参数及行业优势,满足您的采购需求。"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "在线客服",
|
||||||
|
"description": "请输入您的问题...按Enter发送",
|
||||||
|
"littleTitle": "您好!很高兴为您服务,请问有什么可以帮您?"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,54 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 选择产品对话框 -->
|
<!-- 选择产品对话框 -->
|
||||||
<el-dialog :title="title" :visible.sync="open" @close="close" width="800px" append-to-body>
|
<el-dialog :title="$t('chat.title')" :visible.sync="open" @close="close" width="800px" append-to-body class="customer-service-dialog">
|
||||||
<el-card style="height: 600px">
|
<!-- 自定义标题栏 -->
|
||||||
<el-container class="AddressBook-container">
|
<div slot="header" class="dialog-header">
|
||||||
<el-header class="AddressBook-header" style="height: 30px">
|
<div class="service-info">
|
||||||
<span></span>
|
<h3 class="service-name">{{ $t('chat.title') }}</h3>
|
||||||
<!-- <span style="margin-left: 500px">当前在线人数:{{count.split(',')[1]}}</span>-->
|
</div>
|
||||||
</el-header>
|
<button class="close-btn" @click="close" aria-label="关闭对话框">×</button>
|
||||||
<el-container>
|
</div>
|
||||||
<el-container>
|
<!-- 聊天内容区域 -->
|
||||||
<el-main class="AddressBook-main">
|
<div class="chat-container">
|
||||||
<el-row style="margin-top: 20px" v-for="item in message" :key="item.createTime">
|
<!-- 消息列表(内部滚动) -->
|
||||||
<el-row v-if="item.dataFrom==='customer'" type="flex" justify="end">
|
<div class="message-list-wrapper">
|
||||||
<el-col :span="8">
|
<div class="message-list" ref="messageList">
|
||||||
<el-card shadow="always" :style="'item.dataFrom===customer'?'':'background-color: greenyellow;'">
|
<!-- 系统欢迎消息 -->
|
||||||
{{ item.content }}
|
<div v-if="message.length === 0" class="system-message">
|
||||||
</el-card>
|
{{ $t('chat.littleTitle') }}
|
||||||
</el-col>
|
</div>
|
||||||
<el-col :span="2">
|
|
||||||
<el-avatar shape="square" size="medium" icon="el-icon-user-solid"
|
|
||||||
style="margin-left: 5px"></el-avatar>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row v-else type="flex" justify="start">
|
|
||||||
<el-col :span="2">
|
|
||||||
<el-avatar shape="square" size="medium" icon="el-icon-user-solid"
|
|
||||||
style="margin-left: 5px"></el-avatar>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-card shadow="always" style="background-color: greenyellow">
|
|
||||||
{{ item.content }}
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
|
||||||
|
|
||||||
</el-row>
|
<!-- 消息项 -->
|
||||||
</el-row>
|
<div
|
||||||
</el-main>
|
v-for="(msg, index) in message"
|
||||||
<el-footer class="uesrtext" style="height:150px">
|
:key="index"
|
||||||
|
:class="['message-item', msg.dataFrom=='customer' ? 'self-message' : 'other-message']"
|
||||||
|
>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-text">{{ msg.content }}</div>
|
||||||
|
<div class="message-time">{{ formatTime(msg.createTime) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-input type="textarea"
|
<!-- 输入区域 -->
|
||||||
class="inputT"
|
<div class="input-area">
|
||||||
placeholder="按 Enter 发送" v-model="text"
|
<textarea
|
||||||
@keyup.enter.native="sendToServer"
|
v-model="text"
|
||||||
></el-input>
|
class="message-input"
|
||||||
<el-button type="primary" icon="el-icon-s-promotion" @click="sendToServer"></el-button>
|
:placeholder="$t('chat.description')"
|
||||||
</el-footer>
|
@keydown.enter="handleEnterKey"
|
||||||
</el-container>
|
:disabled="isInputDisabled"
|
||||||
</el-container>
|
></textarea>
|
||||||
</el-container>
|
<el-button type="primary"
|
||||||
</el-card>
|
icon="el-icon-s-promotion"
|
||||||
|
@click="sendToServer"
|
||||||
|
:disabled="!text.trim() || isInputDisabled"
|
||||||
|
>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -59,179 +59,237 @@ export default {
|
|||||||
name: 'chatForm',
|
name: 'chatForm',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
//设备唯一码
|
// 设备唯一码
|
||||||
deviceCode:null,
|
deviceCode: null,
|
||||||
open: false,
|
open: false,
|
||||||
title: null,
|
isInputDisabled: false,
|
||||||
//消息集合
|
// 消息集合
|
||||||
message: [],
|
message: [],
|
||||||
//会话ID
|
// 会话ID
|
||||||
sessionId:null,
|
sessionId: null,
|
||||||
//客服ID
|
// 客服ID
|
||||||
serviceId:null,
|
serviceId: null,
|
||||||
//发送消息内容
|
// 发送消息内容
|
||||||
text: null,
|
text: '',
|
||||||
//产品id
|
// 产品id
|
||||||
productId:'',
|
productId: '',
|
||||||
//设备类型
|
// 设备类型
|
||||||
equipment:null,
|
equipment: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
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: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 处理键盘回车事件
|
||||||
|
* @param {Event} event - 键盘事件对象
|
||||||
|
*/
|
||||||
|
handleEnterKey(event) {
|
||||||
|
// 如果按下 Shift+Enter,则允许换行
|
||||||
|
if (event.shiftKey) {
|
||||||
|
return; // 不阻止默认行为,允许换行
|
||||||
|
}
|
||||||
|
|
||||||
/**用户列表*/
|
// 只按下 Enter,发送消息
|
||||||
getUserList() {
|
event.preventDefault();
|
||||||
|
this.sendToServer();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 显示聊天对话框组件
|
||||||
|
* @param {string} id - 产品ID
|
||||||
|
*/
|
||||||
|
show(id) {
|
||||||
|
this.open = true;
|
||||||
|
this.productId = id;
|
||||||
|
this.getChatMain();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件显示
|
* 获取聊天主信息,包括设备指纹、WebSocket连接和聊天会话
|
||||||
*/
|
*/
|
||||||
show(id) {
|
async getChatMain() {
|
||||||
this.open = true
|
try {
|
||||||
this.productId = id
|
// 获取当前浏览器唯一id
|
||||||
this.getChatMain()
|
const components = await new Promise((resolve) => {
|
||||||
},
|
Fingerprint2.get((components) => {
|
||||||
|
resolve(components);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**获取原有聊天记录*/
|
|
||||||
getChatMain() {
|
|
||||||
//获取当前浏览器唯一id
|
|
||||||
Fingerprint2.get((components) => {
|
|
||||||
const values = components.map((component) => component.value);
|
const values = components.map((component) => component.value);
|
||||||
this.deviceCode = Fingerprint2.x64hash128(values.join(''), 31);
|
this.deviceCode = Fingerprint2.x64hash128(values.join(''), 31);
|
||||||
|
|
||||||
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
||||||
// 简单判断是否为手机端
|
// 简单判断是否为手机端
|
||||||
if (/android|webos|iphone|ipod|BlackBerry|iemobile|opera mini/i.test(userAgent.toLowerCase())) {
|
this.equipment = /android|webos|iphone|ipod|BlackBerry|iemobile|opera mini/i
|
||||||
this.equipment = '手机端';
|
.test(userAgent.toLowerCase()) ? '手机端' : 'pc端';
|
||||||
} 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()
|
|
||||||
|
|
||||||
// if (this.chatMain.jsonArray != null){
|
const websocketUrl = `${process.env.NUXT_ENV.VUE_APP_WEBSOCKET}${this.deviceCode}`;
|
||||||
// this.message = this.chatMain.jsonArray
|
|
||||||
// this.$store.dispatch('modules/websocket/set_message',this.chatMain.jsonArray);
|
// 调用Vuex dispatch,初始化WebSocket
|
||||||
// }
|
await this.$store.dispatch('modules/websocket/websocket_init', websocketUrl);
|
||||||
} else {
|
console.log('WebSocket 初始化成功');
|
||||||
//没有,创建新的消息对话
|
|
||||||
this.createNewSession()
|
// 获取活跃聊天
|
||||||
}
|
const res = await this.$axios.$get(`/chat/active?deviceCode=${this.deviceCode}&prodId=${this.productId}`);
|
||||||
}).catch(error => {
|
|
||||||
console.error('请求错误:', error);
|
if (res && res.id != null) {
|
||||||
});
|
// 有活跃的聊天
|
||||||
});
|
this.sessionId = res.id;
|
||||||
},
|
this.serviceId = res.userId;
|
||||||
//加载消息内容
|
// 加载消息内容
|
||||||
loadMessages(){
|
await this.loadMessages();
|
||||||
this.$axios.$get('/chat/session/'+this.sessionId).then((res) => {
|
} else {
|
||||||
this.message = res
|
// 没有,创建新的消息对话
|
||||||
this.$store.dispatch('modules/websocket/set_message',res);
|
await this.createNewSession();
|
||||||
this.scrollBottom()
|
}
|
||||||
}).catch(error => {
|
} catch (error) {
|
||||||
console.error('连接客服失败,请稍后再试:', error);
|
console.error('初始化聊天失败:', error);
|
||||||
});
|
|
||||||
},
|
|
||||||
createNewSession() {
|
|
||||||
const session = {
|
|
||||||
cusCode: this.deviceCode,
|
|
||||||
equipment: this.equipment,
|
|
||||||
prodId: this.productId
|
|
||||||
}
|
}
|
||||||
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 = {
|
const wsMsg = {
|
||||||
type: 2,
|
type: 2,// 2-建立会话关系
|
||||||
toUserId: this.serviceId,
|
toUserId: this.serviceId,
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
fromUserId: this.deviceCode
|
fromUserId: this.deviceCode
|
||||||
}
|
};
|
||||||
this.$store.dispatch('modules/websocket/websocket_send',JSON.stringify(wsMsg));
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('连接客服失败,请稍后再试:', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**弹窗关闭方法*/
|
this.$store.dispatch('modules/websocket/websocket_send', JSON.stringify(wsMsg));
|
||||||
close() {
|
} catch (error) {
|
||||||
this.$store.dispatch('modules/websocket/websocket_close')
|
console.error('创建新会话失败:', error);
|
||||||
// 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.$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实时发送给客服
|
// 成功后再通过WebSocket实时发送给客服
|
||||||
const wsMsg = {
|
const wsMsg = {
|
||||||
type: 1,
|
type: 1,// 1-普通消息
|
||||||
toUserId: this.serviceId,
|
toUserId: this.serviceId,
|
||||||
content: this.text,
|
content: this.text,
|
||||||
sessionId: this.sessionId,
|
sessionId: this.sessionId,
|
||||||
fromUserId: this.deviceCode
|
fromUserId: this.deviceCode
|
||||||
}
|
};
|
||||||
this.$store.dispatch('modules/websocket/websocket_send',JSON.stringify(wsMsg));
|
|
||||||
this.addMyMsg()
|
this.$store.dispatch('modules/websocket/websocket_send', JSON.stringify(wsMsg));
|
||||||
this.scrollBottom()
|
this.addMyMsg();
|
||||||
}).catch(error => {
|
this.scrollBottom();
|
||||||
console.error('消息发送失败,请关闭聊天窗口稍后再试:', error);
|
} catch (error) {
|
||||||
});
|
console.error('消息发送失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
//把自己发的消息添加的消息列表中
|
|
||||||
addMyMsg(){
|
/**
|
||||||
let newMsg = {
|
* 将自己发送的消息添加到消息列表中
|
||||||
id: new Date().getTime(), // 临时ID,实际应该从消息中获取
|
*/
|
||||||
|
addMyMsg() {
|
||||||
|
const newMsg = {
|
||||||
|
id: new Date().getTime(),
|
||||||
mainId: this.sessionId,
|
mainId: this.sessionId,
|
||||||
dataFrom: "customer", // 用户发送
|
dataFrom: "customer",
|
||||||
senderId: this.deviceCode,
|
senderId: this.deviceCode,
|
||||||
receiverId: this.serviceId,
|
receiverId: this.serviceId,
|
||||||
content: this.text,
|
content: this.text,
|
||||||
createTime: new Date().toLocaleString('zh-CN', {
|
createTime: new Date().toLocaleString('zh-CN', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -240,8 +298,9 @@ watch:{
|
|||||||
second: '2-digit',
|
second: '2-digit',
|
||||||
hour12: false
|
hour12: false
|
||||||
}).replace(/\//g, '-'),
|
}).replace(/\//g, '-'),
|
||||||
isRead: 1 // 已读
|
isRead: 1
|
||||||
}
|
};
|
||||||
|
|
||||||
// 创建新的数组而不是直接修改原数组
|
// 创建新的数组而不是直接修改原数组
|
||||||
const updatedMessages = [...this.message, newMsg];
|
const updatedMessages = [...this.message, newMsg];
|
||||||
this.$store.dispatch('modules/websocket/set_message', updatedMessages);
|
this.$store.dispatch('modules/websocket/set_message', updatedMessages);
|
||||||
@ -250,90 +309,358 @@ watch:{
|
|||||||
this.message = updatedMessages;
|
this.message = updatedMessages;
|
||||||
this.text = '';
|
this.text = '';
|
||||||
},
|
},
|
||||||
scrollBottom(){
|
/**
|
||||||
// 确保在对话框打开后滚动到底部
|
* 滚动消息列表到底部
|
||||||
|
*/
|
||||||
|
scrollBottom() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const chatBox = this.$el.querySelector('.AddressBook-main');
|
const messageList = this.$refs.messageList;
|
||||||
if (chatBox) {
|
if (messageList) {
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
.customer-service-dialog {
|
||||||
|
--primary-color: #409eff;
|
||||||
.el-card {
|
--primary-light: #e8f3ff;
|
||||||
background-color: transparent;
|
--primary-dark: #337ecc;
|
||||||
border: none;
|
--bg-color: #f7f9fc;
|
||||||
|
--text-color: #303133;
|
||||||
|
--text-light: #909399;
|
||||||
|
--border-radius: 12px;
|
||||||
|
--shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.AddressBook-container {
|
/* 标题栏样式 */
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-info {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-light);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online-indicator {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #52c41a;
|
||||||
|
box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--text-light);
|
||||||
|
cursor: pointer;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 聊天容器 */
|
||||||
|
.chat-container {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 550px;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息列表容器 */
|
||||||
|
.message-list-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息列表(内部滚动) */
|
||||||
|
.message-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
overflow-y: auto;
|
||||||
border: 1px solid #909399;
|
padding: 20px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
background-image:
|
||||||
|
radial-gradient(var(--primary-color) 0.5px, transparent 0.5px),
|
||||||
|
radial-gradient(var(--primary-color) 0.5px, var(--bg-color) 0.5px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position: 0 0, 10px 10px;
|
||||||
|
background-attachment: local;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条 */
|
||||||
|
.message-list::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(150, 150, 150, 0.3);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AddressBook-header {
|
.message-list::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #0b60b5;
|
background-color: rgba(150, 150, 150, 0.5);
|
||||||
color: #d3dce6;
|
}
|
||||||
display: flex;
|
|
||||||
|
/* 系统消息 */
|
||||||
|
.system-message {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-light);
|
||||||
|
background-color: rgba(255, 255, 255, 0.85);
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 14px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
display: inline-block;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息项 */
|
||||||
|
.message-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
max-width: 70%;
|
||||||
|
display: inline-block;
|
||||||
|
animation: fadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-message {
|
||||||
|
float: right;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-message {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
line-height: 1.6;
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息气泡样式 */
|
||||||
|
.self-message .message-text {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: #fff;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.self-message .message-text:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-message .message-text {
|
||||||
|
background-color: #fff;
|
||||||
|
color: var(--text-color);
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-message .message-text:hover {
|
||||||
|
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息时间 */
|
||||||
|
.message-time {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item:hover .message-time {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-message .message-time {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 正在输入指示器 */
|
||||||
|
.typing-indicator {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
max-width: 70%;
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-dots {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AddressBook-main {
|
.typing-dots span {
|
||||||
height: 380px;
|
width: 8px;
|
||||||
background-color: #DCDFE6;
|
height: 8px;
|
||||||
|
background-color: var(--text-light);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: typing 1.4s infinite ease-in-out both;
|
||||||
}
|
}
|
||||||
|
|
||||||
.AddressBook-aside {
|
.typing-dots span:nth-child(1) { animation-delay: -0.32s; }
|
||||||
background-color: #909399;
|
.typing-dots span:nth-child(2) { animation-delay: -0.16s; }
|
||||||
|
|
||||||
|
/* 输入区域 */
|
||||||
|
.input-area {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userList {
|
.message-input {
|
||||||
padding-top: 3px;
|
flex: 1;
|
||||||
box-shadow: 0px 3px 3px #888888;
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 12px 15px;
|
||||||
|
min-height: 42px;
|
||||||
|
max-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.name {
|
.message-input:focus {
|
||||||
vertical-align: top;
|
border-color: var(--primary-color);
|
||||||
/*margin-top: 2px;*/
|
box-shadow: 0 0 0 3px var(--primary-light);
|
||||||
margin-left: 15px;
|
}
|
||||||
font-size: 15px;
|
|
||||||
|
.message-input:disabled {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发送按钮 */
|
||||||
|
.send-button {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button:hover {
|
||||||
|
background-color: var(--primary-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button:disabled {
|
||||||
|
background-color: #b3d8ff;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
}
|
}
|
||||||
|
to {
|
||||||
}
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
.uesrtext {
|
|
||||||
position: relative;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-top: solid 1px #DDD;
|
|
||||||
background-color: white;
|
|
||||||
|
|
||||||
.inputT {
|
|
||||||
padding-top: 10px;
|
|
||||||
width: 100%;
|
|
||||||
height: 200%;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-avatar {
|
@keyframes typing {
|
||||||
|
0% { transform: scale(0); }
|
||||||
|
40% { transform: scale(1); }
|
||||||
|
80% { transform: scale(0); }
|
||||||
|
100% { transform: scale(0); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -9,90 +9,57 @@ export const state = () => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
WEBSOCKET_INIT(state, url) {
|
WEBSOCKET_INIT(state,url){
|
||||||
state.socket = new WebSocket(url);
|
state.socket = new WebSocket(url);
|
||||||
state.socket.onopen = function() {
|
state.socket.onopen=function () {
|
||||||
console.log("WebSocket连接成功");
|
console.log("WebSocket连接成功");
|
||||||
};
|
};
|
||||||
// 注意:这里不再直接处理 onmessage,而是在 action 中处理
|
state.socket.onmessage = function (e) {
|
||||||
state.socket.onerror = function() {
|
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连接发生错误");
|
console.log("WebSocket连接发生错误");
|
||||||
};
|
};
|
||||||
state.socket.onclose = function(e) {
|
state.socket.onclose = function (e) {
|
||||||
console.log("connection closed (" + e.code + ")");
|
console.log("connection closed (" + e.code + ")");
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
WEBSOCKET_SEND(state,msg){
|
||||||
SET_SOCKET_ONMESSAGE(state, handler) {
|
state.socket.send(msg);
|
||||||
// 设置 WebSocket 的消息处理函数
|
|
||||||
if (state.socket) {
|
|
||||||
state.socket.onmessage = handler;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
WEBSOCKET_CLOSE(state){
|
||||||
WEBSOCKET_SEND(state, msg) {
|
|
||||||
if (state.socket) {
|
|
||||||
state.socket.send(msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
WEBSOCKET_CLOSE(state) {
|
|
||||||
if (state.socket) {
|
if (state.socket) {
|
||||||
state.socket.close();
|
state.socket.close();
|
||||||
state.socket = null;
|
state.socket = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
SET_MESSAGE(state,msg){
|
||||||
SET_MESSAGE(state, msg) {
|
state.messages = 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
websocket_init({ commit, state }, url) {
|
websocket_init({commit}, url) {
|
||||||
commit('WEBSOCKET_INIT', 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_send({commit}, msg) {
|
||||||
websocket_send({ commit }, msg) {
|
commit('WEBSOCKET_SEND', msg)
|
||||||
commit('WEBSOCKET_SEND', msg);
|
|
||||||
},
|
},
|
||||||
|
websocket_close({commit}){
|
||||||
websocket_close({ commit }) {
|
commit('WEBSOCKET_CLOSE')
|
||||||
commit('WEBSOCKET_CLOSE');
|
|
||||||
},
|
},
|
||||||
|
set_message({commit},msg){
|
||||||
set_message({ commit }, msg) {
|
commit('SET_MESSAGE',msg)
|
||||||
commit('SET_MESSAGE', msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user