fix(mp): 安全修复 + 健康Tab重构为总览
Phase 0 安全修复: - 移除 secure-storage-aes.ts 硬编码 'hms-default-key' fallback - production 模式空密钥时拒绝加解密(返回空/不加密) - dev 模式保留明文兼容(warn 日志提醒) - .env/.env.h5 注入随机加密密钥 - secureGet 明文 fallback 按环境分级处理 - 新增 8 个测试覆盖空密钥 dev/production 行为 Phase 1 健康Tab重构: - health/index.tsx 从体征录入页改为健康总览Dashboard - 新增今日体征摘要卡片(2x2 网格 + 状态标签) - 新增快捷入口(录入体征/趋势/报告/用药) - 新增告警提示卡片(待处理告警数量) - 体征录入移至 pkg-health/input/index(已有页面) - useHealthData → useHealthOverview(新增 alertCount) 首页增强: - useHomeData 新增告警计数查询(listPatientAlerts) - 首页新增告警提示卡片入口 - "记录体征"按钮改为跳转录入页而非健康Tab
This commit is contained in:
112
apps/miniprogram/src/pages/health/useHealthOverview.ts
Normal file
112
apps/miniprogram/src/pages/health/useHealthOverview.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useState } from 'react';
|
||||
import { useHealthStore } from '@/stores/health';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { usePageData } from '@/hooks/usePageData';
|
||||
import { getHealthThresholds, DEFAULT_THRESHOLDS, type HealthThreshold } from '@/services/health';
|
||||
import { listPendingSuggestions, type AiSuggestionItem } from '@/services/ai-analysis';
|
||||
import { listPatientAlerts } from '@/services/alert';
|
||||
|
||||
export type VitalType = 'blood_pressure' | 'heart_rate' | 'blood_sugar' | 'weight';
|
||||
|
||||
export const VITAL_TABS: { key: VitalType; label: string }[] = [
|
||||
{ key: 'blood_pressure', label: '血压' },
|
||||
{ key: 'heart_rate', label: '心率' },
|
||||
{ key: 'blood_sugar', label: '血糖' },
|
||||
{ key: 'weight', label: '体重' },
|
||||
];
|
||||
|
||||
export interface TrendPoint {
|
||||
date: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export function useHealthOverview() {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const todaySummary = useHealthStore((s) => s.todaySummary);
|
||||
const loading = useHealthStore((s) => s.loading);
|
||||
const refreshToday = useHealthStore((s) => s.refreshToday);
|
||||
const fetchTrend = useHealthStore((s) => s.getTrend);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<VitalType>('blood_pressure');
|
||||
const [trendData, setTrendData] = useState<TrendPoint[]>([]);
|
||||
const [trendLoading, setTrendLoading] = useState(false);
|
||||
const [aiSuggestions, setAiSuggestions] = useState<AiSuggestionItem[]>([]);
|
||||
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
|
||||
const [alertCount, setAlertCount] = useState(0);
|
||||
|
||||
const loadTrend = async (type: VitalType) => {
|
||||
setTrendLoading(true);
|
||||
try {
|
||||
const indicatorMap: Record<VitalType, string> = {
|
||||
blood_pressure: 'systolic_bp_morning',
|
||||
heart_rate: 'heart_rate',
|
||||
blood_sugar: 'blood_sugar',
|
||||
weight: 'weight',
|
||||
};
|
||||
const points = await fetchTrend(indicatorMap[type], '7d');
|
||||
setTrendData(points);
|
||||
} catch (err) {
|
||||
console.warn('[health] 加载趋势数据失败:', err);
|
||||
setTrendData([]);
|
||||
} finally {
|
||||
setTrendLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAiSuggestions = async () => {
|
||||
try {
|
||||
const items = await listPendingSuggestions();
|
||||
setAiSuggestions(items.slice(0, 3));
|
||||
} catch {
|
||||
setAiSuggestions([]);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAlertCount = async () => {
|
||||
const patientId = useAuthStore.getState().currentPatient?.id;
|
||||
if (!patientId) return;
|
||||
try {
|
||||
const res = await listPatientAlerts(patientId, { status: 'pending', page: 1, page_size: 1 });
|
||||
setAlertCount(res.total ?? 0);
|
||||
} catch {
|
||||
setAlertCount(0);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchData = async () => {
|
||||
const results = await Promise.allSettled([
|
||||
refreshToday(),
|
||||
loadTrend(activeTab),
|
||||
loadAiSuggestions(),
|
||||
loadAlertCount(),
|
||||
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); }),
|
||||
]);
|
||||
return results;
|
||||
};
|
||||
|
||||
usePageData(fetchData, {
|
||||
throttleMs: 5000,
|
||||
enablePullDown: true,
|
||||
enabled: !!user,
|
||||
});
|
||||
|
||||
const handleTabChange = (tab: VitalType) => {
|
||||
setActiveTab(tab);
|
||||
loadTrend(tab);
|
||||
};
|
||||
|
||||
return {
|
||||
user,
|
||||
todaySummary,
|
||||
loading,
|
||||
error: false,
|
||||
activeTab,
|
||||
trendData,
|
||||
trendLoading,
|
||||
aiSuggestions,
|
||||
thresholds,
|
||||
alertCount,
|
||||
handleTabChange,
|
||||
fetchData,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user