diff --git a/apps/miniprogram/config/prod.ts b/apps/miniprogram/config/prod.ts index 1925f04..8a43222 100644 --- a/apps/miniprogram/config/prod.ts +++ b/apps/miniprogram/config/prod.ts @@ -8,7 +8,7 @@ export default { compress: { drop_console: true, drop_debugger: true, - pure_funcs: ['console.log', 'console.info', 'console.debug'], + pure_funcs: ['console.log', 'console.info', 'console.debug', 'console.warn', 'console.error'], }, format: { comments: false, diff --git a/apps/miniprogram/src/components/DeviceCard/index.scss b/apps/miniprogram/src/components/DeviceCard/index.scss deleted file mode 100644 index 4da8394..0000000 --- a/apps/miniprogram/src/components/DeviceCard/index.scss +++ /dev/null @@ -1,51 +0,0 @@ -@import '../../styles/variables.scss'; - -.device-card { - display: flex; - align-items: center; - padding: 24rpx; - background: $card; - border-radius: $r; - margin-bottom: 16rpx; - box-shadow: $shadow-sm; - - .device-icon { - font-size: var(--tk-font-h2); - margin-right: 20rpx; - } - - .device-info { - flex: 1; - - .device-name { - font-size: var(--tk-font-cap); - font-weight: 600; - color: $tx; - display: block; - } - - .device-status { - font-size: var(--tk-font-micro); - margin-top: 4rpx; - display: block; - - &.connected { color: $pri; } - &.idle { color: $tx3; } - } - - .last-sync { - font-size: var(--tk-font-micro); - color: $tx3; - margin-top: 4rpx; - display: block; - } - } - - .sync-btn { - padding: 12rpx 28rpx; - background: $pri; - color: $white; - border-radius: $r-pill; - font-size: var(--tk-font-micro); - } -} diff --git a/apps/miniprogram/src/components/DeviceCard/index.tsx b/apps/miniprogram/src/components/DeviceCard/index.tsx deleted file mode 100644 index 2166575..0000000 --- a/apps/miniprogram/src/components/DeviceCard/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { View, Text } from '@tarojs/components'; -import Taro from '@tarojs/taro'; -import './index.scss'; - -interface DeviceCardProps { - deviceName: string; - deviceType: string; - lastSyncAt?: string; - status: 'connected' | 'disconnected' | 'never'; -} - -const DEVICE_ICONS: Record = { - blood_pressure: '\u{1FA7A}', - blood_glucose: '\u{1FA78}', - heart_rate: '\u{2764}', - blood_oxygen: '\u{1FAB1}', -}; - -export default function DeviceCard({ deviceName, deviceType, lastSyncAt, status }: DeviceCardProps) { - const icon = DEVICE_ICONS[deviceType] || '\u{1F4F1}'; - const statusLabel = status === 'connected' ? '已连接' : status === 'disconnected' ? '未连接' : '未配对'; - const statusClass = status === 'connected' ? 'connected' : 'idle'; - - const handleSync = () => { - Taro.navigateTo({ url: '/pages/pkg-health/device-sync/index' }); - }; - - return ( - - {icon} - - {deviceName} - {statusLabel} - {lastSyncAt && 最近同步: {lastSyncAt}} - - 同步 - - ); -} diff --git a/apps/miniprogram/src/hooks/usePageData.ts b/apps/miniprogram/src/hooks/usePageData.ts index ed0926f..0962831 100644 --- a/apps/miniprogram/src/hooks/usePageData.ts +++ b/apps/miniprogram/src/hooks/usePageData.ts @@ -1,4 +1,4 @@ -import { useRef, useCallback } from 'react'; +import { useRef, useState, useCallback } from 'react'; import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; interface UsePageDataOptions { @@ -22,6 +22,7 @@ export function usePageData( const enabled = options?.enabled ?? true; const loadingRef = useRef(false); + const [loading, setLoading] = useState(false); const lastRunRef = useRef(0); const fetcherRef = useRef(fetcher); fetcherRef.current = fetcher; @@ -30,11 +31,13 @@ export function usePageData( if (!enabled || loadingRef.current) return; if (!force && Date.now() - lastRunRef.current < throttleMs) return; loadingRef.current = true; + setLoading(true); lastRunRef.current = Date.now(); try { await fetcherRef.current(); } finally { loadingRef.current = false; + setLoading(false); } }, [enabled, throttleMs]); @@ -49,11 +52,13 @@ export function usePageData( const refresh = useCallback(async () => { if (loadingRef.current) return; loadingRef.current = true; + setLoading(true); lastRunRef.current = Date.now(); try { await fetcherRef.current(); } finally { loadingRef.current = false; + setLoading(false); } }, []); @@ -66,5 +71,5 @@ export function usePageData( } }); - return { loading: loadingRef.current, refresh, trigger }; + return { loading, refresh, trigger }; } diff --git a/apps/miniprogram/src/pages/legal/privacy-policy.tsx b/apps/miniprogram/src/pages/legal/privacy-policy.tsx index a787ed6..81b0e87 100644 --- a/apps/miniprogram/src/pages/legal/privacy-policy.tsx +++ b/apps/miniprogram/src/pages/legal/privacy-policy.tsx @@ -23,9 +23,9 @@ const PRIVACY_CONTENT = `

三、信息存储与保护

1. 您的信息存储在中华人民共和国境内的安全服务器中

-

2. 我们采用加密传输(HTTPS)和加密存储等安全措施

+

2. 我们采用加密传输(HTTPS)确保数据在传输过程中的安全

3. 严格的内部数据访问权限控制

-

4. Token 等敏感凭证采用混淆加密存储

+

4. Token 等敏感凭证通过 HTTPS 加密传输,本地存储依赖微信小程序安全沙箱保护

四、信息共享

未经您的同意,我们不会与任何第三方共享您的个人信息,以下情况除外:

diff --git a/apps/miniprogram/src/pages/pkg-health/daily-monitoring/constants.ts b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/constants.ts new file mode 100644 index 0000000..924d2de --- /dev/null +++ b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/constants.ts @@ -0,0 +1,42 @@ +export const BP_RANGE = { min: 30, minMsg: '血压值不能低于30', max: 300, maxMsg: '血压值不能高于300', optional: true }; +export const WEIGHT_RANGE = { min: 1, minMsg: '体重不能低于1kg', max: 500, maxMsg: '体重不能高于500kg', optional: true }; +export const SUGAR_RANGE = { min: 0.1, minMsg: '血糖值不能低于0.1', max: 50, maxMsg: '血糖值不能高于50', optional: true }; +export const VOLUME_RANGE = { min: 0, minMsg: '数值不能为负', max: 10000, maxMsg: '数值超出合理范围', optional: true }; + +export const REFERENCE_RANGES: Record = { + systolic: { min: 90, max: 140 }, + diastolic: { min: 60, max: 90 }, + bloodSugar: { min: 3.9, max: 6.1 }, + weight: null, + fluidIntake: null, + urineOutput: null, +}; + +export type AbnormalResult = { abnormal: boolean; direction: 'high' | 'low' | null }; + +export const checkAbnormal = (value: string, field: string): AbnormalResult => { + const ref = REFERENCE_RANGES[field]; + if (!value || !ref) return { abnormal: false, direction: null }; + const num = parseFloat(value); + if (isNaN(num)) return { abnormal: false, direction: null }; + if (num > ref.max) return { abnormal: true, direction: 'high' }; + if (num < ref.min) return { abnormal: true, direction: 'low' }; + return { abnormal: false, direction: null }; +}; + +export type SectionKey = 'morning' | 'evening' | 'other'; + +export const FIELD_LABELS: Record = { + morningSystolic: '晨间收缩压', + morningDiastolic: '晨间舒张压', + eveningSystolic: '晚间收缩压', + eveningDiastolic: '晚间舒张压', + bloodSugar: '血糖', +}; + +export 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}`; +} diff --git a/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx index 251cdb6..869716a 100644 --- a/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/daily-monitoring/index.tsx @@ -9,56 +9,14 @@ import { usePointsStore } from '@/stores/points'; import { clearRequestCache } from '@/services/request'; import { trackEvent } from '@/services/analytics'; import { useSafeTimeout } from '@/hooks/useSafeTimeout'; -import { useElderClass } from '../../../hooks/useElderClass'; +import { useElderClass } from '@/hooks/useElderClass'; +import { + BP_RANGE, WEIGHT_RANGE, SUGAR_RANGE, VOLUME_RANGE, + checkAbnormal, formatDate, FIELD_LABELS, + type SectionKey, type AbnormalResult, +} from './constants'; import './index.scss'; -const BP_RANGE = { min: 30, minMsg: '血压值不能低于30', max: 300, maxMsg: '血压值不能高于300', optional: true }; -const WEIGHT_RANGE = { min: 1, minMsg: '体重不能低于1kg', max: 500, maxMsg: '体重不能高于500kg', optional: true }; -const SUGAR_RANGE = { min: 0.1, minMsg: '血糖值不能低于0.1', max: 50, maxMsg: '血糖值不能高于50', optional: true }; -const VOLUME_RANGE = { min: 0, minMsg: '数值不能为负', max: 10000, maxMsg: '数值超出合理范围', optional: true }; - -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}`; -} - -// ── Abnormal value detection ── - -const REFERENCE_RANGES: Record = { - systolic: { min: 90, max: 140 }, - diastolic: { min: 60, max: 90 }, - bloodSugar: { min: 3.9, max: 6.1 }, - weight: null, - fluidIntake: null, - urineOutput: null, -}; - -type AbnormalResult = { abnormal: boolean; direction: 'high' | 'low' | null }; - -const checkAbnormal = (value: string, field: string): AbnormalResult => { - const ref = REFERENCE_RANGES[field]; - if (!value || !ref) return { abnormal: false, direction: null }; - const num = parseFloat(value); - if (isNaN(num)) return { abnormal: false, direction: null }; - if (num > ref.max) return { abnormal: true, direction: 'high' }; - if (num < ref.min) return { abnormal: true, direction: 'low' }; - return { abnormal: false, direction: null }; -}; - -// ── Section state type ── - -type SectionKey = 'morning' | 'evening' | 'other'; - -const FIELD_LABELS: Record = { - morningSystolic: '晨间收缩压', - morningDiastolic: '晨间舒张压', - eveningSystolic: '晚间收缩压', - eveningDiastolic: '晚间舒张压', - bloodSugar: '血糖', -}; - export default function DailyMonitoring() { const modeClass = useElderClass(); const currentPatient = useAuthStore((s) => s.currentPatient); diff --git a/apps/miniprogram/src/pages/pkg-profile/consents/index.tsx b/apps/miniprogram/src/pages/pkg-profile/consents/index.tsx index 1fca613..11bb2d7 100644 --- a/apps/miniprogram/src/pages/pkg-profile/consents/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/consents/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listConsents, revokeConsent } from '@/services/consent'; import type { Consent } from '@/services/consent'; import EmptyState from '@/components/EmptyState'; @@ -33,7 +34,7 @@ export default function ConsentList() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setConsents([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/diagnoses/index.tsx b/apps/miniprogram/src/pages/pkg-profile/diagnoses/index.tsx index e2f7306..2a52610 100644 --- a/apps/miniprogram/src/pages/pkg-profile/diagnoses/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/diagnoses/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listDiagnoses, Diagnosis } from '../../../services/health-record'; import EmptyState from '../../../components/EmptyState'; import Loading from '../../../components/Loading'; @@ -29,7 +30,7 @@ export default function Diagnoses() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setRecords([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/dialysis-prescriptions/index.tsx b/apps/miniprogram/src/pages/pkg-profile/dialysis-prescriptions/index.tsx index 77f8c3d..28df6ce 100644 --- a/apps/miniprogram/src/pages/pkg-profile/dialysis-prescriptions/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/dialysis-prescriptions/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listDialysisPrescriptions } from '@/services/dialysis'; import type { DialysisPrescription } from '@/services/dialysis'; import EmptyState from '@/components/EmptyState'; @@ -24,7 +25,7 @@ export default function DialysisPrescriptionList() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setPrescriptions([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/dialysis-records/index.tsx b/apps/miniprogram/src/pages/pkg-profile/dialysis-records/index.tsx index 7b3632c..eefcad2 100644 --- a/apps/miniprogram/src/pages/pkg-profile/dialysis-records/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/dialysis-records/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listDialysisRecords } from '@/services/dialysis'; import type { DialysisRecord } from '@/services/dialysis'; import EmptyState from '@/components/EmptyState'; @@ -30,7 +31,7 @@ export default function DialysisRecordList() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setRecords([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/health-records/index.tsx b/apps/miniprogram/src/pages/pkg-profile/health-records/index.tsx index e0fe119..611e2e0 100644 --- a/apps/miniprogram/src/pages/pkg-profile/health-records/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/health-records/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listHealthRecords, HealthRecord } from '../../../services/health-record'; import EmptyState from '../../../components/EmptyState'; import Loading from '../../../components/Loading'; @@ -23,7 +24,7 @@ export default function HealthRecords() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setRecords([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx b/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx index c9e38a7..e42ef41 100644 --- a/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/medication/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text, Input, Picker } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import EmptyState from '../../../components/EmptyState'; import { listReminders, @@ -69,7 +70,7 @@ export default function MedicationReminder() { Taro.showToast({ title: '请输入药品名称', icon: 'none' }); return; } - const patientId = Taro.getStorageSync('current_patient_id'); + const patientId = getCachedPatientId(); if (!patientId) { Taro.showToast({ title: '请先绑定患者档案', icon: 'none' }); return; diff --git a/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx b/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx index 3273dbe..e04a8f8 100644 --- a/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/reports/index.tsx @@ -2,6 +2,7 @@ import React, { useState, useCallback } from 'react'; import { View, Text } from '@tarojs/components'; import Taro, { useReachBottom } from '@tarojs/taro'; import { usePageData } from '@/hooks/usePageData'; +import { getCachedPatientId } from '@/services/request'; import { listReports, LabReport } from '../../../services/report'; import EmptyState from '../../../components/EmptyState'; import Loading from '../../../components/Loading'; @@ -17,7 +18,7 @@ export default function MyReports() { const [hasPatient, setHasPatient] = useState(true); const fetchData = useCallback(async (p: number, append = false) => { - const patientId = Taro.getStorageSync('current_patient_id') || ''; + const patientId = getCachedPatientId(); if (!patientId) { setReports([]); setHasPatient(false); diff --git a/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx b/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx index 823e4ba..870cce0 100644 --- a/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/settings/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, Text } from '@tarojs/components'; import Taro from '@tarojs/taro'; import { useAuthStore } from '../../../stores/auth'; +import { invalidateHeadersCache, clearRequestCache } from '@/services/request'; import { useElderClass } from '../../../hooks/useElderClass'; import './index.scss'; @@ -35,6 +36,8 @@ export default function Settings() { ), ); + clearRequestCache(); + invalidateHeadersCache(); Taro.showToast({ title: '缓存已清除', icon: 'success' }); }; diff --git a/apps/miniprogram/src/services/doctor/appointment.ts b/apps/miniprogram/src/services/doctor/appointment.ts index e49745d..f586713 100644 --- a/apps/miniprogram/src/services/doctor/appointment.ts +++ b/apps/miniprogram/src/services/doctor/appointment.ts @@ -1,4 +1,5 @@ import { api } from '../request'; +import type { Appointment } from '../appointment'; // ── Appointments (doctor view) ───────────────────── @@ -7,6 +8,7 @@ export async function listAppointments(params?: { page_size?: number; status?: string; date?: string; + patient_id?: string; }) { - return api.get<{ data: any[]; total: number }>('/health/appointments', params); + return api.get<{ data: Appointment[]; total: number }>('/health/appointments', params); } diff --git a/apps/miniprogram/src/services/medication-reminder.ts b/apps/miniprogram/src/services/medication-reminder.ts index 4a54467..9e30f28 100644 --- a/apps/miniprogram/src/services/medication-reminder.ts +++ b/apps/miniprogram/src/services/medication-reminder.ts @@ -1,5 +1,4 @@ -import Taro from '@tarojs/taro'; -import { api } from './request'; +import { api, getCachedPatientId } from './request'; export interface MedicationReminder { id: string; @@ -42,7 +41,7 @@ export interface UpdateReminderReq { } function getPatientId(): string { - return Taro.getStorageSync('current_patient_id') || ''; + return getCachedPatientId(); } export async function listReminders() { diff --git a/apps/miniprogram/src/services/request.ts b/apps/miniprogram/src/services/request.ts index 1570304..2942745 100644 --- a/apps/miniprogram/src/services/request.ts +++ b/apps/miniprogram/src/services/request.ts @@ -307,6 +307,10 @@ export function setCachedPatientId(id: string): void { responseCache.setPatientId(id); } +export function getCachedPatientId(): string { + return responseCache.getPatientId(); +} + export function clearRequestCache(prefix?: string): void { responseCache.clear(prefix); } diff --git a/apps/miniprogram/src/stores/health.ts b/apps/miniprogram/src/stores/health.ts index a1e2dae..817f1bd 100644 --- a/apps/miniprogram/src/stores/health.ts +++ b/apps/miniprogram/src/stores/health.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import Taro from '@tarojs/taro'; import * as healthApi from '@/services/health'; +import { getCachedPatientId } from '@/services/request'; interface CachedTrend { data: { date: string; value: number }[]; @@ -12,6 +12,7 @@ interface HealthState { todaySummaryFetchedAt: number; trendData: Record; loading: boolean; + _refreshingToday: boolean; refreshToday: (force?: boolean) => Promise; getTrend: (indicator: string, range: string) => Promise<{ date: string; value: number }[]>; clearCache: () => void; @@ -21,30 +22,26 @@ const CACHE_TTL = 5 * 60 * 1000; const TODAY_SUMMARY_TTL = 60_000; const MAX_TREND_KEYS = 20; -let refreshingToday = false; - export const useHealthStore = create((set, get) => ({ todaySummary: null, todaySummaryFetchedAt: 0, trendData: {}, loading: false, + _refreshingToday: false, refreshToday: async (force = false) => { - if (refreshingToday) return; + if (get()._refreshingToday) return; const state = get(); if (!force && state.todaySummary && Date.now() - state.todaySummaryFetchedAt < TODAY_SUMMARY_TTL) { return; } - refreshingToday = true; - set({ loading: true }); + set({ _refreshingToday: true, loading: true }); try { - const patientId = Taro.getStorageSync('current_patient_id') || undefined; + const patientId = getCachedPatientId() || undefined; const data = await healthApi.getTodaySummary(patientId); - set({ todaySummary: data, todaySummaryFetchedAt: Date.now(), loading: false }); + set({ todaySummary: data, todaySummaryFetchedAt: Date.now(), loading: false, _refreshingToday: false }); } catch { - set({ loading: false }); - } finally { - refreshingToday = false; + set({ loading: false, _refreshingToday: false }); } }, @@ -74,5 +71,5 @@ export const useHealthStore = create((set, get) => ({ } }, - clearCache: () => set({ trendData: {}, todaySummary: null, todaySummaryFetchedAt: 0 }), + clearCache: () => set({ trendData: {}, todaySummary: null, todaySummaryFetchedAt: 0, _refreshingToday: false }), }));