feat: 会话列表实时同步未读与最新消息
This commit is contained in:
parent
5b0a14ae57
commit
890a3cee94
49
App.vue
49
App.vue
|
|
@ -200,11 +200,11 @@ export default {
|
||||||
if (type === "pong") return;
|
if (type === "pong") return;
|
||||||
|
|
||||||
// 退出登录通知
|
// 退出登录通知
|
||||||
if (type === "Exit") {
|
if (type === "Exit") {
|
||||||
try {
|
try {
|
||||||
this.ws && this.ws.close();
|
this.ws && this.ws.close();
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
// 清理本地登录信息并返回登录页
|
// 清理本地登录信息并返回登录页
|
||||||
this.$u.vuex("vuex_user", "");
|
this.$u.vuex("vuex_user", "");
|
||||||
this.$u.vuex("vuex_token", "");
|
this.$u.vuex("vuex_token", "");
|
||||||
uni.clearStorage();
|
uni.clearStorage();
|
||||||
|
|
@ -220,24 +220,35 @@ export default {
|
||||||
this.$u.vuex("vuex_tabbar", tab);
|
this.$u.vuex("vuex_tabbar", tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收到私聊消息
|
// 收到私聊消息
|
||||||
if (type === "PrivateChatMessage") {
|
if (type === "PrivateChatMessage") {
|
||||||
const data = msgData.Data || {};
|
const data = msgData.Data || {};
|
||||||
const processData = {
|
const processData = {
|
||||||
dialogueManagementId: data.DialogueManagementId,
|
dialogueManagementId: data.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", {
|
this.$store.commit("push_Msg", {
|
||||||
...processData,
|
...processData,
|
||||||
messageType: 0,
|
messageType: 0,
|
||||||
filePath: "",
|
filePath: "",
|
||||||
id: Math.random().toString(36).substring(2),
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
// 连接关闭
|
// 连接关闭
|
||||||
handleWsClose(e) {
|
handleWsClose(e) {
|
||||||
// console.log(`[WebSocket] 连接关闭: code=${e.code}, reason=${e.reason}`);
|
// console.log(`[WebSocket] 连接关闭: code=${e.code}, reason=${e.reason}`);
|
||||||
|
|
|
||||||
|
|
@ -63,12 +63,120 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
// 补充展示字段,便于模板直接使用
|
// 补充展示字段,便于模板直接使用
|
||||||
|
unreadMap() {
|
||||||
|
const map = {};
|
||||||
|
const list = this.$store?.state?.vuex_userMsgList || [];
|
||||||
|
list.forEach((item) => {
|
||||||
|
const id = this.getDialogueId(item);
|
||||||
|
const count =
|
||||||
|
item?.unReadCount ??
|
||||||
|
item?.unreadCount ??
|
||||||
|
item?.unread ??
|
||||||
|
null;
|
||||||
|
if (id && typeof count === "number") {
|
||||||
|
map[id] = count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
},
|
||||||
displayChatList() {
|
displayChatList() {
|
||||||
return (this.chatList || []).map((item) => ({
|
// 合并接口首帧列表 + 实时更新列表,按 id 匹配覆盖
|
||||||
...item,
|
const realtime = this.$store?.state?.vuex_userMsgList || [];
|
||||||
displayTime: item.lastMessageTime || "",
|
const baseList = this.chatList || [];
|
||||||
displayPreview: item.lastMessage || "暂无消息",
|
|
||||||
}));
|
const rtMap = {};
|
||||||
|
realtime.forEach((item) => {
|
||||||
|
const id = this.getDialogueId(item);
|
||||||
|
if (id) rtMap[id] = item;
|
||||||
|
});
|
||||||
|
|
||||||
|
const merged = baseList.map((item) => {
|
||||||
|
const id = this.getDialogueId(item);
|
||||||
|
const rt = id ? rtMap[id] : null;
|
||||||
|
const source = rt || item;
|
||||||
|
const unread =
|
||||||
|
typeof this.unreadMap[id] === "number"
|
||||||
|
? this.unreadMap[id]
|
||||||
|
: source?.unReadCount ??
|
||||||
|
source?.unreadCount ??
|
||||||
|
source?.unread ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
...rt,
|
||||||
|
id,
|
||||||
|
dialogueManagementId: id,
|
||||||
|
DialogueManagementId: id,
|
||||||
|
userId:
|
||||||
|
source?.receiverId ||
|
||||||
|
source?.friendId ||
|
||||||
|
source?.userId ||
|
||||||
|
item?.userId ||
|
||||||
|
"",
|
||||||
|
avatar:
|
||||||
|
source?.receiverHeadSculptureUrl ||
|
||||||
|
source?.avatar ||
|
||||||
|
source?.friendAvatar ||
|
||||||
|
"/static/avatar/default-avatar.png",
|
||||||
|
displayTime: this.formatTime(
|
||||||
|
source?.lastMessageTime ||
|
||||||
|
source?.lastSendTime ||
|
||||||
|
source?.sendDate ||
|
||||||
|
item?.lastMessageTime ||
|
||||||
|
""
|
||||||
|
),
|
||||||
|
displayPreview:
|
||||||
|
source?.lastMessage ||
|
||||||
|
source?.message ||
|
||||||
|
item?.lastMessage ||
|
||||||
|
"暂无消息",
|
||||||
|
unreadCount: unread,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 追加实时列表中基线不存在的会话
|
||||||
|
realtime.forEach((item) => {
|
||||||
|
const id = this.getDialogueId(item);
|
||||||
|
if (!id) return;
|
||||||
|
const exists = merged.some((row) => row.id === id);
|
||||||
|
if (!exists) {
|
||||||
|
const unread =
|
||||||
|
typeof this.unreadMap[id] === "number"
|
||||||
|
? this.unreadMap[id]
|
||||||
|
: item?.unReadCount ??
|
||||||
|
item?.unreadCount ??
|
||||||
|
item?.unread ??
|
||||||
|
0;
|
||||||
|
merged.unshift({
|
||||||
|
...item,
|
||||||
|
id,
|
||||||
|
dialogueManagementId: id,
|
||||||
|
DialogueManagementId: id,
|
||||||
|
userId:
|
||||||
|
item?.receiverId ||
|
||||||
|
item?.friendId ||
|
||||||
|
item?.userId ||
|
||||||
|
"",
|
||||||
|
avatar:
|
||||||
|
item?.receiverHeadSculptureUrl ||
|
||||||
|
item?.avatar ||
|
||||||
|
item?.friendAvatar ||
|
||||||
|
"/static/avatar/default-avatar.png",
|
||||||
|
displayTime: this.formatTime(
|
||||||
|
item?.lastMessageTime ||
|
||||||
|
item?.lastSendTime ||
|
||||||
|
item?.sendDate ||
|
||||||
|
""
|
||||||
|
),
|
||||||
|
displayPreview:
|
||||||
|
item?.lastMessage || item?.message || "暂无消息",
|
||||||
|
unreadCount: unread,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return merged;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -130,6 +238,8 @@ export default {
|
||||||
const list = (res && res.data && res.data.item1) || [];
|
const list = (res && res.data && res.data.item1) || [];
|
||||||
|
|
||||||
this.chatList = this.normalizeDialogueList(list);
|
this.chatList = this.normalizeDialogueList(list);
|
||||||
|
// 同步到全局会话列表,便于后续实时合并
|
||||||
|
this.$store.commit("set_UserMsgList", this.chatList);
|
||||||
this.totalCount = res?.data?.item2 || list.length;
|
this.totalCount = res?.data?.item2 || list.length;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[在线咨询] 获取会话列表失败", error);
|
console.error("[在线咨询] 获取会话列表失败", error);
|
||||||
|
|
@ -155,9 +265,20 @@ export default {
|
||||||
? item.unReadCount
|
? item.unReadCount
|
||||||
: item?.unreadCount || 0;
|
: item?.unreadCount || 0;
|
||||||
|
|
||||||
|
const id =
|
||||||
|
item?.dialogueManagementId ||
|
||||||
|
item?.DialogueManagementId ||
|
||||||
|
item?.dialogueId ||
|
||||||
|
item?.friendId ||
|
||||||
|
item?.id ||
|
||||||
|
index;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item?.dialogueManagementId || item?.dialogueId || item?.id || index,
|
id,
|
||||||
|
dialogueManagementId: id,
|
||||||
|
DialogueManagementId: id,
|
||||||
userId: item?.receiverId || item?.friendId || item?.userId || "",
|
userId: item?.receiverId || item?.friendId || item?.userId || "",
|
||||||
|
friendId: item?.receiverId || item?.friendId || item?.userId || "",
|
||||||
name:
|
name:
|
||||||
item?.receiverName ||
|
item?.receiverName ||
|
||||||
item?.friendName ||
|
item?.friendName ||
|
||||||
|
|
@ -202,6 +323,18 @@ export default {
|
||||||
// 回退到字符串裁剪
|
// 回退到字符串裁剪
|
||||||
return String(timeStr).replace("T", " ").slice(5, 16);
|
return String(timeStr).replace("T", " ").slice(5, 16);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 统一获取会话唯一标识
|
||||||
|
getDialogueId(item) {
|
||||||
|
return (
|
||||||
|
item?.dialogueManagementId ||
|
||||||
|
item?.DialogueManagementId ||
|
||||||
|
item?.dialogueId ||
|
||||||
|
item?.friendId ||
|
||||||
|
item?.id ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,63 @@ const store = new Vuex.Store({
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// WebSocket 实时消息:更新会话列表的未读数、文案与时间
|
||||||
|
apply_RealtimeMessageToList(
|
||||||
|
state,
|
||||||
|
{ dialogueId, message, sendDate, senderId, receiverId }
|
||||||
|
) {
|
||||||
|
if (!dialogueId) return;
|
||||||
|
const activeId =
|
||||||
|
state.vuex_msgUser?.dialogueManagementId ||
|
||||||
|
state.vuex_msgUser?.friendId ||
|
||||||
|
state.vuex_msgUser?.id;
|
||||||
|
const isActive = activeId && activeId === dialogueId;
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
state.vuex_userMsgList = (state.vuex_userMsgList || []).map((item) => {
|
||||||
|
const itemId =
|
||||||
|
item?.dialogueManagementId ||
|
||||||
|
item?.DialogueManagementId ||
|
||||||
|
item?.friendId ||
|
||||||
|
item?.id;
|
||||||
|
if (itemId === dialogueId) {
|
||||||
|
found = true;
|
||||||
|
const currentUnread =
|
||||||
|
Number(
|
||||||
|
item?.unReadCount ??
|
||||||
|
item?.unreadCount ??
|
||||||
|
item?.unread ??
|
||||||
|
0
|
||||||
|
) || 0;
|
||||||
|
const nextUnread = isActive ? 0 : currentUnread + 1;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
lastMessage: message ?? item?.lastMessage ?? "",
|
||||||
|
lastMessageTime: sendDate ?? item?.lastMessageTime ?? "",
|
||||||
|
unReadCount: nextUnread,
|
||||||
|
unreadCount: nextUnread,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 未在列表中则插入一条最小信息的会话(保持实时感知)
|
||||||
|
if (!found) {
|
||||||
|
const nextUnread = isActive ? 0 : 1;
|
||||||
|
state.vuex_userMsgList.unshift({
|
||||||
|
id: dialogueId,
|
||||||
|
dialogueManagementId: dialogueId,
|
||||||
|
DialogueManagementId: dialogueId,
|
||||||
|
friendId: receiverId || senderId || dialogueId,
|
||||||
|
name: "",
|
||||||
|
avatar: "",
|
||||||
|
lastMessage: message || "",
|
||||||
|
lastMessageTime: sendDate || "",
|
||||||
|
unReadCount: nextUnread,
|
||||||
|
unreadCount: nextUnread,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
// 更新消息窗口滚动位置
|
// 更新消息窗口滚动位置
|
||||||
set_MsgScrollTop(state, top) {
|
set_MsgScrollTop(state, top) {
|
||||||
state.vuex_msgScrollTop = Number(top) || 0;
|
state.vuex_msgScrollTop = Number(top) || 0;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue