feat(chat): 添加智能客服转人工功能及消息处理逻辑

This commit is contained in:
yangzhe 2025-12-23 10:17:28 +08:00
parent f6486a69ea
commit 98bbb1c8d8
3 changed files with 260 additions and 73 deletions

18
App.vue
View File

@ -223,19 +223,29 @@ export default {
// //
if (type === "PrivateChatMessage") { if (type === "PrivateChatMessage") {
const data = msgData.Data || {}; const data = msgData.Data || {};
const dialogueManagementId = data.DialogueManagementId || "";
const processData = { const processData = {
dialogueManagementId: data.DialogueManagementId, dialogueManagementId,
senderId: data.SenderId, senderId: data.SenderId,
receiverId: data.ReceiverId, receiverId: data.ReceiverId,
sendDate: data.SendDate, sendDate: data.SendDate,
message: data.Message, message: data.Message,
}; };
this.$store.commit("push_Msg", { const id = data.Id || data.id || Math.random().toString(36).substring(2);
const msg = {
...processData, ...processData,
messageType: 0, messageType: 0,
filePath: "", filePath: "",
id: Math.random().toString(36).substring(2), id,
}); };
const storeState = (this.$store && this.$store.state) || {};
const isTransferChat = !!storeState.vuex_isTransferChat;
if (isTransferChat) {
this.$store.commit("push_AiLiveAgentMsg", msg);
} else {
this.$store.commit("push_Msg", msg);
}
// / / // / /
if (processData.dialogueManagementId) { if (processData.dialogueManagementId) {

View File

@ -54,7 +54,7 @@
<view class="feature-grid"> <view class="feature-grid">
<view <view
class="feature-item" class="feature-item"
v-for="(item, index) in features" v-for="(item, index) in displayFeatures"
:key="index" :key="index"
:style="{ :style="{
background: item.background, background: item.background,
@ -282,6 +282,33 @@
</view> </view>
</view> </view>
</view> </view>
<!-- 转人工消息 -->
<!-- 8 人工回复 -->
<view
class="message-left"
v-else-if="message.interactMode === 8"
:id="'msg-' + message.id"
>
<image
class="ai-avatar"
:src="receiverHeadSculptureUrl"
mode="scaleToFill"
/>
<view
class="message-content"
:class="{
'message-content-width': !message.isLoading,
}"
>
<view v-if="message.isLoading" class="loading-dots">
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
<markdown-viewer v-else :content="message.message" />
</view>
</view>
</block> </block>
</view> </view>
</scroll-view> </scroll-view>
@ -485,14 +512,50 @@ export default {
return "/static/common/images/avatar.png"; return "/static/common/images/avatar.png";
}, },
receiverHeadSculptureUrl() {
const url =
(this.vuex_msgUser &&
(this.vuex_msgUser.headSculptureUrl ||
this.vuex_msgUser.HeadSculptureUrl)) ||
"";
if (url) {
return this.baseUrl + "/" + url;
}
return "/static/common/images/avatar_default2.png";
},
aiMessageGroups() { aiMessageGroups() {
return Array.isArray(this.vuex_aiMessageGroups) return Array.isArray(this.vuex_aiMessageGroups)
? this.vuex_aiMessageGroups ? this.vuex_aiMessageGroups
: []; : [];
}, },
displayFeatures() {
const list = Array.isArray(this.features) ? this.features : [];
return list.map((item) => {
if (item && item.title === "转人工") {
return {
...item,
title: this.vuex_isTransferChat ? "结束会话" : "转人工",
};
}
return item;
});
},
isLiveAgentChat() {
if (!this.vuex_isTransferChat) return false;
const current = this.currentDMid || "";
const active = this.vuex_msgUser && this.vuex_msgUser.dialogueManagementId;
return !!(current && active && String(active) === String(current));
},
}, },
watch: { watch: {
currentDMid: {
handler(val) {
this.$store.commit("set_AiActiveDMid", val || "");
},
immediate: true,
},
// //
aiMessageGroups: { aiMessageGroups: {
handler() { handler() {
@ -619,6 +682,10 @@ export default {
this.handleTransferEntryClick(); this.handleTransferEntryClick();
return; return;
} }
if (item.title === "结束会话") {
this.$store.commit("set_IsTransferChat", false);
return;
}
if (item.title === "电话咨询") { if (item.title === "电话咨询") {
this.advicePhoneShow = true; this.advicePhoneShow = true;
@ -709,6 +776,17 @@ export default {
(m) => m && m.customType === "transferCard" (m) => m && m.customType === "transferCard"
); );
}; };
const updateTransferCard = (patch) => {
const list = this.aiMessageGroups;
const idx = findCardIndex(list);
if (idx < 0) return false;
const current = list[idx] || {};
const next = { ...current, ...patch };
const nextList = list.slice();
nextList.splice(idx, 1, next);
this.$store.commit("push_AiMsgList", nextList);
return true;
};
const list = this.aiMessageGroups; const list = this.aiMessageGroups;
const idx = findCardIndex(list); const idx = findCardIndex(list);
if (idx < 0) return; if (idx < 0) return;
@ -716,15 +794,11 @@ export default {
const current = list[idx] || {}; const current = list[idx] || {};
if (this.isTransferSubmitting || current.transferStatus === "done") return; if (this.isTransferSubmitting || current.transferStatus === "done") return;
const next = { updateTransferCard({
...current,
transferStatus: "idle", transferStatus: "idle",
selectedConsultationType: consultationType, selectedConsultationType: consultationType,
transferTipText: "", transferTipText: "",
}; });
const nextList = list.slice();
nextList.splice(idx, 1, next);
this.$store.commit("push_AiMsgList", nextList);
this.isTransferSubmitting = true; this.isTransferSubmitting = true;
const params = { const params = {
@ -740,48 +814,59 @@ export default {
this.$u.api this.$u.api
.TransferToALiveAgentApi(params) .TransferToALiveAgentApi(params)
.then((res) => { .then((res) => {
this.isTransferSubmitting = false;
const latestList = this.aiMessageGroups;
const nextIdx = findCardIndex(latestList);
if (nextIdx < 0) return;
const succeed = res && res.succeed !== false; const succeed = res && res.succeed !== false;
const serverTip = const serverTip = (res && res.data && res.data.message) || (res && res.error) || "";
(res && res.data && res.data.message) ||
(res && res.error) ||
"";
if (!succeed) { if (!succeed) {
const updated = { updateTransferCard({
...latestList[nextIdx],
transferStatus: "idle", transferStatus: "idle",
transferTipText: serverTip, transferTipText: serverTip,
}; });
const updatedList = latestList.slice();
updatedList.splice(nextIdx, 1, updated);
this.$store.commit("push_AiMsgList", updatedList);
return; return;
} }
const updated = {
...latestList[nextIdx], const dialogueManagementId =
this.currentDMid ||
(res &&
res.data &&
(res.data.dialogueManagementId ||
res.data.DialogueManagementId)) ||
"";
const receiverId =
(res &&
res.data &&
(res.data.receiverId ||
res.data.ReceiverId ||
res.data.liveAgentId ||
res.data.agentId ||
res.data.userId)) ||
"";
if (!this.currentDMid && dialogueManagementId) {
this.currentDMid = dialogueManagementId;
}
updateTransferCard({
transferStatus: "done", transferStatus: "done",
transferTipText: serverTip, transferTipText: serverTip,
}; });
const updatedList = latestList.slice();
updatedList.splice(nextIdx, 1, updated); if (dialogueManagementId && receiverId) {
this.$store.commit("push_AiMsgList", updatedList); this.$store.commit("set_IsTransferChat", true);
this.$store.dispatch("selectTeacherChatItem", {
id: dialogueManagementId,
receiverId,
navigate: false,
});
}
}) })
.catch(() => { .catch(() => {
this.isTransferSubmitting = false; updateTransferCard({
const latestList = this.aiMessageGroups;
const nextIdx = findCardIndex(latestList);
if (nextIdx < 0) return;
const updated = {
...latestList[nextIdx],
transferStatus: "idle", transferStatus: "idle",
transferTipText: "转人工失败,请稍后重试", transferTipText: "转人工失败,请稍后重试",
}; });
const updatedList = latestList.slice(); })
updatedList.splice(nextIdx, 1, updated); .finally(() => {
this.$store.commit("push_AiMsgList", updatedList); this.isTransferSubmitting = false;
}); });
}, },
@ -814,6 +899,11 @@ export default {
this.$store.commit("push_AiMsg", userMessage); this.$store.commit("push_AiMsg", userMessage);
this.messageValue = ""; this.messageValue = "";
if (this.isLiveAgentChat) {
this.sendLiveAgentTextMessage(sendMessage, userMessage.id);
return;
}
// AI // AI
const loadingMessage = { const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15), id: "loading_" + Math.random().toString(36).substring(2, 15),
@ -929,6 +1019,11 @@ export default {
// //
this.$store.commit("push_AiMsg", userMessage); this.$store.commit("push_AiMsg", userMessage);
if (this.isLiveAgentChat) {
this.sendLiveAgentTextMessage(sendMessage, userMessage.id);
return;
}
// AI // AI
const loadingMessage = { const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15), id: "loading_" + Math.random().toString(36).substring(2, 15),
@ -1147,6 +1242,7 @@ export default {
console.log("触发上拉刷新"); console.log("触发上拉刷新");
if (!this.currentDMid) return; if (!this.currentDMid) return;
if (this.isLiveAgentChat) return;
// //
if (this.noMoreData || this.isSwitchingConversation) { if (this.noMoreData || this.isSwitchingConversation) {
@ -1203,6 +1299,41 @@ export default {
}); });
}, },
sendLiveAgentTextMessage(messageText, localId) {
const dialogueManagementId = this.currentDMid || "";
const receiverId =
(this.vuex_msgUser &&
(this.vuex_msgUser.id ||
this.vuex_msgUser.Id ||
this.vuex_msgUser.receiverId)) ||
"";
if (!dialogueManagementId || !receiverId) {
this.$refs.uToast.show({
title: "人工会话未初始化,请重新转人工",
type: "warning",
});
return;
}
const payload = {
dialogueManagementId,
receiverId,
message: messageText,
messageType: 0,
filePath: "",
ip: "",
};
this.$u.api.SendMessage_PrivateApi(payload).then((res) => {
if (res && res.succeed) return;
this.$refs.uToast.show({
title: (res && (res.msg || res.message)) || "发送失败",
type: "warning",
});
});
},
// //
hotQARefresh() { hotQARefresh() {
console.log("刷新问题"); console.log("刷新问题");

View File

@ -45,6 +45,8 @@ const store = new Vuex.Store({
vuex_version: "1.0.0", vuex_version: "1.0.0",
// 智能客服消息组列表 // 智能客服消息组列表
vuex_aiMessageGroups: [], vuex_aiMessageGroups: [],
vuex_aiActiveDMid: "",
vuex_isTransferChat: false,
// 当前聊天窗口消息列表(数组 // 当前聊天窗口消息列表(数组
vuex_msgList: [], vuex_msgList: [],
// 最近联系人/会话列表 // 最近联系人/会话列表
@ -181,6 +183,40 @@ const store = new Vuex.Store({
}, },
// ===== 智能客服:消息组列表维护(参照 vuex_msgList 维护方式) ===== // ===== 智能客服:消息组列表维护(参照 vuex_msgList 维护方式) =====
set_AiActiveDMid(state, dmid) {
state.vuex_aiActiveDMid = dmid || "";
},
set_IsTransferChat(state, val) {
state.vuex_isTransferChat = !!val;
},
push_AiLiveAgentMsg(state, msg) {
if (!msg) return;
const msgId = msg.id || msg.Id;
if (!msgId) return;
const exists = (state.vuex_aiMessageGroups || []).some(
(item) => item && item.id === msgId
);
if (exists) return;
const userId =
(state.vuex_user && (state.vuex_user.Id || state.vuex_user.id)) || "";
const senderId = msg.senderId || msg.SenderId || "";
const isSelf =
userId && senderId && String(userId) === String(senderId);
state.vuex_aiMessageGroups.push({
id: msgId,
message: msg.message || msg.Message || "",
sendDate: msg.sendDate || msg.SendDate || "",
isSend: true,
isRead: true,
interactMode: isSelf ? 0 : 8,
messageType: msg.messageType || msg.MessageType || 0,
timeLabel: 0,
displayTime: "",
});
},
// 覆盖整个列表 // 覆盖整个列表
push_AiMsgList(state, list) { push_AiMsgList(state, list) {
state.vuex_aiMessageGroups = Array.isArray(list) ? list : []; state.vuex_aiMessageGroups = Array.isArray(list) ? list : [];
@ -291,6 +327,8 @@ const store = new Vuex.Store({
state.vuex_userMsgList = []; state.vuex_userMsgList = [];
state.vuex_msgUser = null; state.vuex_msgUser = null;
state.vuex_msgScrollTop = 0; state.vuex_msgScrollTop = 0;
state.vuex_aiActiveDMid = "";
state.vuex_isTransferChat = false;
}, },
// 统一登出清理:清除 token 与用户数据,并同步到本地存储 // 统一登出清理:清除 token 与用户数据,并同步到本地存储
// 注意:仅清理与登录态相关的关键字段,避免影响其他持久化数据 // 注意:仅清理与登录态相关的关键字段,避免影响其他持久化数据
@ -304,6 +342,8 @@ const store = new Vuex.Store({
state.vuex_userMsgList = []; state.vuex_userMsgList = [];
state.vuex_msgUser = null; state.vuex_msgUser = null;
state.vuex_msgScrollTop = 0; state.vuex_msgScrollTop = 0;
state.vuex_aiActiveDMid = "";
state.vuex_isTransferChat = false;
// 同步更新本地持久化 // 同步更新本地持久化
saveLifeData("vuex_token", state.vuex_token); saveLifeData("vuex_token", state.vuex_token);
saveLifeData("vuex_teacherInfo", state.vuex_teacherInfo); saveLifeData("vuex_teacherInfo", state.vuex_teacherInfo);
@ -402,19 +442,25 @@ const store = new Vuex.Store({
}, },
// 点击聊天记录,切换到该会话 // 点击聊天记录,切换到该会话
selectTeacherChatItem({ commit, dispatch }, { id, receiverId }) { selectTeacherChatItem(
{ commit, dispatch },
{ id, receiverId, navigate = true } = {}
) {
if (!id || !receiverId) return;
// 清空消息列表,避免旧消息干扰 // 清空消息列表,避免旧消息干扰
commit("push_MsgList", []); commit("push_MsgList", []);
Vue.prototype.$u.api return Vue.prototype.$u.api
.GetReceiverUserInfoApi({ Id: receiverId }) .GetReceiverUserInfoApi({ Id: receiverId })
.then((res) => { .then((res) => {
if (res.succeed && res.data) { if (res.succeed && res.data) {
commit("set_MsgUser", { ...res.data, dialogueManagementId: id }); commit("set_MsgUser", { ...res.data, dialogueManagementId: id });
uni.navigateTo({ if (navigate) {
url: `/pages/chat/index`, uni.navigateTo({
}); url: `/pages/chat/index`,
return; });
}
return res.data;
} }
}); });
}, },