This commit is contained in:
wangdongbo 2026-01-30 10:28:34 +08:00
parent 7f737dfd8a
commit 3ad6140829
4 changed files with 247 additions and 57 deletions

View File

@ -7,8 +7,14 @@
<view v-if="customScroll" class="page-scroll"> <view v-if="customScroll" class="page-scroll">
<slot></slot> <slot></slot>
</view> </view>
<scroll-view v-else scroll-y="true" :scroll-top="scrollTop" class="page-scroll" @scrolltolower="scrolltolower" <scroll-view
@scroll="onScroll"> v-else
scroll-y="true"
:scroll-top="scrollTop"
class="page-scroll"
@scrolltolower="scrolltolower"
@scroll="onScroll"
>
<slot></slot> <slot></slot>
</scroll-view> </scroll-view>
</view> </view>
@ -16,22 +22,22 @@
<slot name="footer"></slot> <slot name="footer"></slot>
</view> </view>
<!-- #ifdef MP--> <!-- #ifdef MP-->
<view v-if="showSafeArea" class="safeareaBottom"></view> <!-- <view v-if="showSafeArea" class="safeareaBottom"></view> -->
<!-- #endif --> <!-- #endif -->
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, useSlots, ref } from 'vue'; import { computed, useSlots, ref } from "vue";
import useDebounce from '@/utils/useDebounce'; import useDebounce from "@/utils/useDebounce";
const emits = defineEmits(['reachBottom']); const emits = defineEmits(["reachBottom"]);
const props = defineProps({ const props = defineProps({
customScroll: { type: Boolean, default: false }, customScroll: { type: Boolean, default: false },
mainClass: { type: String, default: '' }, mainClass: { type: String, default: "" },
mainStyle: { default: '' }, mainStyle: { default: "" },
pageClass: { type: String, default: '' }, pageClass: { type: String, default: "" },
pageStyle: { default: '' }, pageStyle: { default: "" },
showSafeArea: { type: Boolean, default: true } showSafeArea: { type: Boolean, default: true },
}); });
const slots = useSlots(); const slots = useSlots();
const hasHeader = computed(() => !!slots.header); const hasHeader = computed(() => !!slots.header);
@ -40,7 +46,7 @@ const hasFooter = computed(() => !!slots.footer);
const scrollTop = ref(0); const scrollTop = ref(0);
const scrolltolower = useDebounce(() => { const scrolltolower = useDebounce(() => {
emits('reachBottom'); emits("reachBottom");
}); });
const onScroll = useDebounce((e) => { const onScroll = useDebounce((e) => {
@ -52,9 +58,8 @@ function scrollToBottom() {
} }
defineExpose({ defineExpose({
scrollToBottom scrollToBottom,
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.full-page { .full-page {

View File

@ -65,12 +65,12 @@ const buttons = ref([
icon: "/static/icon/zhuiwen.png", icon: "/static/icon/zhuiwen.png",
loading: false, loading: false,
}, },
{ // {
id: "aiAssistant", // id: "aiAssistant",
text: "开启AI助手", // text: "AI",
icon: "/static/icon/kaiqiAI.png", // icon: "/static/icon/kaiqiAI.png",
loading: false, // loading: false,
}, // },
{ {
id: "supplementRecord", id: "supplementRecord",
text: "补充病历", text: "补充病历",
@ -241,7 +241,7 @@ const handleCaseTypeSelect = async (type) => {
title: error.message || "生成病历失败", title: error.message || "生成病历失败",
icon: "none", icon: "none",
}); });
} },
}); });
} catch (error) { } catch (error) {
console.error("补充病历失败:", error); console.error("补充病历失败:", error);
@ -254,13 +254,22 @@ const handleCaseTypeSelect = async (type) => {
}; };
// //
const requestWithStream = async ({ url, data, onProgress, onComplete, onError }) => { const requestWithStream = async ({
try {
// loading false
const result = await request({
url, url,
data, data,
}, false); onProgress,
onComplete,
onError,
}) => {
try {
// loading false
const result = await request(
{
url,
data,
},
false
);
if (result.success && result.data) { if (result.success && result.data) {
// //
@ -275,7 +284,7 @@ const requestWithStream = async ({ url, data, onProgress, onComplete, onError })
const [key, value] = fields[i]; const [key, value] = fields[i];
// "" // ""
await new Promise(resolve => setTimeout(resolve, delay)); await new Promise((resolve) => setTimeout(resolve, delay));
onProgress({ key, value }); onProgress({ key, value });
progressValue += Math.floor(60 / fields.length); progressValue += Math.floor(60 / fields.length);
progressRef.value?.updateProgress(Math.min(progressValue, 80)); progressRef.value?.updateProgress(Math.min(progressValue, 80));

View File

@ -1,5 +1,13 @@
<template> <template>
<view class="message-page"> <view class="message-page">
<!-- 团队切换头部 -->
<view class="header-container">
<view class="team-selector" @click="showTeamPicker = true">
<text class="team-name">{{ currentTeamName }}</text>
<text class="arrow-icon"></text>
</view>
</view>
<!-- 标签页切换 --> <!-- 标签页切换 -->
<view class="tabs-container"> <view class="tabs-container">
<view <view
@ -20,6 +28,27 @@
</view> </view>
</view> </view>
<!-- 团队选择弹窗 -->
<view v-if="showTeamPicker" class="team-picker-overlay" @click="showTeamPicker = false">
<view class="team-picker-content" @click.stop>
<view class="team-picker-header">
<text class="picker-title">选择团队</text>
</view>
<scroll-view class="team-list" scroll-y>
<view
v-for="team in teamList"
:key="team._id"
class="team-item"
:class="{ active: currentTeamId === team._id }"
@click="selectTeam(team)"
>
<text class="team-item-name">{{ team.name }}</text>
<text v-if="currentTeamId === team._id" class="check-icon"></text>
</view>
</scroll-view>
</view>
</view>
<!-- 消息列表 --> <!-- 消息列表 -->
<scroll-view <scroll-view
class="message-list" class="message-list"
@ -101,12 +130,33 @@ import { ref, watch, computed } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app"; import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js"; import useAccountStore from "@/store/account.js";
import useTeamStore from "@/store/team.js";
import { globalTimChatManager } from "@/utils/tim-chat.js"; import { globalTimChatManager } from "@/utils/tim-chat.js";
import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js"; import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js";
// //
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore()); const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { initIMAfterLogin } = useAccountStore(); const { initIMAfterLogin } = useAccountStore();
//
const teamStore = useTeamStore();
const { teams } = storeToRefs(teamStore);
const { getTeams } = teamStore;
//
const showTeamPicker = ref(false);
const currentTeamId = ref(""); // ""
const teamList = computed(() => {
// ""
const allOption = { _id: "", name: "全部会话消息" };
return [allOption, ...(teams.value || [])];
});
const currentTeamName = computed(() => {
if (!currentTeamId.value) return "全部会话消息";
const team = teams.value.find((t) => t._id === currentTeamId.value);
return team ? team.name : "全部会话消息";
});
// IM // IM
watch(isIMInitialized, (newValue) => { watch(isIMInitialized, (newValue) => {
console.log("IM初始化状态变化:", newValue); console.log("IM初始化状态变化:", newValue);
@ -126,25 +176,39 @@ const activeTab = ref("processing");
// orderStatus // orderStatus
const filteredConversationList = computed(() => { const filteredConversationList = computed(() => {
let filtered = [];
if (activeTab.value === "processing") { if (activeTab.value === "processing") {
// pending() processing() // pending() processing()
const filtered = conversationList.value.filter( filtered = conversationList.value.filter(
(conv) => (conv) =>
conv.orderStatus === "pending" || conv.orderStatus === "processing" conv.orderStatus === "pending" || conv.orderStatus === "processing"
); );
return filtered;
} else { } else {
// cancelled()completed()finished() // cancelled()completed()finished()
const filtered = conversationList.value.filter( filtered = conversationList.value.filter(
(conv) => (conv) =>
conv.orderStatus === "cancelled" || conv.orderStatus === "cancelled" ||
conv.orderStatus === "completed" || conv.orderStatus === "completed" ||
conv.orderStatus === "finished" conv.orderStatus === "finished"
); );
return filtered;
} }
//
if (currentTeamId.value) {
filtered = filtered.filter((conv) => conv.teamId === currentTeamId.value);
}
return filtered;
}); });
//
const selectTeam = (team) => {
currentTeamId.value = team._id;
showTeamPicker.value = false;
console.log("切换到团队:", team.name);
};
// //
const switchTab = (tab) => { const switchTab = (tab) => {
if (activeTab.value === tab) return; if (activeTab.value === tab) return;
@ -311,13 +375,17 @@ const setupConversationListener = () => {
existing.patientSex !== conversationData.patientSex || existing.patientSex !== conversationData.patientSex ||
existing.patientAge !== conversationData.patientAge existing.patientAge !== conversationData.patientAge
) { ) {
Object.assign( //
conversationList.value[existingIndex], conversationList.value[existingIndex] = {
conversationData ...conversationData,
); //
avatar: existing.avatar || conversationData.avatar,
//
unreadCount: Math.max(existing.unreadCount || 0, conversationData.unreadCount || 0)
};
needSort = true; needSort = true;
console.log( console.log(
`已更新会话: ${conversationData.name}, unreadCount: ${conversationData.unreadCount}` `已更新会话: ${conversationData.name}, unreadCount: ${conversationList.value[existingIndex].unreadCount}`
); );
} }
} else { } else {
@ -350,19 +418,23 @@ const setupConversationListener = () => {
if (conversationIndex !== -1) { if (conversationIndex !== -1) {
const conversation = conversationList.value[conversationIndex]; const conversation = conversationList.value[conversationIndex];
// //
const pages = getCurrentPages(); const pages = getCurrentPages();
const currentPage = pages[pages.length - 1]; const currentPage = pages[pages.length - 1];
const isViewingConversation =
currentPage?.route === "pages/message/index";
// // groupID
if (isViewingConversation) { const currentGroupID = currentPage?.options?.groupID;
const isViewingThisConversation =
currentPage?.route === "pages/message/index" &&
currentGroupID === conversation.groupID;
//
if (isViewingThisConversation) {
console.log("用户正在查看该会话,不增加未读数"); console.log("用户正在查看该会话,不增加未读数");
return; return;
} }
// //
conversation.unreadCount = (conversation.unreadCount || 0) + 1; conversation.unreadCount = (conversation.unreadCount || 0) + 1;
console.log( console.log(
"已更新会话未读数:", "已更新会话未读数:",
@ -488,6 +560,9 @@ onLoad(() => {
// //
onShow(async () => { onShow(async () => {
try { try {
//
await getTeams();
// IM // IM
const imReady = await initIM(); const imReady = await initIM();
if (!imReady) { if (!imReady) {
@ -531,6 +606,111 @@ onHide(() => {
flex-direction: column; flex-direction: column;
} }
.header-container {
background-color: #fff;
padding: 20rpx 32rpx;
border-bottom: 1rpx solid #f0f0f0;
flex-shrink: 0;
}
.team-selector {
display: flex;
align-items: center;
cursor: pointer;
&:active {
opacity: 0.7;
}
}
.team-name {
font-size: 36rpx;
font-weight: 500;
color: #333;
flex: 1;
}
.arrow-icon {
font-size: 20rpx;
color: #999;
margin-left: 12rpx;
transform: scale(0.8);
}
.team-picker-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 200rpx;
}
.team-picker-content {
width: 600rpx;
max-height: 800rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.team-picker-header {
padding: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
text-align: center;
}
.picker-title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.team-list {
flex: 1;
overflow-y: auto;
}
.team-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
cursor: pointer;
&:active {
background-color: #f5f5f5;
}
&.active {
background-color: #e6f7ff;
.team-item-name {
color: #1890ff;
}
}
}
.team-item-name {
font-size: 32rpx;
color: #333;
flex: 1;
}
.check-icon {
font-size: 36rpx;
color: #1890ff;
font-weight: bold;
}
.tabs-container { .tabs-container {
display: flex; display: flex;
background-color: #fff; background-color: #fff;

View File

@ -48,18 +48,14 @@ export async function mergeConversationWithGroupDetails(conversationList, option
console.error('获取群组详细信息失败:', response?.message || '未知错误') console.error('获取群组详细信息失败:', response?.message || '未知错误')
return [] return []
} }
const groupDetailsMap = createGroupDetailsMap(response.data?.list || []) const groupDetailsMap = createGroupDetailsMap(response.data?.list || [])
console.log('获取到的群组详细信息数量:', Object.keys(groupDetailsMap).size) console.log('获取到的群组详细信息数量:', Object.keys(groupDetailsMap).size)
// 5. 合并数据并过滤 // 5. 合并数据并过滤
const mergedList = conversationList const mergedList = conversationList
.map(conversation => mergeConversationData(conversation, groupDetailsMap)) .map(conversation => mergeConversationData(conversation, groupDetailsMap))
.filter(item => item !== null) // 过滤掉后端不存在的会话 .filter(item => item !== null);
console.log('合并后的会话列表数量:', mergedList.length) console.log('合并后的会话列表数量:', mergedList.length)
console.log('过滤掉的会话数量:', conversationList.length - mergedList.length) console.log('过滤掉的会话数量:', conversationList.length - mergedList.length)
// 6. 格式化并排序会话列表 // 6. 格式化并排序会话列表
const formattedList = mergedList const formattedList = mergedList
.map((group) => ({ .map((group) => ({
@ -156,8 +152,8 @@ function mergeConversationData(conversation, groupDetailsMap) {
// 更新显示名称(使用后端的患者信息) // 更新显示名称(使用后端的患者信息)
name: formatConversationName(groupDetail), name: formatConversationName(groupDetail),
// 更新头像 // 更新头像(优先使用已有头像,避免闪动)
avatar: groupDetail.patient?.avatar || conversation.avatar || '/static/default-avatar.png' avatar: conversation.avatar || groupDetail.patient?.avatar || '/static/default-avatar.png'
} }
} }