提交
This commit is contained in:
parent
7f737dfd8a
commit
3ad6140829
@ -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 {
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user