YingXingAI/pages/transfer/index.vue

478 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page-container">
<view class="page-header">
<PageHeader title="人工转接" :is-back="false" :border-bottom="false" />
</view>
<scroll-view
class="page-main"
scroll-y
enable-back-to-top
>
<view class="main-content">
<!-- 会话列表人工转接 -->
<view
class="chat-item"
v-for="(item, index) in displayChatList"
:key="item.id || index"
@click="openChat(item)"
>
<view class="chat-avatar-wrapper">
<image class="chat-avatar" :src="item.avatar"></image>
<view class="unread-badge" v-if="item.unreadCount > 0">
{{ item.unreadCount > 99 ? '99+' : item.unreadCount }}
</view>
</view>
<view class="chat-content">
<view class="chat-header">
<text class="chat-name">{{ item.name }}</text>
<text class="chat-time">{{ item.displayTime }}</text>
</view>
<view class="chat-preview">
<text class="preview-text">{{ item.displayPreview }}</text>
</view>
</view>
</view>
<view class="empty-container" v-if="chatList.length === 0">
<image class="empty-image" src="/static/common/icon/empty-chat.png"></image>
<text class="empty-text">暂无人工转接消息</text>
<text class="empty-hint">人工客服接入后会显示在这里</text>
</view>
</view>
</scroll-view>
<view class="page-tabbar">
<TabBar :currentPath="'/pages/transfer/index'" @change="handleTabChange" />
</view>
</view>
</template>
<script>
import TabBar from "@/components/TabBar-optimized.vue";
import PageHeader from "@/components/PageHeader.vue";
export default {
name: "TransferPage",
components: {
TabBar,
PageHeader,
},
computed: {
dialogueTypeMap() {
return this.$store?.state?.vuex_dialogueTypeMap || {};
},
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() {
// 未完成首帧加载前不渲染,避免显示上一页的会话
if (!this.hasLoaded) return [];
const realtime = this.$store?.state?.vuex_userMsgList || [];
const baseList = this.chatList || [];
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;
// 只展示标记为人工转接2的会话
const type = this.dialogueTypeMap[id];
if (type && Number(type) !== 2) return null;
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,
};
}).filter(Boolean);
realtime.forEach((item) => {
const id = this.getDialogueId(item);
if (!id) return;
const exists = merged.some((row) => row.id === id);
const type = this.dialogueTypeMap[id];
if (!exists && (!type || Number(type) === 2)) {
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() {
return {
chatList: [],
totalCount: 0,
isLoading: false,
hasLoaded: false,
};
},
onLoad() {
this.loadChatList().finally(() => {
this.hasLoaded = true;
});
},
onShow() {
if (this.hasLoaded) {
this.refreshChatList();
}
},
methods: {
handleTabChange(path, index) {
console.log("切换到标签页:", path, index);
},
async openChat(item) {
if (item?.id) {
try {
await this.$u.api.ReadMessageApi({
dialogueManagementId: item.id,
});
} catch (err) {
console.error("[人工转接] 标记已读失败", err);
}
}
this.$store.dispatch("selectTeacherChatItem", {
id: item.id,
receiverId: item.userId,
});
},
async loadChatList() {
if (this.isLoading) return;
this.isLoading = true;
try {
const res = await this.$u.api.GetDialogueListApi({
"Item1.OnlineConsultationType": 2,
});
const list = (res && res.data && res.data.item1) || [];
this.chatList = this.normalizeDialogueList(list);
this.$store.commit("set_UserMsgList", this.chatList);
this.$store.commit("upsert_DialogueTypeMap", {
list: this.chatList,
type: 2,
});
this.totalCount = res?.data?.item2 || list.length;
} catch (error) {
console.error("[人工转接] 获取会话列表失败", error);
uni.showToast({
title: "获取会话列表失败",
icon: "none",
});
} finally {
this.isLoading = false;
}
},
refreshChatList() {
this.loadChatList();
},
normalizeDialogueList(list = []) {
return list.map((item, index) => {
const unread =
typeof item?.unReadCount === "number"
? item.unReadCount
: item?.unreadCount || 0;
const id =
item?.dialogueManagementId ||
item?.DialogueManagementId ||
item?.dialogueId ||
item?.friendId ||
item?.id ||
index;
return {
id,
dialogueManagementId: id,
DialogueManagementId: id,
userId: item?.receiverId || item?.friendId || item?.userId || "",
friendId: item?.receiverId || item?.friendId || item?.userId || "",
name:
item?.receiverName ||
item?.friendName ||
item?.userName ||
item?.title ||
"",
avatar: this.buildAvatarUrl(
item?.receiverHeadSculptureUrl ||
item?.avatar ||
item?.friendAvatar
),
lastMessage: item?.lastMessage || "暂无消息",
lastMessageTime: this.formatTime(
item?.lastMessageTime || item?.lastSendTime || item?.startTime
),
unreadCount: unread,
};
});
},
buildAvatarUrl(url) {
const fallback = "/static/avatar/default-avatar.png";
if (!url) return fallback;
if (/^https?:\/\//i.test(url)) return url;
const baseUrl = this.$u?.http?.config?.baseUrl || "";
if (baseUrl) return `${baseUrl}/${url}`;
return url.startsWith("/") ? url : `/${url}`;
},
formatTime(timeStr) {
if (!timeStr) return "";
const date = new Date(timeStr);
if (!isNaN(date.getTime())) {
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
return `${month}/${day} ${hours}:${minutes}`;
}
return String(timeStr).replace("T", " ").slice(5, 16);
},
getDialogueId(item) {
return (
item?.dialogueManagementId ||
item?.DialogueManagementId ||
item?.dialogueId ||
item?.friendId ||
item?.id ||
""
);
},
},
};
</script>
<style scoped>
/* ===== 页面容器 - 主流三段式布局 ===== */
.page-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
background-color: #f5f6fa;
overflow: hidden;
}
/* ===== 头部导航 ===== */
.page-header {
flex-shrink: 0;
z-index: 100;
}
/* ===== 内容区域 ===== */
.page-main {
flex: 1;
height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* ===== 内容内层 ===== */
.main-content {
padding: 10px;
padding-bottom: calc(50px + env(safe-area-inset-bottom) + 10px);
min-height: 100%;
}
/* ===== 底部导航 ===== */
.page-tabbar {
flex-shrink: 0;
z-index: 100;
}
.chat-item {
display: flex;
padding: 15px;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
transition: background-color 0.2s;
}
.chat-item:active {
background-color: #f5f5f5;
}
.chat-avatar-wrapper {
position: relative;
margin-right: 12px;
}
.chat-avatar {
width: 50px;
height: 50px;
border-radius: 8px;
background-color: #e8e8e8;
}
.unread-badge {
position: absolute;
top: -5px;
right: -5px;
min-width: 18px;
height: 18px;
padding: 0 5px;
background: #ff4d4f;
border-radius: 9px;
color: #fff;
font-size: 11px;
line-height: 18px;
text-align: center;
border: 2px solid #fff;
}
.chat-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.chat-name {
font-size: 16px;
font-weight: 500;
color: #333;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-time {
font-size: 12px;
color: #999;
flex-shrink: 0;
}
.chat-preview {
display: flex;
align-items: center;
}
.preview-text {
font-size: 13px;
color: #999;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100px 20px;
}
.empty-image {
width: 120px;
height: 120px;
margin-bottom: 20px;
}
.empty-text {
font-size: 16px;
color: #666;
margin-bottom: 8px;
}
.empty-hint {
font-size: 13px;
color: #999;
}
</style>