完善客服模块功能

This commit is contained in:
赵毅 2025-09-24 17:24:22 +08:00
parent e20e9a3552
commit 088891bdbb
8 changed files with 199 additions and 97 deletions

View File

@ -6,14 +6,15 @@
<view v-if="isLoading" class="loading">加载中...</view>
<view v-else-if="recordsList.length === 0" class="empty-records">暂无聊天记录</view>
<view v-else>
<view v-for="record in recordsList" :key="record.mchId" class="record-item" @tap="goToChatPage(record)">
<image class="record-avatar" :src="record.avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="record-info">
<view class="record-title-row">
<text class="record-title">{{ record.title }}</text>
<text class="record-time">{{ formatTime(record.lastMsgTime) }}</text>
<view v-for="record in recordsList" :key="record.id">
<view class="record-item" @tap="goToChatPage(record)">
<image class="record-avatar" :src="record.avatar || '/static/logo.png'" mode="aspectFill"></image>
<view class="record-info">
<view class="record-title-row">
<text class="record-title">{{ record.contact_name }}</text>
<text class="record-time">{{ record.update_time }}</text>
</view>
</view>
<text class="record-last-msg">{{ record.lastMsg || '暂无消息' }}</text>
</view>
</view>
</view>
@ -61,63 +62,14 @@ export default {
page_num: this.page_num,
page_size: this.page_size,
}).then((res) => {
console.log("🚀 ~ loadChattingRecords ~ res:", res)
this.recordsList = res.msg_list
})
// const response = await this.$http.get(apiArr.csGetMsgList);
// if (response.success && response.data && response.data.list) {
// this.recordsList = response.data.list.map(item => ({
// mchId: item.mchId,
// title: item.title || `${item.mchId}`,
// avatar: item.avatar,
// lastMsg: item.lastMsg,
// lastMsgTime: item.lastMsgTime || Date.now(),
// unreadCount: item.unreadCount || 0
// }));
// } else {
// // 使
// this.recordsList = this.getMockRecords();
// }
} catch (error) {
console.error('加载聊天记录失败', error);
// 使
this.recordsList = this.getMockRecords();
} finally {
this.isLoading = false;
}
},
//
getMockRecords() {
return [
{
mchId: '1001',
title: '客服小明',
avatar: '/static/logo.png',
lastMsg: '您好,请问有什么可以帮助您的吗?',
lastMsgTime: Date.now() - 30 * 60 * 1000,
unreadCount: 0
},
{
mchId: '1002',
title: '客服小丽',
avatar: '/static/logo.png',
lastMsg: '您的问题我已经记录,稍后会有专人与您联系。',
lastMsgTime: Date.now() - 2 * 60 * 60 * 1000,
unreadCount: 1
},
{
mchId: '1003',
title: '客服小张',
avatar: '/static/logo.png',
lastMsg: '感谢您的咨询,还有其他问题吗?',
lastMsgTime: Date.now() - 5 * 60 * 60 * 1000,
unreadCount: 0
}
];
},
//
formatTime(time) {
const date = new Date(time);
@ -152,13 +104,29 @@ export default {
//
goToChatPage(record) {
// 使
uni.setStorageSync('currentChatTarget', record);
const params = {
mch_id: record.mch_id,
}
//
uni.navigateTo({
url: '/packages/customerService/index/index?mchId=' + record.mchId
});
request(apiArr.csGetMchContactList, "POST", params).then((res) => {
if (res.rows && res.rows.length > 0) {
res.rows.map(item => {
item.employee_image = picUrl + item.employee_image;
})
const itemObj = res.rows.find(item => item.employee_mobile === record.two.account)
//
uni.navigateTo({
url: '/packages/customerService/index/index?item=' + JSON.stringify(itemObj)
});
} else {
console.log("没有获取到客服列表数据");
uni.showToast({
title: '该客服不存在',
icon: 'none'
})
}
})
}
}
};

View File

@ -1,7 +1,8 @@
/* 客服聊天页面样式 */
page {
background-color: #f6f7fb;
overflow-y: hidden;
height: 100vh;
overflow: hidden;
}
/* 聊天容器 */
@ -10,6 +11,11 @@ page {
flex-direction: column;
height: 100vh;
background-color: #f6f7fb;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1;
}
/* 聊天头部 */
@ -22,6 +28,8 @@ page {
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding-bottom: 15rpx;
position: relative;
z-index: 2;
}
.back-btn {
@ -130,6 +138,8 @@ page {
padding: 10px 15px;
border-top: 1px solid #eee;
margin-bottom: 20rpx;
position: relative;
z-index: 2;
}
.input-container {

View File

@ -16,7 +16,7 @@
<!-- 聊天消息区域 -->
<scroll-view :scroll-into-view="scrollToView" class="chat-messages" scroll-y="true"
@scrolltoupper="loadMoreHistory">
@scrolltoupper="loadMoreHistory" @scrolltolower="loadMoreHistory" lower-threshold="100" upper-threshold="100">
<!-- 加载历史消息提示 -->
<view v-if="isLoadingHistory" class="message-time">加载历史消息...</view>
@ -31,7 +31,7 @@
'other': !message.isSelf,
'loading': message.isLoading
}" class="message-item">
<image :src="message.isSelf ? userAvatar : (chatTarget.avatar || '/static/logo.png')" class="message-avatar"
<image :src="message.isSelf ? userAvatar : (chatTarget.employee_image)" class="message-avatar"
mode="aspectFill"></image>
<view class="message-content">
{{ message.content }}
@ -44,7 +44,9 @@
<view class="chat-input-area">
<view class="input-container">
<textarea v-model="inputMessage" :adjust-position="true" class="message-input" placeholder="请输入消息..."
@confirm="sendMessage" @input="handleInput"></textarea>
@confirm="sendMessage" @input="handleInput" auto-height hold-keyboard="true"
enable-keyboard-accessory-view="true" cursor-spacing="10" maxlength="500"
@focus="onInputFocus" @blur="onInputBlur"></textarea>
<button :disabled="!canSend || !client || !isConnected" class="send-btn" @tap="sendMessage">
发送
</button>
@ -54,9 +56,9 @@
</template>
<script>
import { menuButtonInfo, request } from '@/utils'
import { picUrl, menuButtonInfo, request, NavgateTo } from "../../../utils";
import { apiArr } from '@/api/customerService'
import MqttUtils from '@/utils/mqtt'
import mqttTool from '@/utils/mqtt'
export default {
data(){
@ -71,7 +73,7 @@ export default {
openId: '' // open_id
},
//
userAvatar: '/static/logo.png',
userAvatar: '',
//
messages: [],
//
@ -93,7 +95,14 @@ export default {
client: null,
mqttConfig: {},
//
reconnectFailedTimer: null
reconnectFailedTimer: null,
//
pageNum: 1,
pageSize: 10,
//
hasMoreHistory: true,
//
scrollToBottomFlag: false
}
},
onLoad(options){
@ -108,16 +117,41 @@ export default {
// MQTT
this.initChat()
this.getMqttConfig()
//
this.userAvatar = picUrl + uni.getStorageSync('headPhoto')
},
onShow(){
},
methods: {
async connect(){
this.client = null
const options = {
clientId: this.selfClientId
}
this.client = MqttUtils.connect(options)
//
const callbacks = {
onConnect: () => {
console.log('客服连接成功')
this.isConnected = true
this.connectingStatus = ''
},
onDisconnect: this.onDisconnect.bind(this),
onError: (error) => {
console.error('客服连接错误:', error)
this.isConnected = false
this.connectingStatus = '连接错误,请重试'
},
onReconnect: () => {
console.log('客服正在重连...')
this.isConnected = false
this.connectingStatus = '连接已断开,正在重连...'
}
}
this.client = mqttTool.connect(options, callbacks)
this.isConnected = !!this.client
await this.subscribe()
this.client.on('message', (topic, message) => {
let de = new TextDecoder('utf-8')
@ -142,9 +176,10 @@ export default {
},
async subscribe(){
if (this.isConnected && this.client) {
this.client.subscribe('contact/message/receive_msg', { qos: 0 }, function(err){
this.client.subscribe('contact/message/receive_msg', { qos: 0 }, (err) => {
if (!err) {
console.log('订阅成功', 'contact/message/receive_msg', { qos: 0 })
this.connectingStatus = ''
} else {
console.log('订阅失败:', err)
this.connectingStatus = '订阅失败,请重试'
@ -152,20 +187,25 @@ export default {
})
} else {
console.log('连接失败', this.isConnected, this.client)
this.connectingStatus = '连接失败,请重试'
}
},
//
async initChat(){
try {
//
this.connectingStatus = '连接中...'
this.connectingStatus = '正在连接客服...'
await this.connect()
//
this.startKeepalive()
//
this.loadHistoryMessages()
} catch (error) {
console.error('初始化聊天失败', error)
this.connectingStatus = '连接失败,请重试'
this.connectingStatus = '连接失败,请检查网络'
//
setTimeout(() => {
this.reconnectFailedTimer = setTimeout(() => {
this.initChat()
}, 3000)
}
@ -173,7 +213,6 @@ export default {
// MQTT
async getMqttConfig(){
console.log('🚀 ~ getMqttConfig ~ getMqttConfig:', 11111)
try {
// clientIdAPI
return new Promise((resolve, reject) => {
@ -204,23 +243,20 @@ export default {
}
},
// MQTT ()
// MQTT
onDisconnect(packet){
console.log('MQTT连接断开', packet)
this.isConnected = false
this.client = null
//
if (packet.error) {
if (packet && packet.error) {
//
this.connectingStatus = '连接失败,请检查网络或服务器'
} else
if (packet.reconnecting) {
//
this.connectingStatus = '连接已断开,正在重连...'
} else {
//
this.connectingStatus = '连接已断开,正在重连...'
}
this.connectingStatus = '连接失败,请检查网络'
} else {
//
this.connectingStatus = '连接已断开,正在重连...'
}
//
this.stopKeepalive()
@ -228,8 +264,53 @@ export default {
//
async loadHistoryMessages(){
if (!this.hasMoreHistory || this.isLoadingHistory) {
return
}
try {
this.isLoadingHistory = true
// mqttConfig.bind_id
if (!this.mqttConfig.bind_id) {
await this.getMqttConfig()
}
const params = {
bind_id: this.mqttConfig.bind_id,
order: 'desc', //
page_num: this.pageNum,
page_size: this.pageSize
}
console.log('请求历史消息参数:', params)
const res = await request(apiArr.csGetMsgRecord, 'POST', params)
console.log('历史消息返回结果:', res)
if (res && res.code === 1 && res.data && res.data.msg_record) {
const historyMessages = res.data.msg_record
//
if (historyMessages.length === 0) {
this.hasMoreHistory = false
return
}
//
//
const formattedMessages = historyMessages.map(msg => ({
content: msg.content,
time: new Date(msg.create_time).getTime(),
isSelf: msg.send_client === this.mqttConfig.clientId,
isLoading: false
})).reverse() //
//
this.messages = [...formattedMessages, ...this.messages]
//
this.pageNum++
}
} catch (error) {
console.error('加载历史消息失败', error)
} finally {
@ -239,9 +320,9 @@ export default {
//
loadMoreHistory(){
if (this.isLoadingHistory) {
if (!this.isLoadingHistory && this.hasMoreHistory) {
this.loadHistoryMessages()
}
//
},
//
@ -284,6 +365,21 @@ export default {
handleInput(){
this.canSend = this.inputMessage.trim().length > 0
},
onInputFocus() {
//
this.scrollToBottomFlag = true;
setTimeout(() => {
if (this.scrollToBottomFlag) {
this.scrollToBottom();
}
}, 300);
},
onInputBlur() {
//
this.scrollToBottomFlag = false;
},
// 线
needShowTime(index){

View File

@ -233,7 +233,7 @@
已支付成功
</view>
<view class="boxshadow_img">
<view>核销码{{ verifyCode }}</view>
<view v-if="verifyCode">核销码{{ verifyCode }}</view>
<!-- <image
src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/shop_share_img.png">
</image> -->

View File

@ -109,6 +109,7 @@
icon: 'success',
mask: true
})
uni.setStorageSync('headPhoto', this.avatarInfo.picUrl)
setTimeout(() => {
uni.navigateBack({ delta: 1 })
}, 2000)

View File

@ -97,7 +97,8 @@
<image :src="serverLeft" mode="aspectFill" />
</view>
<view class="serverList1_right">
<view :class="['serverItemRight', `serverItemRight${index + 1}`]" @tap="headerServerClick(item)"
<view :class="['serverItemRight', `serverItemRight${index + 1}`]"
@tap="index === 1 ? toAdvertisingView(serverRightList) : headerServerClick(item)"
v-for="(item, index) in serverRightList" :key="index">
<image :src="item.pic_src" mode="" />
</view>

View File

@ -90,6 +90,7 @@ export default {
uni.setStorageSync('userId', res2.user_id);
uni.setStorageSync('openId', res2.open_id);
uni.setStorageSync('shopId', res2.wshop_id);
uni.setStorageSync('headPhoto', res2.img);
this.isLogin = true;
that.getUserInfo();

View File

@ -5,7 +5,7 @@ let mqttTool = {
client: null
}
mqttTool.connect = function(params){
mqttTool.connect = function(params, callbacks = {}){
let options = {
clientId: params.clientId,
username: 'dev01',
@ -20,11 +20,36 @@ mqttTool.connect = function(params){
client = mqtt.connect('wx://api.hshuishang.com:8084/mqtt', options)
console.log('WX', client)
mqttTool.client = client
if (mqttTool.client) {
console.log('连接成功')
} else {
console.log('连接失败')
// 设置连接状态回调
if (callbacks.onConnect) {
client.on('connect', callbacks.onConnect)
}
if (callbacks.onDisconnect) {
client.on('disconnect', callbacks.onDisconnect)
}
if (callbacks.onError) {
client.on('error', callbacks.onError)
}
if (callbacks.onReconnect) {
client.on('reconnect', callbacks.onReconnect)
}
client.on('connect', function() {
console.log('MQTT连接成功')
})
client.on('error', function(error) {
console.log('MQTT连接错误:', error)
})
client.on('reconnect', function() {
console.log('MQTT正在重连...')
})
return client
}