| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060 |
- <template>
- <CustomNav title="医生首页" leftType="scan" @scan="handleScan" :opacity="0" />
- <view class="page-container">
- <view class="content">
- <view class="user-info">
- <!-- 用户信息骨架屏 -->
- <view v-if="userInfoLoading" class="user-info-skeleton">
- <view class="avatar-section">
- <view class="avatar">
- <view class="avatar-frame">
- <view class="skeleton-avatar-placeholder"></view>
- </view>
- </view>
- <view class="user-details">
- <view class="skeleton-line skeleton-nickname"></view>
- <view class="skeleton-line skeleton-title"></view>
- </view>
- <view class="message-button" @click="onMessageClick">
- <image src="/static/icons/remixicon/message-3-line.svg" class="message-icon" />
- <view class="badge" v-if="unreadMessageCount > 0">{{ unreadMessageCount > 99 ? '99+' : unreadMessageCount }}
- </view>
- </view>
- <view class="qr-button" @click="onQrClick">
- <image src="/static/icons/remixicon/qr-code-line.svg" class="qr-icon" />
- </view>
- </view>
- </view>
- <!-- 实际用户信息 -->
- <view v-if="!userInfoLoading" class="avatar-section">
- <view class="avatar">
- <view class="avatar-frame">
- <image class="avatar-img" :src="avatarSrc" mode="aspectFill" />
- </view>
- </view>
- <view class="user-details">
- <text class="username">{{ user.nickname || '未登录' }}</text>
- <text class="user-title" v-if="user.title">职称: {{ user.title }}</text>
- </view>
- <view class="message-button" @click="onMessageClick">
- <image src="/static/icons/remixicon/message-3-line.svg" class="message-icon" />
- <view class="badge" v-if="unreadMessageCount > 0">{{ unreadMessageCount > 99 ? '99+' : unreadMessageCount }}
- </view>
- </view>
- <view class="qr-button" @click="onQrClick">
- <image src="/static/icons/remixicon/qr-code-line.svg" class="qr-icon" />
- </view>
- </view>
- </view>
- <view class="function-container">
- <view class="function-row">
- <view class="function-item blue" @click="onItemClick('我的病人')">
- <view class="item-content">
- <view class="title-row">
- <view class="item-line"></view>
- <text class="item-title">我的病人</text>
- </view>
- <text class="item-desc">管理慢性病患者</text>
- </view>
- </view>
- <view class="function-item orange" @click="onItemClick('药品管理')">
- <view class="item-content">
- <view class="title-row">
- <view class="item-line"></view>
- <text class="item-title">药品管理</text>
- </view>
- <text class="item-desc">管理药品信息</text>
- </view>
- </view>
- </view>
- <view class="function-row">
- <view class="function-item purple" @click="onItemClick('危急值管理')">
- <view class="item-content">
- <view class="title-row">
- <view class="item-line"></view>
- <text class="item-title">危急值管理</text>
- </view>
- <text class="item-desc">设置与管理危急值阈值</text>
- </view>
- </view>
- <view class="function-item green" @click="onItemClick('健康资讯管理')">
- <view class="item-content">
- <view class="title-row">
- <view class="item-line"></view>
- <text class="item-title">健康资讯管理</text>
- </view>
- <text class="item-desc">管理健康资讯与文章</text>
- </view>
- </view>
- </view>
- </view>
- <!--
- <view class="today-reminder-card">
- <view class="card-header">
- <text class="card-title">复诊概览</text>
- </view>
- <view class="card-content">
- <view class="reminder-item" @click="onItemClick('复诊管理')">
- <view class="reminder-icon">
- <image src="/static/icons/remixicon/question-line.svg" class="icon" />
- </view>
- <view class="reminder-text">
- <text class="reminder-number">{{ todayReminders.pendingCount }}</text>
- <text class="reminder-label">待确认复诊</text>
- </view>
- </view>
- <view class="reminder-item" @click="onItemClick('复诊管理')">
- <view class="reminder-icon">
- <image src="/static/icons/remixicon/time-line.svg" class="icon" />
- </view>
- <view class="reminder-text">
- <text class="reminder-number">{{ todayReminders.confirmedCount }}</text>
- <text class="reminder-label">待完成复诊</text>
- </view>
- </view>
- </view>
- </view>
- -->
- <view class="patient-activity-card">
- <view class="card-header">
- <text class="card-title">患者动态</text>
- </view>
- <view class="activity-card-content">
- <!-- 骨架屏 -->
- <view v-if="patientActivitiesLoading" class="skeleton-container">
- <view class="skeleton-item" v-for="i in 3" :key="i">
- <view class="skeleton-avatar"></view>
- <view class="skeleton-text">
- <view class="skeleton-line skeleton-desc"></view>
- <view class="skeleton-line skeleton-time"></view>
- </view>
- </view>
- </view>
- <!-- 实际内容 -->
- <view v-if="!patientActivitiesLoading">
- <view class="activity-item" v-for="(activity, index) in patientActivities" :key="index">
- <view class="activity-avatar">
- <image :src="activity.patientAvatar" class="avatar-img" mode="aspectFill" />
- </view>
- <view class="activity-text">
- <text class="activity-desc">{{ activity.desc }}</text>
- <text class="activity-time">{{ activity.time }}</text>
- </view>
- </view>
- <view v-if="patientActivities.length === 0 && !patientActivitiesError" class="no-activity">
- <text>暂无患者动态</text>
- </view>
- <view v-if="patientActivitiesError" class="error-activity">
- <text class="error-text">加载失败,请重试</text>
- <view class="retry-button" @click="fetchPatientActivities">
- <text class="retry-text">重试</text>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- <TabBar />
- </template>
- <script setup lang="ts">
- import { ref, computed } from 'vue'
- import { onShow, onHide } from '@dcloudio/uni-app'
- import CustomNav from '@/components/custom-nav.vue'
- import TabBar from '@/components/tab-bar.vue'
- import { fetchUserInfo as fetchUserInfoApi } from '@/api/user'
- import request from '@/api/request'
- import { handleQrScanResult } from '@/utils/qr'
- import { queryBoundPatientsActivities } from '@/api/userActivity'
- import { avatarCache } from '@/utils/avatarCache'
- import { getUnreadMessageCount, getMessageList, markMessageAsRead, type MessageQueryParams } from '@/api/message'
- const user = ref<{ avatar?: string; nickname?: string; title?: string }>({})
- // 用户信息加载状态
- const userInfoLoading = ref(true)
- // 未读消息数量
- const unreadMessageCount = ref(0)
- // 页面活跃状态标志
- const isPageActive = ref(false)
- const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
- const avatarSrc = computed(() => {
- const a = user.value?.avatar
- if (!a) return defaultAvatarUrl
- try {
- const s = String(a)
- if (/^(https?:\/\/|data:|wxfile:\/\/|file:\/\/|\/static\/)/i.test(s)) {
- return s
- }
- if (/^(\.|\/|temp)/i.test(s)) return s
- } catch (e) {
- // fallback
- }
- return defaultAvatarUrl
- })
- /*
- const todayReminders = ref({
- pendingCount: 0,
- confirmedCount: 0
- })
- */
- const patientActivities = ref<Array<{
- desc: string
- time: string
- patientAvatar: string
- }>>([])
- // 患者动态加载状态
- const patientActivitiesLoading = ref(true)
- // 患者动态获取失败状态
- const patientActivitiesError = ref(false)
- const loadUser = () => {
- try {
- const u = uni.getStorageSync('user_info')
- if (u) {
- user.value = u
- }
- } catch (e) {
- // ignore
- }
- }
- const fetchUserInfo = async () => {
- try {
- userInfoLoading.value = true
- const token = uni.getStorageSync('token')
- if (!token) {
- userInfoLoading.value = false
- return
- }
- const response = await fetchUserInfoApi()
- uni.hideLoading()
- console.log('User info response:', response)
- const resp = response.data as any
- if (response.statusCode === 401) {
- // Token 无效,清除并跳转登录
- uni.removeStorageSync('token')
- uni.removeStorageSync('role')
- user.value = {}
- uni.reLaunch({ url: '/pages/public/login/index' })
- userInfoLoading.value = false
- return
- }
- if (resp && resp.code === 200 && resp.data) {
- // 如果头像无效(不是有效的 http URL),则下载头像
- if (!resp.data.avatar || !resp.data.avatar.startsWith('http')) {
- const userIdRaw = resp.data.id || resp.data.userId
- const userId = userIdRaw ? String(userIdRaw) : ''
- if (userId) {
- try {
- // 使用 avatarCache.getOrFetch 做并发去重,loader 只在真正需要下载时被调用一次
- const path = await avatarCache.getOrFetch(userId, async (id) => {
- try {
- const downloadRes = await uni.downloadFile({
- url: `https://wx.baiyun.work/user/avatar/${id}`,
- header: {
- Authorization: `Bearer ${token}`
- }
- })
- if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
- return downloadRes.tempFilePath
- }
- } catch (e) {
- console.error('Download avatar error:', e)
- }
- return undefined
- })
- if (path) resp.data.avatar = path
- } catch (e) {
- console.error('avatar fetch error:', e)
- }
- }
- }
- user.value = resp.data
- uni.setStorageSync('user_info', resp.data)
- if (!resp.data.nickname || !resp.data.avatar) {
- uni.navigateTo({ url: '/pages/doctor/profile/index' })
- }
- }
- } catch (err) {
- uni.hideLoading()
- console.error('Fetch user info error:', err)
- } finally {
- userInfoLoading.value = false
- }
- }
- /*
- const fetchTodayReminders = async () => {
- try {
- const token = uni.getStorageSync('token')
- if (!token) return
-
- // 暂时移除复诊数据获取逻辑
- todayReminders.value = {
- pendingCount: 0,
- confirmedCount: 0
- }
- } catch (err) {
- console.error('Fetch today reminders error:', err)
- }
- }
- */
- const fetchPatientActivities = async () => {
- try {
- patientActivitiesLoading.value = true
- patientActivitiesError.value = false
- const token = uni.getStorageSync('token')
- if (!token) {
- console.log('No token found, skipping fetchPatientActivities')
- patientActivitiesLoading.value = false
- return
- }
- console.log('Fetching patient activities...')
- // 调用真实接口获取患者动态
- const response = await queryBoundPatientsActivities({
- pageNum: 1,
- pageSize: 10
- })
- console.log('Patient activities response:', response)
- const resp = response.data as any
- if (resp && resp.code === 200 && resp.data) {
- console.log('Patient activities data:', resp.data)
- // 转换数据格式,异步获取头像
- const activitiesPromises = resp.data.records.map(async (activity: any) => ({
- desc: formatActivityDescription(activity),
- time: formatTime(activity.createTime),
- // 强制把 userId 转为字符串,避免数值精度或类型不匹配引起的问题
- patientAvatar: await getPatientAvatar(String(activity.userId))
- }))
- // 等待所有头像获取完成
- const activities = await Promise.all(activitiesPromises)
- console.log('Converted activities:', activities)
- patientActivities.value = activities
- } else {
- console.log('No patient activities data or invalid response')
- patientActivities.value = []
- }
- } catch (err) {
- console.error('Fetch patient activities error:', err)
- // 如果接口调用失败,显示错误状态
- patientActivitiesError.value = true
- patientActivities.value = []
- } finally {
- patientActivitiesLoading.value = false
- }
- }
- // 获取未读消息数量
- const fetchUnreadMessageCount = async () => {
- try {
- const response = await getUnreadMessageCount()
- const resp = response.data as any
- if (resp && resp.code === 200) {
- unreadMessageCount.value = resp.data || 0
- }
- } catch (error) {
- console.error('获取未读消息数量失败:', error)
- }
- }
- // 检查并显示需要弹窗的消息
- const checkAndShowPopupMessages = async () => {
- try {
- // 获取最近的消息,检查是否有需要弹窗的未读消息
- const params: MessageQueryParams = {
- page: 1,
- size: 10,
- status: 0 // 未读消息
- }
- const response = await getMessageList(params)
- const resp = response.data as any
- if (resp && resp.code === 200 && resp.data) {
- const messages = resp.data.records || []
- // 查找需要弹窗的消息(notifyPopup为true且未读)
- const popupMessages = messages.filter((msg: any) => msg.notifyPopup && msg.status === 0)
- if (popupMessages.length > 0) {
- // 显示最新的弹窗消息
- const latestMessage = popupMessages[0]
- // 延迟一点时间显示弹窗,确保页面加载完成
- setTimeout(() => {
- // 检查页面是否仍然活跃
- if (!isPageActive.value) {
- return
- }
- uni.showModal({
- title: getMessageTitle(latestMessage.type),
- content: latestMessage.content,
- showCancel: false,
- confirmText: '我知道了',
- success: () => {
- // 用户确认后,自动标记为已读
- markMessageAsReadLocal(latestMessage.id)
- }
- })
- }, 1000)
- }
- }
- } catch (error) {
- console.error('检查弹窗消息失败:', error)
- }
- }
- // 标记消息已读
- const markMessageAsReadLocal = async (messageId: string) => {
- try {
- await markMessageAsRead(messageId)
- // 更新未读消息数量
- if (unreadMessageCount.value > 0) {
- unreadMessageCount.value--
- }
- } catch (error) {
- console.error('标记消息已读失败:', error)
- }
- }
- // 获取消息标题
- const getMessageTitle = (type: string) => {
- switch (type) {
- case 'SYSTEM_DAILY':
- return '系统通知'
- case 'DOCTOR':
- return '医生消息'
- case 'SYSTEM_ANOMALY':
- return '异常通知'
- default:
- return '消息提醒'
- }
- }
- // 格式化时间显示
- const formatTime = (createTime: string) => {
- try {
- const now = new Date()
- const create = new Date(createTime)
- const diff = now.getTime() - create.getTime()
- const minutes = Math.floor(diff / (1000 * 60))
- const hours = Math.floor(diff / (1000 * 60 * 60))
- const days = Math.floor(diff / (1000 * 60 * 60 * 24))
- if (minutes < 1) return '刚刚'
- if (minutes < 60) return `${minutes}分钟前`
- if (hours < 24) return `${hours}小时前`
- if (days < 7) return `${days}天前`
- return create.toLocaleDateString('zh-CN')
- } catch (e) {
- return createTime
- }
- }
- function onMessageClick() {
- uni.navigateTo({
- url: '/pages/public/message-detail',
- events: {
- // 监听消息详情页的消息已读事件
- messageRead: () => {
- fetchUnreadMessageCount()
- }
- }
- })
- }
- // 获取患者头像
- const getPatientAvatar = async (userId: string | number): Promise<string> => {
- try {
- const token = uni.getStorageSync('token')
- if (!token) return defaultAvatarUrl
- // 使用 getOrFetch 去重并缓存下载结果
- const idStr = String(userId)
- try {
- const path = await avatarCache.getOrFetch(idStr, async (id) => {
- try {
- const downloadRes = await uni.downloadFile({
- url: `https://wx.baiyun.work/user/avatar/${id}`,
- header: {
- Authorization: `Bearer ${token}`
- }
- })
- if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
- return downloadRes.tempFilePath
- }
- } catch (e) {
- console.error('Download patient avatar error:', e)
- }
- return undefined
- })
- if (path) return path
- } catch (e) {
- console.error('getPatientAvatar error:', e)
- }
- } catch (e) {
- console.error('Download patient avatar error:', e)
- }
- // 如果获取失败,使用默认头像
- return defaultAvatarUrl
- }
- // 如果在微信小程序端且未登录,自动跳转到登录页
- onShow(() => {
- isPageActive.value = true
- const token = uni.getStorageSync('token')
- if (!token) {
- // 使用 uni.reLaunch 替代 navigateTo,确保页面栈被清空
- uni.reLaunch({ url: '/pages/public/login/index' })
- } else {
- fetchUserInfo()
- // fetchTodayReminders()
- fetchPatientActivities()
- fetchUnreadMessageCount()
- checkAndShowPopupMessages()
- }
- })
- // 页面隐藏时设置不活跃
- onHide(() => {
- isPageActive.value = false
- })
- function handleScan(res: any) {
- return handleQrScanResult(res)
- }
- function onItemClick(type: string) {
- switch (type) {
- case '我的病人':
- uni.navigateTo({ url: '/pages/doctor/index/my-patients' })
- break
- case '药品管理':
- uni.navigateTo({ url: '/pages/doctor/manage/medicine' })
- break
- case '健康资讯管理':
- uni.navigateTo({ url: '/pages/doctor/manage/news' })
- break
- case '危急值管理':
- uni.navigateTo({ url: '/pages/doctor/manage/critical-values' })
- break
- default:
- uni.showToast({ title: '功能正在开发中', icon: 'none' })
- }
- }
- function onQrClick() {
- uni.navigateTo({ url: '/pages/public/profile/qr/index' })
- }
- // 格式化活动描述
- const formatActivityDescription = (activity: any) => {
- // 如果已经有友好的描述,直接返回
- if (activity.friendlyDescription) {
- return activity.friendlyDescription
- }
- // 根据 activityDescription 内容进一步细化描述
- const description = activity.activityDescription || ''
- let baseDescription = ''
- // 根据活动类型生成基础描述
- switch (activity.activityType) {
- case 'BLOOD_GLUCOSE_UPLOAD':
- baseDescription = '上传了血糖数据'
- break
- case 'BLOOD_GLUCOSE_UPDATE':
- baseDescription = '更新了血糖数据'
- break
- case 'BLOOD_PRESSURE_UPLOAD':
- baseDescription = '上传了血压数据'
- break
- case 'HEART_RATE_UPLOAD':
- baseDescription = '上传了心率数据'
- break
- case 'PHYSICAL_DATA_UPLOAD':
- baseDescription = '上传了体格数据'
- break
- case 'HEALTH_RECORD_CREATE':
- // 根据描述内容判断是创建还是更新
- if (description.includes('save') || description.includes('update')) {
- baseDescription = '更新了健康档案'
- } else {
- baseDescription = '创建了健康档案'
- }
- break
- case 'HEALTH_RECORD_UPDATE':
- baseDescription = '更新了健康档案'
- break
- case 'MEDICATION_CREATE':
- baseDescription = '添加了用药记录'
- break
- case 'MEDICATION_UPDATE':
- baseDescription = '更新了用药记录'
- break
- case 'USER_BINDING_CREATE':
- baseDescription = '绑定了新患者'
- break
- case 'USER_BINDING_DELETE':
- baseDescription = '解除了患者绑定'
- break
- default:
- // 如果没有匹配的类型,尝试使用 activityDescription 或返回默认值
- baseDescription = description && !description.includes('Controller')
- ? description
- : '执行了操作'
- }
- return baseDescription
- }
- </script>
- <style>
- .page-container {
- min-height: 100vh;
- padding-top: calc(var(--status-bar-height) + 44px);
- padding-bottom: 100rpx;
- box-sizing: border-box;
- justify-content: center;
- align-items: center;
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
- }
- .content {
- width: 100%;
- }
- .user-info {
- /* background-color: #fff; */
- padding: 40rpx;
- margin-top: 0rpx;
- }
- .avatar-section {
- display: flex;
- align-items: center;
- }
- .avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- border: 1px solid rgba(128, 128, 128, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 30rpx;
- }
- .avatar-frame {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .avatar-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .user-details {
- flex: 1;
- }
- .username {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- display: block;
- margin-bottom: 10rpx;
- }
- .user-title {
- font-size: 28rpx;
- color: #666;
- display: block;
- margin-bottom: 10rpx;
- }
- .qr-button {
- width: 100rpx;
- height: 100rpx;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-left: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
- }
- .qr-icon {
- width: 60rpx;
- height: 60rpx;
- }
- .message-button {
- width: 100rpx;
- height: 100rpx;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-left: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
- position: relative;
- }
- .message-icon {
- width: 60rpx;
- height: 60rpx;
- }
- .badge {
- position: absolute;
- top: -10rpx;
- right: -10rpx;
- background-color: red;
- color: white;
- border-radius: 50%;
- width: 40rpx;
- height: 40rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 24rpx;
- font-weight: bold;
- }
- .function-container {
- padding-inline: 20rpx;
- }
- .function-row {
- display: flex;
- justify-content: space-between;
- margin-bottom: 20rpx;
- }
- .function-row:last-child {
- margin-bottom: 0;
- }
- .function-item {
- flex: 1;
- height: 160rpx;
- background-color: #fff;
- border-radius: 20rpx;
- margin: 0 10rpx;
- position: relative;
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
- overflow: hidden;
- }
- .item-content {
- position: absolute;
- top: 20rpx;
- left: 20rpx;
- display: flex;
- flex-direction: column;
- }
- .title-row {
- display: flex;
- align-items: center;
- margin-bottom: 10rpx;
- }
- .item-line {
- width: 8rpx;
- height: 48rpx;
- margin-right: 15rpx;
- border-radius: 10rpx;
- }
- .blue .item-line {
- background-color: #3742fa;
- }
- .orange .item-line {
- background-color: #ffa502;
- }
- .green .item-line {
- background-color: #2ecc71;
- }
- .purple .item-line {
- background-color: #9b59b6;
- }
- .item-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- }
- .item-desc {
- font-size: 28rpx;
- color: #666;
- }
- /*
- 以下为复诊概览卡片样式,已注释以便临时隐藏该功能
- .today-reminder-card { ... }
- .card-header { ... }
- .card-title { ... }
- .card-content { ... }
- .reminder-item { ... }
- .reminder-icon { ... }
- .icon { ... }
- .reminder-text { ... }
- .reminder-number { ... }
- .reminder-label { ... }
- */
- .activity-card-content {
- padding: 20rpx;
- display: block;
- }
- .patient-activity-card {
- background-color: #fff;
- border-radius: 20rpx;
- margin: 20rpx;
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
- overflow: hidden;
- }
- /* 卡片头样式(用于患者动态等卡片) */
- .card-header {
- padding: 24rpx 28rpx;
- display: flex;
- align-items: center;
- background: linear-gradient(90deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.75));
- border-bottom: 1rpx solid #f3f4f6;
- }
- .card-title {
- font-size: 32rpx;
- font-weight: 700;
- color: #222;
- }
- .activity-item {
- display: flex;
- align-items: center;
- margin-bottom: 20rpx;
- padding: 20rpx;
- border-bottom: 1rpx solid #eee;
- }
- .activity-item:last-child {
- margin-bottom: 0;
- border-bottom: none;
- }
- .activity-avatar {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- border: 1px solid rgba(128, 128, 128, 0.5);
- margin-right: 20rpx;
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .activity-text {
- flex: 1;
- }
- .activity-desc {
- font-size: 32rpx;
- color: #333;
- display: block;
- margin-bottom: 10rpx;
- }
- .activity-time {
- font-size: 28rpx;
- color: #666;
- }
- .no-activity {
- padding: 40rpx;
- text-align: center;
- color: #999;
- }
- .error-activity {
- padding: 40rpx;
- text-align: center;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .error-text {
- color: #ff6b6b;
- font-size: 28rpx;
- margin-bottom: 20rpx;
- }
- .retry-button {
- background-color: #007aff;
- color: white;
- padding: 16rpx 32rpx;
- border-radius: 8rpx;
- display: inline-block;
- }
- .retry-text {
- font-size: 28rpx;
- color: white;
- }
- /* 骨架屏样式 */
- .skeleton-container {
- padding: 20rpx;
- }
- .skeleton-item {
- display: flex;
- align-items: center;
- margin-bottom: 20rpx;
- padding: 20rpx;
- border-bottom: 1rpx solid #eee;
- }
- .skeleton-item:last-child {
- margin-bottom: 0;
- border-bottom: none;
- }
- .skeleton-avatar {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
- margin-right: 20rpx;
- }
- .skeleton-text {
- flex: 1;
- }
- .skeleton-line {
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
- border-radius: 6rpx;
- }
- .skeleton-desc {
- width: 70%;
- height: 32rpx;
- margin-bottom: 10rpx;
- }
- .skeleton-time {
- width: 40%;
- height: 28rpx;
- }
- /* 用户信息骨架屏样式 */
- .user-info-skeleton .avatar-section {
- display: flex;
- align-items: center;
- }
- .user-info-skeleton .avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- border: 1px solid rgba(128, 128, 128, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 30rpx;
- }
- .skeleton-avatar-placeholder {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
- }
- .user-info-skeleton .user-details {
- flex: 1;
- }
- .skeleton-nickname {
- width: 60%;
- height: 36rpx;
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
- border-radius: 6rpx;
- margin-bottom: 10rpx;
- }
- .skeleton-title {
- width: 40%;
- height: 28rpx;
- background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s infinite;
- border-radius: 6rpx;
- }
- @keyframes skeleton-loading {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
- }
- </style>
|