Merge remote-tracking branch 'origin/dev-hjf'

This commit is contained in:
huxuejian 2026-02-04 13:29:16 +08:00
commit 7c43e5bec4
34 changed files with 1471 additions and 248 deletions

View File

@ -1,5 +1,5 @@
<template>
<view class="flex flex-col items-center justify-center rounded 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()">
<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>
@ -17,6 +17,11 @@ const props = defineProps({
type: Array,
default: () => []
// default: ()=>new Array(9).fill('https://picsum.photos/300/300')
},
classType: {
type: String,
default: 'circle',
validator: (value) => ['circle', 'square'].includes(value)
}
})
@ -65,3 +70,8 @@ function getList(size = 9) {
}
</script>
<style scoped>
.rounded-circle {
border-radius: 50%;
}
</style>

View File

@ -1,28 +1,28 @@
{
"name" : "ykt-wxapp",
"appid" : "__UNI__06F5B0E",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"name": "ykt-team-wxapp",
"appid": "__UNI__3EBDA15",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
"app-plus": {
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
/* */
"modules" : {},
"modules": {},
/* */
"distribute" : {
"distribute": {
/* android */
"android" : {
"permissions" : [
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
@ -41,32 +41,32 @@
]
},
/* ios */
"ios" : {},
"ios": {},
/* SDK */
"sdkConfigs" : {}
"sdkConfigs": {}
}
},
/* */
"quickapp" : {},
"quickapp": {},
/* */
"mp-weixin" : {
"appid" : "wx93af55767423938e",
"setting" : {
"urlCheck" : false
"mp-weixin": {
"appid": "wx93af55767423938e",
"setting": {
"urlCheck": false
},
"usingComponents" : true
"usingComponents": true
},
"mp-alipay" : {
"usingComponents" : true
"mp-alipay": {
"usingComponents": true
},
"mp-baidu" : {
"usingComponents" : true
"mp-baidu": {
"usingComponents": true
},
"mp-toutiao" : {
"usingComponents" : true
"mp-toutiao": {
"usingComponents": true
},
"uniStatistics" : {
"enable" : false
"uniStatistics": {
"enable": false
},
"vueVersion" : "3"
}
"vueVersion": "3"
}

View File

@ -1,5 +1,5 @@
{
"name": "ykt-wxapp",
"name": "医客通患者端",
"version": "1.0.0",
"description": "",
"main": "main.js",

View File

@ -1,21 +1,22 @@
<template>
<view v-if="articles.length" class="mt-12 px-15 flex items-center justify-between">
<view class="text-lg font-semibold text-dark">健康宣教</view>
<view class="flex items-center" @click="toList()">
<view class="mr-5 text-base text-gray">更多</view>
<uni-icons type="right" color="#999"></uni-icons>
<view v-if="articles.length" class="article-container">
<view class="flex items-center justify-between">
<view class="module-title">健康宣教</view>
<view class="flex items-center" @click="toList()">
<view class="mr-5 text-base text-gray">更多</view>
<image class="arrow-icon" src="/static/home/arrow-right-gray.png" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="px-15 mt-10">
<view class="shadow-lg bg-white rounded">
<view v-for="article in articles" :key="article._id"
class="flex px-15 py-12 border-b border-solid border-gray-200">
<image class="flex-shrink-0 mr-10 cover" :src="article.cover || '/static/book.svg'" />
<view class="mt-10">
<view v-for="(article, index) in articles" :key="article._id"
class="article-card flex"
:class="{'mb-15': index < articles.length - 1}">
<image class="flex-shrink-0 cover" :src="article.cover || '/static/home/health-education.png'" mode="aspectFill" />
<view class="w-0 flex-grow">
<view class="text-base leading-normal font-semibold truncate mb-5">
<view class="article-title truncate mb-10">
{{ article.title }}
</view>
<view v-if="article.summary" class="text-base text-gray line-clamp-2">
<view v-if="article.summary" class="article-summary line-clamp-2">
{{ article.summary }}
</view>
</view>
@ -63,16 +64,74 @@ watch(articleIds, n => {
}, { immediate: true })
</script>
<style scoped>
.article-container {
margin: 0 30rpx;
margin-top: 24rpx;
padding-bottom: 40rpx;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
}
.module-title {
color: #000000;
font-size: 36rpx;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.article-card {
background: white;
border-radius: 16rpx;
box-shadow: 0 8rpx 10rpx 0 rgba(60, 169, 145, 0.06);
transition: all 0.3s;
min-height: 188rpx;
padding: 20rpx;
align-items: flex-start;
}
.article-card:active {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.cover {
width: 128rpx;
height: 128rpx;
width: 272rpx;
height: 151rpx;
border-radius: 12rpx;
margin-right: 20rpx;
object-fit: cover;
}
.min-w-120 {
min-width: 240rpx;
.mb-15 {
margin-bottom: 20rpx;
}
.w-80 {
width: 160rpx;
.article-title {
color: #333333;
font-size: 32rpx;
font-weight: 500;
line-height: normal;
}
.article-summary {
max-width: 402rpx;
color: #666666;
text-align: justify;
font-size: 28rpx;
font-weight: 400;
line-height: normal;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
line-height: 1.5;
}
</style>

1
pages/home/avatar.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -9,8 +9,8 @@
:key="item.id"
@click="handleItemClick(item)"
>
<view class="item-icon" :style="{ backgroundColor: item.bgColor }">
<image :src="item.icon" class="icon-img" mode="aspectFit" />
<view class="item-icon">
<image :src="item.icon" class="icon-img" mode="aspectFill" />
</view>
<view class="item-label">{{ item.label }}</view>
</view>
@ -58,29 +58,25 @@ const consultItems = ref([
{
id: "chat",
label: "聊天咨询",
icon: "/static/homepage/chat-icon.png",
bgColor: "#5DADE2",
icon: "/static/home/chat-consult.png",
needSelectConsultant: true,
},
{
id: "education",
label: "我的宣教",
icon: "/static/homepage/education-icon.png",
bgColor: "#F4D03F",
icon: "/static/home/my-education.png",
path: "/pages/article/article-list",
},
{
id: "survey",
label: "我的问卷",
icon: "/static/homepage/survey-icon.png",
bgColor: "#58D68D",
icon: "/static/home/my-questionnaire.png",
path: "/pages/survey/survey-list",
},
{
id: "rating",
label: "服务评价",
icon: "/static/homepage/rating-icon.png",
bgColor: "#5DADE2",
icon: "/static/home/service-rating.png",
path: "",
},
]);
@ -155,41 +151,48 @@ function handleAddNewArchive() {
<style lang="scss" scoped>
.consult-container {
padding: 32rpx;
background: #fff;
border-radius: 16rpx;
margin: 24rpx;
margin: 0 30rpx;
margin-top: 24rpx;
}
.consult-title {
font-size: 32rpx;
color: #000000;
font-size: 36rpx;
font-style: normal;
font-weight: 600;
color: #333;
margin-bottom: 32rpx;
line-height: normal;
margin-bottom: 24rpx;
}
.consult-grid {
height: 208rpx;
box-sizing: border-box;
background: #fff;
border-radius: 16rpx;
padding: 24rpx 30rpx;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
align-items: center;
gap: 32rpx;
box-shadow: 0 8rpx 10rpx 0 rgba(60, 169, 145, 0.06);
}
.consult-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
gap: 12rpx;
cursor: pointer;
}
.item-icon {
width: 96rpx;
height: 96rpx;
border-radius: 20rpx;
width: 80rpx;
height: 80;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
overflow: hidden;
}
.consult-item:active .item-icon {
@ -197,14 +200,14 @@ function handleAddNewArchive() {
}
.icon-img {
width: 56rpx;
height: 56rpx;
width: 80rpx;
height: 80rpx;
}
.item-label {
font-size: 28rpx;
color: #333;
color: #666d76;
text-align: center;
font-weight: 600;
font-weight: 400;
}
</style>

View File

@ -1,11 +1,12 @@
<template>
<view class="px-15 py-12 mb-10 bg-white shadow-lg">
<view class="archive-container">
<view class="mb-10 flex items-center justify-between">
<view class="flex-shrink-0 text-lg font-semibold truncate">
<view class="module-title flex-shrink-0 truncate">
成员档案
</view>
<view class="px-10 leading-normal border-dashed-auto text-base text-primary rounded-sm" @click="toManagePage()">
档案管理
<view class="flex items-center px-10 leading-normal rounded-sm" @click="toManagePage()">
<image class="manage-icon mr-5" src="/static/home/archive-manage.png" mode="aspectFit"></image>
<view style="font-size: 28rpx; color: #065BD6;">档案管理</view>
</view>
</view>
<view v-if="customers.length === 0" class="flex items-center justify-center h-80 border-dashed text-dark rounded">
@ -15,21 +16,33 @@
<scroll-view scroll-x="true">
<view class="flex flex-nowrap pb-5 ">
<view v-for="i in customers" :key="i._id"
class="flex-shrink-0 min-w-100 mr-10 p-10 rounded relative border-primary"
:class="current && i._id === current._id ? 'bg-primary current-customer' : ''" @click="toggle(i)">
<view class="flex justify-between mb-5">
<view class="text-base leading-normal font-semibold whitespace-nowrap"
:class="current && i._id === current._id ? 'text-white' : 'text-dark'">
class="customer-card flex-shrink-0 mr-15 relative"
:class="current && i._id === current._id ? 'current-customer' : ''" @click="toggle(i)">
<!-- 关系标签 -->
<view v-if="i.relationship" class="relationship-tag"
:class="i.relationship === '本人' ? 'tag-blue' : 'tag-green'">
{{ i.relationship }}
</view>
<view class="flex flex-col items-center">
<view class="customer-name text-lg leading-normal font-semibold whitespace-nowrap mb-8"
:class="current && i._id === current._id ? 'text-primary' : 'text-dark'">
{{ i.name }}
</view>
<view v-if="i.relationship" class="flex-shrink-0 px-5 rounded-sm border-auto text-sm leading-normal"
:class="current && i._id === current._id ? 'text-white' : 'text-gray'">
{{ i.relationship }}
<view class="flex items-center mb-5">
<image
v-if="i.sex"
class="sex-icon mr-5"
:src="i.sex === '男' ? '/static/home/male.svg' : '/static/home/female.svg'"
/>
<view class="customer-age text-base leading-normal text-gray">
{{ i.age > 0 ? i.age + '岁' : '' }}
</view>
</view>
</view>
<view class="text-base leading-normal h-normal"
:class="current && i._id === current._id ? 'text-white' : 'text-gray'">
{{ i.sex }} {{ i.age > 0 ? i.age + '岁' : '' }}
<!-- 选中状态底部条和三角 -->
<view v-if="current && i._id === current._id" class="active-indicator">
<view class="active-bar"></view>
<view class="active-triangle"></view>
</view>
</view>
</view>
@ -42,24 +55,26 @@
授权
</view>
</view>
<view v-if="current" class="flex mt-10">
<view class="flex-grow p-10 rounded bg-gray mr-10" @click="fillBaseInfo()">
<view class="flex items-center justify-between mb-5">
<view class="text-lg">个人基本信息</view>
<uni-icons color="#999" type="arrowright"></uni-icons>
<view v-if="current" class="flex mt-15">
<view class="info-card-new flex-grow mr-10" @click="fillBaseInfo()">
<view class="info-bg info-bg-base"></view>
<view class="info-content">
<view class="flex items-center justify-between mb-8">
<view class="info-title">个人基本信息</view>
<image class="arrow-icon-small" src="/static/home/arrow-right-blue.png" mode="aspectFit"></image>
</view>
<view class="info-subtitle">完善个人信息</view>
</view>
<!-- <view v-if="formError.base" class="text-sm text-danger">
请完善您的个人信息 v-else
</view> -->
<view class="text-base text-gray">基础信息填写</view>
</view>
<!-- v-if="healthTempList && healthTempList.length" -->
<view class="flex-grow p-10 rounded bg-gray" @click="toHealthList()">
<view class="flex items-center justify-between mb-5">
<view class="text-lg">个人健康信息</view>
<uni-icons color="#999" type="arrowright"></uni-icons>
<view class="info-card-new flex-grow" @click="toHealthList()">
<view class="info-bg info-bg-health"></view>
<view class="info-content">
<view class="flex items-center justify-between mb-8">
<view class="info-title">健康信息</view>
<image class="arrow-icon-small" src="/static/home/arrow-right-blue.png" mode="aspectFit"></image>
</view>
<view class="info-subtitle">上传健康档案</view>
</view>
<view class="text-base text-gray">健康信息列表</view>
</view>
</view>
</view>
@ -164,6 +179,199 @@ watch(() => props.corpId, n => {
</script>
<style scoped>
.archive-container {
padding: 24rpx 30rpx;
margin: 0 30rpx;
background: linear-gradient(181deg, #C2DCFF 1.01%, #FFFFFF 43.31%);
border-radius: 16rpx;
box-shadow:
inset 0 2rpx 0 0 rgba(255, 255, 255, 0.82),
0 8rpx 10rpx 0 rgba(60, 169, 145, 0.06);
}
.manage-icon {
width: 32rpx;
height: 32rpx;
}
.module-title {
color: #000000;
font-size: 36rpx;
font-weight: 600;
line-height: normal;
}
.customer-card {
width: 160rpx;
height: 160rpx;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, #FFFFFF 0%, #E6EFFB 100%);
border-radius: 12rpx;
border: 2rpx solid #AECAF2;
transition: all 0.2s;
position: relative;
}
.current-customer {
background: #FFFFFF;
border: 2rpx solid #065BD6;
}
.customer-name {
margin-top: 12rpx;
}
.relationship-tag {
position: absolute;
top: -2rpx;
right: -10rpx;
padding: 4rpx 16rpx;
font-size: 20rpx;
line-height: normal;
color: #fff;
border-top-left-radius: 16rpx;
border-bottom-left-radius: 16rpx;
z-index: 10;
}
.relationship-tag::after {
content: '';
position: absolute;
right: 0;
bottom: -8rpx;
width: 0;
height: 0;
border-right: 10rpx solid transparent;
}
.tag-blue {
background: #065BD6;
}
.tag-blue::after {
border-top: 8rpx solid #003F96;
}
.tag-green {
background: #1DBF98;
}
.tag-green::after {
border-top: 8rpx solid #0F8C6D;
}
.active-indicator {
position: absolute;
bottom: -2rpx;
left: -2rpx;
right: -2rpx;
height: 10rpx;
z-index: 5;
}
.active-bar {
width: 100%;
height: 100%;
background: #065bd6;
border-bottom-left-radius: 12rpx;
border-bottom-right-radius: 12rpx;
box-sizing: border-box;
}
.active-triangle {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%) rotate(45deg);
width: 12rpx;
height: 12rpx;
background: #065BD6;
margin-top: -8rpx;
}
.customer-age {
color: #999999;
}
.mr-15 {
margin-right: 20rpx;
}
.mt-15 {
margin-top: 24rpx;
}
.mb-8 {
margin-bottom: 12rpx;
}
.sex-icon {
width: 32rpx;
height: 32rpx;
}
.info-card-new {
position: relative;
padding: 24rpx;
border-radius: 16rpx;
background: linear-gradient(115deg, #F4F9FF 14.74%, #DBEAFF 66.11%);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
width: 310rpx;
height: 94rpx;
flex: 1;
}
.info-bg {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-repeat: no-repeat;
background-position: right bottom;
background-size: cover;
opacity: 0.95;
pointer-events: none;
}
.info-content {
position: relative;
z-index: 1;
}
.info-bg-base {
background-image: url('/static/home/basic-info-bg.svg');
}
.info-bg-health {
background-image: url('/static/home/health-info-bg.svg');
}
.info-title {
color: #213E80;
font-size: 32rpx;
font-weight: 500;
}
.info-subtitle {
color: #78808F;
font-size: 24rpx;
}
.arrow-icon-small {
width: 24rpx;
height: 24rpx;
flex-shrink: 0;
}
.font-medium {
font-weight: 500;
}
.h-80 {
height: 160rpx;
}
@ -176,15 +384,7 @@ watch(() => props.corpId, n => {
min-width: 200rpx;
}
.current-customer::after {
bottom: -1px;
left: calc(50% - 10px);
border: 10px solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: white;
.rounded-lg {
border-radius: 16rpx;
}
</style>

View File

@ -1,14 +1,21 @@
<template>
<page-loading v-if="loading" />
<full-page v-else-if="teams.length && team">
<full-page v-else-if="teams.length && team" class="home-container" :pageStyle="pageStyle">
<template #header>
<team-head :team="team" :teams="teams" @changeTeam="changeTeam" />
<view class="pb-10"></view>
</template>
<customer-archive :corpId="corpId" :team="team" @update:customers="handleCustomersUpdate" />
<consult :corpId="corpId" :teamId="team.teamId" :customers="customers" />
<team-mate :team="team" />
<article-list :team="team" />
<view class="home-section home-section--first">
<customer-archive :corpId="corpId" :team="team" @update:customers="handleCustomersUpdate" />
</view>
<view class="home-section">
<consult :corpId="corpId" :teamId="team.teamId" :customers="customers" />
</view>
<view class="home-section">
<team-mate :team="team" />
</view>
<view class="home-section">
<article-list :team="team" />
</view>
</full-page>
<yc-home v-else />
</template>
@ -39,6 +46,10 @@ const customers = ref([]);
const corpId = computed(() => team.value?.corpId);
// UI 281px + 375 稿281px 562rpx
const pageStyle =
'background: linear-gradient(180deg, #065BD6 15.05%, #F6FAFA 95.37%) 0 0/100% 562rpx no-repeat, #F6FAFA;';
function handleCustomersUpdate(newCustomers) {
customers.value = newCustomers;
}
@ -80,3 +91,16 @@ useShow(() => {
})
</script>
<style scoped>
.home-container {
min-height: 100vh;
}
.home-section {
margin-top: 24rpx;
}
.home-section--first {
margin-top: 0;
}
</style>

View File

@ -1,41 +1,72 @@
<template>
<view>
<view :style="{ height: statusBarHeight }" class="bg-primary"></view>
<view class="relative z-3 flex items-center px-15 py-12 bg-primary">
<view :style="{ height: statusBarHeight }" class="status-bar"></view>
<view class="relative z-3 flex items-center px-15 py-12 header-bar">
<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 class="w-0 flex-grow ">
<view class="flex mb-10">
<view class="w-0 flex-grow truncate text-lg font-semibold text-white">{{ team.name }}</view>
<view v-if="teams.length > 1" class="flex-shinrk-0 flex items-center px-10 bg-white rounded-sm"
@click="showDropDown = true">
<view class="text-base">切换</view>
<uni-icons type="down" size="12"></uni-icons>
<view class="w-0 flex-grow">
<view class="flex items-center mb-10">
<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"
@click="showDropDown = true"
>
<image
class="switch-icon"
src="/static/home/switch-team.png"
mode="aspectFit"
></image>
</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 v-if="menuButtonInfo && menuButtonInfo.width > 0" class="flex-shrink-0"
:style="{ width: menuButtonInfo.width + 'px', height: menuButtonInfo.height + 'px' }">
<view
v-if="menuButtonInfo && menuButtonInfo.width > 0"
class="flex-shrink-0"
:style="{
width: menuButtonInfo.width + 'px',
height: menuButtonInfo.height + 'px',
}"
>
</view>
</view>
<view class="relative">
<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 v-for="item in teams" :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">
<view
v-for="item in teams"
: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" />
</view>
<view class="w-0 flex-grow mr-5">
<view class="mb-5 text-lg font-semibold text-dark">{{ item.name }}</view>
<view class="text-base text-gray leading-normal">{{ item.corpName }}</view>
<view class="mb-5 text-lg font-semibold text-dark">{{
item.name
}}</view>
<view class="text-base text-gray leading-normal">{{
item.corpName
}}</view>
</view>
<view class="flex">
<image class="check-icon"
:src="team && team.teamId === item.teamId ? '/static/form/checked.svg' : '/static/form/unchecked.svg'">
<image
class="check-icon"
:src="
team && team.teamId === item.teamId
? '/static/form/checked.svg'
: '/static/form/unchecked.svg'
"
>
</image>
</view>
</view>
@ -43,59 +74,182 @@
</scroll-view>
</view>
</view>
<view v-if="team.teamTroduce" class="px-15 py-12 flex bg-white border-b shadow-lg">
<image class="laba-icon flex-shrink-0 mr-5" src="/static/laba.svg"></image>
<view class="w-0 flex-grow text-sm text-gray leading-normal line-clamp-2">{{ team.teamTroduce }} </view>
<view v-if="team.teamTroduce" class="team-introduce-wrapper">
<view class="team-introduce flex items-center">
<!-- 顶部小三角形 -->
<view class="triangle-wrapper">
<view class="team-triangle"></view>
</view>
<image
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 v-if="showDropDown" class="mask" @click="showDropDown = false"></view>
</template>
<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 showDropDown = ref(false)
const showDropDown = ref(false);
const emits = defineEmits(['changeTeam']);
const emits = defineEmits(["changeTeam"]);
const props = defineProps({
team: {
type: Object,
default: () => ({})
default: () => ({}),
},
teams: {
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) {
emits('changeTeam', team)
showDropDown.value = false
emits("changeTeam", team);
showDropDown.value = false;
}
watch(() => props.teams, (teams) => {
if (teams.length && !(currentTeam.value && teams.some(i => i.teamId === currentTeam.value.teamId))) {
emits('changeTeam', teams[0])
watch(
() => props.teams,
(teams) => {
if (
teams.length &&
!(
currentTeam.value &&
teams.some((i) => i.teamId === currentTeam.value.teamId)
)
) {
emits("changeTeam", teams[0]);
}
}
})
);
onMounted(() => {
const win = uni.getWindowInfo();
if (win && win.statusBarHeight > 0) {
statusBarHeight.value = win.statusBarHeight + 'px';
statusBarHeight.value = win.statusBarHeight + "px";
}
menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
})
});
</script>
<style scoped>
.status-bar {
background: transparent;
}
.header-bar {
background: transparent;
}
.laba-icon {
width: 36rpx;
height: 36rpx;
width: 48rpx;
height: 48rpx;
margin-left: 12rpx;
margin-right: 12rpx;
}
.team-name {
color: #ffffff;
font-size: 36rpx;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.switch-icon {
width: 40rpx;
height: 40rpx;
}
.switch-btn {
padding: 4rpx;
}
.ml-10 {
margin-left: 12rpx;
}
.team-introduce-wrapper {
padding: 0 30rpx;
margin-top: 20rpx;
margin-bottom: 30rpx;
position: relative;
z-index: 2;
}
.team-introduce {
width: 690rpx;
min-height: 111rpx;
box-sizing: border-box;
background: linear-gradient(
186deg,
rgba(255, 255, 255, 0.4) 13.34%,
rgba(255, 255, 255, 0.6) 99.17%
);
border-radius: 16rpx;
padding: 20rpx 20rpx 20rpx 0;
position: relative;
overflow: visible;
}
.triangle-wrapper {
position: absolute;
top: -12rpx;
left: 60rpx;
width: 24rpx;
height: 12rpx;
overflow: hidden;
z-index: 10;
transform: translateX(-50%);
}
.team-triangle {
position: absolute;
left: 50%;
bottom: -10rpx;
width: 16rpx;
height: 16rpx;
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);
}
.introduce-text {
max-width: 594rpx;
color: #000000;
font-size: 24rpx;
font-style: normal;
font-weight: 400;
line-height: normal;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
.leading-relaxed {
line-height: 1.6;
}
.check-icon {
@ -105,7 +259,7 @@ onMounted(() => {
.mask {
position: fixed;
z-index: 2;
z-index: 99;
left: 0;
top: 0;
right: 0;
@ -114,7 +268,7 @@ onMounted(() => {
}
.z-3 {
z-index: 3;
z-index: 101;
}
.team-dropdown {
@ -122,8 +276,12 @@ onMounted(() => {
left: 0;
right: 0;
top: 0;
z-index: 3;
z-index: 100;
border-bottom-left-radius: 16rpx;
border-bottom-right-radius: 16rpx;
}
.rounded-circle {
border-radius: 50%;
}
</style>

View File

@ -1,33 +1,37 @@
<template>
<view class="mt-12 px-15 flex items-center justify-between">
<view class="text-lg font-semibold text-dark">团队成员</view>
<view class="flex items-center" @click="toTeamDetail()">
<view class="mr-5 text-base text-gray">团队详情</view>
<uni-icons type="right" color="#999"></uni-icons>
<view class="team-mate-container">
<view class="flex items-center justify-between">
<view class="module-title">团队成员</view>
<view class="flex items-center" @click="toTeamDetail()">
<view class="mr-5 text-base text-gray">团队详情</view>
<image class="arrow-icon" src="/static/home/arrow-right-gray.png" mode="aspectFit"></image>
</view>
</view>
</view>
<view class="px-15 mt-10">
<scroll-view scroll-x="true">
<view class="flex flex-nowrap pb-5 border-b">
<view v-for="i in teamates" :key="i.userid"
class="flex flex-shrink-0 min-w-120 p-10 mr-10 rounded-sm border-auto text-primary bg-white"
@click="toHomePage(i)">
<image class="flex-shrink-0 avatar mr-5" :src="i.avatar || '/static/default-avatar.png'" />
<view class="flex-grow flex flex-col">
<view class="leading-normal h-24 text-lg font-semibold text-dark whitespace-nowrap">
{{ i.anotherName }}
</view>
<view class="max-w-100 h-21 leading-normal text-base text-gray truncate">
{{ memberJob[i.userid] }}
</view>
<view v-if="i.canAddFriend" class="w-80 text-base leading-none border text-center text-dark rounded-full"
@click.stop="toQrcode(i)">
添加好友
<view class="mt-10">
<scroll-view scroll-x="true">
<view class="flex flex-nowrap pb-5">
<view v-for="i in teamates" :key="i.userid"
class="member-card flex flex-shrink-0 min-w-120 mr-15 p-15"
@click="toHomePage(i)">
<image class="flex-shrink-0 avatar mr-10" :src="i.avatar || '/static/default-avatar.png'" />
<view class="flex-grow flex flex-col justify-between">
<view>
<view class="member-name leading-normal h-24 text-base font-semibold text-dark whitespace-nowrap">
{{ i.anotherName }}
</view>
<view class="member-job max-w-100 h-21 leading-normal text-sm text-gray truncate">
{{ memberJob[i.userid] }}
</view>
</view>
<view v-if="i.canAddFriend" class="add-friend-btn text-sm leading-none text-center text-primary"
@click.stop="toQrcode(i)">
添加好友
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</scroll-view>
</view>
</view>
</template>
<script setup>
@ -67,28 +71,85 @@ watch(teamates, val => {
</script>
<style scoped>
.team-mate-container {
margin: 0 30rpx;
margin-top: 24rpx;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
}
.member-card {
background: white;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s;
}
.member-card:active {
transform: translateY(-2rpx);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}
.avatar {
width: 120rpx;
height: 128rpx;
border-radius: 12rpx;
object-fit: cover;
}
.add-friend-btn {
width: 128rpx;
height: 48rpx;
line-height: 48rpx;
padding: 0;
background: rgba(6, 91, 214, 0.10);
color: #065BD6;
border-radius: 8rpx;
margin-top: 10rpx;
font-weight: 400;
}
.module-title {
color: #000000;
font-size: 36rpx;
font-style: normal;
font-weight: 600;
line-height: normal;
}
.member-job {
color: #999999;
font-size: 24rpx;
font-weight: 400;
}
.member-name {
color: #333333;
font-size: 32rpx;
font-weight: 600;
line-height: normal;
}
.h-24 {
height: 48rpx;
min-height: 48rpx;
}
.h-21 {
height: 42rpx;
min-height: 42rpx;
}
.min-w-120 {
min-width: 240rpx;
min-width: 260rpx;
}
.max-w-100 {
max-width: 200rpx;
}
.w-80 {
width: 160rpx;
.mr-15 {
margin-right: 24rpx;
}
</style>

View File

@ -16,6 +16,101 @@ $primary-color: #0877F1;
background-color: #f5f5f5;
}
/* 患者信息栏样式 */
.patient-info-bar {
position: relative;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
padding: 20rpx 32rpx;
z-index: 10;
flex-shrink: 0;
}
.patient-info-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.patient-basic-info {
display: flex;
align-items: center;
gap: 16rpx;
flex: 1;
min-width: 0;
}
.patient-name {
font-size: 32rpx;
color: #333;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
.patient-detail {
font-size: 28rpx;
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
.status-badge {
display: flex;
align-items: center;
gap: 4rpx;
padding: 8rpx 16rpx;
border-radius: 20rpx;
flex-shrink: 0;
}
.badge-text {
font-size: 24rpx;
font-weight: 500;
}
.badge-pending {
background: #fff3cd;
.badge-text {
color: #856404;
}
}
.badge-processing {
background: #d1ecf1;
.badge-text {
color: #0c5460;
}
}
.badge-finished {
background: #d4edda;
.badge-text {
color: #155724;
}
}
.badge-cancelled {
background: #f8d7da;
.badge-text {
color: #721c24;
}
}
.badge-rejected {
background: #f8d7da;
.badge-text {
color: #721c24;
}
}
.chat-content {
flex: 1;
box-sizing: border-box;
@ -348,7 +443,7 @@ $primary-color: #0877F1;
.text-input,
.voice-input-btn {
flex: 1;
padding: 16rpx 46rpx;
padding: 26rpx 46rpx;
background-color: #f3f5fa;
border-radius: 20rpx;
margin: 0 16rpx;
@ -931,7 +1026,7 @@ $primary-color: #0877F1;
.text-input::-moz-placeholder,
.text-input:-ms-input-placeholder,
.text-input::placeholder {
line-height: normal;
line-height: 1.5;
}
.voice-input-btn::-webkit-input-placeholder,

View 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
}
}

View File

@ -1,13 +1,18 @@
import { ref, computed } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import api from '@/utils/api.js'
import useTeamStore from '@/store/team.js'
/**
* 简单的群聊hook
* 群聊hook - 管理群聊成员和头像
* @param {string} groupID 群组ID
*/
export default function useGroupChat(groupID) {
const groupInfo = ref({})
const members = ref([])
const teamMemberIds = ref([]) // 存储团队成员的userId列表
const patientId = ref('') // 存储患者ID
const teamStore = useTeamStore()
// 群聊成员映射
const chatMember = computed(() => {
@ -15,30 +20,92 @@ export default function useGroupChat(groupID) {
members.value.forEach(member => {
res[member.id] = {
name: member.name,
avatar: member.avatar || '/static/default-avatar.png'
avatar: member.avatar,
isTeamMember: member.isTeamMember // 标记是否为团队成员
}
// 如果成员有 miniAppId患者的聊天 userID也添加一个映射用于消息的 from 字段)
if (member.miniAppId) {
res[member.miniAppId] = {
name: member.name,
avatar: member.avatar,
isTeamMember: member.isTeamMember
}
}
})
return res
})
// 获取群聊信息
// 判断某个userId是否为团队成员
const isTeamMember = (userId) => {
return teamMemberIds.value.includes(userId)
}
// 获取用户头像(根据是否为团队成员返回不同的默认头像)
const getUserAvatar = (userId) => {
const member = chatMember.value[userId]
if (!member) {
// 如果找不到成员信息,根据是否为团队成员返回默认头像
// 团队成员和患者都使用 default-avatar.png
return '/static/default-avatar.png'
}
// 如果有头像且不为空字符串,返回头像
if (member.avatar && member.avatar.trim() !== '') {
return member.avatar
}
// 否则使用默认头像
return '/static/default-avatar.png'
}
// 获取群聊信息和成员头像
async function getGroupInfo() {
const gid = typeof groupID === 'string' ? groupID : groupID.value
if (!gid) return
try {
// 这里可以调用API获取群聊信息
// const res = await getGroupDetail(gid)
// if (res && res.success) {
// groupInfo.value = res.data
// members.value = res.data.members || []
// }
// 1. 获取群聊基本信息
const groupResult = await api('getGroupListByGroupId', { groupId: gid })
// 暂时使用本地数据
groupInfo.value = {
groupID: gid,
name: '群聊',
status: 'active'
if (groupResult && groupResult.success && groupResult.data) {
groupInfo.value = {
groupID: gid,
name: groupResult.data.team?.name || '群聊',
status: groupResult.data.orderStatus || 'active',
teamId: groupResult.data.teamId
}
// 2. 如果有teamId获取团队成员头像和名称
if (groupResult.data.teamId) {
const memberMap = await teamStore.getTeamMemberAvatarsAndName(groupResult.data.teamId)
// 3. 存储团队成员ID列表
teamMemberIds.value = Object.keys(memberMap)
// 4. 构建团队成员列表(从返回的 { userId: { avatar, name } } 中提取)
members.value = teamMemberIds.value.map(userId => {
const memberInfo = memberMap[userId] || {}
return {
id: userId,
name: memberInfo.name || userId, // 使用返回的 name如果没有则用 userId
avatar: memberInfo.avatar || '', // 使用返回的 avatar
isTeamMember: true
}
})
// 5. 添加患者信息(使用默认患者头像)
if (groupResult.data.patient) {
const pid = groupResult.data.patientId?.toString() || ''
patientId.value = pid
members.value.push({
id: pid,
name: groupResult.data.patient.name || '患者',
avatar: '', // 患者不设置头像,使用默认
isTeamMember: false,
miniAppId: groupResult.data.patient.miniAppId || '' // 患者的聊天 userID
})
}
}
}
} catch (error) {
console.error('获取群聊信息失败:', error)
@ -57,6 +124,8 @@ export default function useGroupChat(groupID) {
groupInfo,
members,
chatMember,
getGroupInfo
getGroupInfo,
isTeamMember,
getUserAvatar
}
}

View File

@ -1,5 +1,20 @@
<template>
<view class="chat-page">
<!-- 患者信息栏 -->
<view class="patient-info-bar" v-if="patientInfo.name">
<view class="patient-info-content">
<view class="patient-basic-info">
<text class="patient-name">{{ patientInfo.name }}</text>
<text class="patient-detail"
>{{ patientInfo.sex }} · {{ patientInfo.age }}</text
>
</view>
<view class="status-badge" :class="chatStatusInfo.badgeClass" v-if="chatStatusInfo.badgeText">
<text class="badge-text">{{ chatStatusInfo.badgeText }}</text>
</view>
</view>
</view>
<!-- 聊天消息区域 -->
<scroll-view
class="chat-content"
@ -50,23 +65,19 @@
<!-- 消息内容 -->
<view v-else class="message-content">
<!-- 医生头像左侧 -->
<!-- 发送者头像统一处理 -->
<image
v-if="message.flow === 'in'"
class="doctor-msg-avatar"
:src="
chatMember[message.from]?.avatar || '/static/default-avatar.png'
"
:src="getUserAvatar(message.from)"
mode="aspectFill"
/>
<!-- 患者头像右侧 -->
<!-- 发送者头像统一处理 -->
<image
v-if="message.flow === 'out'"
class="user-msg-avatar"
:src="
chatMember[message.from]?.avatar || '/static/home/avatar.svg'
"
:src="getUserAvatar(message.from)"
mode="aspectFill"
/>
@ -180,12 +191,12 @@ const { initIMAfterLogin } = useAccountStore();
const chatInputRef = ref(null);
const groupId = ref("");
const { chatMember, getGroupInfo } = useGroupChat(groupId);
const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId);
//
const updateNavigationTitle = () => {
const updateNavigationTitle = (title = "群聊") => {
uni.setNavigationBarTitle({
title: "群聊",
title: title,
});
};
@ -202,6 +213,26 @@ const isEvaluationPopupOpen = ref(false);
//
const orderStatus = ref("");
//
const patientInfo = ref({
name: "",
sex: "",
age: "",
mobile: "",
});
// ID
const patientId = ref("");
//
const chatStatusInfo = ref({
show: false,
title: "",
detail: "",
badgeText: "",
badgeClass: "",
});
//
const showConsultCancel = computed(() => orderStatus.value === "pending");
const showConsultApply = computed(
@ -258,9 +289,33 @@ const fetchGroupOrderStatus = async () => {
if (result.success && result.data) {
orderStatus.value = result.data.orderStatus || "";
corpId.value = result.data.corpId || "";
//
const teamName = result.data.team?.name || "群聊";
updateNavigationTitle(teamName);
//
if (result.data.patient) {
patientInfo.value = {
name: result.data.patient.name || "",
sex: result.data.patient.sex || "",
age: result.data.patient.age || "",
mobile: result.data.patient.mobile || "",
};
}
// ID
if (result.data.patientId) {
patientId.value = result.data.patientId.toString();
}
//
updateChatStatusInfo(result.data);
console.log("获取群组订单状态:", {
orderStatus: orderStatus.value,
corpId: corpId.value,
teamName: teamName,
patientInfo: patientInfo.value,
});
} else {
console.error("获取群组订单状态失败:", result.message);
@ -270,7 +325,72 @@ const fetchGroupOrderStatus = async () => {
}
};
//
//
const updateChatStatusInfo = (groupData) => {
const status = groupData.orderStatus || "";
let statusConfig = {
show: false,
title: "",
detail: "",
badgeText: "",
badgeClass: "",
};
switch (status) {
case "pending":
statusConfig = {
show: true,
title: "等待医生接受",
detail: "您的咨询申请已提交",
badgeText: "待处理",
badgeClass: "badge-pending",
};
break;
case "processing":
statusConfig = {
show: true,
title: "咨询进行中",
detail: "医生正在为您服务",
badgeText: "进行中",
badgeClass: "badge-processing",
};
break;
case "finished":
statusConfig = {
show: true,
title: "咨询已完成",
detail: "感谢您的咨询",
badgeText: "已完成",
badgeClass: "badge-finished",
};
break;
case "cancelled":
statusConfig = {
show: true,
title: "咨询已取消",
detail: "您已取消此次咨询",
badgeText: "已取消",
badgeClass: "badge-cancelled",
};
break;
case "rejected":
statusConfig = {
show: true,
title: "咨询已拒绝",
detail: "医生已拒绝您的咨询申请",
badgeText: "已拒绝",
badgeClass: "badge-rejected",
};
break;
default:
statusConfig.show = false;
}
chatStatusInfo.value = statusConfig;
};
//
function handleSystemMessageReceived(message) {
try {
if (!message.payload?.data) return;
@ -696,7 +816,7 @@ const handleScrollToUpper = async () => {
onShow(() => {
if (!account.value || !openid.value) {
uni.redirectTo({
url: "/pages-center/login/login",
url: "/pages/login/login",
});
return;
}

View File

@ -24,10 +24,10 @@
@click="handleClickConversation(conversation)"
>
<view class="avatar-container">
<image
class="avatar"
:src="conversation.avatar || '/static/default-avatar.png'"
mode="aspectFill"
<GroupAvatar
:avatarList="getAvatarList(conversation.groupID)"
:size="96"
classType="square"
/>
<view v-if="conversation.unreadCount > 0" class="unread-badge">
<text class="unread-text">{{
@ -81,6 +81,8 @@ import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js";
import { globalTimChatManager } from "@/utils/tim-chat.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());
@ -93,6 +95,9 @@ const loadingMore = ref(false);
const hasMore = ref(false);
const refreshing = ref(false);
//
const { loadGroupAvatars, getAvatarList } = useGroupAvatars();
// IM
const initIM = async () => {
console.log("=== message.vue initIM 开始 ===");
@ -184,7 +189,14 @@ const loadConversationList = async () => {
conversationList.value = await mergeConversationWithGroupDetails(
result.groupList
);
console.log("群聊列表加载成功,共", conversationList.value, "个会话");
console.log(
"群聊列表加载成功,共",
conversationList.value.length,
"个会话"
);
//
await loadGroupAvatars(conversationList.value);
} else {
console.error("加载群聊列表失败:", result);
uni.showToast({
@ -291,7 +303,10 @@ const setupConversationListener = () => {
//
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;
console.log(
@ -331,11 +346,11 @@ const setupConversationListener = () => {
//
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// groupID
const currentGroupID = currentPage?.options?.groupID;
const isViewingThisConversation =
currentPage?.route === "pages/message/index" &&
const isViewingThisConversation =
currentPage?.route === "pages/message/index" &&
currentGroupID === conversation.groupID;
//
@ -533,6 +548,7 @@ onHide(() => {
.avatar-container {
position: relative;
margin-right: 24rpx;
flex-shrink: 0;
}
.avatar {

0
static/doctor-avatar.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

View File

@ -0,0 +1,81 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 155 72" class="design-iconfont">
<g clip-path="url(#uzvb9kyd7__clip0_256_1041)">
<rect width="155" height="72" rx="8" fill="url(#uzvb9kyd7__paint0_linear_256_1041)"/>
<path transform="rotate(-45 -62 31.4264)" fill="url(#uzvb9kyd7__paint1_linear_256_1041)" fill-opacity=".06" d="M-62 31.4264H-2V111.4264H-62z"/>
<path transform="rotate(-45 -50 3.42639)" fill="url(#uzvb9kyd7__paint2_linear_256_1041)" fill-opacity=".06" d="M-50 3.42639H10V83.42639H-50z"/>
<g filter="url(#uzvb9kyd7__filter0_f_256_1041)">
<path fill="#D0B783" fill-opacity=".1" d="M147 44A45 45 0 1 0 147 134A45 45 0 1 0 147 44Z"/>
</g>
<rect x="92" y="62" width="32" height="24" rx="6" transform="rotate(-30 92 62)" fill="url(#uzvb9kyd7__paint3_linear_256_1041)" fill-opacity=".7"/>
<rect x="101" y="50.6808" width="40" height="32" rx="6" transform="rotate(-20 101 50.6808)" fill="url(#uzvb9kyd7__paint4_linear_256_1041)"/>
<path d="M115 43C115 39.6863 117.686 37 121 37H149C152.314 37 155 39.6863 155 43V73H115V43Z" fill="url(#uzvb9kyd7__paint5_linear_256_1041)"/>
<g filter="url(#uzvb9kyd7__filter1_d_256_1041)">
<rect x="123" y="44" width="24" height="4" rx="2" fill="url(#uzvb9kyd7__paint6_linear_256_1041)" shape-rendering="crispEdges"/>
</g>
<g filter="url(#uzvb9kyd7__filter2_d_256_1041)">
<path d="M136 52C137.105 52 138 52.8954 138 54V59H143C144.105 59 145 59.8954 145 61C145 62.1046 144.105 63 143 63H138V68C138 69.1046 137.105 70 136 70C134.895 70 134 69.1046 134 68V63H129C127.895 63 127 62.1046 127 61C127 59.8954 127.895 59 129 59H134V54C134 52.8954 134.895 52 136 52Z" fill="url(#uzvb9kyd7__paint7_linear_256_1041)" shape-rendering="crispEdges"/>
</g>
</g>
<defs>
<linearGradient id="uzvb9kyd7__paint0_linear_256_1041" x1="25.5294" y1="8" x2="82.8964" y2="66.7287" gradientUnits="userSpaceOnUse">
<stop stop-color="#F4F9FF"/>
<stop offset="1" stop-color="#DBEAFF"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint1_linear_256_1041" x1="-61.3449" y1="67.5409" x2="-16.3935" y2="44.3755" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B2F3" stop-opacity="0"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint2_linear_256_1041" x1="-49.3449" y1="39.5409" x2="-4.39355" y2="16.3755" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B2F3" stop-opacity="0"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint3_linear_256_1041" x1="108" y1="62" x2="108" y2="86" gradientUnits="userSpaceOnUse">
<stop stop-color="#AAF5FA"/>
<stop offset="1" stop-color="#3D9CCC" stop-opacity=".28"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint4_linear_256_1041" x1="115.841" y1="53.7618" x2="115.417" y2="87.4082" gradientUnits="userSpaceOnUse">
<stop stop-color="#4BCFB5"/>
<stop offset="1" stop-color="#3987CC"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint5_linear_256_1041" x1="115" y1="33.4" x2="168.238" y2="60.2917" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B8F3"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint6_linear_256_1041" x1="123" y1="46" x2="147" y2="46" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity=".7"/>
<stop offset="1" stop-color="#fff" stop-opacity=".6"/>
</linearGradient>
<linearGradient id="uzvb9kyd7__paint7_linear_256_1041" x1="125" y1="52" x2="137" y2="76" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff"/>
<stop offset="1" stop-color="#fff" stop-opacity=".3"/>
</linearGradient>
<filter id="uzvb9kyd7__filter0_f_256_1041" x="82" y="24" width="130" height="130" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10" result="effect1_foregroundBlur_256_1041"/>
</filter>
<filter id="uzvb9kyd7__filter1_d_256_1041" x="119" y="42" width="32" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix values="0 0 0 0 0.103529 0 0 0 0 0.241569 0 0 0 0 0.517647 0 0 0 0.2 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_256_1041"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow_256_1041" result="shape"/>
</filter>
<filter id="uzvb9kyd7__filter2_d_256_1041" x="123" y="50" width="26" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix values="0 0 0 0 0.103529 0 0 0 0 0.241569 0 0 0 0 0.517647 0 0 0 0.2 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_256_1041"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow_256_1041" result="shape"/>
</filter>
<clipPath id="uzvb9kyd7__clip0_256_1041">
<rect width="155" height="72" rx="8" fill="#fff"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

1
static/home/female.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770103681929" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5978" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M877.714286 365.714286C877.714286 163.7504 713.963886 0 512 0S146.285714 163.7504 146.285714 365.714286c0 183.285029 134.999771 334.6432 310.857143 361.142857L457.142857 804.571429l-164.571429 0 0 109.714286 164.571429 0 0 109.714286 109.714286 0 0-109.714286 164.571429 0 0-109.714286-164.571429 0 0-77.714286C742.714514 700.357486 877.714286 548.999314 877.714286 365.714286zM256 365.714286c0-141.143771 114.856229-256 256-256s256 114.856229 256 256-114.856229 256-256 256S256 506.858057 256 365.714286z" fill="#F69661" p-id="5979"></path></svg>

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -0,0 +1,76 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 155 72" class="design-iconfont">
<g clip-path="url(#ntcrfa3do__clip0_256_3589)">
<rect width="155" height="72" rx="8" fill="url(#ntcrfa3do__paint0_linear_256_3589)"/>
<path transform="rotate(-45 -62 31.4264)" fill="url(#ntcrfa3do__paint1_linear_256_3589)" fill-opacity=".06" d="M-62 31.4264H-2V111.4264H-62z"/>
<path transform="rotate(-45 -50 3.42639)" fill="url(#ntcrfa3do__paint2_linear_256_3589)" fill-opacity=".06" d="M-50 3.42639H10V83.42639H-50z"/>
<g filter="url(#ntcrfa3do__filter0_f_256_3589)">
<path fill="#D0B783" fill-opacity=".1" d="M147 44A45 45 0 1 0 147 134A45 45 0 1 0 147 44Z"/>
</g>
<g filter="url(#ntcrfa3do__filter1_f_256_3589)">
<path fill="url(#ntcrfa3do__paint3_linear_256_3589)" fill-opacity=".1" d="M157 7A45 45 0 1 0 157 97A45 45 0 1 0 157 7Z"/>
</g>
<path d="M92 61C92 56.5817 95.5817 53 100 53H112V77H92V61Z" fill="url(#ntcrfa3do__paint4_linear_256_3589)" fill-opacity=".7"/>
<path d="M112 47.6667C112 46.1939 113.194 45 114.667 45H155V74.3333C155 75.8061 153.806 77 152.333 77H114.667C113.194 77 112 75.8061 112 74.3333V47.6667Z" fill="url(#ntcrfa3do__paint5_linear_256_3589)"/>
<path d="M135 35C139.418 35 143 38.5817 143 43V75H135V74C135 72.8954 134.105 72 133 72H131C129.895 72 129 72.8954 129 74V75H121V74C121 72.8954 120.105 72 119 72H117C115.895 72 115 72.8954 115 74V75H107V43C107 38.5817 110.582 35 115 35H135ZM117 59C115.895 59 115 59.8954 115 61V65C115 66.1046 115.895 67 117 67H119C120.105 67 121 66.1046 121 65V61C121 59.8954 120.105 59 119 59H117ZM131 59C129.895 59 129 59.8954 129 61V65C129 66.1046 129.895 67 131 67H133C134.105 67 135 66.1046 135 65V61C135 59.8954 134.105 59 133 59H131Z" fill="url(#ntcrfa3do__paint6_linear_256_3589)"/>
<g filter="url(#ntcrfa3do__filter2_d_256_3589)">
<path d="M125 41C126.105 41 127 41.8954 127 43V47H131C132.105 47 133 47.8954 133 49C133 50.1046 132.105 51 131 51H127V55C127 56.1046 126.105 57 125 57C123.895 57 123 56.1046 123 55V51H119C117.895 51 117 50.1046 117 49C117 47.8954 117.895 47 119 47H123V43C123 41.8954 123.895 41 125 41Z" fill="url(#ntcrfa3do__paint7_linear_256_3589)" shape-rendering="crispEdges"/>
</g>
</g>
<defs>
<linearGradient id="ntcrfa3do__paint0_linear_256_3589" x1="25.5294" y1="8" x2="82.8964" y2="66.7287" gradientUnits="userSpaceOnUse">
<stop stop-color="#F4F9FF"/>
<stop offset="1" stop-color="#DBEAFF"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint1_linear_256_3589" x1="-61.3449" y1="67.5409" x2="-16.3935" y2="44.3755" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B2F3" stop-opacity="0"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint2_linear_256_3589" x1="-49.3449" y1="39.5409" x2="-4.39355" y2="16.3755" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B2F3" stop-opacity="0"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint3_linear_256_3589" x1="157" y1="7" x2="157" y2="97" gradientUnits="userSpaceOnUse">
<stop stop-color="#D0B783"/>
<stop offset="1" stop-color="#EAD2A4"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint4_linear_256_3589" x1="102" y1="53" x2="102" y2="77" gradientUnits="userSpaceOnUse">
<stop stop-color="#AAF5FA"/>
<stop offset="1" stop-color="#3D9CCC" stop-opacity=".28"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint5_linear_256_3589" x1="134" y1="45" x2="155" y2="77" gradientUnits="userSpaceOnUse">
<stop stop-color="#4BCFB5"/>
<stop offset="1" stop-color="#3987CC"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint6_linear_256_3589" x1="107" y1="31" x2="158.516" y2="52.0775" gradientUnits="userSpaceOnUse">
<stop stop-color="#79B8F3"/>
<stop offset="1" stop-color="#3063C7"/>
</linearGradient>
<linearGradient id="ntcrfa3do__paint7_linear_256_3589" x1="115.222" y1="41" x2="125.889" y2="62.3333" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff"/>
<stop offset="1" stop-color="#fff" stop-opacity=".3"/>
</linearGradient>
<filter id="ntcrfa3do__filter0_f_256_3589" x="82" y="24" width="130" height="130" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10" result="effect1_foregroundBlur_256_3589"/>
</filter>
<filter id="ntcrfa3do__filter1_f_256_3589" x="92" y="-13" width="130" height="130" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10" result="effect1_foregroundBlur_256_3589"/>
</filter>
<filter id="ntcrfa3do__filter2_d_256_3589" x="113" y="39" width="24" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix values="0 0 0 0 0.103529 0 0 0 0 0.241569 0 0 0 0 0.517647 0 0 0 0.2 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_256_3589"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow_256_3589" result="shape"/>
</filter>
<clipPath id="ntcrfa3do__clip0_256_3589">
<rect width="155" height="72" rx="8" fill="#fff"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

1
static/home/male.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1770103702423" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2069" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M914.285714 0 658.285714 0l0 109.714286 178.428343 0-160.393143 160.393143C605.429029 215.606857 516.928 182.857143 420.571429 182.857143 188.286171 182.857143 0 371.143314 0 603.428571s188.286171 420.571429 420.571429 420.571429 420.571429-188.286171 420.571429-420.571429c0-96.356571-32.749714-184.8576-87.250286-255.749486L914.285714 187.285943 914.285714 365.714286l109.714286 0L1024 109.714286 1024 0 914.285714 0zM420.571429 914.285714c-171.392 0-310.857143-139.465143-310.857143-310.857143s139.465143-310.857143 310.857143-310.857143 310.857143 139.465143 310.857143 310.857143S591.963429 914.285714 420.571429 914.285714z" fill="#1296db" p-id="2070"></path></svg>

After

Width:  |  Height:  |  Size: 1004 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
static/home/switch-team.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

55
store/team.js Normal file
View File

@ -0,0 +1,55 @@
import { computed, ref } from "vue";
import { defineStore, storeToRefs } from "pinia";
import api from '@/utils/api';
import { toast } from '@/utils/widget';
import useAccountStore from "./account";
export default defineStore("teamStore", () => {
const { account, doctorInfo } = storeToRefs(useAccountStore());
const teams = ref([]);
const chargeTeams = computed(() => {
const userid = doctorInfo.value?.userid;
return teams.value.filter(team => {
const memberLeaderList = Array.isArray(team.memberLeaderList) ? team.memberLeaderList : [];
return memberLeaderList.includes(userid);
});
})
async function getTeam(teamId) {
if (!teamId || !account.value?.corpId) return;
const res = await api('getTeamData', { teamId, corpId: account.value.corpId });
if (res && res.data) {
return res.data;
} else {
toast(res?.message || '获取团队信息失败')
}
}
async function getTeams() {
const corpId = account.value?.corpId;
const mateId = doctorInfo.value?.userid;
if (!corpId || !mateId) return;
const res = await api('getJoinedTeams', { corpId, mateId });
teams.value = res && Array.isArray(res.data) ? res.data : [];
}
// 获取团队成员头像和名称映射
async function getTeamMemberAvatarsAndName(teamId) {
if (!teamId || !account.value?.corpId) return {};
const res = await api('getTeamMemberAvatarsAndName', {
teamId,
corpId: account.value.corpId
});
if (res && res.success && res.data) {
return res.data;
}
return {};
}
return { teams, chargeTeams, getTeam, getTeams, getTeamMemberAvatarsAndName }
})

View File

@ -12,6 +12,7 @@ const urlsConfig = {
getCorpMemberJob: "getCorpMemberJob",
bindWxappWithTeam: 'bindWxappWithTeam',
getWxappRelateTeams: 'getWxappRelateTeams',
getTeamMemberAvatarsAndName: "getTeamMemberAvatarsAndName"
},
knowledgeBase: {

View File

@ -926,7 +926,7 @@ class TimChatManager {
uni.removeStorageSync('account')
uni.removeStorageSync('openid')
uni.reLaunch({
url: '/pages-center/login/login'
url: '/pages/login/login'
})
}
})

View File

@ -50,4 +50,89 @@ export async function confirm(content, opt = {}) {
}
})
})
}
}
// 保存图片到相册
export async function saveImageToPhotosAlbum(filePath) {
try {
// 检查授权
const authRes = await uni.getSetting()
if (!authRes[1].authSetting['scope.writePhotosAlbum']) {
// 请求授权
try {
await uni.authorize({ scope: 'scope.writePhotosAlbum' })
} catch (err) {
// 用户拒绝授权,引导去设置
const [modalErr, modalRes] = await uni.showModal({
title: '提示',
content: '需要您授权保存相册',
confirmText: '去设置',
cancelText: '取消'
})
if (modalRes && modalRes.confirm) {
await uni.openSetting()
}
return false
}
}
// 保存图片
await uni.saveImageToPhotosAlbum({ filePath })
await toast('保存成功')
} catch (err) {
console.error('保存图片失败:', err)
await toast('保存失败')
}
}
// 分享到微信
export function shareToWeChat(options = {}) {
const { title = '', path = '', imageUrl = '' } = options
return {
title,
path,
imageUrl,
success: () => {
toast('分享成功')
},
fail: (err) => {
console.error('分享失败:', err)
toast('分享失败')
}
}
}
// 分享第三方小程序给微信好友
export function shareThirdPartyMiniProgram(options = {}) {
const {
title = '',
appId = '',
path = '',
imageUrl = '',
webUrl = '',
miniProgramType = 0
} = options
if (!appId) {
console.error('分享第三方小程序需要提供 appId')
toast('分享配置错误')
return null
}
return {
title,
path,
imageUrl,
miniProgramType, // 0-正式版 1-开发版 2-体验版
webpageUrl: webUrl, // 兼容低版本的网页链接
success: () => {
toast('分享成功')
},
fail: (err) => {
console.error('分享失败:', err)
toast('分享失败')
}
}
}