1
This commit is contained in:
parent
0a793caf80
commit
a33b1bff7e
@ -80,4 +80,8 @@ public class BusiChatMain extends DlBaseEntity
|
|||||||
/** 未读消息数量 */
|
/** 未读消息数量 */
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
private Integer unreadCount;
|
private Integer unreadCount;
|
||||||
|
|
||||||
|
/** 产品名称 */
|
||||||
|
@TableField(exist = false)
|
||||||
|
private String prodName;
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,8 @@ public class BusiChatMainServiceImpl extends ServiceImpl<BusiChatMainMapper,Busi
|
|||||||
for (BusiChatMain session : sessions) {
|
for (BusiChatMain session : sessions) {
|
||||||
session.setUnreadCount(busiChatItemService.selectUnreadCount(session.getId(),DATA_FROM_CUSTOMER));
|
session.setUnreadCount(busiChatItemService.selectUnreadCount(session.getId(),DATA_FROM_CUSTOMER));
|
||||||
}
|
}
|
||||||
|
//对会话未读消息数量进行倒叙排列
|
||||||
|
sessions.sort((o1, o2) -> o2.getUnreadCount() - o1.getUnreadCount());
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,15 +5,15 @@ spring:
|
|||||||
driverClassName: com.mysql.cj.jdbc.Driver
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
druid:
|
druid:
|
||||||
# 主库数据源-点亮开发库
|
# 主库数据源-点亮开发库
|
||||||
# master:
|
|
||||||
# url: jdbc:mysql://82.156.161.160:3306/dl_site_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
|
||||||
# username: site
|
|
||||||
# password: 123456
|
|
||||||
#主库数据源-客户测试服务器
|
|
||||||
master:
|
master:
|
||||||
url: jdbc:mysql://127.0.0.1:3306/dl_site_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
url: jdbc:mysql://82.156.161.160:3306/dl_site_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
username: site
|
username: site
|
||||||
password: 123456
|
password: 123456
|
||||||
|
#主库数据源-客户测试服务器
|
||||||
|
# master:
|
||||||
|
# url: jdbc:mysql://127.0.0.1:3306/dl_site_system?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
|
# username: site
|
||||||
|
# password: 123456
|
||||||
# 从库数据源
|
# 从库数据源
|
||||||
slave:
|
slave:
|
||||||
# 从数据源开关/默认关闭
|
# 从数据源开关/默认关闭
|
||||||
|
@ -134,13 +134,15 @@
|
|||||||
</select>
|
</select>
|
||||||
<select id="selectByServiceId" resultType="com.ruoyi.busi.domain.BusiChatMain">
|
<select id="selectByServiceId" resultType="com.ruoyi.busi.domain.BusiChatMain">
|
||||||
SELECT
|
SELECT
|
||||||
*
|
dbcm.*,
|
||||||
|
dbpn.title AS prodName
|
||||||
FROM
|
FROM
|
||||||
dl_busi_chat_main
|
dl_busi_chat_main dbcm
|
||||||
|
LEFT JOIN dl_busi_prod_new dbpn ON dbcm.prod_id = dbpn.id
|
||||||
WHERE
|
WHERE
|
||||||
user_id = #{serviceId}
|
dbcm.user_id =#{serviceId}
|
||||||
AND `status` = 1
|
AND dbcm.`status` = 1
|
||||||
AND del_flag = '0'
|
AND dbcm.del_flag = '0'
|
||||||
</select>
|
</select>
|
||||||
<select id="selectActiveSession" resultType="com.ruoyi.busi.domain.BusiChatMain">
|
<select id="selectActiveSession" resultType="com.ruoyi.busi.domain.BusiChatMain">
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -14,4 +14,4 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
|||||||
VUE_APP_WEBSOCKET = 'ws://localhost:8099/ws/asset/'
|
VUE_APP_WEBSOCKET = 'ws://localhost:8099/ws/asset/'
|
||||||
|
|
||||||
# 产品、文章预览
|
# 产品、文章预览
|
||||||
VUE_APP_PREVIEW = 'http://www.lighting-it.cn/admin-preview/'
|
VUE_APP_PREVIEW = 'http://192.168.1.13:3001/admin-preview/'
|
||||||
|
@ -5,10 +5,10 @@ VUE_APP_TITLE = 成事达管理平台
|
|||||||
ENV = 'production'
|
ENV = 'production'
|
||||||
|
|
||||||
# 成事达管理平台/生产环境
|
# 成事达管理平台/生产环境
|
||||||
VUE_APP_BASE_API = 'http://114.132.197.85:8099'
|
VUE_APP_BASE_API = 'http://1.92.99.15:8099'
|
||||||
|
|
||||||
# websocket
|
# websocket
|
||||||
VUE_APP_WEBSOCKET = 'ws://114.132.197.85:8099/ws/asset/'
|
VUE_APP_WEBSOCKET = 'ws://1.92.99.15:8099/ws/asset/'
|
||||||
|
|
||||||
# 产品、文章预览
|
# 产品、文章预览
|
||||||
VUE_APP_PREVIEW = 'http://www.lighting-it.cn/admin-preview/'
|
VUE_APP_PREVIEW = 'http://www.lighting-it.cn/admin-preview/'
|
||||||
|
@ -1,39 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 选择产品对话框 -->
|
<!-- 选择产品对话框 -->
|
||||||
<el-dialog @close="close" :title="title" :visible.sync="open" width="800px" append-to-body>
|
<el-dialog @close="close" :title="title" :visible.sync="open" width="800px" append-to-body class="customer-service-dialog">
|
||||||
<div class="dl-chat-box" >
|
<!-- 自定义标题栏 -->
|
||||||
<template v-for="(item,index) in messages">
|
<div slot="header" class="dialog-header">
|
||||||
<div v-if="item.dataFrom=='customer'" class="dl-customer-dom">
|
<div class="service-info">
|
||||||
<div class="dl-customer-photo">
|
<h3 class="service-name">{{ title }}</h3>
|
||||||
<img src="@/assets/images/customer.jpg" >
|
</div>
|
||||||
</div>
|
<button class="close-btn" @click="close" aria-label="关闭对话框">×</button>
|
||||||
<div class="dl-customer-right">
|
</div>
|
||||||
<div class="dl-customer-time">{{item.createTime}}</div>
|
<div class="dl-prod-box" @click="goProdDetail">咨询产品:<span>{{session.prodName}}</span></div>
|
||||||
<div class="dl-customer-content">{{ item.content }}</div>
|
<!-- 聊天内容区域 -->
|
||||||
|
<div class="chat-container">
|
||||||
|
<!-- 消息列表(内部滚动) -->
|
||||||
|
<div class="message-list-wrapper">
|
||||||
|
<div class="message-list" ref="messageList">
|
||||||
|
<!-- 消息项 -->
|
||||||
|
<div
|
||||||
|
v-for="(msg, index) in messages"
|
||||||
|
:key="index"
|
||||||
|
:class="['message-item', msg.dataFrom=='platform' ? '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>
|
</div>
|
||||||
<div v-if="item.dataFrom=='platform'" class="dl-platform-dom">
|
</div>
|
||||||
|
|
||||||
<div class="dl-platform-right">
|
<!-- 输入区域 -->
|
||||||
<div class="dl-platform-time">{{item.createTime}}</div>
|
<div class="input-area">
|
||||||
<div class="dl-platform-content">{{ item.content }}</div>
|
<textarea
|
||||||
</div>
|
v-model="text"
|
||||||
<div class="dl-platform-photo">
|
class="message-input"
|
||||||
<img src="@/assets/images/customer.jpg" >
|
placeholder="按Enter发送"
|
||||||
</div>
|
@keydown.enter="sendToServer"
|
||||||
</div>
|
:disabled="isInputDisabled || readOnly"
|
||||||
</template>
|
></textarea>
|
||||||
|
<el-button type="primary"
|
||||||
|
icon="el-icon-s-promotion"
|
||||||
|
@click="sendToServer"
|
||||||
|
:disabled="!text.trim() || isInputDisabled || readOnly"
|
||||||
|
>
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button type="primary"
|
||||||
|
:disabled="readOnly"
|
||||||
|
@click="closeCurrentSession"
|
||||||
|
>结束会话
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div slot="footer" class="dialog-footer">
|
|
||||||
<el-input type="textarea"
|
|
||||||
class="inputT"
|
|
||||||
placeholder="按 Enter 发送" v-model="text"
|
|
||||||
@keyup.enter.native="sendToServer"
|
|
||||||
></el-input>
|
|
||||||
<el-button type="primary" icon="el-icon-s-promotion" @click="sendToServer"></el-button>
|
|
||||||
<el-button type="warning" @click="closeCurrentSession">结束会话</el-button>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -44,6 +62,9 @@ export default {
|
|||||||
name: 'chatForm',
|
name: 'chatForm',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
//只读
|
||||||
|
readOnly:false,
|
||||||
|
isInputDisabled: false,
|
||||||
picPrex:process.env.VUE_APP_BASE_API,
|
picPrex:process.env.VUE_APP_BASE_API,
|
||||||
// 弹出层标题
|
// 弹出层标题
|
||||||
title: "聊天记录",
|
title: "聊天记录",
|
||||||
@ -53,7 +74,9 @@ export default {
|
|||||||
messages: [],
|
messages: [],
|
||||||
//发送的消息
|
//发送的消息
|
||||||
text:'',
|
text:'',
|
||||||
session: null,
|
session: {
|
||||||
|
prodName:""
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -86,9 +109,9 @@ export default {
|
|||||||
scrollBottom(){
|
scrollBottom(){
|
||||||
// 确保在对话框打开后滚动到底部
|
// 确保在对话框打开后滚动到底部
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const chatBox = this.$el.querySelector('.dl-chat-box');
|
const messageList = this.$refs.messageList;
|
||||||
if (chatBox) {
|
if (messageList) {
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
messageList.scrollTop = messageList.scrollHeight;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -131,105 +154,369 @@ export default {
|
|||||||
},
|
},
|
||||||
//来新消息
|
//来新消息
|
||||||
addNewMsg(msg){
|
addNewMsg(msg){
|
||||||
this.messages.push(msg)
|
console.log(msg,"msg")
|
||||||
this.scrollBottom()
|
// this.messages.push(msg)
|
||||||
|
setTimeout(()=>{
|
||||||
|
this.scrollBottom()
|
||||||
|
},0.5)
|
||||||
},
|
},
|
||||||
close(){
|
close(){
|
||||||
this.open = false;
|
this.open = false;
|
||||||
//回调父组件方法
|
//回调父组件方法
|
||||||
this.$emit("closeForm")
|
this.$emit("closeForm")
|
||||||
}
|
},
|
||||||
|
/**
|
||||||
|
* 格式化时间显示
|
||||||
|
* @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 '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goProdDetail(){
|
||||||
|
this.$router.push({path:'/product/prodForm',query:{id:this.session.prodId}})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.dl-chat-box{
|
/deep/.el-dialog__body{
|
||||||
width: 100%;
|
padding: 10px 20px !important;
|
||||||
background: #F1F3F7;
|
|
||||||
max-height:500px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: start;
|
|
||||||
overflow-y: scroll;
|
|
||||||
padding: 10px 20px;
|
|
||||||
}
|
}
|
||||||
.dl-customer-dom{
|
.customer-service-dialog {
|
||||||
padding: 15px 0;
|
--primary-color: #409eff;
|
||||||
width: 100%;
|
--primary-light: #e8f3ff;
|
||||||
display: flex;
|
--primary-dark: #337ecc;
|
||||||
|
--bg-color: #f7f9fc;
|
||||||
|
--text-color: #303133;
|
||||||
|
--text-light: #909399;
|
||||||
|
--border-radius: 12px;
|
||||||
|
--shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-customer-photo{
|
/* 标题栏样式 */
|
||||||
width: 60px;
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
padding: 16px 20px;
|
||||||
.dl-customer-photo img{
|
border-bottom: 1px solid #f0f0f0;
|
||||||
width: 40px;
|
background-color: #fff;
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.dl-customer-right{
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: start;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-customer-time{
|
.service-info {
|
||||||
font-size: 10px;
|
line-height: 1.6;
|
||||||
color: #878B90;
|
|
||||||
}
|
|
||||||
.dl-customer-content{
|
|
||||||
margin-top: 10px;
|
|
||||||
width: auto;
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-platform-dom{
|
.service-name {
|
||||||
padding: 15px 0;
|
font-size: 16px;
|
||||||
width: 100%;
|
font-weight: 600;
|
||||||
display: flex;
|
color: var(--text-color);
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-platform-photo{
|
.service-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-light);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 60px;
|
gap: 6px;
|
||||||
}
|
|
||||||
.dl-platform-photo img{
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.dl-platform-right{
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: end;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dl-platform-time{
|
.online-indicator {
|
||||||
font-size: 10px;
|
display: inline-block;
|
||||||
color: #878B90;
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #52c41a;
|
||||||
|
box-shadow: 0 0 0 3px rgba(82, 196, 26, 0.15);
|
||||||
}
|
}
|
||||||
.dl-platform-content{
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
.dl-prod-box{
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.dl-prod-box span{
|
||||||
|
color: rgb(64, 158, 255);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* 聊天容器 */
|
||||||
|
.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%;
|
||||||
|
overflow-y: auto;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(150, 150, 150, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 系统消息 */
|
||||||
|
.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;
|
text-align: right;
|
||||||
margin-top: 10px;
|
white-space: nowrap;
|
||||||
width: auto;
|
opacity: 0.8;
|
||||||
background-color: white;
|
transition: opacity 0.2s;
|
||||||
color: black;
|
}
|
||||||
border-radius: 5px;
|
|
||||||
padding: 12px;
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-dots span {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: var(--text-light);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: typing 1.4s infinite ease-in-out both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-dots span:nth-child(1) { animation-delay: -0.32s; }
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
flex: 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typing {
|
||||||
|
0% { transform: scale(0); }
|
||||||
|
40% { transform: scale(1); }
|
||||||
|
80% { transform: scale(0); }
|
||||||
|
100% { transform: scale(0); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,33 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="session-list">
|
<div class="session-list">
|
||||||
<div class="search-box">
|
<div class="toggle-button" @click="toggleCollapse" :title="isCollapsed?'点击展开':'点击收起'">
|
||||||
<el-input placeholder="搜索会话..." v-model="searchKeyword" clearable size="small"></el-input>
|
<i :class="isCollapsed ? 'el-icon-d-arrow-left' : 'el-icon-d-arrow-right'"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="session-item"
|
<div v-show="!isCollapsed" class="session-content">
|
||||||
v-for="session in filteredSessions"
|
<div class="search-box">
|
||||||
:key="session.id"
|
<el-input placeholder="搜索会话..." v-model="searchKeyword" clearable size="small"></el-input>
|
||||||
:class="{ 'active': currentSessionId === session.id, 'unread': session.unreadCount > 0 }"
|
|
||||||
@click="switchSession(session)"
|
|
||||||
>
|
|
||||||
<div class="avatar">
|
|
||||||
<img :src="session.userAvatar || '/img/user.png'" alt="用户头像">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="session-info">
|
<div v-show="filteredSessions.length>0" class="session-item"
|
||||||
<div class="session-header">
|
v-for="session in filteredSessions"
|
||||||
<span class="user-name">{{ session.userName || '匿名用户' }}</span>
|
:key="session.id"
|
||||||
<span class="time">{{ formatTime(session.lastTime) }}</span>
|
:class="{ 'active': currentSessionId === session.id, 'unread': session.unreadCount > 0 }"
|
||||||
</div>
|
@click="switchSession(session)"
|
||||||
<div class="last-message">
|
>
|
||||||
<span>{{ session.lastMessage || '暂无消息' }}</span>
|
<div class="session-info">
|
||||||
<span class="unread-count" v-if="session.unreadCount > 0">{{ session.unreadCount }}</span>
|
<div class="session-header">
|
||||||
|
<span class="user-name">来自{{ session.oceania || '未知洲' }}-{{session.national||'未知国家'}}的会话</span>
|
||||||
|
<span class="time">{{ formatTime(session.lastTime) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="last-message">
|
||||||
|
<span>{{ session.lastMessage || '暂无消息' }}</span>
|
||||||
|
<span class="unread-count" v-if="session.unreadCount > 0">{{ session.unreadCount }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-show="filteredSessions.length==0" style="text-align: center;color: rgb(90, 94, 102);padding-top: 5px;font-size: 14px">暂无会话</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getByServiceId, closeSession } from '@/api/busi/chatMain'
|
import { getByServiceId, closeSession } from '@/api/busi/chatMain'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'messageList',
|
name: 'messageList',
|
||||||
data() {
|
data() {
|
||||||
@ -49,11 +53,18 @@ export default {
|
|||||||
// 输入的消息
|
// 输入的消息
|
||||||
inputMessage: '',
|
inputMessage: '',
|
||||||
// WebSocket连接
|
// WebSocket连接
|
||||||
websocket: null
|
websocket: null,
|
||||||
|
// 是否收起
|
||||||
|
isCollapsed: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.loadSessions()
|
this.loadSessions()
|
||||||
|
// 从本地存储获取收起状态
|
||||||
|
const collapsed = localStorage.getItem('messageListCollapsed')
|
||||||
|
if (collapsed) {
|
||||||
|
this.isCollapsed = JSON.parse(collapsed)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKeyword(val) {
|
searchKeyword(val) {
|
||||||
@ -62,10 +73,18 @@ export default {
|
|||||||
(session.lastMessage && session.lastMessage.includes(val))
|
(session.lastMessage && session.lastMessage.includes(val))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
// 监听收起状态变化,保存到本地存储
|
||||||
|
isCollapsed(val) {
|
||||||
|
localStorage.setItem('messageListCollapsed', JSON.stringify(val))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 切换收起/展开状态
|
||||||
|
toggleCollapse() {
|
||||||
|
this.isCollapsed = !this.isCollapsed
|
||||||
|
},
|
||||||
//结束当前会话
|
//结束当前会话
|
||||||
closeCurrentSession(){
|
closeCurrentSession() {
|
||||||
closeSession(this.currentSessionId).then(() => {
|
closeSession(this.currentSessionId).then(() => {
|
||||||
this.$message.success('会话已结束')
|
this.$message.success('会话已结束')
|
||||||
// 通知用户会话已结束
|
// 通知用户会话已结束
|
||||||
@ -75,7 +94,7 @@ export default {
|
|||||||
content: '客服已结束会话',
|
content: '客服已结束会话',
|
||||||
sessionId: this.currentSessionId
|
sessionId: this.currentSessionId
|
||||||
}
|
}
|
||||||
this.$store.dispatch('websocket_send',JSON.stringify(wsMsg));
|
this.$store.dispatch('websocket_send', JSON.stringify(wsMsg))
|
||||||
// 切换会话列表
|
// 切换会话列表
|
||||||
this.currentSessionId = null
|
this.currentSessionId = null
|
||||||
this.currentSession = {}
|
this.currentSession = {}
|
||||||
@ -83,13 +102,13 @@ export default {
|
|||||||
// 重新加载会话列表
|
// 重新加载会话列表
|
||||||
this.loadSessions()
|
this.loadSessions()
|
||||||
// 关闭会话
|
// 关闭会话
|
||||||
this.$emit("closeForm")
|
this.$emit('closeForm')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
//关闭聊天窗口了
|
//关闭聊天窗口了
|
||||||
closeForm(){
|
closeForm() {
|
||||||
this.currentSessionId=null
|
this.currentSessionId = null
|
||||||
this.currentSession= {}
|
this.currentSession = {}
|
||||||
},
|
},
|
||||||
// 加载会话列表
|
// 加载会话列表
|
||||||
loadSessions() {
|
loadSessions() {
|
||||||
@ -102,10 +121,10 @@ export default {
|
|||||||
switchSession(session) {
|
switchSession(session) {
|
||||||
this.currentSessionId = session.id
|
this.currentSessionId = session.id
|
||||||
this.currentSession = session
|
this.currentSession = session
|
||||||
this.$emit("switchSession",session)
|
this.$emit('switchSession', session)
|
||||||
},
|
},
|
||||||
//更新会话未读状态
|
//更新会话未读状态
|
||||||
updateSessionRead(sessionId){
|
updateSessionRead(sessionId) {
|
||||||
const session = this.sessions.find(s => s.id === sessionId)
|
const session = this.sessions.find(s => s.id === sessionId)
|
||||||
if (session) {
|
if (session) {
|
||||||
session.unreadCount = 0
|
session.unreadCount = 0
|
||||||
@ -127,11 +146,11 @@ export default {
|
|||||||
let newMsg = {
|
let newMsg = {
|
||||||
id: new Date().getTime(), // 临时ID,实际应该从消息中获取
|
id: new Date().getTime(), // 临时ID,实际应该从消息中获取
|
||||||
mainId: message.sessionId,
|
mainId: message.sessionId,
|
||||||
dataFrom: "customer", // 用户发送
|
dataFrom: 'customer', // 用户发送
|
||||||
senderId: message.fromUserId,
|
senderId: message.fromUserId,
|
||||||
receiverId: this.serviceId,
|
receiverId: this.serviceId,
|
||||||
content: message.content,
|
content: message.content,
|
||||||
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',
|
||||||
@ -143,7 +162,7 @@ export default {
|
|||||||
isRead: 1 // 已读
|
isRead: 1 // 已读
|
||||||
}
|
}
|
||||||
//调用父组件方法传递过去
|
//调用父组件方法传递过去
|
||||||
this.$emit("addNewMag",newMsg)
|
this.$emit('addNewMag', newMsg)
|
||||||
} else {
|
} else {
|
||||||
// 不是当前查看的会话,更新未读数量
|
// 不是当前查看的会话,更新未读数量
|
||||||
const session = this.sessions.find(s => s.id === message.sessionId)
|
const session = this.sessions.find(s => s.id === message.sessionId)
|
||||||
@ -151,10 +170,14 @@ export default {
|
|||||||
session.unreadCount = (session.unreadCount || 0) + 1
|
session.unreadCount = (session.unreadCount || 0) + 1
|
||||||
session.lastMessage = message.content
|
session.lastMessage = message.content
|
||||||
session.lastTime = new Date()
|
session.lastTime = new Date()
|
||||||
|
let str = message.content
|
||||||
|
if(str.length>15){
|
||||||
|
str = str.substring(0,15)+'...'
|
||||||
|
}
|
||||||
// 提示有新消息
|
// 提示有新消息
|
||||||
this.$notify.info({
|
this.$notify.info({
|
||||||
title: '新消息',
|
title: '新消息',
|
||||||
message: `来自 ${session.userName || '匿名用户'} 的消息`,
|
message: str,
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -192,25 +215,65 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.session-list {
|
.session-list {
|
||||||
width: 300px;
|
max-width: 350px;
|
||||||
height: 200px;
|
max-height: 400px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
top: 84px;
|
top: 84px;
|
||||||
background-color: white;
|
padding: 5px 0 5px 5px;
|
||||||
|
background-color: white;
|
||||||
border: 1px solid #c0c0c0;
|
border: 1px solid #c0c0c0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
transition: background-color 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.session-content {
|
||||||
|
width: 300px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
padding-right: 8px;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: slideIn 0.3s ease-out forwards;
|
||||||
|
}
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当元素隐藏时添加过渡效果 */
|
||||||
|
.session-content[v-if] {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.toggle-button {
|
||||||
|
width: 10px;
|
||||||
|
max-height: 400px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-button:hover {
|
||||||
|
color:rgb(64, 158, 255) ;
|
||||||
|
}
|
||||||
.search-box {
|
.search-box {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-bottom: 1px solid #e6e6e6;
|
border-bottom: 1px solid #e6e6e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-item {
|
.session-item {
|
||||||
padding: 10px 15px;
|
padding: 10px 8px;
|
||||||
border-bottom: 1px solid #f5f5f5;
|
border-bottom: 1px solid #f5f5f5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -238,7 +301,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.session-info {
|
.session-info {
|
||||||
margin-left: 10px;
|
margin-left: 5px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user