Compare commits

...

2 Commits

3 changed files with 260 additions and 73 deletions

70
App.vue
View File

@ -200,11 +200,11 @@ export default {
if (type === "pong") return;
// 退
if (type === "Exit") {
try {
this.ws && this.ws.close();
} catch (err) {}
//
if (type === "Exit") {
try {
this.ws && this.ws.close();
} catch (err) {}
//
this.$u.vuex("vuex_user", "");
this.$u.vuex("vuex_token", "");
uni.clearStorage();
@ -220,35 +220,45 @@ export default {
this.$u.vuex("vuex_tabbar", tab);
}
//
if (type === "PrivateChatMessage") {
const data = msgData.Data || {};
const processData = {
dialogueManagementId: data.DialogueManagementId,
senderId: data.SenderId,
//
if (type === "PrivateChatMessage") {
const data = msgData.Data || {};
const dialogueManagementId = data.DialogueManagementId || "";
const processData = {
dialogueManagementId,
senderId: data.SenderId,
receiverId: data.ReceiverId,
sendDate: data.SendDate,
message: data.Message,
};
this.$store.commit("push_Msg", {
...processData,
messageType: 0,
filePath: "",
id: Math.random().toString(36).substring(2),
});
// / /
if (processData.dialogueManagementId) {
this.$store.commit("apply_RealtimeMessageToList", {
dialogueId: processData.dialogueManagementId,
message: processData.message,
sendDate: processData.sendDate,
senderId: processData.senderId,
receiverId: processData.receiverId,
});
}
}
},
const id = data.Id || data.id || Math.random().toString(36).substring(2);
const msg = {
...processData,
messageType: 0,
filePath: "",
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) {
this.$store.commit("apply_RealtimeMessageToList", {
dialogueId: processData.dialogueManagementId,
message: processData.message,
sendDate: processData.sendDate,
senderId: processData.senderId,
receiverId: processData.receiverId,
});
}
}
},
//
handleWsClose(e) {
// console.log(`[WebSocket] : code=${e.code}, reason=${e.reason}`);

View File

@ -54,7 +54,7 @@
<view class="feature-grid">
<view
class="feature-item"
v-for="(item, index) in features"
v-for="(item, index) in displayFeatures"
:key="index"
:style="{
background: item.background,
@ -282,6 +282,33 @@
</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>
</view>
</scroll-view>
@ -485,14 +512,50 @@ export default {
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() {
return Array.isArray(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: {
currentDMid: {
handler(val) {
this.$store.commit("set_AiActiveDMid", val || "");
},
immediate: true,
},
//
aiMessageGroups: {
handler() {
@ -619,6 +682,10 @@ export default {
this.handleTransferEntryClick();
return;
}
if (item.title === "结束会话") {
this.$store.commit("set_IsTransferChat", false);
return;
}
if (item.title === "电话咨询") {
this.advicePhoneShow = true;
@ -709,6 +776,17 @@ export default {
(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 idx = findCardIndex(list);
if (idx < 0) return;
@ -716,15 +794,11 @@ export default {
const current = list[idx] || {};
if (this.isTransferSubmitting || current.transferStatus === "done") return;
const next = {
...current,
updateTransferCard({
transferStatus: "idle",
selectedConsultationType: consultationType,
transferTipText: "",
};
const nextList = list.slice();
nextList.splice(idx, 1, next);
this.$store.commit("push_AiMsgList", nextList);
});
this.isTransferSubmitting = true;
const params = {
@ -740,48 +814,59 @@ export default {
this.$u.api
.TransferToALiveAgentApi(params)
.then((res) => {
this.isTransferSubmitting = false;
const latestList = this.aiMessageGroups;
const nextIdx = findCardIndex(latestList);
if (nextIdx < 0) return;
const succeed = res && res.succeed !== false;
const serverTip =
(res && res.data && res.data.message) ||
(res && res.error) ||
"";
const serverTip = (res && res.data && res.data.message) || (res && res.error) || "";
if (!succeed) {
const updated = {
...latestList[nextIdx],
updateTransferCard({
transferStatus: "idle",
transferTipText: serverTip,
};
const updatedList = latestList.slice();
updatedList.splice(nextIdx, 1, updated);
this.$store.commit("push_AiMsgList", updatedList);
});
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",
transferTipText: serverTip,
};
const updatedList = latestList.slice();
updatedList.splice(nextIdx, 1, updated);
this.$store.commit("push_AiMsgList", updatedList);
});
if (dialogueManagementId && receiverId) {
this.$store.commit("set_IsTransferChat", true);
this.$store.dispatch("selectTeacherChatItem", {
id: dialogueManagementId,
receiverId,
navigate: false,
});
}
})
.catch(() => {
this.isTransferSubmitting = false;
const latestList = this.aiMessageGroups;
const nextIdx = findCardIndex(latestList);
if (nextIdx < 0) return;
const updated = {
...latestList[nextIdx],
updateTransferCard({
transferStatus: "idle",
transferTipText: "转人工失败,请稍后重试",
};
const updatedList = latestList.slice();
updatedList.splice(nextIdx, 1, updated);
this.$store.commit("push_AiMsgList", updatedList);
});
})
.finally(() => {
this.isTransferSubmitting = false;
});
},
@ -814,6 +899,11 @@ export default {
this.$store.commit("push_AiMsg", userMessage);
this.messageValue = "";
if (this.isLiveAgentChat) {
this.sendLiveAgentTextMessage(sendMessage, userMessage.id);
return;
}
// AI
const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15),
@ -929,6 +1019,11 @@ export default {
//
this.$store.commit("push_AiMsg", userMessage);
if (this.isLiveAgentChat) {
this.sendLiveAgentTextMessage(sendMessage, userMessage.id);
return;
}
// AI
const loadingMessage = {
id: "loading_" + Math.random().toString(36).substring(2, 15),
@ -1147,6 +1242,7 @@ export default {
console.log("触发上拉刷新");
if (!this.currentDMid) return;
if (this.isLiveAgentChat) return;
//
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() {
console.log("刷新问题");

View File

@ -45,6 +45,8 @@ const store = new Vuex.Store({
vuex_version: "1.0.0",
// 智能客服消息组列表
vuex_aiMessageGroups: [],
vuex_aiActiveDMid: "",
vuex_isTransferChat: false,
// 当前聊天窗口消息列表(数组
vuex_msgList: [],
// 最近联系人/会话列表
@ -183,6 +185,40 @@ const store = new Vuex.Store({
},
// ===== 智能客服:消息组列表维护(参照 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) {
state.vuex_aiMessageGroups = Array.isArray(list) ? list : [];
@ -293,6 +329,8 @@ const store = new Vuex.Store({
state.vuex_userMsgList = [];
state.vuex_msgUser = null;
state.vuex_msgScrollTop = 0;
state.vuex_aiActiveDMid = "";
state.vuex_isTransferChat = false;
},
// 统一登出清理:清除 token 与用户数据,并同步到本地存储
// 注意:仅清理与登录态相关的关键字段,避免影响其他持久化数据
@ -306,6 +344,8 @@ const store = new Vuex.Store({
state.vuex_userMsgList = [];
state.vuex_msgUser = null;
state.vuex_msgScrollTop = 0;
state.vuex_aiActiveDMid = "";
state.vuex_isTransferChat = false;
// 同步更新本地持久化
saveLifeData("vuex_token", state.vuex_token);
saveLifeData("vuex_teacherInfo", state.vuex_teacherInfo);
@ -419,19 +459,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", []);
Vue.prototype.$u.api
return Vue.prototype.$u.api
.GetReceiverUserInfoApi({ Id: receiverId })
.then((res) => {
if (res.succeed && res.data) {
commit("set_MsgUser", { ...res.data, dialogueManagementId: id });
uni.navigateTo({
url: `/pages/chat/index`,
});
return;
if (navigate) {
uni.navigateTo({
url: `/pages/chat/index`,
});
}
return res.data;
}
});
},