Merge remote-tracking branch 'origin/dev-wdb' into dev-hjf
This commit is contained in:
commit
f0722190c0
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="flex flex-col items-center justify-center rounded-circle overflow-hidden bg-gray border"
|
<view :class="['flex', 'flex-col', 'items-center', 'justify-center', 'overflow-hidden', 'bg-gray', 'border', { 'rounded-circle': classType === 'circle' }]"
|
||||||
:style="`width:${size.lg}rpx; height: ${size.lg}rpx;`" @click="reGenerate()">
|
:style="`width:${size.lg}rpx; height: ${size.lg}rpx;`" @click="reGenerate()">
|
||||||
<view v-for="(item, index) in groups.list" :key="index" class="flex justify-center">
|
<view v-for="(item, index) in groups.list" :key="index" class="flex justify-center">
|
||||||
<image v-for="(url, idx) in item" :key="idx" :src="url" :style="groups.style"></image>
|
<image v-for="(url, idx) in item" :key="idx" :src="url" :style="groups.style"></image>
|
||||||
@ -17,6 +17,11 @@ const props = defineProps({
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
// default: ()=>new Array(9).fill('https://picsum.photos/300/300')
|
// default: ()=>new Array(9).fill('https://picsum.photos/300/300')
|
||||||
|
},
|
||||||
|
classType: {
|
||||||
|
type: String,
|
||||||
|
default: 'circle',
|
||||||
|
validator: (value) => ['circle', 'square'].includes(value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -3,38 +3,70 @@
|
|||||||
<view :style="{ height: statusBarHeight }" class="status-bar"></view>
|
<view :style="{ height: statusBarHeight }" class="status-bar"></view>
|
||||||
<view class="relative z-3 flex items-center px-15 py-12 header-bar">
|
<view class="relative z-3 flex items-center px-15 py-12 header-bar">
|
||||||
<view class="flex-shrink-0 mr-5">
|
<view class="flex-shrink-0 mr-5">
|
||||||
<group-avatar :size="120" :avatarList="currentTeam ? currentTeam.avatarList : []" />
|
<group-avatar
|
||||||
|
:size="120"
|
||||||
|
:avatarList="currentTeam ? currentTeam.avatarList : []"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="w-0 flex-grow ">
|
<view class="w-0 flex-grow">
|
||||||
<view class="flex items-center mb-10">
|
<view class="flex items-center mb-10">
|
||||||
<view class="team-name flex-shrink-0">{{ team.name }}</view>
|
<view class="team-name flex-shrink-0">{{ team.name }}</view>
|
||||||
<view v-if="teams.length > 1" class="flex-shrink-0 flex items-center switch-btn ml-10"
|
<view
|
||||||
@click="showDropDown = true">
|
v-if="teams.length > 1"
|
||||||
<image class="switch-icon" src="/static/home/switch-team.png" mode="aspectFit"></image>
|
class="flex-shrink-0 flex items-center switch-btn ml-10"
|
||||||
|
@click="showDropDown = true"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="switch-icon"
|
||||||
|
src="/static/home/switch-team.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="currentTeam" class="text-base text-white truncate">{{ currentTeam.corpName }}</view>
|
<view v-if="currentTeam" class="text-base text-white truncate">{{
|
||||||
|
currentTeam.corpName
|
||||||
|
}}</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="menuButtonInfo && menuButtonInfo.width > 0" class="flex-shrink-0"
|
<view
|
||||||
:style="{ width: menuButtonInfo.width + 'px', height: menuButtonInfo.height + 'px' }">
|
v-if="menuButtonInfo && menuButtonInfo.width > 0"
|
||||||
|
class="flex-shrink-0"
|
||||||
|
:style="{
|
||||||
|
width: menuButtonInfo.width + 'px',
|
||||||
|
height: menuButtonInfo.height + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="relative">
|
<view class="relative">
|
||||||
<view v-if="showDropDown" class="team-dropdown py-12 bg-white shadow-lg">
|
<view v-if="showDropDown" class="team-dropdown py-12 bg-white shadow-lg">
|
||||||
<scroll-view scroll-y="true" style="max-height: 50vh;">
|
<scroll-view scroll-y="true" style="max-height: 50vh">
|
||||||
<view class="px-15">
|
<view class="px-15">
|
||||||
<view v-for="item in teams" :key="item.teamId" class="mb-10 p-10 flex items-center bg-gray rounded-sm"
|
<view
|
||||||
@click="select(item)">
|
v-for="item in teams"
|
||||||
<view class="flex-shrink-0 mr-5">
|
:key="item.teamId"
|
||||||
|
class="mb-10 p-10 flex items-center bg-gray rounded-sm"
|
||||||
|
@click="select(item)"
|
||||||
|
>
|
||||||
|
<view class="flex-shrink-0 mr-5 rounded-circle">
|
||||||
<group-avatar :size="96" :avatarList="item.avatarList" />
|
<group-avatar :size="96" :avatarList="item.avatarList" />
|
||||||
</view>
|
</view>
|
||||||
<view class="w-0 flex-grow mr-5">
|
<view class="w-0 flex-grow mr-5">
|
||||||
<view class="mb-5 text-lg font-semibold text-dark">{{ item.name }}</view>
|
<view class="mb-5 text-lg font-semibold text-dark">{{
|
||||||
<view class="text-base text-gray leading-normal">{{ item.corpName }}</view>
|
item.name
|
||||||
|
}}</view>
|
||||||
|
<view class="text-base text-gray leading-normal">{{
|
||||||
|
item.corpName
|
||||||
|
}}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex">
|
<view class="flex">
|
||||||
<image class="check-icon"
|
<image
|
||||||
:src="team && team.teamId === item.teamId ? '/static/form/checked.svg' : '/static/form/unchecked.svg'">
|
class="check-icon"
|
||||||
|
:src="
|
||||||
|
team && team.teamId === item.teamId
|
||||||
|
? '/static/form/checked.svg'
|
||||||
|
: '/static/form/unchecked.svg'
|
||||||
|
"
|
||||||
|
>
|
||||||
</image>
|
</image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -48,54 +80,71 @@
|
|||||||
<view class="triangle-wrapper">
|
<view class="triangle-wrapper">
|
||||||
<view class="team-triangle"></view>
|
<view class="team-triangle"></view>
|
||||||
</view>
|
</view>
|
||||||
<image class="laba-icon flex-shrink-0" src="/static/home/speaker-intro.png" mode="aspectFit"></image>
|
<image
|
||||||
<view class="introduce-text flex-grow line-clamp-2">{{ team.teamTroduce }}</view>
|
class="laba-icon flex-shrink-0"
|
||||||
|
src="/static/home/speaker-intro.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
></image>
|
||||||
|
<view class="introduce-text flex-grow line-clamp-2">{{
|
||||||
|
team.teamTroduce
|
||||||
|
}}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="showDropDown" class="mask" @click="showDropDown = false"></view>
|
<view v-if="showDropDown" class="mask" @click="showDropDown = false"></view>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted, watch } from 'vue';
|
import { computed, ref, onMounted, watch } from "vue";
|
||||||
|
|
||||||
import groupAvatar from '@/components/group-avatar.vue';
|
import groupAvatar from "@/components/group-avatar.vue";
|
||||||
|
|
||||||
const statusBarHeight = ref('50px');
|
const statusBarHeight = ref("50px");
|
||||||
const menuButtonInfo = ref(null);
|
const menuButtonInfo = ref(null);
|
||||||
const showDropDown = ref(false)
|
const showDropDown = ref(false);
|
||||||
|
|
||||||
const emits = defineEmits(['changeTeam']);
|
const emits = defineEmits(["changeTeam"]);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
team: {
|
team: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
teams: {
|
teams: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const currentTeam = computed(() => props.teams.find(i => props.team && i.teamId === props.team.teamId))
|
const currentTeam = computed(() =>
|
||||||
|
props.teams.find((i) => props.team && i.teamId === props.team.teamId)
|
||||||
|
);
|
||||||
|
|
||||||
function select(team) {
|
function select(team) {
|
||||||
emits('changeTeam', team)
|
emits("changeTeam", team);
|
||||||
showDropDown.value = false
|
showDropDown.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => props.teams, (teams) => {
|
watch(
|
||||||
if (teams.length && !(currentTeam.value && teams.some(i => i.teamId === currentTeam.value.teamId))) {
|
() => props.teams,
|
||||||
emits('changeTeam', teams[0])
|
(teams) => {
|
||||||
|
if (
|
||||||
|
teams.length &&
|
||||||
|
!(
|
||||||
|
currentTeam.value &&
|
||||||
|
teams.some((i) => i.teamId === currentTeam.value.teamId)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
emits("changeTeam", teams[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const win = uni.getWindowInfo();
|
const win = uni.getWindowInfo();
|
||||||
if (win && win.statusBarHeight > 0) {
|
if (win && win.statusBarHeight > 0) {
|
||||||
statusBarHeight.value = win.statusBarHeight + 'px';
|
statusBarHeight.value = win.statusBarHeight + "px";
|
||||||
}
|
}
|
||||||
menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
|
menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.status-bar {
|
.status-bar {
|
||||||
@ -146,7 +195,11 @@ onMounted(() => {
|
|||||||
width: 690rpx;
|
width: 690rpx;
|
||||||
min-height: 111rpx;
|
min-height: 111rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: linear-gradient(186deg, rgba(255, 255, 255, 0.4) 13.34%, rgba(255, 255, 255, 0.6) 99.17%);
|
background: linear-gradient(
|
||||||
|
186deg,
|
||||||
|
rgba(255, 255, 255, 0.4) 13.34%,
|
||||||
|
rgba(255, 255, 255, 0.6) 99.17%
|
||||||
|
);
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 20rpx 20rpx 20rpx 0;
|
padding: 20rpx 20rpx 20rpx 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -170,7 +223,11 @@ onMounted(() => {
|
|||||||
bottom: -10rpx;
|
bottom: -10rpx;
|
||||||
width: 16rpx;
|
width: 16rpx;
|
||||||
height: 16rpx;
|
height: 16rpx;
|
||||||
background: linear-gradient(186deg, rgba(255, 255, 255, 0.4) 13.34%, rgba(255, 255, 255, 0.6) 99.17%);
|
background: linear-gradient(
|
||||||
|
186deg,
|
||||||
|
rgba(255, 255, 255, 0.4) 13.34%,
|
||||||
|
rgba(255, 255, 255, 0.6) 99.17%
|
||||||
|
);
|
||||||
transform: translateX(-50%) rotate(45deg);
|
transform: translateX(-50%) rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,4 +280,8 @@ onMounted(() => {
|
|||||||
border-bottom-left-radius: 16rpx;
|
border-bottom-left-radius: 16rpx;
|
||||||
border-bottom-right-radius: 16rpx;
|
border-bottom-right-radius: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rounded-circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
107
pages/message/hooks/use-group-avatars.js
Normal file
107
pages/message/hooks/use-group-avatars.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import api from '@/utils/api.js'
|
||||||
|
import useTeamStore from '@/store/team.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群聊头像管理hook - 为消息列表中的每个群聊获取成员头像
|
||||||
|
* 用于在消息列表中显示群聊的group-avatar组件
|
||||||
|
*/
|
||||||
|
export default function useGroupAvatars() {
|
||||||
|
const groupAvatarMap = ref({}) // { groupID: [avatarUrl1, avatarUrl2, ...] }
|
||||||
|
const teamStore = useTeamStore()
|
||||||
|
const patientDefaultAvatar = '/static/default-avatar.png'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个群聊的头像列表
|
||||||
|
* @param {string} groupID 群组ID
|
||||||
|
* @param {string} teamId 团队ID
|
||||||
|
* @param {string} patientId 患者ID
|
||||||
|
* @returns {Promise<Array>} 头像URL数组
|
||||||
|
*/
|
||||||
|
async function getGroupAvatarList(groupID, teamId, patientId) {
|
||||||
|
try {
|
||||||
|
if (!teamId) {
|
||||||
|
console.warn(`群聊 ${groupID} 没有 teamId,无法获取头像`)
|
||||||
|
return [patientDefaultAvatar]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取团队成员的头像和名称
|
||||||
|
const memberMap = await teamStore.getTeamMemberAvatarsAndName(teamId)
|
||||||
|
|
||||||
|
if (!memberMap || Object.keys(memberMap).length === 0) {
|
||||||
|
console.warn(`群聊 ${groupID} 的团队成员为空`)
|
||||||
|
return [patientDefaultAvatar]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取头像列表(过滤掉空头像,使用默认头像替代)
|
||||||
|
const avatarList = Object.values(memberMap)
|
||||||
|
.map(member => {
|
||||||
|
// 如果成员有头像且不为空,使用成员头像;否则使用默认头像
|
||||||
|
return (member.avatar && member.avatar.trim() !== '')
|
||||||
|
? member.avatar
|
||||||
|
: patientDefaultAvatar
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加患者默认头像
|
||||||
|
avatarList.push(patientDefaultAvatar)
|
||||||
|
|
||||||
|
console.log(`群聊 ${groupID} 的头像列表已加载,共 ${avatarList.length} 个头像`)
|
||||||
|
return avatarList
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`获取群聊 ${groupID} 的头像列表失败:`, error)
|
||||||
|
return [patientDefaultAvatar]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取多个群聊的头像列表
|
||||||
|
* @param {Array} conversationList 会话列表
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function loadGroupAvatars(conversationList) {
|
||||||
|
if (!conversationList || conversationList.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 并发加载所有群聊的头像
|
||||||
|
const promises = conversationList.map(async (conversation) => {
|
||||||
|
const avatarList = await getGroupAvatarList(
|
||||||
|
conversation.groupID,
|
||||||
|
conversation.teamId,
|
||||||
|
conversation.patientId
|
||||||
|
)
|
||||||
|
groupAvatarMap.value[conversation.groupID] = avatarList
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(promises)
|
||||||
|
console.log('所有群聊头像加载完成')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('批量加载群聊头像失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定群聊的头像列表
|
||||||
|
* @param {string} groupID 群组ID
|
||||||
|
* @returns {Array} 头像URL数组
|
||||||
|
*/
|
||||||
|
function getAvatarList(groupID) {
|
||||||
|
return groupAvatarMap.value[groupID] || [patientDefaultAvatar]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空缓存
|
||||||
|
*/
|
||||||
|
function clearCache() {
|
||||||
|
groupAvatarMap.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupAvatarMap,
|
||||||
|
getGroupAvatarList,
|
||||||
|
loadGroupAvatars,
|
||||||
|
getAvatarList,
|
||||||
|
clearCache
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,10 +24,10 @@
|
|||||||
@click="handleClickConversation(conversation)"
|
@click="handleClickConversation(conversation)"
|
||||||
>
|
>
|
||||||
<view class="avatar-container">
|
<view class="avatar-container">
|
||||||
<image
|
<GroupAvatar
|
||||||
class="avatar"
|
:avatarList="getAvatarList(conversation.groupID)"
|
||||||
:src="conversation.avatar || '/static/default-avatar.png'"
|
:size="96"
|
||||||
mode="aspectFill"
|
classType="square"
|
||||||
/>
|
/>
|
||||||
<view v-if="conversation.unreadCount > 0" class="unread-badge">
|
<view v-if="conversation.unreadCount > 0" class="unread-badge">
|
||||||
<text class="unread-text">{{
|
<text class="unread-text">{{
|
||||||
@ -81,6 +81,8 @@ import { storeToRefs } from "pinia";
|
|||||||
import useAccountStore from "@/store/account.js";
|
import useAccountStore from "@/store/account.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";
|
||||||
|
import useGroupAvatars from "./hooks/use-group-avatars.js";
|
||||||
|
import GroupAvatar from "@/components/group-avatar.vue";
|
||||||
|
|
||||||
// 获取登录状态
|
// 获取登录状态
|
||||||
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
|
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
|
||||||
@ -93,6 +95,9 @@ const loadingMore = ref(false);
|
|||||||
const hasMore = ref(false);
|
const hasMore = ref(false);
|
||||||
const refreshing = ref(false);
|
const refreshing = ref(false);
|
||||||
|
|
||||||
|
// 群聊头像管理
|
||||||
|
const { loadGroupAvatars, getAvatarList } = useGroupAvatars();
|
||||||
|
|
||||||
// 初始化IM
|
// 初始化IM
|
||||||
const initIM = async () => {
|
const initIM = async () => {
|
||||||
console.log("=== message.vue initIM 开始 ===");
|
console.log("=== message.vue initIM 开始 ===");
|
||||||
@ -184,7 +189,14 @@ const loadConversationList = async () => {
|
|||||||
conversationList.value = await mergeConversationWithGroupDetails(
|
conversationList.value = await mergeConversationWithGroupDetails(
|
||||||
result.groupList
|
result.groupList
|
||||||
);
|
);
|
||||||
console.log("群聊列表加载成功,共", conversationList.value, "个会话");
|
console.log(
|
||||||
|
"群聊列表加载成功,共",
|
||||||
|
conversationList.value.length,
|
||||||
|
"个会话"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 加载所有群聊的头像
|
||||||
|
await loadGroupAvatars(conversationList.value);
|
||||||
} else {
|
} else {
|
||||||
console.error("加载群聊列表失败:", result);
|
console.error("加载群聊列表失败:", result);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@ -291,7 +303,10 @@ const setupConversationListener = () => {
|
|||||||
// 保持原有头像,避免闪动
|
// 保持原有头像,避免闪动
|
||||||
avatar: existing.avatar || conversationData.avatar,
|
avatar: existing.avatar || conversationData.avatar,
|
||||||
// 保留较大的未读数(避免被后端数据覆盖)
|
// 保留较大的未读数(避免被后端数据覆盖)
|
||||||
unreadCount: Math.max(existing.unreadCount || 0, conversationData.unreadCount || 0)
|
unreadCount: Math.max(
|
||||||
|
existing.unreadCount || 0,
|
||||||
|
conversationData.unreadCount || 0
|
||||||
|
),
|
||||||
};
|
};
|
||||||
needSort = true;
|
needSort = true;
|
||||||
console.log(
|
console.log(
|
||||||
@ -331,11 +346,11 @@ const setupConversationListener = () => {
|
|||||||
// 检查当前页面栈,判断用户是否正在查看该会话的聊天详情页
|
// 检查当前页面栈,判断用户是否正在查看该会话的聊天详情页
|
||||||
const pages = getCurrentPages();
|
const pages = getCurrentPages();
|
||||||
const currentPage = pages[pages.length - 1];
|
const currentPage = pages[pages.length - 1];
|
||||||
|
|
||||||
// 获取当前页面的 groupID 参数(如果在聊天详情页)
|
// 获取当前页面的 groupID 参数(如果在聊天详情页)
|
||||||
const currentGroupID = currentPage?.options?.groupID;
|
const currentGroupID = currentPage?.options?.groupID;
|
||||||
const isViewingThisConversation =
|
const isViewingThisConversation =
|
||||||
currentPage?.route === "pages/message/index" &&
|
currentPage?.route === "pages/message/index" &&
|
||||||
currentGroupID === conversation.groupID;
|
currentGroupID === conversation.groupID;
|
||||||
|
|
||||||
// 如果用户正在查看这个具体的会话,不增加未读数
|
// 如果用户正在查看这个具体的会话,不增加未读数
|
||||||
@ -533,6 +548,7 @@ onHide(() => {
|
|||||||
.avatar-container {
|
.avatar-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 24rpx;
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
|
|||||||
0
static/doctor-avatar.png
Normal file
0
static/doctor-avatar.png
Normal file
Loading…
x
Reference in New Issue
Block a user