feat: 积分商城子页面 + 日常监测 + 统计报表 (Chunk 6)
小程序 — 积分商城 (3 新页面): - mall/exchange: 兑换确认 (余额校验/QR码生成) - mall/orders: 我的订单 (状态筛选/分页/QR展示) - mall/detail: 积分明细 (余额卡片/收入支出筛选/流水列表) 小程序 — 上报 Tab 改造: - health/daily-monitoring: 日常监测表单 (血压/体重/血糖/出入量) - health/index: 增加快捷操作/打卡状态/近期监测卡片 - consultation: 替换占位为咨询列表 (会话/状态/未读) - profile: 新增积分余额/打卡天数/我的订单/积分明细入口 小程序 — 新增服务: - services/consultation.ts: 咨询会话 API - services/points.ts: 扩展兑换/订单/流水 API - services/health.ts: 扩展日常监测 API PC 管理端: - StatisticsDashboard: 统计报表仪表盘 (患者/咨询/随访/积分卡片 + Top10排行 + 快速链接) - 侧边栏新增统计报表入口 (健康模块首页)
This commit is contained in:
140
apps/miniprogram/src/pages/health/daily-monitoring/index.scss
Normal file
140
apps/miniprogram/src/pages/health/daily-monitoring/index.scss
Normal file
@@ -0,0 +1,140 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
.dm-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.dm-section {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.dm-section-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid $pri;
|
||||
}
|
||||
|
||||
.dm-date-picker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.dm-date-value {
|
||||
font-size: 28px;
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-date-arrow {
|
||||
font-size: 28px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.dm-bp-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dm-bp-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dm-field-label {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dm-bp-sep {
|
||||
font-size: 40px;
|
||||
color: $tx3;
|
||||
padding-bottom: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.dm-field-unit {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.dm-single-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dm-field-unit-inline {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dm-input {
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dm-input-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dm-submit {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
box-shadow: 0 4px 12px rgba(8, 145, 178, 0.3);
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.dm-submit-disabled {
|
||||
opacity: 0.6;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dm-submit-text {
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-reset {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.dm-reset-text {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
}
|
||||
287
apps/miniprogram/src/pages/health/daily-monitoring/index.tsx
Normal file
287
apps/miniprogram/src/pages/health/daily-monitoring/index.tsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text, Input, Picker } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { createDailyMonitoring } from '@/services/health';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { trackEvent } from '@/services/analytics';
|
||||
import './index.scss';
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
export default function DailyMonitoring() {
|
||||
const { currentPatient } = useAuthStore();
|
||||
|
||||
const today = formatDate(new Date());
|
||||
const [dateIdx, setDateIdx] = useState(0);
|
||||
const [dateList] = useState(() => {
|
||||
const list: string[] = [];
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - i);
|
||||
list.push(formatDate(d));
|
||||
}
|
||||
return list;
|
||||
});
|
||||
const recordDate = dateList[dateIdx];
|
||||
|
||||
const [morningSystolic, setMorningSystolic] = useState('');
|
||||
const [morningDiastolic, setMorningDiastolic] = useState('');
|
||||
const [eveningSystolic, setEveningSystolic] = useState('');
|
||||
const [eveningDiastolic, setEveningDiastolic] = useState('');
|
||||
const [weight, setWeight] = useState('');
|
||||
const [bloodSugar, setBloodSugar] = useState('');
|
||||
const [fluidIntake, setFluidIntake] = useState('');
|
||||
const [urineOutput, setUrineOutput] = useState('');
|
||||
const [notes, setNotes] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useDidShow(() => {
|
||||
Taro.setNavigationBarTitle({ title: '日常监测上报' });
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
setMorningSystolic('');
|
||||
setMorningDiastolic('');
|
||||
setEveningSystolic('');
|
||||
setEveningDiastolic('');
|
||||
setWeight('');
|
||||
setBloodSugar('');
|
||||
setFluidIntake('');
|
||||
setUrineOutput('');
|
||||
setNotes('');
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!currentPatient) {
|
||||
Taro.showToast({ title: '请先选择就诊人', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
const hasData =
|
||||
morningSystolic || morningDiastolic ||
|
||||
eveningSystolic || eveningDiastolic ||
|
||||
weight || bloodSugar || fluidIntake || urineOutput;
|
||||
|
||||
if (!hasData) {
|
||||
Taro.showToast({ title: '请至少填写一项数据', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if ((morningSystolic && !morningDiastolic) || (!morningSystolic && morningDiastolic)) {
|
||||
Taro.showToast({ title: '晨起血压请同时填写收缩压和舒张压', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if ((eveningSystolic && !eveningDiastolic) || (!eveningSystolic && eveningDiastolic)) {
|
||||
Taro.showToast({ title: '晚间血压请同时填写收缩压和舒张压', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await createDailyMonitoring({
|
||||
patient_id: currentPatient.id,
|
||||
record_date: recordDate,
|
||||
morning_bp_systolic: morningSystolic ? parseFloat(morningSystolic) : undefined,
|
||||
morning_bp_diastolic: morningDiastolic ? parseFloat(morningDiastolic) : undefined,
|
||||
evening_bp_systolic: eveningSystolic ? parseFloat(eveningSystolic) : undefined,
|
||||
evening_bp_diastolic: eveningDiastolic ? parseFloat(eveningDiastolic) : undefined,
|
||||
weight: weight ? parseFloat(weight) : undefined,
|
||||
blood_sugar: bloodSugar ? parseFloat(bloodSugar) : undefined,
|
||||
fluid_intake: fluidIntake ? parseFloat(fluidIntake) : undefined,
|
||||
urine_output: urineOutput ? parseFloat(urineOutput) : undefined,
|
||||
notes: notes || undefined,
|
||||
});
|
||||
|
||||
trackEvent('daily_monitoring_submit', { date: recordDate });
|
||||
Taro.showToast({ title: '上报成功', icon: 'success' });
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.showToast({ title: '+10 健康积分', icon: 'none', duration: 1500 });
|
||||
}, 1600);
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack();
|
||||
}, 3200);
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : '上报失败';
|
||||
if (msg.includes('已有记录') || msg.includes('already exists')) {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '该日期已有监测记录,请选择其他日期',
|
||||
showCancel: false,
|
||||
});
|
||||
} else {
|
||||
Taro.showToast({ title: msg, icon: 'none' });
|
||||
}
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='dm-page'>
|
||||
{/* 日期选择 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>记录日期</Text>
|
||||
<Picker
|
||||
mode='selector'
|
||||
range={dateList}
|
||||
value={dateIdx}
|
||||
onChange={(e) => setDateIdx(Number(e.detail.value))}
|
||||
>
|
||||
<View className='dm-date-picker'>
|
||||
<Text className='dm-date-value'>{recordDate}</Text>
|
||||
<Text className='dm-date-arrow'>▾</Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* 晨起血压 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>晨起血压</Text>
|
||||
<View className='dm-bp-row'>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 120'
|
||||
value={morningSystolic}
|
||||
onInput={(e) => setMorningSystolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<Text className='dm-bp-sep'>/</Text>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 80'
|
||||
value={morningDiastolic}
|
||||
onInput={(e) => setMorningDiastolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text className='dm-field-unit'>mmHg</Text>
|
||||
</View>
|
||||
|
||||
{/* 晚间血压 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>晚间血压</Text>
|
||||
<View className='dm-bp-row'>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 120'
|
||||
value={eveningSystolic}
|
||||
onInput={(e) => setEveningSystolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<Text className='dm-bp-sep'>/</Text>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 80'
|
||||
value={eveningDiastolic}
|
||||
onInput={(e) => setEveningDiastolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<Text className='dm-field-unit'>mmHg</Text>
|
||||
</View>
|
||||
|
||||
{/* 体重 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>体重</Text>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 65.0'
|
||||
value={weight}
|
||||
onInput={(e) => setWeight(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>kg</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 血糖 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>血糖</Text>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 5.6'
|
||||
value={bloodSugar}
|
||||
onInput={(e) => setBloodSugar(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>mmol/L</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 饮水量 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>饮水量</Text>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 2000'
|
||||
value={fluidIntake}
|
||||
onInput={(e) => setFluidIntake(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 尿量 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>尿量</Text>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
placeholder='如 1500'
|
||||
value={urineOutput}
|
||||
onInput={(e) => setUrineOutput(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 备注 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>备注</Text>
|
||||
<Input
|
||||
className='dm-input dm-input-full'
|
||||
placeholder='如:头晕、乏力等(可选)'
|
||||
value={notes}
|
||||
onInput={(e) => setNotes(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 提交 */}
|
||||
<View
|
||||
className={`dm-submit ${submitting ? 'dm-submit-disabled' : ''}`}
|
||||
onClick={submitting ? undefined : handleSubmit}
|
||||
>
|
||||
<Text className='dm-submit-text'>{submitting ? '提交中...' : '提交上报'}</Text>
|
||||
</View>
|
||||
|
||||
{/* 重置 */}
|
||||
<View className='dm-reset' onClick={resetForm}>
|
||||
<Text className='dm-reset-text'>清空表单</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,113 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// ---- Quick Actions (快捷操作) ----
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.quick-action-item {
|
||||
flex: 1;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.15s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
.quick-action-icon-wrap {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.quick-action-icon-primary {
|
||||
background: $pri-l;
|
||||
}
|
||||
|
||||
.quick-action-icon-green {
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
.quick-action-icon-orange {
|
||||
background: $wrn-l;
|
||||
}
|
||||
|
||||
.quick-action-icon {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.quick-action-label {
|
||||
font-size: 24px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// ---- Checkin Status (打卡状态) ----
|
||||
|
||||
.checkin-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin: 0 24px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.checkin-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.checkin-done {
|
||||
font-size: 28px;
|
||||
color: $acc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.checkin-streak {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.checkin-pending {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.checkin-go-btn {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.checkin-go-text {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// ---- Health Grid (体征概览) ----
|
||||
|
||||
.health-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
@@ -90,6 +197,8 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// ---- Trend Actions (趋势快捷入口) ----
|
||||
|
||||
.health-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
@@ -116,3 +225,64 @@
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
// ---- Recent Daily Monitoring (最近日常监测) ----
|
||||
|
||||
.recent-section {
|
||||
padding: 0 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.recent-section-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid $pri;
|
||||
}
|
||||
|
||||
.recent-record {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.recent-record-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.recent-record-date {
|
||||
font-size: 26px;
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recent-record-data {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.recent-data-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.recent-data-label {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.recent-data-value {
|
||||
font-size: 26px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { useHealthStore } from '../../stores/health';
|
||||
import { listDailyMonitoring, DailyMonitoring } from '../../services/health';
|
||||
import { getCheckinStatus, CheckinStatus } from '../../services/points';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { trackEvent } from '../../services/analytics';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
@@ -13,19 +18,53 @@ function getStatusStyle(status?: string) {
|
||||
|
||||
export default function Health() {
|
||||
const { todaySummary, loading, refreshToday } = useHealthStore();
|
||||
const { currentPatient } = useAuthStore();
|
||||
const [checkinStatus, setCheckinStatus] = useState<CheckinStatus | null>(null);
|
||||
const [recentRecords, setRecentRecords] = useState<DailyMonitoring[]>([]);
|
||||
|
||||
useDidShow(() => {
|
||||
refreshToday();
|
||||
loadExtraData();
|
||||
});
|
||||
|
||||
const loadExtraData = async () => {
|
||||
try {
|
||||
const status = await getCheckinStatus();
|
||||
setCheckinStatus(status);
|
||||
} catch {
|
||||
// ignore — points API may not be available
|
||||
}
|
||||
|
||||
if (currentPatient) {
|
||||
try {
|
||||
const resp = await listDailyMonitoring(currentPatient.id, { page: 1, page_size: 3 });
|
||||
setRecentRecords(resp.data || []);
|
||||
} catch {
|
||||
// ignore — daily monitoring API may not be available yet
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const goToInput = () => {
|
||||
Taro.navigateTo({ url: '/pages/health/input/index' });
|
||||
};
|
||||
|
||||
const goToDailyMonitoring = () => {
|
||||
Taro.navigateTo({ url: '/pages/health/daily-monitoring/index' });
|
||||
};
|
||||
|
||||
const goToTrend = (indicator: string) => {
|
||||
Taro.navigateTo({ url: `/pages/health/trend/index?indicator=${indicator}` });
|
||||
};
|
||||
|
||||
const goToTrendPage = () => {
|
||||
Taro.navigateTo({ url: '/pages/health/trend/index?indicator=blood_pressure_systolic' });
|
||||
};
|
||||
|
||||
const goToMall = () => {
|
||||
Taro.switchTab({ url: '/pages/mall/index' });
|
||||
};
|
||||
|
||||
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 },
|
||||
@@ -34,6 +73,17 @@ export default function Health() {
|
||||
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '--', unit: 'kg', indicator: 'weight', status: summary.weight?.status, ref: summary.weight?.reference_range },
|
||||
];
|
||||
|
||||
const formatBp = (record: DailyMonitoring) => {
|
||||
const parts: string[] = [];
|
||||
if (record.morning_bp_systolic && record.morning_bp_diastolic) {
|
||||
parts.push(`晨 ${record.morning_bp_systolic}/${record.morning_bp_diastolic}`);
|
||||
}
|
||||
if (record.evening_bp_systolic && record.evening_bp_diastolic) {
|
||||
parts.push(`晚 ${record.evening_bp_systolic}/${record.evening_bp_diastolic}`);
|
||||
}
|
||||
return parts.length > 0 ? parts.join(' ') : '--';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='health-page'>
|
||||
<View className='health-header'>
|
||||
@@ -43,6 +93,52 @@ export default function Health() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 快捷操作 */}
|
||||
<View className='quick-actions'>
|
||||
<View className='quick-action-item' onClick={goToDailyMonitoring}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-primary'>
|
||||
<Text className='quick-action-icon'>📋</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>日常上报</Text>
|
||||
</View>
|
||||
<View className='quick-action-item' onClick={goToInput}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-green'>
|
||||
<Text className='quick-action-icon'>💉</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>体征录入</Text>
|
||||
</View>
|
||||
<View className='quick-action-item' onClick={goToTrendPage}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-orange'>
|
||||
<Text className='quick-action-icon'>📈</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>查看趋势</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 打卡状态 */}
|
||||
{checkinStatus && (
|
||||
<View className='checkin-card'>
|
||||
<View className='checkin-left'>
|
||||
{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-btn' onClick={goToMall}>
|
||||
<Text className='checkin-go-text'>去打卡</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 今日体征概览 */}
|
||||
{loading && !todaySummary ? (
|
||||
<Loading />
|
||||
) : (
|
||||
@@ -64,6 +160,7 @@ export default function Health() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 原有趋势快捷入口 */}
|
||||
<View className='health-actions'>
|
||||
<View className='action-card' onClick={() => goToTrend('blood_pressure_systolic')}>
|
||||
<Text className='action-icon'>📈</Text>
|
||||
@@ -78,6 +175,38 @@ export default function Health() {
|
||||
<Text className='action-label'>血糖趋势</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 最近日常监测记录 */}
|
||||
{recentRecords.length > 0 && (
|
||||
<View className='recent-section'>
|
||||
<Text className='recent-section-title'>最近监测记录</Text>
|
||||
{recentRecords.map((record) => (
|
||||
<View className='recent-record' key={record.id}>
|
||||
<View className='recent-record-header'>
|
||||
<Text className='recent-record-date'>{record.record_date}</Text>
|
||||
</View>
|
||||
<View className='recent-record-data'>
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>血压</Text>
|
||||
<Text className='recent-data-value'>{formatBp(record)}</Text>
|
||||
</View>
|
||||
{record.weight != null && (
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>体重</Text>
|
||||
<Text className='recent-data-value'>{record.weight} kg</Text>
|
||||
</View>
|
||||
)}
|
||||
{record.blood_sugar != null && (
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>血糖</Text>
|
||||
<Text className='recent-data-value'>{record.blood_sugar} mmol/L</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user