Merge branch 'main' of http://sl.vrgon.com:3000/JiXinHui/YingXingAI
This commit is contained in:
commit
217d0bd20f
40
App.vue
40
App.vue
|
|
@ -10,7 +10,7 @@ export default {
|
||||||
// WebSocket 实例与连接控制
|
// WebSocket 实例与连接控制
|
||||||
ws: null, // 当前 WebSocket 连接
|
ws: null, // 当前 WebSocket 连接
|
||||||
lockReconnect: false, // 是否处于稳定连接,防止重复重连
|
lockReconnect: false, // 是否处于稳定连接,防止重复重连
|
||||||
timeout: 30000, // 心跳间隔(毫秒)
|
// timeout: 30000, // 心跳间隔(毫秒)
|
||||||
timeoutObj: null, // 心跳倒计时定时器
|
timeoutObj: null, // 心跳倒计时定时器
|
||||||
serverTimeoutObj: null, // 心跳响应等待定时器
|
serverTimeoutObj: null, // 心跳响应等待定时器
|
||||||
timeoutnum: null, // 重连延时定时器
|
timeoutnum: null, // 重连延时定时器
|
||||||
|
|
@ -84,8 +84,6 @@ export default {
|
||||||
},
|
},
|
||||||
// 初始化原生 WebSocket 连接
|
// 初始化原生 WebSocket 连接
|
||||||
initWebSocket() {
|
initWebSocket() {
|
||||||
console.log(this.buildWsUrl());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.ws = new WebSocket(this.buildWsUrl());
|
this.ws = new WebSocket(this.buildWsUrl());
|
||||||
this.ws.onopen = () => this.handleWsOpen();
|
this.ws.onopen = () => this.handleWsOpen();
|
||||||
|
|
@ -114,32 +112,29 @@ export default {
|
||||||
// 启动心跳与超时处理
|
// 启动心跳与超时处理
|
||||||
start() {
|
start() {
|
||||||
this.timeoutObj = setTimeout(() => {
|
this.timeoutObj = setTimeout(() => {
|
||||||
try {
|
//这里发送一个心跳,后端收到后,返回一个心跳消息
|
||||||
if (
|
if (
|
||||||
this.ws &&
|
this.ws &&
|
||||||
this.ws.readyState === 1 &&
|
this.ws.readyState === 1 &&
|
||||||
this.vuex_user &&
|
this.vuex_user &&
|
||||||
(this.vuex_user.id || this.vuex_user.Id)
|
(this.vuex_user.Id || this.vuex_user.id)
|
||||||
) {
|
) {
|
||||||
|
//如果连接正常
|
||||||
this.ws.send("heartCheck");
|
this.ws.send("heartCheck");
|
||||||
} else {
|
} else {
|
||||||
this.lockReconnect = false;
|
//否则重连
|
||||||
this.reconnect();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.lockReconnect = false;
|
this.lockReconnect = false;
|
||||||
this.reconnect();
|
this.reconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 心跳发送后等待后端响应,超时则断开重连
|
// 心跳发送后等待后端响应,超时则断开重连
|
||||||
this.serverTimeoutObj = setTimeout(() => {
|
this.serverTimeoutObj = setTimeout(() => {
|
||||||
try {
|
// console.log("[WebSocket] 心跳响应超时,断开重连");
|
||||||
this.ws && this.ws.close();
|
this.ws && this.ws.close();
|
||||||
} catch (e) {}
|
|
||||||
this.lockReconnect = false;
|
this.lockReconnect = false;
|
||||||
this.reconnect();
|
this.reconnect();
|
||||||
}, this.timeout);
|
}, 30000);
|
||||||
}, this.timeout);
|
}, 30000);
|
||||||
},
|
},
|
||||||
// 连接成功
|
// 连接成功
|
||||||
handleWsOpen() {
|
handleWsOpen() {
|
||||||
|
|
@ -153,10 +148,12 @@ export default {
|
||||||
// 收到任何消息都重置心跳
|
// 收到任何消息都重置心跳
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
|
console.log("[WebSocket] 收到消息:", e);
|
||||||
|
|
||||||
// 心跳消息不处理
|
// 心跳消息不处理
|
||||||
if (typeof e.data === "string" && e.data.indexOf("heartCheck") >= 0) {
|
// if (typeof e.data === "string" && e.data.indexOf("heartCheck") >= 0) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 尝试解析为 JSON(与 oa-web-phone 保持一致的结构:{ Type, Data })
|
// 尝试解析为 JSON(与 oa-web-phone 保持一致的结构:{ Type, Data })
|
||||||
let msgData = null;
|
let msgData = null;
|
||||||
|
|
@ -169,6 +166,10 @@ export default {
|
||||||
|
|
||||||
const type = msgData.Type;
|
const type = msgData.Type;
|
||||||
|
|
||||||
|
console.log("收到消息类型:", type);
|
||||||
|
// 收到服务端心跳响应不处理
|
||||||
|
if (type === "pong") return;
|
||||||
|
|
||||||
// 退出登录通知
|
// 退出登录通知
|
||||||
if (type === "Exit") {
|
if (type === "Exit") {
|
||||||
try {
|
try {
|
||||||
|
|
@ -209,14 +210,13 @@ export default {
|
||||||
},
|
},
|
||||||
// 连接关闭
|
// 连接关闭
|
||||||
handleWsClose(e) {
|
handleWsClose(e) {
|
||||||
console.log(`[WebSocket] 连接关闭: code=${e.code}, reason=${e.reason}`);
|
// console.log(`[WebSocket] 连接关闭: code=${e.code}, reason=${e.reason}`);
|
||||||
this.lockReconnect = false;
|
this.lockReconnect = false;
|
||||||
this.reconnect();
|
this.reconnect();
|
||||||
},
|
},
|
||||||
// 连接错误
|
// 连接错误
|
||||||
handleWsError(e) {
|
handleWsError(e) {
|
||||||
console.log("[WebSocket] 连接错误:", e);
|
// console.log("[WebSocket] 连接错误:", e);
|
||||||
|
|
||||||
this.lockReconnect = false;
|
this.lockReconnect = false;
|
||||||
this.reconnect();
|
this.reconnect();
|
||||||
},
|
},
|
||||||
|
|
@ -237,7 +237,7 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
// 使用与 oa-web-phone 相同的原生 WebSocket 通信方式
|
// 使用与 oa-web-phone 相同的原生 WebSocket 通信方式
|
||||||
// this.startLink(); // 现在一直断线重连,先注释
|
this.startLink(); // 现在一直断线重连,先注释
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -218,9 +218,12 @@ const install = (Vue, vm) => {
|
||||||
// 获取教师列表(学生端-招办在线)
|
// 获取教师列表(学生端-招办在线)
|
||||||
let GetTeacherListApi = (params = {}) =>
|
let GetTeacherListApi = (params = {}) =>
|
||||||
vm.$u.get("api/Dialogue/GetTeacherList", params);
|
vm.$u.get("api/Dialogue/GetTeacherList", params);
|
||||||
// 获取会话列表
|
// 获取会话列表-教师
|
||||||
let GetDialogueListApi = (params = {}) =>
|
let GetDialogueListApi = (params = {}) =>
|
||||||
vm.$u.get("api/Dialogue/GetDialogueList", params);
|
vm.$u.get("api/Dialogue/GetDialogueList", params);
|
||||||
|
// 获取会话列表-用户
|
||||||
|
let GetDialogueList_UserApi = (params = {}) =>
|
||||||
|
vm.$u.get("api/Dialogue/GetDialogueList_User", params);
|
||||||
// 创建会话
|
// 创建会话
|
||||||
let AddDialogueApi = (params = {}) =>
|
let AddDialogueApi = (params = {}) =>
|
||||||
vm.$u.post("api/Dialogue/AddDialogue", params);
|
vm.$u.post("api/Dialogue/AddDialogue", params);
|
||||||
|
|
@ -236,6 +239,9 @@ const install = (Vue, vm) => {
|
||||||
// 置顶一个会话
|
// 置顶一个会话
|
||||||
let OverheadOneDialogueApi = (params = {}) =>
|
let OverheadOneDialogueApi = (params = {}) =>
|
||||||
vm.$u.post("api/Dialogue/OverheadOneDialogue", params);
|
vm.$u.post("api/Dialogue/OverheadOneDialogue", params);
|
||||||
|
// 删除会话
|
||||||
|
let DeleteDialogueApi = (params = {}) =>
|
||||||
|
vm.$u.post("api/Dialogue/DeleteDialogue", params);
|
||||||
|
|
||||||
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
||||||
vm.$u.api = {
|
vm.$u.api = {
|
||||||
|
|
@ -297,6 +303,7 @@ const install = (Vue, vm) => {
|
||||||
UpdateUserApi,
|
UpdateUserApi,
|
||||||
GetTeacherListApi,
|
GetTeacherListApi,
|
||||||
GetDialogueListApi,
|
GetDialogueListApi,
|
||||||
|
GetDialogueList_UserApi,
|
||||||
AddDialogueApi,
|
AddDialogueApi,
|
||||||
SendMessage_PrivateApi,
|
SendMessage_PrivateApi,
|
||||||
GetChatHistoryDataApi,
|
GetChatHistoryDataApi,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
>
|
>
|
||||||
<!-- 使用新的数据结构渲染聊天历史 -->
|
<!-- 使用新的数据结构渲染聊天历史 -->
|
||||||
<view
|
<view
|
||||||
v-for="(group, groupIndex) in chatHistoryList3"
|
v-for="(group, groupIndex) in currentChatHistory"
|
||||||
:key="'group-' + group.id + groupIndex"
|
:key="'group-' + group.id + groupIndex"
|
||||||
@click="closePopover"
|
@click="closePopover"
|
||||||
>
|
>
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
'chat-item-active': item.isActiveChat,
|
'chat-item-active': item.isActiveChat,
|
||||||
}"
|
}"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
selectChatItem(groupIndex, index, item.id, item.conversationId)
|
selectChatItem(item.id, item.conversationId, item.receiverId)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<view class="chat-item-content">
|
<view class="chat-item-content">
|
||||||
|
|
@ -90,12 +90,7 @@
|
||||||
<view
|
<view
|
||||||
class="popover-item"
|
class="popover-item"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
selectChatItem(
|
selectChatItem(item.id, item.conversationId, item.receiverId)
|
||||||
groupIndex,
|
|
||||||
index,
|
|
||||||
item.id,
|
|
||||||
item.conversationId
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<u-icon
|
<u-icon
|
||||||
|
|
@ -155,7 +150,11 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
chatHistoryList3: {
|
chatHistoryAI: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
chatHistoryTeacher: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
|
@ -172,8 +171,6 @@ export default {
|
||||||
return {
|
return {
|
||||||
baseUrl: "",
|
baseUrl: "",
|
||||||
showPopup: false,
|
showPopup: false,
|
||||||
currentActiveGroup: -1,
|
|
||||||
currentActiveIndex: -1,
|
|
||||||
activeItemId: "", // 存储当前激活项的ID
|
activeItemId: "", // 存储当前激活项的ID
|
||||||
scrollToView: "", // 用于scroll-into-view属性
|
scrollToView: "", // 用于scroll-into-view属性
|
||||||
tabList: [
|
tabList: [
|
||||||
|
|
@ -193,6 +190,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
currentChatHistory() {
|
||||||
|
return this.currentTab === 0
|
||||||
|
? this.chatHistoryAI
|
||||||
|
: this.chatHistoryTeacher;
|
||||||
|
},
|
||||||
|
|
||||||
headSculptureUrl() {
|
headSculptureUrl() {
|
||||||
if (this.vuex_user.HeadSculptureUrl) {
|
if (this.vuex_user.HeadSculptureUrl) {
|
||||||
return this.baseUrl + "/" + this.vuex_user.HeadSculptureUrl;
|
return this.baseUrl + "/" + this.vuex_user.HeadSculptureUrl;
|
||||||
|
|
@ -224,7 +227,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 监听聊天历史数据变化,找到激活项并滚动
|
// 监听聊天历史数据变化,找到激活项并滚动
|
||||||
chatHistoryList3: {
|
chatHistoryAI: {
|
||||||
handler() {
|
handler() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollToActiveItem();
|
this.scrollToActiveItem();
|
||||||
|
|
@ -308,10 +311,10 @@ export default {
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let groupIndex = 0;
|
let groupIndex = 0;
|
||||||
groupIndex < this.chatHistoryList3.length;
|
groupIndex < this.chatHistoryAI.length;
|
||||||
groupIndex++
|
groupIndex++
|
||||||
) {
|
) {
|
||||||
const group = this.chatHistoryList3[groupIndex];
|
const group = this.chatHistoryAI[groupIndex];
|
||||||
for (let index = 0; index < group.conversation.length; index++) {
|
for (let index = 0; index < group.conversation.length; index++) {
|
||||||
const item = group.conversation[index];
|
const item = group.conversation[index];
|
||||||
if (item.isActiveChat) {
|
if (item.isActiveChat) {
|
||||||
|
|
@ -326,16 +329,21 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectChatItem(groupIndex, index, id, conversationId) {
|
selectChatItem(id, conversationId = null, receiverId = null) {
|
||||||
// this.currentActiveGroup = groupIndex;
|
if (this.currentTab === 0) {
|
||||||
// this.currentActiveIndex = index;
|
// 点击AI聊天项
|
||||||
console.log("selectChatItem", groupIndex, index, id, conversationId);
|
|
||||||
|
|
||||||
// 向父组件发送选中的对话信息
|
// 向父组件发送选中的对话信息
|
||||||
this.$emit("select-conversation", {
|
this.$emit("select-conversation", {
|
||||||
id,
|
id,
|
||||||
conversationId,
|
conversationId,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// 点击教师聊天项
|
||||||
|
this.$store.dispatch("selectTeacherChatItem", {
|
||||||
|
id,
|
||||||
|
receiverId,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleCreateConversation() {
|
handleCreateConversation() {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<view class="chat-page">
|
<view class="chat-page">
|
||||||
<!-- 顶部导航 -->
|
<!-- 顶部导航 -->
|
||||||
<header-bar
|
<header-bar
|
||||||
:title="vuex_msgUser.receiverName"
|
:title="vuex_msgUser.name"
|
||||||
leftIcon="arrow-left"
|
leftIcon="arrow-left"
|
||||||
@leftClick="handleLeftClick"
|
@leftClick="handleLeftClick"
|
||||||
></header-bar>
|
></header-bar>
|
||||||
|
|
@ -15,6 +15,30 @@
|
||||||
:scroll-into-view="scrollToView"
|
:scroll-into-view="scrollToView"
|
||||||
scroll-with-animation
|
scroll-with-animation
|
||||||
>
|
>
|
||||||
|
<!-- 教师信息 -->
|
||||||
|
<div class="teacher-info-card">
|
||||||
|
<image class="teacher-avatar" :src="receiverHeadSculptureUrl"></image>
|
||||||
|
<div class="teacher-info">
|
||||||
|
<div class="teacher-name">{{ vuex_msgUser.name }}</div>
|
||||||
|
|
||||||
|
<div class="teacher-school">
|
||||||
|
<image
|
||||||
|
class="school-icon"
|
||||||
|
src="/static/common/images/icon_college.png"
|
||||||
|
></image>
|
||||||
|
<text class="school-text">{{ vuex_msgUser.collegeName }}</text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="teacher-college">
|
||||||
|
<image
|
||||||
|
class="college-icon"
|
||||||
|
src="/static/common/images/icon_major.png"
|
||||||
|
></image>
|
||||||
|
<text class="college-text">{{ vuex_msgUser.collegeName }}</text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<view
|
<view
|
||||||
v-for="(message, index) in vuex_msgList"
|
v-for="(message, index) in vuex_msgList"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
|
|
@ -28,7 +52,7 @@
|
||||||
<!-- 0 发送消息 -->
|
<!-- 0 发送消息 -->
|
||||||
<view
|
<view
|
||||||
class="message-right"
|
class="message-right"
|
||||||
v-if="message.interactMode === 0"
|
v-if="message.senderId === vuex_user.Id"
|
||||||
:id="'msg-' + message.id"
|
:id="'msg-' + message.id"
|
||||||
>
|
>
|
||||||
<view class="message-content">
|
<view class="message-content">
|
||||||
|
|
@ -44,12 +68,12 @@
|
||||||
<!-- 1 收到消息 -->
|
<!-- 1 收到消息 -->
|
||||||
<view
|
<view
|
||||||
class="message-left"
|
class="message-left"
|
||||||
v-if="message.interactMode === 1"
|
v-if="message.senderId !== vuex_user.Id"
|
||||||
:id="'msg-' + message.id"
|
:id="'msg-' + message.id"
|
||||||
>
|
>
|
||||||
<image
|
<image
|
||||||
class="ai-avatar"
|
class="ai-avatar"
|
||||||
src="/static/common/images/avatar_ai.png"
|
:src="receiverHeadSculptureUrl"
|
||||||
mode="scaleToFill"
|
mode="scaleToFill"
|
||||||
/>
|
/>
|
||||||
<view class="message-content">
|
<view class="message-content">
|
||||||
|
|
@ -155,6 +179,7 @@ export default {
|
||||||
|
|
||||||
onLoad(options) {
|
onLoad(options) {
|
||||||
console.log(this.vuex_msgList);
|
console.log(this.vuex_msgList);
|
||||||
|
console.log(this.vuex_msgUser, "this.vuex_msgUser");
|
||||||
|
|
||||||
this.baseUrl = this.$u.http.config.baseUrl;
|
this.baseUrl = this.$u.http.config.baseUrl;
|
||||||
|
|
||||||
|
|
@ -168,6 +193,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
receiverHeadSculptureUrl() {
|
||||||
|
if (this.vuex_msgUser.headSculptureUrl) {
|
||||||
|
return this.baseUrl + "/" + this.vuex_msgUser.headSculptureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/static/common/images/avatar_default2.png";
|
||||||
|
},
|
||||||
|
|
||||||
headSculptureUrl() {
|
headSculptureUrl() {
|
||||||
if (this.vuex_user.HeadSculptureUrl) {
|
if (this.vuex_user.HeadSculptureUrl) {
|
||||||
return this.baseUrl + "/" + this.vuex_user.HeadSculptureUrl;
|
return this.baseUrl + "/" + this.vuex_user.HeadSculptureUrl;
|
||||||
|
|
@ -231,7 +264,7 @@ export default {
|
||||||
message: this.messageValue,
|
message: this.messageValue,
|
||||||
messageType: 0,
|
messageType: 0,
|
||||||
filePath: "",
|
filePath: "",
|
||||||
interactMode: 0,
|
// interactMode: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$store.commit("push_Msg", msgUserData);
|
this.$store.commit("push_Msg", msgUserData);
|
||||||
|
|
@ -274,17 +307,7 @@ export default {
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const msgList = res.data.item1.reverse();
|
const msgList = res.data.item1.reverse();
|
||||||
// 注:现在的消息返回值缺少 interactMode 字段,先手动mock
|
|
||||||
|
|
||||||
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);
|
this.$store.commit("push_MsgList", msgList);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -399,6 +422,62 @@ export default {
|
||||||
// padding: 10rpx 0;
|
// padding: 10rpx 0;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
.teacher-info-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin: 30rpx 0;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 36rpx;
|
||||||
|
|
||||||
|
.teacher-avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-right: 30rpx;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8rpx;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.teacher-name {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teacher-school,
|
||||||
|
.teacher-college {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.school-icon,
|
||||||
|
.college-icon {
|
||||||
|
margin-right: 16rpx;
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-text,
|
||||||
|
.college-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-time {
|
.message-time {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
<view class="teacher-list">
|
<view class="teacher-list">
|
||||||
<!-- 教师列表 -->
|
<!-- 教师列表 -->
|
||||||
|
<view v-if="teacherList.length > 0">
|
||||||
<view
|
<view
|
||||||
class="teacher-item"
|
class="teacher-item"
|
||||||
v-for="(teacher, index) in teacherList"
|
v-for="(teacher, index) in teacherList"
|
||||||
|
|
@ -36,7 +37,9 @@
|
||||||
<text>在线</text>
|
<text>在线</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="teacher-department">{{ teacher.collegeName }}</view>
|
<view class="teacher-department">{{
|
||||||
|
teacher.collegeName
|
||||||
|
}}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="ask-button" @click="handleAskQuestion(teacher)">
|
<view class="ask-button" @click="handleAskQuestion(teacher)">
|
||||||
立即提问
|
立即提问
|
||||||
|
|
@ -44,6 +47,11 @@
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view v-else style="margin-top: 200rpx">
|
||||||
|
<u-empty text="今日暂无值班客服教师" mode="list"></u-empty>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 留言弹出层 -->
|
<!-- 留言弹出层 -->
|
||||||
|
|
@ -74,20 +82,20 @@ export default {
|
||||||
{ id: 1, name: "迎新在线" },
|
{ id: 1, name: "迎新在线" },
|
||||||
],
|
],
|
||||||
teacherList: [
|
teacherList: [
|
||||||
{
|
// {
|
||||||
id: 1,
|
// id: 1,
|
||||||
name: "孙老师",
|
// name: "孙老师",
|
||||||
department: "招就处",
|
// department: "招就处",
|
||||||
avatar: "/static/common/images/avatar.png",
|
// avatar: "/static/common/images/avatar.png",
|
||||||
online: true,
|
// online: true,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 2,
|
// id: 2,
|
||||||
name: "杨老师",
|
// name: "杨老师",
|
||||||
department: "电子信息学院",
|
// department: "电子信息学院",
|
||||||
avatar: "/static/common/images/student.png",
|
// avatar: "/static/common/images/student.png",
|
||||||
online: false,
|
// online: false,
|
||||||
},
|
// },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -106,14 +114,8 @@ export default {
|
||||||
handleAskQuestion(teacher) {
|
handleAskQuestion(teacher) {
|
||||||
console.log("点击咨询:", teacher);
|
console.log("点击咨询:", teacher);
|
||||||
|
|
||||||
var msgUserData = {
|
// 点击立即提问,进入对话会话
|
||||||
avatar: teacher.headSculptureUrl,
|
this.$store.dispatch("createDialogue", teacher);
|
||||||
friendId: teacher.id,
|
|
||||||
friendName: teacher.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 进入对话会话
|
|
||||||
this.$store.dispatch("openOrCreateDialogue", msgUserData);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,8 @@
|
||||||
<!-- 对话弹出层 -->
|
<!-- 对话弹出层 -->
|
||||||
<chat-history
|
<chat-history
|
||||||
:show.sync="popupShow"
|
:show.sync="popupShow"
|
||||||
:chat-history-list3="chatHistoryList3"
|
:chatHistoryAI="chatHistoryAI"
|
||||||
|
:chatHistoryTeacher="chatHistoryTeacher"
|
||||||
:user-name="vuex_user ? vuex_user.Name : ''"
|
:user-name="vuex_user ? vuex_user.Name : ''"
|
||||||
@select-conversation="handleSelectConversation"
|
@select-conversation="handleSelectConversation"
|
||||||
@create-conversation="handleCreateConversation"
|
@create-conversation="handleCreateConversation"
|
||||||
|
|
@ -353,7 +354,7 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
popupShow: false,
|
popupShow: false,
|
||||||
chatHistoryList3: [
|
chatHistoryAI: [
|
||||||
// {
|
// {
|
||||||
// "id": "今天",
|
// "id": "今天",
|
||||||
// "conversation": [
|
// "conversation": [
|
||||||
|
|
@ -385,6 +386,7 @@ export default {
|
||||||
// ]
|
// ]
|
||||||
// }
|
// }
|
||||||
],
|
],
|
||||||
|
chatHistoryTeacher: [],
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
commonQuestions: [
|
commonQuestions: [
|
||||||
"新生报到流程",
|
"新生报到流程",
|
||||||
|
|
@ -467,6 +469,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async handleLeftClick() {
|
async handleLeftClick() {
|
||||||
await this.getChatHistoryList();
|
await this.getChatHistoryList();
|
||||||
|
await this.GetDialogueList_User();
|
||||||
this.handlePopupShow();
|
this.handlePopupShow();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -493,11 +496,12 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getChatHistoryList() {
|
// 获取人工咨询历史记录
|
||||||
this.$u.api.GetConversationPage().then((res) => {
|
async GetDialogueList_User() {
|
||||||
this.chatHistoryList3 = res.data;
|
this.$u.api.GetDialogueList_UserApi().then((res) => {
|
||||||
if (this.chatHistoryList3.length > 0) {
|
this.chatHistoryTeacher = res.data;
|
||||||
this.chatHistoryList3 = res.data.map((group) => {
|
if (this.chatHistoryTeacher.length > 0) {
|
||||||
|
this.chatHistoryTeacher = res.data.map((group) => {
|
||||||
// 对每个组的conversation数组进行倒序排序
|
// 对每个组的conversation数组进行倒序排序
|
||||||
return {
|
return {
|
||||||
...group,
|
...group,
|
||||||
|
|
@ -511,7 +515,29 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("this.chatHistoryList3", this.chatHistoryList3);
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取ai历史记录
|
||||||
|
async getChatHistoryList() {
|
||||||
|
this.$u.api.GetConversationPage().then((res) => {
|
||||||
|
this.chatHistoryAI = res.data;
|
||||||
|
if (this.chatHistoryAI.length > 0) {
|
||||||
|
this.chatHistoryAI = res.data.map((group) => {
|
||||||
|
// 对每个组的conversation数组进行倒序排序
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
conversation: group.conversation.sort((a, b) => {
|
||||||
|
// 将日期字符串转换为时间戳并比较(倒序)
|
||||||
|
return (
|
||||||
|
new Date(b.startTime).getTime() -
|
||||||
|
new Date(a.startTime).getTime()
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("this.chatHistoryAI", this.chatHistoryAI);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
|
|
@ -94,8 +94,6 @@ const store = new Vuex.Store({
|
||||||
// text: "我的"
|
// text: "我的"
|
||||||
// }
|
// }
|
||||||
],
|
],
|
||||||
vuex_education: [],
|
|
||||||
vuex_schoolName: "",
|
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
$uStore(state, payload) {
|
$uStore(state, payload) {
|
||||||
|
|
@ -237,7 +235,7 @@ const store = new Vuex.Store({
|
||||||
// 获取聊天记录(私聊)
|
// 获取聊天记录(私聊)
|
||||||
async fetchChatRecord(
|
async fetchChatRecord(
|
||||||
{ commit, state },
|
{ commit, state },
|
||||||
{ userId, friendId, chatType = 0, PageIndex = 1, PageSize = 20 }
|
{ userId, friendId, PageIndex = 1, PageSize = 20 }
|
||||||
) {
|
) {
|
||||||
const params = { userId, friendId, PageIndex, PageSize };
|
const params = { userId, friendId, PageIndex, PageSize };
|
||||||
|
|
||||||
|
|
@ -262,58 +260,73 @@ const store = new Vuex.Store({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 统一入口:打开或创建会话
|
// 点击聊天记录,切换到该会话
|
||||||
// 使用位置:所有需要进入聊天的入口调用该方法,避免各页面重复逻辑
|
selectTeacherChatItem({ commit, dispatch }, { id, receiverId }) {
|
||||||
// 1) 获取最新会话列表
|
// 清空消息列表,避免旧消息干扰
|
||||||
// 2) 查找是否存在 friendId 对应的会话
|
commit("push_MsgList", []);
|
||||||
// 3) 有则设置当前会话并(可选)跳转;无则创建,成功后递归重试
|
|
||||||
async openOrCreateDialogue({ commit, state, dispatch }, user) {
|
Vue.prototype.$u.api
|
||||||
const { friendId, friendName, avatar, chatType = 0 } = user || {};
|
.GetReceiverUserInfoApi({ Id: receiverId })
|
||||||
const attempt =
|
.then((res) => {
|
||||||
user && typeof user.attempt === "number" ? user.attempt : 1;
|
if (res.succeed && res.data) {
|
||||||
if (!friendId) return Promise.reject(new Error("缺少 friendId"));
|
commit("set_MsgUser", { ...res.data, dialogueManagementId: id });
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/chat/index`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 点击立即提问进入会话
|
||||||
|
// 1) 创建新会话
|
||||||
|
// 2) 获取接收者信息
|
||||||
|
// 3) 进入会话
|
||||||
|
async createDialogue({ commit, state, dispatch }, user) {
|
||||||
|
const { id, dialogueManagementId } = user || {};
|
||||||
|
if (!id) return Promise.reject(new Error("缺少 id"));
|
||||||
|
|
||||||
// 清空消息列表,避免旧消息干扰
|
// 清空消息列表,避免旧消息干扰
|
||||||
commit("push_MsgList", []);
|
commit("push_MsgList", []);
|
||||||
|
|
||||||
// 第一步:获取列表(复用现有 action,保证 state 同步更新)
|
if (
|
||||||
const list = await dispatch("getUserlist");
|
dialogueManagementId &&
|
||||||
|
dialogueManagementId !== "00000000-0000-0000-0000-000000000000"
|
||||||
// 第二步:查找是否存在该会话(兼容后端字段 friendId/receiverId)
|
) {
|
||||||
const target = (list || []).find(
|
// 有会话ID,直接进入会话
|
||||||
(i) => i && (i.receiverId === friendId || i.friendId === friendId)
|
commit("set_MsgUser", { ...user });
|
||||||
);
|
|
||||||
if (target) {
|
|
||||||
commit("set_MsgUser", target);
|
|
||||||
|
|
||||||
// 跳转到对话页,参数按需调整
|
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/chat/index`,
|
url: `/pages/chat/index`,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二次调用时,无论成功与否均在第三步(创建)之前结束
|
// 没有会话id创建新会话
|
||||||
if (attempt >= 2) {
|
const res1 = await Vue.prototype.$u.api.AddDialogueApi({
|
||||||
return false;
|
receiverId: id,
|
||||||
}
|
onlineConsultationType: 1, // 创建在线咨询固定传1
|
||||||
|
|
||||||
// 第三步:不存在则创建,成功后重试本流程
|
|
||||||
const res = await Vue.prototype.$u.api.AddDialogueApi({
|
|
||||||
receiverId: friendId,
|
|
||||||
onlineConsultationType: 0, // 0:招生在线;1:迎新在线(可按业务传入)
|
|
||||||
});
|
});
|
||||||
if (res && res.succeed === true) {
|
|
||||||
// 创建成功后,重新执行流程(再次获取列表并进入会话)
|
const resId = res1.data?.dialogueManagementId || "";
|
||||||
return dispatch("openOrCreateDialogue", {
|
|
||||||
friendId,
|
if (res1 && res1.succeed) {
|
||||||
friendName,
|
// 获取接收者信息,这里没啥用(先注释)
|
||||||
avatar,
|
// Vue.prototype.$u.api.GetReceiverUserInfoApi({ Id: id }).then((res) => {
|
||||||
chatType,
|
// if (res.succeed && res.data) {
|
||||||
attempt: attempt + 1,
|
// commit("set_MsgUser", { ...res.data, dialogueManagementId });
|
||||||
|
// uni.navigateTo({
|
||||||
|
// url: `/pages/chat/index`,
|
||||||
|
// });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
commit("set_MsgUser", { ...user, dialogueManagementId: resId });
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/chat/index`,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(res?.error || "创建会话失败"));
|
return Promise.reject(new Error(res?.error || "创建会话失败"));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ declare global {
|
||||||
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
|
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
|
||||||
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
|
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
|
||||||
type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B;
|
type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B;
|
||||||
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> =
|
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string = N1, N3 extends string = N1> =
|
||||||
N1 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N1] } :
|
N1 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N1] } :
|
||||||
N2 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N2] } :
|
N2 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N2] } :
|
||||||
N3 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N3] } :
|
N3 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N3] } :
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue