feat(miniprogram): Phase 5 UI/UX 优化 — 8 项改进
- 首页: 健康资讯推荐 + 空状态引导 + 快捷服务字符图标优化 - 健康 Hub: sparkline bar + 参考范围 + 打卡合并到快捷操作 - 日常监测: 3 分组折叠(晨间/晚间/其他) + 异常值高亮 + 提交前确认 - 预约: 已满时段 pointer-events:none + opacity 优化 - 咨询聊天: 消息日期分组(今天/昨天) + 图片预览 - 积分商城: 确认已有余额大字+签到+库存提示 - 医护工作台: 异常体征横幅 + 患者搜索入口 + 快捷操作扩展 - 趋势图表: 骨架屏加载状态 + ECharts 异常标记已有
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { useHealthStore } from '../../stores/health';
|
||||
import { listDailyMonitoring, DailyMonitoring } from '../../services/health';
|
||||
@@ -16,6 +16,27 @@ function getStatusTag(status?: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 根据 status 计算 sparkline bar 的颜色 */
|
||||
function getBarColor(status?: string): string {
|
||||
if (status === 'normal') return 'bar-green';
|
||||
if (status === 'high' || status === 'low') return 'bar-orange';
|
||||
return 'bar-green';
|
||||
}
|
||||
|
||||
/** 计算数值在参考范围中的位置百分比 (0-100) */
|
||||
function getBarPercent(value: number | undefined, ref?: string): number {
|
||||
if (!value || !ref) return 50;
|
||||
const match = ref.match(/([\d.]+)\s*[-–]\s*([\d.]+)/);
|
||||
if (!match) return 50;
|
||||
const low = parseFloat(match[1]);
|
||||
const high = parseFloat(match[2]);
|
||||
if (high <= low) return 50;
|
||||
// 将值映射到 0-100 范围,参考范围占据中间 70%(15%-85%)
|
||||
const range = high - low;
|
||||
const normalized = (value - low + range * 0.3) / (range * 1.6);
|
||||
return Math.max(5, Math.min(95, normalized * 100));
|
||||
}
|
||||
|
||||
export default function Health() {
|
||||
const { todaySummary, loading, refreshToday } = useHealthStore();
|
||||
const { currentPatient } = useAuthStore();
|
||||
@@ -63,10 +84,10 @@ export default function Health() {
|
||||
|
||||
const summary = todaySummary || {};
|
||||
const items = [
|
||||
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '--/--', unit: 'mmHg', indicator: 'blood_pressure_systolic', status: summary.blood_pressure?.status, ref: summary.blood_pressure?.reference_range },
|
||||
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '--', unit: 'bpm', indicator: 'heart_rate', status: summary.heart_rate?.status, ref: summary.heart_rate?.reference_range },
|
||||
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '--', unit: 'mmol/L', indicator: 'blood_sugar_fasting', status: summary.blood_sugar?.status, ref: summary.blood_sugar?.reference_range },
|
||||
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '--', unit: 'kg', indicator: 'weight', status: summary.weight?.status, ref: summary.weight?.reference_range },
|
||||
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '--/--', unit: 'mmHg', indicator: 'blood_pressure_systolic', status: summary.blood_pressure?.status, ref: summary.blood_pressure?.reference_range, numValue: summary.blood_pressure?.systolic },
|
||||
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '--', unit: 'bpm', indicator: 'heart_rate', status: summary.heart_rate?.status, ref: summary.heart_rate?.reference_range, numValue: summary.heart_rate?.value },
|
||||
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '--', unit: 'mmol/L', indicator: 'blood_sugar_fasting', status: summary.blood_sugar?.status, ref: summary.blood_sugar?.reference_range, numValue: summary.blood_sugar?.value },
|
||||
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '--', unit: 'kg', indicator: 'weight', status: summary.weight?.status, ref: summary.weight?.reference_range, numValue: summary.weight?.value },
|
||||
];
|
||||
|
||||
const quickActions = [
|
||||
@@ -102,7 +123,7 @@ export default function Health() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 快捷操作 */}
|
||||
{/* 快捷操作 + 打卡状态紧凑合并 */}
|
||||
<View className='health-actions-row'>
|
||||
{quickActions.map((a) => (
|
||||
<View className='action-item' key={a.label} onClick={a.action}>
|
||||
@@ -112,30 +133,22 @@ export default function Health() {
|
||||
<Text className='action-label'>{a.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 打卡状态 */}
|
||||
{checkinStatus && (
|
||||
<View className='checkin-card'>
|
||||
<View className='checkin-info'>
|
||||
{checkinStatus.checked_in_today ? (
|
||||
<>
|
||||
<Text className='checkin-done'>今日已打卡</Text>
|
||||
{checkinStatus.consecutive_days > 0 && (
|
||||
<Text className='checkin-streak'>连续 {checkinStatus.consecutive_days} 天</Text>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Text className='checkin-pending'>今日未打卡</Text>
|
||||
)}
|
||||
</View>
|
||||
{!checkinStatus.checked_in_today && (
|
||||
<View className='checkin-go' onClick={goToMall}>
|
||||
<Text className='checkin-go-text'>去打卡</Text>
|
||||
{checkinStatus && (
|
||||
<View
|
||||
className='action-item checkin-badge'
|
||||
onClick={!checkinStatus.checked_in_today ? goToMall : undefined}
|
||||
>
|
||||
<View className={`action-icon ${checkinStatus.checked_in_today ? 'icon-accent' : 'icon-warn'}`}>
|
||||
<Text className='action-char'>卡</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
<Text className='action-label'>
|
||||
{checkinStatus.checked_in_today
|
||||
? (checkinStatus.consecutive_days > 0 ? `已打卡${checkinStatus.consecutive_days}天` : '已打卡')
|
||||
: '去打卡'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 今日体征概览 */}
|
||||
<View className='health-section'>
|
||||
@@ -146,6 +159,8 @@ export default function Health() {
|
||||
<View className='vitals-grid'>
|
||||
{items.map((item) => {
|
||||
const tag = getStatusTag(item.status);
|
||||
const barColor = getBarColor(item.status);
|
||||
const barPercent = getBarPercent(item.numValue, item.ref);
|
||||
return (
|
||||
<View className='vital-card' key={item.label} onClick={() => goToTrend(item.indicator)}>
|
||||
<Text className='vital-label'>{item.label}</Text>
|
||||
@@ -154,6 +169,12 @@ export default function Health() {
|
||||
<Text className='vital-unit'>{item.unit}</Text>
|
||||
{tag && <Text className={`vital-tag ${tag.cls}`}>{tag.label}</Text>}
|
||||
</View>
|
||||
{/* Sparkline bar */}
|
||||
{item.ref && item.numValue != null && (
|
||||
<View className='vital-bar-track'>
|
||||
<View className={`vital-bar-fill ${barColor}`} style={`width: ${barPercent}%`} />
|
||||
</View>
|
||||
)}
|
||||
{item.ref && <Text className='vital-ref'>参考 {item.ref}</Text>}
|
||||
</View>
|
||||
);
|
||||
@@ -162,20 +183,20 @@ export default function Health() {
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 趋势快捷入口 */}
|
||||
{/* 趋势快捷入口 — 水平滚动卡片 */}
|
||||
<View className='health-section'>
|
||||
<Text className='section-title'>健康趋势</Text>
|
||||
<View className='trend-row'>
|
||||
<ScrollView className='trend-scroll' scrollX>
|
||||
{trendLinks.map((t) => (
|
||||
<View className='trend-item' key={t.label} onClick={() => goToTrend(t.indicator)}>
|
||||
<View className='trend-icon'>
|
||||
<Text className='trend-char'>{t.char}</Text>
|
||||
<View className='trend-card' key={t.label} onClick={() => goToTrend(t.indicator)}>
|
||||
<View className='trend-card-icon'>
|
||||
<Text className='trend-card-char'>{t.char}</Text>
|
||||
</View>
|
||||
<Text className='trend-label'>{t.label}</Text>
|
||||
<Text className='trend-arrow'>›</Text>
|
||||
<Text className='trend-card-label'>{t.label}</Text>
|
||||
<Text className='trend-card-arrow'>查看 ›</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* 最近监测记录 */}
|
||||
|
||||
Reference in New Issue
Block a user