Merge branch 'main' of http://sl.vrgon.com:3000/JiXinHui/YingXingAI
This commit is contained in:
commit
0a3a521a32
|
|
@ -236,18 +236,21 @@ const install = (Vue, vm) => {
|
|||
// 获取消息接收方用户信息
|
||||
let GetReceiverUserInfoApi = (params = {}) =>
|
||||
vm.$u.get("api/Dialogue/GetReceiverUserInfo", params);
|
||||
// 置顶一个会话
|
||||
let OverheadOneDialogueApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/OverheadOneDialogue", params);
|
||||
// 将会话消息标记为已读
|
||||
let ReadMessageApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/ReadMessage", params);
|
||||
// 置顶一个会话
|
||||
let OverheadOneDialogueApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/OverheadOneDialogue", params);
|
||||
// 将会话消息标记为已读
|
||||
let ReadMessageApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/ReadMessage", params);
|
||||
// 删除会话
|
||||
let DeleteDialogueApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/DeleteDialogue", params);
|
||||
// 转人工服务
|
||||
let TransferToALiveAgentApi = (params = {}) =>
|
||||
vm.$u.post("api/Dialogue/TransferToALiveAgent", params);
|
||||
|
||||
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
||||
vm.$u.api = {
|
||||
vm.$u.api = {
|
||||
UploadSingleImage,
|
||||
getTeacherInfo,
|
||||
getData,
|
||||
|
|
@ -306,16 +309,17 @@ const install = (Vue, vm) => {
|
|||
UpdateUserApi,
|
||||
GetTeacherListApi,
|
||||
GetDialogueListApi,
|
||||
GetDialogueList_UserApi,
|
||||
AddDialogueApi,
|
||||
SendMessage_PrivateApi,
|
||||
GetChatHistoryDataApi,
|
||||
GetReceiverUserInfoApi,
|
||||
OverheadOneDialogueApi,
|
||||
ReadMessageApi,
|
||||
DeleteDialogueApi,
|
||||
};
|
||||
};
|
||||
GetDialogueList_UserApi,
|
||||
AddDialogueApi,
|
||||
SendMessage_PrivateApi,
|
||||
GetChatHistoryDataApi,
|
||||
GetReceiverUserInfoApi,
|
||||
OverheadOneDialogueApi,
|
||||
ReadMessageApi,
|
||||
DeleteDialogueApi,
|
||||
TransferToALiveAgentApi,
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
install,
|
||||
|
|
|
|||
|
|
@ -374,13 +374,6 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(date) {
|
||||
const hours = date.getHours().toString().padStart(2, "0");
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
|
||||
// 是否显示时间
|
||||
isShowTime(index) {
|
||||
return shouldShowTime(this.vuex_msgList, index);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<view class="date-header">{{ group.id }}</view>
|
||||
|
||||
<view v-for="item in group.conversation" :key="item.id">
|
||||
<uni-swipe-action ref="swipeActionRef">
|
||||
<uni-swipe-action :ref="'swipe-' + item.id">
|
||||
<uni-swipe-action-item
|
||||
:disabled="isBatchDeleteMode"
|
||||
:threshold="0.3"
|
||||
|
|
@ -123,7 +123,10 @@
|
|||
</view>
|
||||
|
||||
<!-- 批量删除操作栏 -->
|
||||
<view class="batch-actions" v-if="isBatchDeleteMode && currentHistoryItems.length">
|
||||
<view
|
||||
class="batch-actions"
|
||||
v-if="isBatchDeleteMode && currentHistoryItems.length"
|
||||
>
|
||||
<view class="select-all-container" @click="toggleSelectAll">
|
||||
<view class="checkbox" :class="{ checked: selectAll }">
|
||||
<text class="checkmark" v-if="selectAll">✓</text>
|
||||
|
|
@ -164,6 +167,7 @@ export default {
|
|||
chatHistoryAI: [], // ai咨询历史记录
|
||||
chatHistoryTeacher: [], // 人工咨询历史记录
|
||||
openedItems: {}, // 记录每个项目的滑动状态
|
||||
openedSwipeId: "", // 当前展开的侧滑项 id
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -207,16 +211,9 @@ export default {
|
|||
|
||||
// 重置滑动状态
|
||||
closeAllSwipeActions() {
|
||||
const swipeActionRefs = this.$refs.swipeActionRef || [];
|
||||
const refs = Array.isArray(swipeActionRefs)
|
||||
? swipeActionRefs
|
||||
: [swipeActionRefs];
|
||||
|
||||
refs.forEach((ref) => {
|
||||
if (ref && typeof ref.closeAll === "function") {
|
||||
ref.closeAll();
|
||||
}
|
||||
});
|
||||
const ids = this.currentHistoryItemIds || [];
|
||||
ids.forEach((id) => this.closeSwipeActionById(id));
|
||||
this.openedSwipeId = "";
|
||||
},
|
||||
|
||||
// 清空 openedItems 并调用 closeAllSwipeActions()
|
||||
|
|
@ -225,6 +222,16 @@ export default {
|
|||
this.closeAllSwipeActions();
|
||||
},
|
||||
|
||||
closeSwipeActionById(itemId) {
|
||||
if (!itemId) return;
|
||||
const swipeRef = this.$refs[`swipe-${itemId}`];
|
||||
const ref = Array.isArray(swipeRef) ? swipeRef[0] : swipeRef;
|
||||
if (ref && typeof ref.closeAll === "function") {
|
||||
ref.closeAll();
|
||||
}
|
||||
this.$set(this.openedItems, itemId, "none");
|
||||
},
|
||||
|
||||
// 获取ai历史记录
|
||||
async getChatHistoryList() {
|
||||
this.$u.api.GetConversationPage().then((res) => {
|
||||
|
|
@ -465,6 +472,16 @@ export default {
|
|||
// 处理滑动操作变化
|
||||
handleSwipeChange(e, itemId) {
|
||||
this.$set(this.openedItems, itemId, e);
|
||||
if (e === "right" || e === "left") {
|
||||
if (this.openedSwipeId && this.openedSwipeId !== itemId) {
|
||||
this.closeSwipeActionById(this.openedSwipeId);
|
||||
}
|
||||
this.openedSwipeId = itemId;
|
||||
return;
|
||||
}
|
||||
if (e === "none" && this.openedSwipeId === itemId) {
|
||||
this.openedSwipeId = "";
|
||||
}
|
||||
},
|
||||
|
||||
// 处理单个删除操作
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
|
||||
<!-- 消息循环渲染 -->
|
||||
<block
|
||||
v-for="(message, index) in messageGroups"
|
||||
v-for="(message, index) in aiMessageGroups"
|
||||
:key="'msg-' + index"
|
||||
>
|
||||
<!-- 时间 -->
|
||||
|
|
@ -136,11 +136,77 @@
|
|||
{{ message.displayTime }}
|
||||
</view>
|
||||
|
||||
<!-- 转人工选择卡片(独立消息,不属于AI回复,不展示头像/反馈) -->
|
||||
<view
|
||||
class="message-transfer"
|
||||
v-if="message.customType === 'transferCard'"
|
||||
:id="'msg-' + message.id"
|
||||
>
|
||||
<view class="transfer-card">
|
||||
<view class="transfer-card-title">
|
||||
请选择您要咨询的类型,以便系统为您转接对应的客服
|
||||
</view>
|
||||
<view class="transfer-card-options">
|
||||
<view
|
||||
class="transfer-option"
|
||||
:class="{
|
||||
selected: message.selectedConsultationType === 1,
|
||||
disabled:
|
||||
isTransferSubmitting ||
|
||||
message.transferStatus === 'done',
|
||||
}"
|
||||
@click.stop="handleTransferTypeSelect(1)"
|
||||
>
|
||||
<u-icon
|
||||
name="account"
|
||||
size="44"
|
||||
:color="
|
||||
message.selectedConsultationType === 1
|
||||
? '#4A6CF7'
|
||||
: '#C0C4CC'
|
||||
"
|
||||
></u-icon>
|
||||
<text class="transfer-option-text">招生咨询</text>
|
||||
</view>
|
||||
<view
|
||||
class="transfer-option"
|
||||
:class="{
|
||||
selected: message.selectedConsultationType === 0,
|
||||
disabled:
|
||||
isTransferSubmitting ||
|
||||
message.transferStatus === 'done',
|
||||
}"
|
||||
@click.stop="handleTransferTypeSelect(0)"
|
||||
>
|
||||
<u-icon
|
||||
name="account"
|
||||
size="44"
|
||||
:color="
|
||||
message.selectedConsultationType === 0
|
||||
? '#4A6CF7'
|
||||
: '#C0C4CC'
|
||||
"
|
||||
></u-icon>
|
||||
<text class="transfer-option-text">迎新咨询</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="transfer-card-status"
|
||||
v-if="
|
||||
isTransferSubmitting ||
|
||||
!!message.transferTipText
|
||||
"
|
||||
>
|
||||
{{ getTransferCardTip(message) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户消息 -->
|
||||
<!-- 0 用户消息 -->
|
||||
<view
|
||||
class="message-right"
|
||||
v-if="message.interactMode === 0"
|
||||
v-else-if="message.interactMode === 0"
|
||||
:id="'msg-' + message.id"
|
||||
>
|
||||
<view class="message-content">
|
||||
|
|
@ -157,7 +223,9 @@
|
|||
<!-- 1 Ai回复 3 热门回复 -->
|
||||
<view
|
||||
class="message-left"
|
||||
v-if="message.interactMode === 1 || message.interactMode === 3"
|
||||
v-else-if="
|
||||
message.interactMode === 1 || message.interactMode === 3
|
||||
"
|
||||
:id="'msg-' + message.id"
|
||||
>
|
||||
<image
|
||||
|
|
@ -285,6 +353,11 @@ import AdvicePhone from "@/components/AdvicePhone.vue";
|
|||
import PerfectInfo from "@/components/PerfectInfo.vue";
|
||||
import ChatHistory from "@/components/ChatHistory.vue"; // 导入新组件
|
||||
import HeaderBar from "@/components/HeaderBar.vue"; // 导入头部组件
|
||||
import {
|
||||
processChatMessageContent,
|
||||
scrollToBottomByContentHeight,
|
||||
sortChatMessages,
|
||||
} from "@/utils/chat.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -301,6 +374,8 @@ export default {
|
|||
isChat: false,
|
||||
currentConversationId: "", // 当前对话的ID
|
||||
currentDMid: "", // 当前对话的DMId
|
||||
activeTransferCardId: "", // 当前选中的转人工卡片ID
|
||||
isTransferSubmitting: false, // 转人工请求中(用于失败时不改messageGroups)
|
||||
advicePhoneShow: false,
|
||||
perfectInfoShow: false,
|
||||
isLoading: false, // loadingMore
|
||||
|
|
@ -396,32 +471,6 @@ export default {
|
|||
"我什么时候能推知道自己是否被录取?",
|
||||
],
|
||||
scrollToView: "",
|
||||
// 修改后的messageGroups数据结构
|
||||
messageGroups: [
|
||||
// {
|
||||
// id: "9cac8661-bf09-4b63-ab15-1a0a36b921e0",
|
||||
// message: "你知道今年的录取分数线吗",
|
||||
// sendDate: "2025-07-10T15:11:32.886075",
|
||||
// isSend: true,
|
||||
// isRead: false,
|
||||
// interactMode: 0,
|
||||
// messageType: 0,
|
||||
// timeLabel: 1,
|
||||
// displayTime: "15:11",
|
||||
// },
|
||||
// {
|
||||
// id: "02306fc3-c821-4a23-ad66-0bd77d154105",
|
||||
// message:
|
||||
// "目前,2025年的录取分数线还没有正式公布,因为学校需要等招生工作结束后才会公布最终的录取分数线。不过,你可以参考2023-2024年的录取分数线作为大致的参考。\n\n如果你想知道具体专业的录取分数线,可以告诉我你所在省份和科类(历史类或物理类),我可以根据往年数据给你一些参考建议。如果需要更准确的信息,建议关注江西新能源科技职业学院的官网(www.jxnee.edu.cn)或者直接联系招生办(电话:0790-6764666/6765666)。",
|
||||
// sendDate: "2025-07-10T15:11:32.88644",
|
||||
// isSend: true,
|
||||
// isRead: false,
|
||||
// interactMode: 1,
|
||||
// messageType: 0,
|
||||
// timeLabel: 0,
|
||||
// displayTime: "",
|
||||
// },
|
||||
],
|
||||
scrollTop: 0,
|
||||
|
||||
messageValue: "", // 输入框内容
|
||||
|
|
@ -436,11 +485,16 @@ export default {
|
|||
|
||||
return "/static/common/images/avatar.png";
|
||||
},
|
||||
aiMessageGroups() {
|
||||
return Array.isArray(this.vuex_aiMessageGroups)
|
||||
? this.vuex_aiMessageGroups
|
||||
: [];
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
// 确保消息更新后自动滚动到底部
|
||||
messageGroups: {
|
||||
aiMessageGroups: {
|
||||
handler() {
|
||||
// 只有在不是加载更多的情况下才滚动到底部
|
||||
if (!this.isLoadingMore) {
|
||||
|
|
@ -484,7 +538,7 @@ export default {
|
|||
this.isSwitchingConversation = true;
|
||||
this.currentConversationId = conversationId;
|
||||
this.currentDMid = dmid;
|
||||
this.messageGroups = [];
|
||||
this.$store.commit("push_AiMsgList", []);
|
||||
this.pageQuery.PageIndex = 1;
|
||||
this.isLoadingMore = false;
|
||||
this.noMoreData = false;
|
||||
|
|
@ -550,28 +604,9 @@ export default {
|
|||
// 新建对话时不执行滚动
|
||||
if (this.isLoadingMore) return;
|
||||
|
||||
// 使用nextTick确保DOM已更新
|
||||
this.$nextTick(() => {
|
||||
// setTimeout(() => {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.select(".chat-content")
|
||||
.boundingClientRect((data) => {
|
||||
if (data) {
|
||||
console.log("chat-content高度:", data.height);
|
||||
|
||||
// 同时设置scrollTop和scrollToView
|
||||
this.scrollTop = data.height + 200;
|
||||
// if (this.messageGroups.length > 0) {
|
||||
// const lastMessage =
|
||||
// this.messageGroups[this.messageGroups.length - 1];
|
||||
// this.scrollToView = "msg-" + lastMessage.id;
|
||||
// console.log("设置scrollToView:", this.scrollToView);
|
||||
// }
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
// }, 300);
|
||||
scrollToBottomByContentHeight(this, {
|
||||
selector: ".chat-content",
|
||||
extraOffset: 200,
|
||||
});
|
||||
},
|
||||
handleFeatureClick(item) {
|
||||
|
|
@ -580,6 +615,11 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (item.title === "转人工") {
|
||||
this.handleTransferEntryClick();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.title === "电话咨询") {
|
||||
this.advicePhoneShow = true;
|
||||
return;
|
||||
|
|
@ -598,6 +638,153 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
createLocalUserTextMessage(messageText) {
|
||||
return {
|
||||
id: Math.random().toString(36).substring(2, 15),
|
||||
message: messageText,
|
||||
sendDate: "",
|
||||
isSend: true,
|
||||
isRead: false,
|
||||
interactMode: 0,
|
||||
messageType: 0,
|
||||
timeLabel: 0,
|
||||
displayTime: "",
|
||||
};
|
||||
},
|
||||
|
||||
createTransferCardMessage() {
|
||||
return {
|
||||
id: "transfer_" + Math.random().toString(36).substring(2, 15),
|
||||
message: "",
|
||||
sendDate: "",
|
||||
isSend: true,
|
||||
isRead: false,
|
||||
interactMode: 1,
|
||||
messageType: 0,
|
||||
timeLabel: 0,
|
||||
displayTime: "",
|
||||
customType: "transferCard",
|
||||
transferStatus: "idle",
|
||||
selectedConsultationType: null,
|
||||
transferTipText: "",
|
||||
};
|
||||
},
|
||||
|
||||
handleTransferEntryClick() {
|
||||
if (!this.isChat) {
|
||||
this.resetChatState();
|
||||
}
|
||||
|
||||
this.isLoadingMore = false;
|
||||
this.isTransferSubmitting = false;
|
||||
|
||||
const nextList = this.aiMessageGroups.filter(
|
||||
(m) => !(m && m.customType === "transferCard")
|
||||
);
|
||||
|
||||
nextList.push(this.createLocalUserTextMessage("转人工"));
|
||||
const card = this.createTransferCardMessage();
|
||||
this.activeTransferCardId = card.id;
|
||||
nextList.push(card);
|
||||
this.$store.commit("push_AiMsgList", nextList);
|
||||
},
|
||||
|
||||
getTransferCardTip(message) {
|
||||
if (!message) return "";
|
||||
if (this.isTransferSubmitting) return "正在为您转接人工客服,请稍等...";
|
||||
if (message.transferTipText) return message.transferTipText;
|
||||
return "";
|
||||
},
|
||||
|
||||
handleTransferTypeSelect(consultationType) {
|
||||
const cardId = this.activeTransferCardId;
|
||||
const findCardIndex = (list) => {
|
||||
if (cardId) {
|
||||
const byId = list.findIndex(
|
||||
(m) => m && m.id === cardId
|
||||
);
|
||||
if (byId >= 0) return byId;
|
||||
}
|
||||
return list.findIndex(
|
||||
(m) => m && m.customType === "transferCard"
|
||||
);
|
||||
};
|
||||
const list = this.aiMessageGroups;
|
||||
const idx = findCardIndex(list);
|
||||
if (idx < 0) return;
|
||||
|
||||
const current = list[idx] || {};
|
||||
if (this.isTransferSubmitting || current.transferStatus === "done") return;
|
||||
|
||||
const next = {
|
||||
...current,
|
||||
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 = {
|
||||
consultationType,
|
||||
message: "转人工",
|
||||
ip: "",
|
||||
};
|
||||
|
||||
if (this.currentDMid) {
|
||||
params.dialogueManagementId = this.currentDMid;
|
||||
}
|
||||
|
||||
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) ||
|
||||
"";
|
||||
if (!succeed) {
|
||||
const updated = {
|
||||
...latestList[nextIdx],
|
||||
transferStatus: "idle",
|
||||
transferTipText: serverTip,
|
||||
};
|
||||
const updatedList = latestList.slice();
|
||||
updatedList.splice(nextIdx, 1, updated);
|
||||
this.$store.commit("push_AiMsgList", updatedList);
|
||||
return;
|
||||
}
|
||||
const updated = {
|
||||
...latestList[nextIdx],
|
||||
transferStatus: "done",
|
||||
transferTipText: serverTip,
|
||||
};
|
||||
const updatedList = latestList.slice();
|
||||
updatedList.splice(nextIdx, 1, updated);
|
||||
this.$store.commit("push_AiMsgList", updatedList);
|
||||
})
|
||||
.catch(() => {
|
||||
this.isTransferSubmitting = false;
|
||||
const latestList = this.aiMessageGroups;
|
||||
const nextIdx = findCardIndex(latestList);
|
||||
if (nextIdx < 0) return;
|
||||
const updated = {
|
||||
...latestList[nextIdx],
|
||||
transferStatus: "idle",
|
||||
transferTipText: "转人工失败,请稍后重试",
|
||||
};
|
||||
const updatedList = latestList.slice();
|
||||
updatedList.splice(nextIdx, 1, updated);
|
||||
this.$store.commit("push_AiMsgList", updatedList);
|
||||
});
|
||||
},
|
||||
|
||||
// 修改发送消息的方法
|
||||
sendMessageFn() {
|
||||
if (!this.messageValue) {
|
||||
|
|
@ -624,7 +811,7 @@ export default {
|
|||
};
|
||||
|
||||
// 添加到消息列表
|
||||
this.messageGroups.push(userMessage);
|
||||
this.$store.commit("push_AiMsg", userMessage);
|
||||
this.messageValue = "";
|
||||
|
||||
// 立即添加一个AI回复的加载状态消息
|
||||
|
|
@ -642,7 +829,7 @@ export default {
|
|||
};
|
||||
|
||||
// 添加加载状态消息到列表
|
||||
this.messageGroups.push(loadingMessage);
|
||||
this.$store.commit("push_AiMsg", loadingMessage);
|
||||
|
||||
const params = {
|
||||
query: sendMessage,
|
||||
|
|
@ -663,7 +850,7 @@ export default {
|
|||
this.currentDMid = data.dmid;
|
||||
|
||||
// 从消息列表中移除加载状态消息
|
||||
this.messageGroups = this.messageGroups.filter(
|
||||
const baseList = this.aiMessageGroups.filter(
|
||||
(msg) => !msg.isLoading
|
||||
);
|
||||
|
||||
|
|
@ -683,13 +870,13 @@ export default {
|
|||
};
|
||||
|
||||
// 添加到消息列表
|
||||
this.messageGroups.push(aiMessage);
|
||||
this.$store.commit("push_AiMsgList", baseList.concat([aiMessage]));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("API请求失败:", error);
|
||||
|
||||
// 从消息列表中移除加载状态消息
|
||||
this.messageGroups = this.messageGroups.filter(
|
||||
const baseList = this.aiMessageGroups.filter(
|
||||
(msg) => !msg.isLoading
|
||||
);
|
||||
|
||||
|
|
@ -706,7 +893,10 @@ export default {
|
|||
displayTime: "",
|
||||
};
|
||||
|
||||
this.messageGroups.push(errorMessage);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
baseList.concat([errorMessage])
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -737,7 +927,7 @@ export default {
|
|||
};
|
||||
|
||||
// 添加到消息列表
|
||||
this.messageGroups.push(userMessage);
|
||||
this.$store.commit("push_AiMsg", userMessage);
|
||||
|
||||
// 立即添加一个AI回复的加载状态消息
|
||||
const loadingMessage = {
|
||||
|
|
@ -754,7 +944,7 @@ export default {
|
|||
};
|
||||
|
||||
// 添加加载状态消息到列表
|
||||
this.messageGroups.push(loadingMessage);
|
||||
this.$store.commit("push_AiMsg", loadingMessage);
|
||||
|
||||
const params = {
|
||||
id: item.id,
|
||||
|
|
@ -772,7 +962,7 @@ export default {
|
|||
const data = res.data.entityInfo;
|
||||
|
||||
// 从消息列表中移除加载状态消息
|
||||
this.messageGroups = this.messageGroups.filter(
|
||||
const baseList = this.aiMessageGroups.filter(
|
||||
(msg) => !msg.isLoading
|
||||
);
|
||||
|
||||
|
|
@ -792,11 +982,15 @@ export default {
|
|||
};
|
||||
|
||||
// 添加到消息列表
|
||||
this.messageGroups.push(aiMessage);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
baseList.concat([aiMessage])
|
||||
);
|
||||
} else {
|
||||
// 从消息列表中移除加载状态消息
|
||||
this.messageGroups = this.messageGroups.filter(
|
||||
(msg) => !msg.isLoading
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
this.aiMessageGroups.filter((msg) => !msg.isLoading)
|
||||
);
|
||||
}
|
||||
})
|
||||
|
|
@ -804,7 +998,7 @@ export default {
|
|||
console.error("API请求失败:", error);
|
||||
|
||||
// 从消息列表中移除加载状态消息
|
||||
this.messageGroups = this.messageGroups.filter(
|
||||
const baseList = this.aiMessageGroups.filter(
|
||||
(msg) => !msg.isLoading
|
||||
);
|
||||
|
||||
|
|
@ -821,17 +1015,13 @@ export default {
|
|||
displayTime: "",
|
||||
};
|
||||
|
||||
this.messageGroups.push(errorMessage);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
baseList.concat([errorMessage])
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
// 格式化时间的辅助方法
|
||||
formatTime(date) {
|
||||
const hours = date.getHours().toString().padStart(2, "0");
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
},
|
||||
|
||||
// 处理选中的对话
|
||||
handleSelectConversation(data) {
|
||||
// 关闭弹窗
|
||||
|
|
@ -848,7 +1038,7 @@ export default {
|
|||
this.isSwitchingConversation = true;
|
||||
|
||||
// 重置消息列表和分页相关状态
|
||||
this.messageGroups = [];
|
||||
this.$store.commit("push_AiMsgList", []);
|
||||
this.pageQuery.PageIndex = 1;
|
||||
this.isLoadingMore = false;
|
||||
this.noMoreData = false;
|
||||
|
|
@ -859,52 +1049,12 @@ export default {
|
|||
|
||||
// 处理消息内容,如果是JSON格式则解析并格式化
|
||||
processMessageContent(message) {
|
||||
try {
|
||||
// 尝试解析 JSON
|
||||
const parsed = JSON.parse(message);
|
||||
|
||||
// 如果是对象且包含特定字段,进行格式化
|
||||
if (typeof parsed === "object" && parsed !== null) {
|
||||
let content = "";
|
||||
|
||||
// 提取字段
|
||||
const { answerTypeLabel, imageUrl, nearbyPaths } = parsed;
|
||||
|
||||
// 拼接内容,如果有值则添加并换行
|
||||
if (answerTypeLabel) content += `${answerTypeLabel}\n`;
|
||||
if (imageUrl) content += `${imageUrl}\n`;
|
||||
if (nearbyPaths) content += `${nearbyPaths}\n`;
|
||||
|
||||
// 如果提取到了内容,返回处理后的文本;否则返回原文本(避免误判)
|
||||
// 去除末尾多余的换行符
|
||||
return content ? content.trim() : message;
|
||||
}
|
||||
} catch (e) {
|
||||
// 解析失败,说明不是JSON格式,直接返回原消息
|
||||
return message;
|
||||
}
|
||||
|
||||
return message;
|
||||
return processChatMessageContent(message);
|
||||
},
|
||||
|
||||
// 公共排序:按时间升序;时间相同,用户消息(interactMode=0)在前
|
||||
sortMessages(list = []) {
|
||||
const processedList = (list || []).map((item) => {
|
||||
// 对每条消息进行处理
|
||||
return {
|
||||
...item,
|
||||
message: this.processMessageContent(item.message),
|
||||
};
|
||||
});
|
||||
|
||||
// console.log("processedList", processedList);
|
||||
|
||||
return processedList.sort((a, b) => {
|
||||
const timeA = new Date(a.sendDate).getTime();
|
||||
const timeB = new Date(b.sendDate).getTime();
|
||||
if (timeA === timeB) return a.interactMode - b.interactMode;
|
||||
return timeA - timeB;
|
||||
});
|
||||
return sortChatMessages(list);
|
||||
},
|
||||
|
||||
// 刷新当前对话的消息详情
|
||||
|
|
@ -922,7 +1072,10 @@ export default {
|
|||
|
||||
const currentList = res.item2 || [];
|
||||
// 将消息按sendDate升序排列,时间相同时用户消息(interactMode=0)排在前面
|
||||
this.messageGroups = this.sortMessages(currentList);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
this.sortMessages(currentList)
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
// 延迟重置切换对话标志位,确保滚动事件处理完成
|
||||
|
|
@ -957,11 +1110,17 @@ export default {
|
|||
})
|
||||
.then((res3) => {
|
||||
const prevList = res3?.item2 || [];
|
||||
this.messageGroups = this.sortMessages(prevList);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
this.sortMessages(prevList)
|
||||
);
|
||||
this.pageQuery.PageIndex = prevIndex;
|
||||
});
|
||||
} else {
|
||||
this.messageGroups = this.sortMessages(currentList);
|
||||
this.$store.commit(
|
||||
"push_AiMsgList",
|
||||
this.sortMessages(currentList)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -1020,23 +1179,12 @@ export default {
|
|||
}
|
||||
|
||||
// 将消息按sendDate升序排列,时间相同时用户消息(interactMode=0)排在前面
|
||||
const nextPageMessageGroups = res.item2.sort((a, b) => {
|
||||
const timeA = new Date(a.sendDate).getTime();
|
||||
const timeB = new Date(b.sendDate).getTime();
|
||||
const nextPageMessageGroups = sortChatMessages(res.item2 || []);
|
||||
|
||||
// 如果时间相同,按interactMode排序(0排在前,1排在后)
|
||||
if (timeA === timeB) {
|
||||
return a.interactMode - b.interactMode;
|
||||
}
|
||||
|
||||
// 否则按时间升序排列
|
||||
return timeA - timeB;
|
||||
});
|
||||
|
||||
this.messageGroups = [
|
||||
...nextPageMessageGroups,
|
||||
...this.messageGroups,
|
||||
];
|
||||
this.$store.commit(
|
||||
"prepend_AiMsgList",
|
||||
nextPageMessageGroups
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
|
|
@ -1521,6 +1669,79 @@ export default {
|
|||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.message-transfer {
|
||||
width: 100%;
|
||||
margin-bottom: 40rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.transfer-card {
|
||||
width: 100%;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 36rpx;
|
||||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.transfer-card-title {
|
||||
padding: 0 16rpx;
|
||||
font-family: PingFang SC;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.transfer-card-options {
|
||||
margin-top: 32rpx;
|
||||
// padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
// background-color: #f6f8ff;
|
||||
display: flex;
|
||||
gap: 36rpx;
|
||||
}
|
||||
|
||||
.transfer-option {
|
||||
flex: 1;
|
||||
height: 112rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #f6f8fa;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.transfer-option.selected {
|
||||
background-color: #eef0fe;
|
||||
border-color: #dbe3ff;
|
||||
}
|
||||
|
||||
.transfer-option.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.transfer-option-text {
|
||||
font-family: PingFang SC;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.transfer-option.selected .transfer-option-text {
|
||||
color: #4a6cf7;
|
||||
}
|
||||
|
||||
.transfer-card-status {
|
||||
margin-top: 24rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-footer {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ const store = new Vuex.Store({
|
|||
vuex_token: lifeData.vuex_token ? lifeData.vuex_token : "",
|
||||
// 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式
|
||||
vuex_version: "1.0.0",
|
||||
// 智能客服消息组列表
|
||||
vuex_aiMessageGroups: [],
|
||||
// 当前聊天窗口消息列表(数组
|
||||
vuex_msgList: [],
|
||||
// 最近联系人/会话列表
|
||||
|
|
@ -179,6 +181,42 @@ const store = new Vuex.Store({
|
|||
if (!msg) return;
|
||||
state.vuex_msgList.unshift(msg);
|
||||
},
|
||||
|
||||
// ===== 智能客服:消息组列表维护(参照 vuex_msgList 维护方式) =====
|
||||
// 覆盖整个列表
|
||||
push_AiMsgList(state, list) {
|
||||
state.vuex_aiMessageGroups = Array.isArray(list) ? list : [];
|
||||
},
|
||||
// 追加历史消息到头部(分页加载)
|
||||
prepend_AiMsgList(state, list) {
|
||||
const prev = state.vuex_aiMessageGroups || [];
|
||||
const next = Array.isArray(list) ? list : [];
|
||||
const merged = [...next, ...prev];
|
||||
|
||||
const seen = new Set();
|
||||
state.vuex_aiMessageGroups = merged.filter((item) => {
|
||||
const id = item && item.id;
|
||||
if (!id) return true;
|
||||
if (seen.has(id)) return false;
|
||||
seen.add(id);
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// 推送一条新消息(去重)
|
||||
push_AiMsg(state, msg) {
|
||||
if (!msg || !msg.id) return;
|
||||
const exists = (state.vuex_aiMessageGroups || []).some(
|
||||
(item) => item && item.id === msg.id
|
||||
);
|
||||
if (!exists) {
|
||||
state.vuex_aiMessageGroups.push(msg);
|
||||
}
|
||||
},
|
||||
// 插入一条消息到头部
|
||||
unshift_AiMsg(state, msg) {
|
||||
if (!msg) return;
|
||||
state.vuex_aiMessageGroups.unshift(msg);
|
||||
},
|
||||
// 更新置顶状态(本地)
|
||||
update_TopState(state, { friendId, isTop }) {
|
||||
state.vuex_userMsgList = (state.vuex_userMsgList || []).map((item) => {
|
||||
|
|
|
|||
|
|
@ -123,3 +123,67 @@ export function scrollToBottomByContentHeight(
|
|||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理聊天消息内容:若内容为 JSON 字符串且符合预期结构,则提取并拼接为可读文本。
|
||||
*
|
||||
* 说明:
|
||||
* - 首页对话存在部分消息内容为 JSON 字符串的情况(如包含 `answerTypeLabel/imageUrl/nearbyPaths`)
|
||||
* - 这里做“尽量解析、解析失败回退原文”的处理,避免误伤普通文本消息
|
||||
*
|
||||
* @param {string} message - 原始消息文本
|
||||
* @returns {string} - 处理后的文本
|
||||
*/
|
||||
export function processChatMessageContent(message) {
|
||||
try {
|
||||
const parsed = JSON.parse(message);
|
||||
if (typeof parsed === "object" && parsed !== null) {
|
||||
let content = "";
|
||||
const { answerTypeLabel, imageUrl, nearbyPaths } = parsed;
|
||||
|
||||
if (answerTypeLabel) content += `${answerTypeLabel}\n`;
|
||||
if (imageUrl) content += `${imageUrl}\n`;
|
||||
if (nearbyPaths) content += `${nearbyPaths}\n`;
|
||||
|
||||
return content ? content.trim() : message;
|
||||
}
|
||||
} catch (e) {
|
||||
return message;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天消息排序:按 `sendDate` 升序;时间相同时,用户消息(`interactMode=0`)排在前面。
|
||||
* 同时会对每条消息的 `message` 字段进行 `processChatMessageContent` 处理。
|
||||
*
|
||||
* @param {Array<any>} list - 会话详情接口返回的消息列表
|
||||
* @returns {Array<any>} - 处理并排序后的新数组
|
||||
*/
|
||||
export function sortChatMessages(list = []) {
|
||||
const processedList = (list || []).map((item) => ({
|
||||
...item,
|
||||
message: processChatMessageContent(item && item.message),
|
||||
}));
|
||||
|
||||
return processedList.sort((a, b) => {
|
||||
const timeA = new Date(a && a.sendDate).getTime();
|
||||
const timeB = new Date(b && b.sendDate).getTime();
|
||||
if (timeA === timeB) return (a && a.interactMode) - (b && b.interactMode);
|
||||
return timeA - timeB;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Date 格式化为 `HH:mm`。
|
||||
*
|
||||
* @param {Date} date - 时间对象
|
||||
* @returns {string} - 格式化字符串
|
||||
*/
|
||||
export function formatHHmm(date) {
|
||||
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return "";
|
||||
const hours = date.getHours().toString().padStart(2, "0");
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue