Merge branch 'dev' of https://gitea.wangzhiwen.top/0214/ALOps_sys1 into dev
commit
07efe8e04b
@ -0,0 +1,248 @@ |
|||||||
|
<template> |
||||||
|
|
||||||
|
<div id="app"> |
||||||
|
<el-card class="customer-service"> |
||||||
|
<!-- 客服头部 --> |
||||||
|
<div class="service-header"> |
||||||
|
<h2>AI智能助手</h2> |
||||||
|
<p>您好,我是智能助手,很高兴为您服务</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="service-container"> |
||||||
|
<!-- 右侧聊天区域 --> |
||||||
|
<div class="chat-section"> |
||||||
|
<div class="chat-messages" ref="messagesContainer"> |
||||||
|
<div |
||||||
|
v-for="(message, index) in messages" |
||||||
|
:key="index" |
||||||
|
:class="['message', message.sender]" |
||||||
|
> |
||||||
|
<div class="message-content"> |
||||||
|
<div>{{ message.text }}</div> |
||||||
|
<div class="timestamp">{{ message.time }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="chat-input"> |
||||||
|
<el-input |
||||||
|
v-model="newMessage" |
||||||
|
placeholder="请输入您的问题..." |
||||||
|
@keyup.enter.native="sendMessage" |
||||||
|
></el-input> |
||||||
|
<el-button |
||||||
|
type="primary" |
||||||
|
@click="sendMessage" |
||||||
|
:disabled="!newMessage.trim()" |
||||||
|
>发送</el-button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</el-card> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect } from "@/api/inMonitoring/assessment" |
||||||
|
import { getToken } from "@/utils/auth" |
||||||
|
import Treeselect from "@riophae/vue-treeselect" |
||||||
|
import "@riophae/vue-treeselect/dist/vue-treeselect.css" |
||||||
|
import { Splitpanes, Pane } from "splitpanes" |
||||||
|
import "splitpanes/dist/splitpanes.css" |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "User", |
||||||
|
dicts: ['sys_normal_disable', 'sys_user_sex'], |
||||||
|
components: { Treeselect, Splitpanes, Pane }, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
newMessage: '', |
||||||
|
messages: [ |
||||||
|
{ |
||||||
|
text: '您好!我是AI智能助手,请问有什么可以帮您?', |
||||||
|
sender: 'service', |
||||||
|
time: this.formatTime(new Date()) |
||||||
|
} |
||||||
|
], |
||||||
|
faqs: [ |
||||||
|
{ question: '如何查询订单状态?', answer: '您可以在"我的订单"页面查看订单的当前状态和物流信息。' }, |
||||||
|
{ question: '如何办理退货?', answer: '请在订单详情页点击"申请退货",填写相关信息后提交申请。' }, |
||||||
|
{ question: '支付方式有哪些?', answer: '我们支持支付宝、微信支付、银联等多种支付方式。' }, |
||||||
|
{ question: '会员有哪些优惠?', answer: '会员可享受购物折扣、专属优惠券、生日特权等多重优惠。' }, |
||||||
|
{ question: '商品何时发货?', answer: '一般情况下,我们会在订单支付成功后24小时内安排发货。' } |
||||||
|
] |
||||||
|
}; |
||||||
|
},mounted() { |
||||||
|
// 初始化时滚动到底部 |
||||||
|
this.scrollToBottom(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
sendMessage() { |
||||||
|
if (!this.newMessage.trim()) return; |
||||||
|
|
||||||
|
// 添加用户消息 |
||||||
|
this.messages.push({ |
||||||
|
text: this.newMessage, |
||||||
|
sender: 'customer', |
||||||
|
time: this.formatTime(new Date()) |
||||||
|
}); |
||||||
|
|
||||||
|
// 模拟自动回复 |
||||||
|
setTimeout(() => { |
||||||
|
this.autoReply(this.newMessage); |
||||||
|
}, 1000); |
||||||
|
|
||||||
|
this.newMessage = ''; |
||||||
|
}, |
||||||
|
autoReply(userMessage) { |
||||||
|
let replyText = ''; |
||||||
|
|
||||||
|
// 简单关键词匹配回复 |
||||||
|
if (userMessage.includes('订单') || userMessage.includes('物流')) { |
||||||
|
replyText = '您可以在"我的订单"页面查看订单详情和物流信息。'; |
||||||
|
} else if (userMessage.includes('支付') || userMessage.includes('付款')) { |
||||||
|
replyText = '我们支持支付宝、微信支付、银联等多种支付方式。'; |
||||||
|
} else if (userMessage.includes('退货') || userMessage.includes('退款')) { |
||||||
|
replyText = '如需退货退款,请在订单详情页提交申请,客服会尽快处理。'; |
||||||
|
} else if (userMessage.includes('会员') || userMessage.includes('优惠')) { |
||||||
|
replyText = '会员可享受专属折扣和优惠券,详情请查看会员中心。'; |
||||||
|
} else if (userMessage.includes('发票')) { |
||||||
|
replyText = '您可以在下单时选择开具发票,我们提供电子发票和纸质发票两种方式。'; |
||||||
|
} else { |
||||||
|
replyText = '抱歉,我没有完全理解您的问题。您可以尝试联系人工客服获得更详细的帮助。'; |
||||||
|
} |
||||||
|
|
||||||
|
// 添加客服回复 |
||||||
|
this.messages.push({ |
||||||
|
text: replyText, |
||||||
|
sender: 'service', |
||||||
|
time: this.formatTime(new Date()) |
||||||
|
}); |
||||||
|
|
||||||
|
// 滚动到底部 |
||||||
|
this.scrollToBottom(); |
||||||
|
}, |
||||||
|
selectFaq(faq) { |
||||||
|
// 添加用户选择的常见问题 |
||||||
|
this.messages.push({ |
||||||
|
text: faq.question, |
||||||
|
sender: 'customer', |
||||||
|
time: this.formatTime(new Date()) |
||||||
|
}); |
||||||
|
|
||||||
|
// 添加对应的回答 |
||||||
|
setTimeout(() => { |
||||||
|
this.messages.push({ |
||||||
|
text: faq.answer, |
||||||
|
sender: 'service', |
||||||
|
time: this.formatTime(new Date()) |
||||||
|
}); |
||||||
|
this.scrollToBottom(); |
||||||
|
}, 1000); |
||||||
|
}, |
||||||
|
scrollToBottom() { |
||||||
|
this.$nextTick(() => { |
||||||
|
const container = this.$refs.messagesContainer; |
||||||
|
container.scrollTop = container.scrollHeight; |
||||||
|
}); |
||||||
|
}, |
||||||
|
formatTime(date) { |
||||||
|
const hours = date.getHours().toString().padStart(2, '0'); |
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0'); |
||||||
|
return `${hours}:${minutes}`; |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
messages: { |
||||||
|
handler() { |
||||||
|
this.scrollToBottom(); |
||||||
|
}, |
||||||
|
deep: true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
<style scoped> |
||||||
|
|
||||||
|
#app { |
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; |
||||||
|
margin: 20px; |
||||||
|
} |
||||||
|
.customer-service { |
||||||
|
/* max-width: 1000px; */ |
||||||
|
margin: 0 auto; |
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
||||||
|
border-radius: 4px; |
||||||
|
} |
||||||
|
.service-header { |
||||||
|
background-color: #409EFF; |
||||||
|
color: white; |
||||||
|
padding: 15px 20px; |
||||||
|
border-top-left-radius: 4px; |
||||||
|
border-top-right-radius: 4px; |
||||||
|
} |
||||||
|
.service-container { |
||||||
|
display: flex; |
||||||
|
min-height: 400px; |
||||||
|
} |
||||||
|
.faq-section { |
||||||
|
width: 30%; |
||||||
|
border-right: 1px solid #e6e6e6; |
||||||
|
overflow-y: auto; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
.chat-section { |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
} |
||||||
|
.chat-messages { |
||||||
|
flex: 1; |
||||||
|
overflow-y: auto; |
||||||
|
padding: 15px; |
||||||
|
} |
||||||
|
.message { |
||||||
|
margin-bottom: 15px; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.message.customer { |
||||||
|
justify-content: flex-end; |
||||||
|
} |
||||||
|
.message-content { |
||||||
|
max-width: 70%; |
||||||
|
padding: 10px 15px; |
||||||
|
border-radius: 4px; |
||||||
|
} |
||||||
|
.message.customer .message-content { |
||||||
|
background-color: #ecf5ff; |
||||||
|
border: 1px solid #d9ecff; |
||||||
|
} |
||||||
|
.message.service .message-content { |
||||||
|
background-color: #f4f4f5; |
||||||
|
border: 1px solid #e9e9eb; |
||||||
|
} |
||||||
|
.chat-input { |
||||||
|
padding: 15px; |
||||||
|
border-top: 1px solid #e6e6e6; |
||||||
|
display: flex; |
||||||
|
} |
||||||
|
.chat-input .el-input { |
||||||
|
margin-right: 10px; |
||||||
|
} |
||||||
|
.faq-item { |
||||||
|
padding: 10px; |
||||||
|
margin-bottom: 10px; |
||||||
|
border-radius: 4px; |
||||||
|
cursor: pointer; |
||||||
|
border: 1px solid #ebeef5; |
||||||
|
} |
||||||
|
.faq-item:hover { |
||||||
|
background-color: #f5f7fa; |
||||||
|
} |
||||||
|
.timestamp { |
||||||
|
font-size: 12px; |
||||||
|
color: #909399; |
||||||
|
margin-top: 5px; |
||||||
|
text-align: right; |
||||||
|
} |
||||||
|
</style> |
||||||
@ -1,272 +0,0 @@ |
|||||||
<template> |
|
||||||
<div class="chat-container"> |
|
||||||
<!-- 消息列表区域 --> |
|
||||||
<el-scrollbar |
|
||||||
ref="messageScroll" |
|
||||||
class="message-area" |
|
||||||
@scroll.native="handleScroll"> |
|
||||||
<div |
|
||||||
v-for="(msg, index) in messages" |
|
||||||
:key="index" |
|
||||||
:class="['message', msg.sender === 'user' ? 'user-msg' : 'customer-msg']"> |
|
||||||
<el-avatar :size="40" :src="msg.avatar"></el-avatar> |
|
||||||
<div class="message-content"> |
|
||||||
<div class="sender">{{ msg.sender === 'user' ? '我' : '客服' }}</div> |
|
||||||
<div class="content">{{ msg.content }}</div> |
|
||||||
<div class="time">{{ formatTime(msg.timestamp) }}</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
<!-- 加载中提示 --> |
|
||||||
<div |
|
||||||
v-if="loading" |
|
||||||
class="loading-indicator"> |
|
||||||
<i class="el-icon-loading"></i> 加载中... |
|
||||||
</div> |
|
||||||
</el-scrollbar> |
|
||||||
|
|
||||||
<!-- 输入区域 --> |
|
||||||
<div class="input-area"> |
|
||||||
<el-input |
|
||||||
v-model="inputMessage" |
|
||||||
type="textarea" |
|
||||||
placeholder="请输入消息..." |
|
||||||
@keyup.enter.native="sendMessage" |
|
||||||
:disabled="!connected"> |
|
||||||
</el-input> |
|
||||||
<div class="action-buttons"> |
|
||||||
<el-button |
|
||||||
type="primary" |
|
||||||
@click="sendMessage" |
|
||||||
:disabled="!connected || !inputMessage.trim()" |
|
||||||
:loading="sending"> |
|
||||||
发送 |
|
||||||
</el-button> |
|
||||||
<el-button @click="toggleConnection"> |
|
||||||
{{ connected ? '断开' : '连接' }} |
|
||||||
</el-button> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script> |
|
||||||
import { Loading } from 'element-ui'; |
|
||||||
|
|
||||||
export default { |
|
||||||
data() { |
|
||||||
return { |
|
||||||
messages: [], |
|
||||||
inputMessage: '', |
|
||||||
connected: false, |
|
||||||
loading: false, |
|
||||||
sending: false, |
|
||||||
page: 1, |
|
||||||
pageSize: 20 |
|
||||||
}; |
|
||||||
}, |
|
||||||
mounted() { |
|
||||||
this.connect(); |
|
||||||
this.scrollToBottom(); |
|
||||||
}, |
|
||||||
methods: { |
|
||||||
// 连接客服系统 |
|
||||||
connect() { |
|
||||||
this.connected = true; |
|
||||||
this.loadMessages(); |
|
||||||
}, |
|
||||||
|
|
||||||
// 断开连接 |
|
||||||
toggleConnection() { |
|
||||||
this.connected = !this.connected; |
|
||||||
if (this.connected) { |
|
||||||
this.loadMessages(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 加载历史消息 |
|
||||||
async loadMessages() { |
|
||||||
this.loading = true; |
|
||||||
try { |
|
||||||
// 模拟API请求 |
|
||||||
const response = await this.mockApiCall(); |
|
||||||
this.messages = response.data; |
|
||||||
this.page++; |
|
||||||
} catch (error) { |
|
||||||
console.error('加载消息失败:', error); |
|
||||||
} finally { |
|
||||||
this.loading = false; |
|
||||||
this.$nextTick(this.scrollToBottom); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 发送消息 |
|
||||||
sendMessage() { |
|
||||||
if (!this.inputMessage.trim()) return; |
|
||||||
|
|
||||||
this.sending = true; |
|
||||||
|
|
||||||
// 模拟发送延时 |
|
||||||
setTimeout(() => { |
|
||||||
this.messages.push({ |
|
||||||
sender: 'user', |
|
||||||
content: this.inputMessage, |
|
||||||
timestamp: Date.now(), |
|
||||||
avatar: 'https://via.placeholder.com/40?text=U' |
|
||||||
}); |
|
||||||
|
|
||||||
this.inputMessage = ''; |
|
||||||
this.sending = false; |
|
||||||
this.scrollToBottom(); |
|
||||||
|
|
||||||
// 模拟客服回复 |
|
||||||
this.simulateCustomerReply(); |
|
||||||
}, 500); |
|
||||||
}, |
|
||||||
|
|
||||||
// 模拟客服自动回复 |
|
||||||
simulateCustomerReply() { |
|
||||||
setTimeout(() => { |
|
||||||
this.messages.push({ |
|
||||||
sender: 'customer', |
|
||||||
content: '您好,已收到您的消息,我们会尽快处理', |
|
||||||
timestamp: Date.now(), |
|
||||||
avatar: 'https://via.placeholder.com/40?text=C' |
|
||||||
}); |
|
||||||
this.scrollToBottom(); |
|
||||||
}, 1000); |
|
||||||
}, |
|
||||||
|
|
||||||
// 滚动到底部 |
|
||||||
scrollToBottom() { |
|
||||||
const scrollContainer = this.$refs.messageScroll.$el.querySelector('.el-scrollbar__wrap'); |
|
||||||
if (scrollContainer) { |
|
||||||
scrollContainer.scrollTop = scrollContainer.scrollHeight; |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 处理滚动事件 |
|
||||||
handleScroll(e) { |
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target; |
|
||||||
const scrollBottom = scrollHeight - scrollTop - clientHeight; |
|
||||||
|
|
||||||
// 滚动到底部加载更多 |
|
||||||
if (scrollBottom < 50 && !this.loading) { |
|
||||||
this.loadMoreMessages(); |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 加载更多消息 |
|
||||||
async loadMoreMessages() { |
|
||||||
this.loading = true; |
|
||||||
try { |
|
||||||
// 模拟API请求更多消息 |
|
||||||
const response = await this.mockApiCall(this.page); |
|
||||||
this.messages = [...response.data, ...this.messages]; |
|
||||||
this.page++; |
|
||||||
} catch (error) { |
|
||||||
console.error('加载更多消息失败:', error); |
|
||||||
} finally { |
|
||||||
this.loading = false; |
|
||||||
} |
|
||||||
}, |
|
||||||
|
|
||||||
// 格式化时间 |
|
||||||
formatTime(timestamp) { |
|
||||||
return new Date(timestamp).toLocaleTimeString(); |
|
||||||
}, |
|
||||||
|
|
||||||
// 模拟API请求 |
|
||||||
mockApiCall(page = 1) { |
|
||||||
return new Promise((resolve) => { |
|
||||||
setTimeout(() => { |
|
||||||
resolve({ |
|
||||||
data: Array(this.pageSize).fill().map((_, i) => ({ |
|
||||||
sender: Math.random() > 0.5 ? 'user' : 'customer', |
|
||||||
content: `消息 ${(page-1)*this.pageSize + i + 1}`, |
|
||||||
timestamp: Date.now() - i * 1000, |
|
||||||
avatar: Math.random() > 0.5 |
|
||||||
? 'https://via.placeholder.com/40?text=U' |
|
||||||
: 'https://via.placeholder.com/40?text=C' |
|
||||||
})) |
|
||||||
}); |
|
||||||
}, 800); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
</script> |
|
||||||
|
|
||||||
<style> |
|
||||||
.chat-container { |
|
||||||
display: flex; |
|
||||||
flex-direction: column; |
|
||||||
height: 100vh; |
|
||||||
border: 1px solid #ebeef5; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
|
|
||||||
.message-area { |
|
||||||
flex: 1; |
|
||||||
padding: 20px; |
|
||||||
overflow-y: auto; |
|
||||||
} |
|
||||||
|
|
||||||
.message { |
|
||||||
display: flex; |
|
||||||
margin-bottom: 20px; |
|
||||||
} |
|
||||||
|
|
||||||
.user-msg { |
|
||||||
flex-direction: row-reverse; |
|
||||||
} |
|
||||||
|
|
||||||
.customer-msg { |
|
||||||
flex-direction: row; |
|
||||||
} |
|
||||||
|
|
||||||
.message-content { |
|
||||||
margin-left: 15px; |
|
||||||
background: #f0f0f0; |
|
||||||
padding: 12px; |
|
||||||
border-radius: 8px; |
|
||||||
max-width: 80%; |
|
||||||
} |
|
||||||
|
|
||||||
.user-msg .message-content { |
|
||||||
background: #e1f0ff; |
|
||||||
} |
|
||||||
|
|
||||||
.sender { |
|
||||||
font-weight: bold; |
|
||||||
margin-bottom: 5px; |
|
||||||
} |
|
||||||
|
|
||||||
.content { |
|
||||||
white-space: pre-wrap; |
|
||||||
} |
|
||||||
|
|
||||||
.time { |
|
||||||
font-size: 0.8em; |
|
||||||
color: #999; |
|
||||||
text-align: right; |
|
||||||
margin-top: 5px; |
|
||||||
} |
|
||||||
|
|
||||||
.input-area { |
|
||||||
padding: 15px; |
|
||||||
border-top: 1px solid #ebeef5; |
|
||||||
background: #fff; |
|
||||||
} |
|
||||||
|
|
||||||
.action-buttons { |
|
||||||
display: flex; |
|
||||||
justify-content: flex-end; |
|
||||||
margin-top: 10px; |
|
||||||
} |
|
||||||
|
|
||||||
.loading-indicator { |
|
||||||
text-align: center; |
|
||||||
padding: 15px; |
|
||||||
color: #999; |
|
||||||
} |
|
||||||
</style> |
|
||||||
Loading…
Reference in new issue