- - - 人工服务 - - - {{ isOnline ? '在线' : '离线' }} + + + + + + {{ item.unreadCount > 99 ? '99+' : item.unreadCount }} - - {{ isOnline ? '客服正在为您服务' : '当前暂无客服在线' }} + + + + + {{ item.name }} + {{ item.displayTime }} + + + {{ item.displayPreview }} - - 在线客服 - - - - - - - - {{ agent.name }} - {{ agent.statusText }} - - - - {{ agent.online ? '转接' : '离线' }} - - - - - - - - 暂无客服在线 + + + 暂无人工转接消息 + 人工客服接入后会显示在这里 - + - - @@ -80,68 +59,266 @@ export default { 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 { - showModal: false, - modalContent: '', - selectedAgent: null, - defaultAvatar: "/static/avatar/default-avatar.png", - isOnline: true, - agentList: [ - { - id: 1, - name: "客服小王", - avatar: "", - online: true, - statusText: "空闲中", - }, - { - id: 2, - name: "客服小李", - avatar: "", - online: true, - statusText: "忙碌中", - }, - { - id: 3, - name: "客服小张", - avatar: "", - online: false, - statusText: "离线", - }, - ], + 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); }, - handleTransfer(agent) { - if (!agent.online) { - uni.showToast({ - title: '该客服当前离线', - icon: 'none' - }); - return; + async openChat(item) { + if (item?.id) { + try { + await this.$u.api.ReadMessageApi({ + dialogueManagementId: item.id, + }); + } catch (err) { + console.error("[人工转接] 标记已读失败", err); + } } - this.selectedAgent = agent; - this.modalContent = `确认转接到${agent.name}吗?`; - this.showModal = true; - }, - confirmTransfer() { - // 这里可以执行实际的转接逻辑 - uni.showToast({ - title: '正在为您转接...', - icon: 'none' + + this.$store.dispatch("selectTeacherChatItem", { + id: item.id, + receiverId: item.userId, }); - this.showModal = false; - - // 模拟跳转到聊天页面 - setTimeout(() => { - // uni.navigateTo({ - // url: '/pages/message/dialogBox/dialogBox?agentId=' + this.selectedAgent.id - // }); - }, 1000); + }, + 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 || + "" + ); }, }, }; @@ -188,123 +365,113 @@ export default { z-index: 100; } -.status-card { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 20px; - margin-bottom: 15px; - border-radius: 12px; - box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); -} - -.status-title { - font-size: 16px; - font-weight: 500; - color: #fff; - margin-bottom: 10px; -} - -.status-content { +.chat-item { display: flex; - align-items: center; -} - -.status-badge { - padding: 4px 12px; - border-radius: 20px; - font-size: 12px; - font-weight: 500; - margin-right: 10px; -} - -.status-badge.online { - background-color: #52c41a; - color: #fff; -} - -.status-badge.offline { - background-color: #999; - color: #fff; -} - -.status-desc { - font-size: 13px; - color: rgba(255, 255, 255, 0.9); -} - -.section-title { - font-size: 15px; - font-weight: 500; - color: #333; - margin: 15px 0 10px 0; -} - -.agent-list { - background-color: #fff; - border-radius: 12px; - overflow: hidden; -} - -.agent-item { - display: flex; - align-items: center; padding: 15px; - border-bottom: 1px solid #f0f0f0; + background: #fff; + border-bottom: 1rpx solid #f0f0f0; + transition: background-color 0.2s; } -.agent-item:last-child { - border-bottom: none; +.chat-item:active { + background-color: #f5f5f5; } -.agent-avatar-wrapper { +.chat-avatar-wrapper { position: relative; margin-right: 12px; } -.agent-avatar { - width: 48px; - height: 48px; - border-radius: 50%; - background-color: #eee; +.chat-avatar { + width: 50px; + height: 50px; + border-radius: 8px; + background-color: #e8e8e8; } -.online-dot { +.unread-badge { position: absolute; - bottom: 2px; - right: 2px; - width: 10px; - height: 10px; - background-color: #52c41a; - border-radius: 50%; + 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; } -.agent-info { +.chat-content { flex: 1; + min-width: 0; display: flex; flex-direction: column; + justify-content: space-between; } -.agent-name { - font-size: 15px; +.chat-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; +} + +.chat-name { + font-size: 16px; font-weight: 500; color: #333; - margin-bottom: 4px; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.agent-status { +.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; } - -.agent-action { - margin-left: 10px; -} - -.empty-tip { - text-align: center; - padding: 50px 20px; - color: #999; - font-size: 14px; -} -