feat(聊天): 重构聊天功能,使用Vuex管理消息状态
- 将聊天消息和用户信息移至Vuex集中管理 - 实现打开或创建会话的统一入口方法openOrCreateDialogue - 更新消息列表获取和发送消息的逻辑 - 添加持久化存储支持保存聊天用户信息
This commit is contained in:
parent
9638bd10a1
commit
60dff46ffe
|
|
@ -2,7 +2,7 @@
|
|||
<view class="chat-page">
|
||||
<!-- 顶部导航 -->
|
||||
<header-bar
|
||||
:title="userName"
|
||||
:title="vuex_msgUser.receiverName"
|
||||
leftIcon="arrow-left"
|
||||
@leftClick="handleLeftClick"
|
||||
></header-bar>
|
||||
|
|
@ -16,12 +16,11 @@
|
|||
scroll-with-animation
|
||||
>
|
||||
<view
|
||||
v-for="(message, index) in messageList"
|
||||
v-for="(message, index) in vuex_msgList"
|
||||
:key="message.id"
|
||||
:id="'msg-' + message.id"
|
||||
>
|
||||
<!-- 时间 -->
|
||||
<!-- v-if="message.timeLabel !== 0" -->
|
||||
<view class="message-time" v-if="isShowTime(index)">
|
||||
{{ formatShowTime(message.sendDate) }}
|
||||
</view>
|
||||
|
|
@ -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) => {
|
||||
console.log(res, "发送消息成功");
|
||||
if (res.succeed) {
|
||||
// 添加到消息列表
|
||||
this.messageList.push(message);
|
||||
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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
133
store/index.js
133
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) {
|
||||
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) => {
|
||||
// 在 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
|
||||
state.vuex_msgUser &&
|
||||
state.vuex_msgUser.friendId === item.friendId
|
||||
? 0
|
||||
: item.unReadCount || 0;
|
||||
return {
|
||||
...item,
|
||||
avatar: baseUrl ? baseUrl + item.avatar : item.avatar,
|
||||
// avatar: item.avatar,
|
||||
unReadCount,
|
||||
};
|
||||
});
|
||||
commit("setUserMsgList", list);
|
||||
}
|
||||
);
|
||||
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 || "创建会话失败"));
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue