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

View File

@ -20,8 +20,8 @@
/>
<!-- 进度显示弹窗 -->
<medical-case-progress
ref="progressRef"
<medical-case-progress
ref="progressRef"
@regenerate="handleRegenerateFromProgress"
@next="handleNextFromProgress"
/>
@ -65,12 +65,12 @@ const buttons = ref([
icon: "/static/icon/zhuiwen.png",
loading: false,
},
{
id: "aiAssistant",
text: "开启AI助手",
icon: "/static/icon/kaiqiAI.png",
loading: false,
},
// {
// id: "aiAssistant",
// text: "AI",
// icon: "/static/icon/kaiqiAI.png",
// loading: false,
// },
{
id: "supplementRecord",
text: "补充病历",
@ -241,7 +241,7 @@ const handleCaseTypeSelect = async (type) => {
title: error.message || "生成病历失败",
icon: "none",
});
}
},
});
} catch (error) {
console.error("补充病历失败:", error);
@ -254,33 +254,42 @@ const handleCaseTypeSelect = async (type) => {
};
//
const requestWithStream = async ({ url, data, onProgress, onComplete, onError }) => {
const requestWithStream = async ({
url,
data,
onProgress,
onComplete,
onError,
}) => {
try {
// loading false
const result = await request({
url,
data,
}, false);
const result = await request(
{
url,
data,
},
false
);
if (result.success && result.data) {
//
const extractedData = result.data.extractedData || {};
//
let progressValue = 20;
const fields = Object.entries(extractedData);
const delay = 300; //
for (let i = 0; i < fields.length; i++) {
const [key, value] = fields[i];
// ""
await new Promise(resolve => setTimeout(resolve, delay));
await new Promise((resolve) => setTimeout(resolve, delay));
onProgress({ key, value });
progressValue += Math.floor(60 / fields.length);
progressRef.value?.updateProgress(Math.min(progressValue, 80));
}
//
onComplete(result.data);
} else {
@ -294,7 +303,7 @@ const requestWithStream = async ({ url, data, onProgress, onComplete, onError })
//
const handleStreamData = (data, caseType) => {
const { key, value } = data;
//
progressRef.value?.addDetectedInfo(key, value);
};

View File

@ -1,5 +1,13 @@
<template>
<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
@ -20,6 +28,27 @@
</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
class="message-list"
@ -101,12 +130,33 @@ import { ref, watch, computed } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js";
import useTeamStore from "@/store/team.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js";
//
const { account, openid, isIMInitialized } = storeToRefs(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
watch(isIMInitialized, (newValue) => {
console.log("IM初始化状态变化:", newValue);
@ -126,25 +176,39 @@ const activeTab = ref("processing");
// orderStatus
const filteredConversationList = computed(() => {
let filtered = [];
if (activeTab.value === "processing") {
// pending() processing()
const filtered = conversationList.value.filter(
filtered = conversationList.value.filter(
(conv) =>
conv.orderStatus === "pending" || conv.orderStatus === "processing"
);
return filtered;
} else {
// cancelled()completed()finished()
const filtered = conversationList.value.filter(
filtered = conversationList.value.filter(
(conv) =>
conv.orderStatus === "cancelled" ||
conv.orderStatus === "completed" ||
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) => {
if (activeTab.value === tab) return;
@ -311,13 +375,17 @@ const setupConversationListener = () => {
existing.patientSex !== conversationData.patientSex ||
existing.patientAge !== conversationData.patientAge
) {
Object.assign(
conversationList.value[existingIndex],
conversationData
);
//
conversationList.value[existingIndex] = {
...conversationData,
//
avatar: existing.avatar || conversationData.avatar,
//
unreadCount: Math.max(existing.unreadCount || 0, conversationData.unreadCount || 0)
};
needSort = true;
console.log(
`已更新会话: ${conversationData.name}, unreadCount: ${conversationData.unreadCount}`
`已更新会话: ${conversationData.name}, unreadCount: ${conversationList.value[existingIndex].unreadCount}`
);
}
} else {
@ -350,19 +418,23 @@ const setupConversationListener = () => {
if (conversationIndex !== -1) {
const conversation = conversationList.value[conversationIndex];
//
//
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const isViewingConversation =
currentPage?.route === "pages/message/index";
// groupID
const currentGroupID = currentPage?.options?.groupID;
const isViewingThisConversation =
currentPage?.route === "pages/message/index" &&
currentGroupID === conversation.groupID;
//
if (isViewingConversation) {
//
if (isViewingThisConversation) {
console.log("用户正在查看该会话,不增加未读数");
return;
}
//
//
conversation.unreadCount = (conversation.unreadCount || 0) + 1;
console.log(
"已更新会话未读数:",
@ -488,6 +560,9 @@ onLoad(() => {
//
onShow(async () => {
try {
//
await getTeams();
// IM
const imReady = await initIM();
if (!imReady) {
@ -531,6 +606,111 @@ onHide(() => {
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 {
display: flex;
background-color: #fff;

View File

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