From 60dff46ffe2464182ead3c65398364e979434574 Mon Sep 17 00:00:00 2001 From: yangzhe Date: Tue, 9 Dec 2025 16:11:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=81=8A=E5=A4=A9):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?Vuex=E7=AE=A1=E7=90=86=E6=B6=88=E6=81=AF=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将聊天消息和用户信息移至Vuex集中管理 - 实现打开或创建会话的统一入口方法openOrCreateDialogue - 更新消息列表获取和发送消息的逻辑 - 添加持久化存储支持保存聊天用户信息 --- pages/chat/index.vue | 118 +++++++++++++++---------- pages/home/admissions/index.vue | 17 +++- store/index.js | 149 +++++++++++++++++++++++--------- 3 files changed, 192 insertions(+), 92 deletions(-) diff --git a/pages/chat/index.vue b/pages/chat/index.vue index f6c238f..bd0700f 100644 --- a/pages/chat/index.vue +++ b/pages/chat/index.vue @@ -2,7 +2,7 @@ @@ -16,12 +16,11 @@ scroll-with-animation > - {{ formatShowTime(message.sendDate) }} @@ -98,10 +97,6 @@ export default { return { baseUrl: "", - // 用户信息 - userId: "", - userName: "", - // 头像 myAvatar: "/static/avatar/default-avatar.png", otherAvatar: "/static/avatar/default-avatar.png", @@ -116,7 +111,6 @@ export default { isRead: false, interactMode: 0, messageType: 0, - timeLabel: 1, }, { id: "02306fc3-c821-4a23-ad66-0bd77d154105", @@ -127,8 +121,6 @@ export default { isRead: false, interactMode: 1, messageType: 0, - timeLabel: 0, - displayTime: "", }, { id: "9cac8661-bf09-4b63-ab15-1a0a36b91110", @@ -138,7 +130,6 @@ export default { isRead: false, interactMode: 0, messageType: 0, - timeLabel: 1, }, { id: "02306fc3-c821-4a23-ad66-0bd788854105", @@ -148,8 +139,6 @@ export default { isRead: false, interactMode: 1, messageType: 0, - timeLabel: 0, - displayTime: "", }, ], @@ -158,21 +147,19 @@ export default { // 滚动位置 scrollToView: "", + + PageIndex: 1, + PageSize: 20, }; }, onLoad(options) { + console.log(this.vuex_msgList); + this.baseUrl = this.$u.http.config.baseUrl; - // 获取传入的用户信息 - this.userId = options.userId || ""; - this.userName = options.name || "用户"; - - console.log("[聊天详情] 用户ID:", this.userId); - console.log("[聊天详情] 用户名:", this.userName); - // 加载历史消息 - this.loadHistoryMessages(); + this.getMsgList(); // 滚动到底部 this.$nextTick(() => { @@ -196,6 +183,18 @@ export default { uni.navigateBack(); }, + // this.vuex_msgUser = { + // dialogueManagementId: "08de36ed-8bc0-4b79-86e7-0128010ccc4b", + // receiverId: "08de33d5-b517-4801-8474-4a4ad5642691", + // receiverName: "华忠林", + // receiverHeadSculptureUrl: "tx.jpg", + // title: "", + // startTime: "2025-12-09T14:38:21.295504", + // lastMessageTime: "0001-01-01T00:00:00", + // isOverhead: false, + // unReadCount: 0, + // }; + // 点击发送 handleSend() { if (!this.messageValue) { @@ -204,10 +203,10 @@ export default { // 构建消息对象 const message = { - dialogueManagementId: "3fa85f64-5717-4562-b3fc-2c963f66afa6", - receiverId: "3fa85f64-5717-4562-b3fc-2c963f66afa6", - messageType: 0, + dialogueManagementId: this.vuex_msgUser.dialogueManagementId, + receiverId: this.vuex_msgUser.receiverId, message: this.messageValue, + messageType: 0, filePath: "", ip: "", }; @@ -217,16 +216,30 @@ export default { // 发送消息 sendMsgFn(message) { - SendMessage_PrivateApi(message) + this.$u.api + .SendMessage_PrivateApi(message) .then((res) => { - // 添加到消息列表 - this.messageList.push(message); - // 清空输入框 - this.messageValue = ""; - // 滚动到底部 - this.$nextTick(() => { - this.scrollToBottom(); - }); + console.log(res, "发送消息成功"); + if (res.succeed) { + // 添加到消息列表 + const msgUserData = { + dialogueManagementId: this.vuex_msgUser.dialogueManagementId, + senderId: this.vuex_user.Id, + receiverId: this.vuex_msgUser.receiverId, + sendDate: new Date().toISOString(), + message: this.messageValue, + messageType: 0, + filePath: "", + }; + + this.$store.commit("push_Msg", msgUserData); + // 清空输入框 + this.messageValue = ""; + // 滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(); + }); + } }) .catch((error) => { return msg.warning("发送失败"); @@ -249,22 +262,35 @@ export default { }); }, - // 加载历史消息 - loadHistoryMessages() { - // TODO: 从服务器加载历史消息 - // this.$u.api.getChatHistory({ - // userId: this.userId - // }).then(res => { - // this.messageList = res.data - // }) + // 加载对话消息 + getMsgList() { + this.$u.api + .GetChatHistoryDataApi({ + "Item1.DialogueManagementId": this.vuex_msgUser.dialogueManagementId, + PageIndex: this.PageIndex, + PageSize: this.PageSize, + }) + .then((res) => { + const msgList = res.data.item1.reverse(); + // 注:现在的消息返回值缺少 interactMode 字段,先手动mock - console.log("[聊天详情] 加载历史消息"); + for (var i = 0; i < msgList.length; i++) { + msgList[i].interactMode = msgList[i].interactMode || 0; + + // if (this.PageIndex != 1) { + // this.$store.commit("unshift_Msg", msgList[i]); + // } else { + // this.$store.commit("push_Msg", msgList[i]); + // } + } + this.$store.commit("push_MsgList", msgList); + }); }, // 滚动到底部 scrollToBottom() { - if (this.messageList.length > 0) { - const lastMsg = this.messageList[this.messageList.length - 1]; + if (this.vuex_msgList.length > 0) { + const lastMsg = this.vuex_msgList[this.vuex_msgList.length - 1]; this.scrollToView = "msg-" + lastMsg.id; } }, @@ -281,8 +307,8 @@ export default { if (index == 0) { return true; } - let isTime = new Date(this.messageList[index].sendDate).getTime(); //当前时间 - const time = new Date(this.messageList[index - 1].sendDate).getTime(); //上条消息的时间 + let isTime = new Date(this.vuex_msgList[index].sendDate).getTime(); //当前时间 + const time = new Date(this.vuex_msgList[index - 1].sendDate).getTime(); //上条消息的时间 // 30 分钟内不显示时间提示 return isTime - time > 30 * 60 * 1000; }, diff --git a/pages/home/admissions/index.vue b/pages/home/admissions/index.vue index 1b16af7..e129c48 100644 --- a/pages/home/admissions/index.vue +++ b/pages/home/admissions/index.vue @@ -104,10 +104,18 @@ export default { uni.navigateBack(); }, handleAskQuestion(teacher) { - // 跳转老师详情 - uni.navigateTo({ - url: `/pages/home/teacherInfo/index?teacherId=${teacher.id}`, - }); + console.log("点击咨询:", teacher); + + var msgUserData = { + avatar: teacher.headSculptureUrl, + friendId: teacher.id, + friendName: teacher.name, + }; + + // 进入对话会话 + this.$store.dispatch("openOrCreateDialogue", msgUserData); + + return; return; // 留言相关的没用了,先注释 @@ -118,6 +126,7 @@ export default { } else { } }, + // 提交留言(此方法废弃) handleMessageSubmit(data) { console.log("提交留言:", data.content, "教师:", data.teacher?.name); diff --git a/store/index.js b/store/index.js index 8300454..3bdb4c7 100644 --- a/store/index.js +++ b/store/index.js @@ -11,7 +11,12 @@ try { } catch (e) {} // 需要永久存储,且下次APP启动需要取出的,在state中的变量名 -let saveStateKeys = ["vuex_user", "vuex_token", "vuex_userType"]; +let saveStateKeys = [ + "vuex_user", + "vuex_token", + "vuex_userType", + "vuex_msgUser", +]; // 保存变量到本地存储中 const saveLifeData = function (key, value) { @@ -45,7 +50,7 @@ const store = new Vuex.Store({ // 本地占位的聊天用户(未在服务端建立的会话,临时展示) vuex_localMsgUserList: [], // 当前聊天用户对象 - vuex_msgUser: null, + vuex_msgUser: lifeData.vuex_msgUser ? lifeData.vuex_msgUser : {}, // 消息窗口滚动位置 vuex_msgScrollTop: 0, // 自定义tabbar数据 @@ -118,7 +123,7 @@ const store = new Vuex.Store({ // ===== 消息中心:列表、当前聊天、消息推送等 ===== // 设置会话列表(服务端返回) - setUserMsgList(state, list) { + set_UserMsgList(state, list) { state.vuex_userMsgList = Array.isArray(list) ? list.slice() : []; if (state.vuex_msgUser && state.vuex_msgUser.friendId) { state.vuex_userMsgList = state.vuex_userMsgList.map((item) => { @@ -130,7 +135,7 @@ const store = new Vuex.Store({ } }, // 插入一个本地占位的聊天用户 - addLocalMsgUser(state, user) { + add_LocalMsgUser(state, user) { const existsLocal = (state.vuex_localMsgUserList || []).some( (v) => v && v.friendId === user.friendId ); @@ -156,7 +161,7 @@ const store = new Vuex.Store({ }); }, // 设置当前聊天用户 - setMsgUser(state, user) { + set_MsgUser(state, user) { state.vuex_msgUser = user || null; if (state.vuex_msgUser && state.vuex_msgUser.friendId) { state.vuex_userMsgList = (state.vuex_userMsgList || []).map((item) => { @@ -167,23 +172,28 @@ const store = new Vuex.Store({ }); } }, + // 现在没有id,先覆盖整个list + push_MsgList(state, list) { + state.vuex_msgList = list || []; + }, // 推送一条新消息(去重) - pushMsg(state, msg) { - if (!msg || !msg.id) return; - const exists = (state.vuex_msgList || []).some( - (item) => item && item.id === msg.id - ); - if (!exists) { - state.vuex_msgList.push(msg); - } + push_Msg(state, msg) { + // 注:现在的消息没有id,暂时注释 + // if (!msg || !msg.id) return; + // const exists = (state.vuex_msgList || []).some( + // (item) => item && item.id === msg.id + // ); + // if (!exists) { + state.vuex_msgList.push(msg); + // } }, // 插入历史消息到头部 - unshiftMsg(state, msg) { + unshift_Msg(state, msg) { if (!msg) return; state.vuex_msgList.unshift(msg); }, // 更新置顶状态(本地) - updateTopState(state, { friendId, isTop }) { + update_TopState(state, { friendId, isTop }) { state.vuex_userMsgList = (state.vuex_userMsgList || []).map((item) => { if (item && item.friendId === friendId) { return { ...item, isTop: !!isTop }; @@ -192,11 +202,11 @@ const store = new Vuex.Store({ }); }, // 更新消息窗口滚动位置 - setMsgScrollTop(state, top) { + set_MsgScrollTop(state, top) { state.vuex_msgScrollTop = Number(top) || 0; }, // 清空消息相关状态(登出/切换账号) - clearMessageState(state) { + clear_MessageState(state) { state.vuex_msgList = []; state.vuex_userMsgList = []; state.vuex_localMsgUserList = []; @@ -220,36 +230,39 @@ const store = new Vuex.Store({ saveLifeData("vuex_token", state.vuex_token); saveLifeData("vuex_teacherInfo", state.vuex_teacherInfo); saveLifeData("vuex_user", state.vuex_user); + saveLifeData("vuex_msgUser", state.vuex_msgUser); }, }, actions: { // 获取聊天列表(最近联系人) async getUserlist({ commit, state }) { - this.$u.api.GetDialogueListApi().then((res) => { - const baseUrl = - (Vue.prototype.$u && - Vue.prototype.$u.http && - Vue.prototype.$u.http.config && - Vue.prototype.$u.http.config.baseUrl) || - ""; - const list = (res && res.data ? res.data : []).map((item) => { - const unReadCount = - state.vuex_msgUser && state.vuex_msgUser.friendId === item.friendId - ? 0 - : item.unReadCount || 0; - return { - ...item, - avatar: baseUrl ? baseUrl + item.avatar : item.avatar, - unReadCount, - }; - }); - commit("setUserMsgList", list); + // 在 Vuex action 中没有组件 this,上下文不包含 $u + // 通过 Vue.prototype.$u 使用 uView 的 http 实例 + return Vue.prototype.$u.api.GetDialogueListApi().then((res) => { + const list = (res && res.data.item1 ? res.data.item1 : []).map( + (item) => { + const unReadCount = + state.vuex_msgUser && + state.vuex_msgUser.friendId === item.friendId + ? 0 + : item.unReadCount || 0; + return { + ...item, + // avatar: item.avatar, + unReadCount, + }; + } + ); + commit("set_UserMsgList", list); + return list; }); }, // 设置当前聊天用户并入队本地占位 setMsgUser({ commit, dispatch }, user) { - commit("setMsgUser", user); - commit("addLocalMsgUser", user); + console.log("setMsgUser执行了", user); + + commit("set_MsgUser", user); + commit("add_LocalMsgUser", user); // 刷新列表,清零未读 dispatch("getUserlist"); }, @@ -260,10 +273,10 @@ const store = new Vuex.Store({ ) { const params = { userId, friendId, PageIndex, PageSize }; - this.$u.api.GetChatHistoryDataApi(params).then((res) => { + Vue.prototype.$u.api.GetChatHistoryDataApi(params).then((res) => { const list = res && res.data ? res.data : []; // 将最新消息插入到消息列表尾部(或头部) - list.forEach((msg) => commit("pushMsg", msg)); + list.forEach((msg) => commit("push_Msg", msg)); }); }, // 设置消息置顶(服务端 + 本地) @@ -272,14 +285,66 @@ const store = new Vuex.Store({ if (user.isTop) { return; } else { - this.$u.api + Vue.prototype.$u.api .OverheadOneDialogueApi({ dialogueManagementId: user.friendId }) .then((res) => { - commit("updateTopState", { friendId: user.friendId, isTop: true }); + commit("update_TopState", { friendId: user.friendId, isTop: true }); dispatch("getUserlist"); }); } }, + + // 统一入口:打开或创建会话 + // 使用位置:所有需要进入聊天的入口调用该方法,避免各页面重复逻辑 + // 1) 获取最新会话列表 + // 2) 查找是否存在 friendId 对应的会话 + // 3) 有则设置当前会话并(可选)跳转;无则创建,成功后递归重试 + async openOrCreateDialogue({ commit, state, dispatch }, user) { + const { friendId, friendName, avatar, chatType = 0 } = user || {}; + const attempt = + user && typeof user.attempt === "number" ? user.attempt : 1; + if (!friendId) return Promise.reject(new Error("缺少 friendId")); + + // 第一步:获取列表(复用现有 action,保证 state 同步更新) + const list = await dispatch("getUserlist"); + + // 第二步:查找是否存在该会话(兼容后端字段 friendId/receiverId) + const target = (list || []).find( + (i) => i && (i.receiverId === friendId || i.friendId === friendId) + ); + if (target) { + commit("set_MsgUser", target); + + // 跳转到对话页,参数按需调整 + uni.navigateTo({ + url: `/pages/chat/index`, + }); + + return target; + } + + // 第二次调用时,无论成功与否均在第三步(创建)之前结束 + if (attempt >= 2) { + return false; + } + + // 第三步:不存在则创建,成功后重试本流程 + const res = await Vue.prototype.$u.api.AddDialogueApi({ + receiverId: friendId, + onlineConsultationType: 0, // 0:招生在线;1:迎新在线(可按业务传入) + }); + if (res && res.succeed === true) { + // 创建成功后,重新执行流程(再次获取列表并进入会话) + return dispatch("openOrCreateDialogue", { + friendId, + friendName, + avatar, + chatType, + attempt: attempt + 1, + }); + } + return Promise.reject(new Error(res?.error || "创建会话失败")); + }, }, });