diff --git a/apps/miniprogram/src/app.tsx b/apps/miniprogram/src/app.tsx index b381a43..879dd81 100644 --- a/apps/miniprogram/src/app.tsx +++ b/apps/miniprogram/src/app.tsx @@ -22,12 +22,12 @@ function App({ children }: PropsWithChildren>) { // 暴露全局 bridge 供 MCP/自动化测试调用(仅 dev 模式) useEffect(() => { if (process.env.NODE_ENV === 'production') return; - (globalThis as any).__hms = { + (globalThis as Record).__hms = { restoreAuth: () => { restoreAuth(); return useAuthStore.getState(); }, restoreUI, getAuthState: () => useAuthStore.getState(), }; - return () => { delete (globalThis as any).__hms; }; + return () => { delete (globalThis as Record).__hms; }; }, []); // Analytics 定时器:仅在页面可见时运行,后台时暂停以节省资源 diff --git a/apps/miniprogram/src/components/TrendChart/index.tsx b/apps/miniprogram/src/components/TrendChart/index.tsx index 1f6dd99..d2ab11f 100644 --- a/apps/miniprogram/src/components/TrendChart/index.tsx +++ b/apps/miniprogram/src/components/TrendChart/index.tsx @@ -46,6 +46,7 @@ export default React.memo(function TrendChart({ height = 500, }: TrendChartProps) { const tokens = useCanvasTokens(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const canvasRef = useRef(null); const [tooltip, setTooltip] = useState<{ date: string; value: number; x: number } | null>(null); @@ -193,7 +194,7 @@ export default React.memo(function TrendChart({ ctx.restore(); }, [data, referenceMin, referenceMax, tokens]); - const handleTouchStart = useCallback((e: any) => { + const handleTouchStart = useCallback((e: { touches: Array<{ x: number }> }) => { if (!data || data.length === 0 || !canvasRef.current) return; const touch = e.touches[0]; const node = canvasRef.current; diff --git a/apps/miniprogram/src/pages/appointment/create/index.tsx b/apps/miniprogram/src/pages/appointment/create/index.tsx index 71895e3..4f156a8 100644 --- a/apps/miniprogram/src/pages/appointment/create/index.tsx +++ b/apps/miniprogram/src/pages/appointment/create/index.tsx @@ -37,6 +37,16 @@ interface TimeSlot { available_count: number; } +interface ScheduleItem { + date?: string; + appointment_date?: string; + start_time?: string; + end_time?: string; + available_count?: number; + max_appointments?: number; + current_appointments?: number; +} + export default function AppointmentCreate() { const [currentStep, setCurrentStep] = useState(0); const [department, setDepartment] = useState(''); @@ -47,7 +57,7 @@ export default function AppointmentCreate() { const [reason, setReason] = useState(''); const [loading, setLoading] = useState(false); const { safeSetTimeout } = useSafeTimeout(); - const [schedules, setSchedules] = useState([]); + const [schedules, setSchedules] = useState([]); const [timeSlots, setTimeSlots] = useState([]); const modeClass = useElderClass(); @@ -55,7 +65,7 @@ export default function AppointmentCreate() { const scheduledDates = useMemo(() => { if (!schedules) return new Set(); - return new Set(schedules.map((s: any) => s.date || s.appointment_date)); + return new Set(schedules.map((s) => s.date || s.appointment_date || '')); }, [schedules]); const onSelectDept = useCallback(async (dept: string) => { @@ -91,12 +101,12 @@ export default function AppointmentCreate() { setAppointmentDate(date); setTimeSlot(''); const daySlots = schedules - .filter((s: any) => (s.date || s.appointment_date) === date) - .map((s: any) => ({ + .filter((s) => (s.date || s.appointment_date) === date) + .map((s) => ({ start_time: s.start_time || '', end_time: s.end_time || '', label: `${s.start_time || ''}-${s.end_time || ''}`, - available_count: s.available_count ?? (s.max_appointments - (s.current_appointments || 0)), + available_count: s.available_count ?? (s.max_appointments ?? 0) - (s.current_appointments ?? 0), })); setTimeSlots(daySlots); }, [schedules]); @@ -129,12 +139,12 @@ export default function AppointmentCreate() { const tmplId = TEMPLATE_IDS.APPOINTMENT_REMINDER; if (tmplId) { try { - await (Taro.requestSubscribeMessage as any)({ tmplIds: [tmplId] }); + await (Taro as { requestSubscribeMessage: (opts: { tmplIds: string[] }) => Promise }).requestSubscribeMessage({ tmplIds: [tmplId] }); } catch { /* 用户拒绝 */ } } safeSetTimeout(() => Taro.navigateBack(), 1500); - } catch (err: any) { - const msg = err?.message || '预约失败'; + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '预约失败'; Taro.showToast({ title: msg.length > 20 ? msg.slice(0, 20) : msg, icon: 'none' }); } finally { setLoading(false); diff --git a/apps/miniprogram/src/pages/consultation/create/index.tsx b/apps/miniprogram/src/pages/consultation/create/index.tsx index de624c5..7199eb2 100644 --- a/apps/miniprogram/src/pages/consultation/create/index.tsx +++ b/apps/miniprogram/src/pages/consultation/create/index.tsx @@ -34,7 +34,7 @@ export default function ConsultationCreate() { if (doctorsLoaded) return; try { const res = await listDoctors(); - const items = (res.data || []).map((d: any) => ({ id: d.id, name: d.name })); + const items = (res.data || []).map((d: { id: string; name: string }) => ({ id: d.id, name: d.name })); setDoctorList(items); setDoctorsLoaded(true); } catch (err) { diff --git a/apps/miniprogram/src/pages/login/index.tsx b/apps/miniprogram/src/pages/login/index.tsx index f99ca4b..dbd13a9 100644 --- a/apps/miniprogram/src/pages/login/index.tsx +++ b/apps/miniprogram/src/pages/login/index.tsx @@ -86,8 +86,8 @@ export default function Login() { setNeedBind(true); Taro.showToast({ title: '请授权手机号完成绑定', icon: 'none' }); } - } catch (err: any) { - const msg = err?.message || '登录失败,请重试'; + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '登录失败,请重试'; Taro.showToast({ title: msg.substring(0, 20), icon: 'none', duration: 3000 }); } }; @@ -104,8 +104,8 @@ export default function Login() { if (success) { navigateAfterLogin(); } - } catch (err: any) { - Taro.showToast({ title: err?.message || '登录失败', icon: 'none' }); + } catch (err: unknown) { + Taro.showToast({ title: err instanceof Error ? err.message : '登录失败', icon: 'none' }); } }; @@ -120,10 +120,10 @@ export default function Login() { if (success) { navigateAfterLogin(); } - } catch (err: any) { + } catch (err: unknown) { Taro.showModal({ title: '绑定手机号失败', - content: err?.message || '绑定失败', + content: err instanceof Error ? err.message : '绑定失败', confirmText: '重新登录', cancelText: '取消', success: (res) => { if (res.confirm) setNeedBind(false); }, diff --git a/apps/miniprogram/src/pages/pkg-doctor-clinical/dialysis/create/index.tsx b/apps/miniprogram/src/pages/pkg-doctor-clinical/dialysis/create/index.tsx index aa46372..4b7c9a5 100644 --- a/apps/miniprogram/src/pages/pkg-doctor-clinical/dialysis/create/index.tsx +++ b/apps/miniprogram/src/pages/pkg-doctor-clinical/dialysis/create/index.tsx @@ -21,7 +21,7 @@ function InputField({ label, value, placeholder, type = 'digit', onChange }: { {label} onChange(e.detail.value)} diff --git a/apps/miniprogram/src/pages/pkg-doctor-clinical/prescription/create/index.tsx b/apps/miniprogram/src/pages/pkg-doctor-clinical/prescription/create/index.tsx index cc6c338..6ab26d3 100644 --- a/apps/miniprogram/src/pages/pkg-doctor-clinical/prescription/create/index.tsx +++ b/apps/miniprogram/src/pages/pkg-doctor-clinical/prescription/create/index.tsx @@ -111,7 +111,7 @@ export default function PrescriptionCreate() { {label} updateField(field, e.detail.value)} diff --git a/apps/miniprogram/src/pages/pkg-health/device-sync/index.tsx b/apps/miniprogram/src/pages/pkg-health/device-sync/index.tsx index e5664ce..25b49d3 100644 --- a/apps/miniprogram/src/pages/pkg-health/device-sync/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/device-sync/index.tsx @@ -66,9 +66,9 @@ export default function DeviceSync() { setLastSyncAt(scheduler.getLastSyncAt()); // 检查是否有未上传的缓冲数据 - const buffer = (bleManager as any).dataBuffer; + const buffer = (bleManager as unknown as { dataBuffer?: Map }).dataBuffer; if (buffer) { - setPendingCount(buffer.size()); + setPendingCount(buffer.size); } // 自动同步:超过间隔时尝试上传缓冲数据 @@ -105,8 +105,8 @@ export default function DeviceSync() { setErrorMsg('未发现支持的设备,请确认设备已开启蓝牙并靠近手机'); } setPageState('idle'); - } catch (e: any) { - setErrorMsg(e.message || '扫描失败'); + } catch (e: unknown) { + setErrorMsg(e instanceof Error ? e.message : '扫描失败'); setPageState('error'); } }, []); @@ -118,8 +118,8 @@ export default function DeviceSync() { try { await getBleManager().connect(device); setPageState('connected'); - } catch (e: any) { - setErrorMsg(e.message || '连接失败'); + } catch (e: unknown) { + setErrorMsg(e instanceof Error ? e.message : '连接失败'); setPageState('error'); } }, []); @@ -169,8 +169,8 @@ export default function DeviceSync() { setErrorMsg(result.error || '同步失败'); setPageState('error'); } - } catch (e: any) { - setErrorMsg(e.message || '同步失败'); + } catch (e: unknown) { + setErrorMsg(e instanceof Error ? e.message : '同步失败'); setPageState('error'); } }, [currentPatient, selectedDevice, liveReadings, returnTo]); diff --git a/apps/miniprogram/src/pages/pkg-health/input/index.tsx b/apps/miniprogram/src/pages/pkg-health/input/index.tsx index 4ece65d..0eabab8 100644 --- a/apps/miniprogram/src/pages/pkg-health/input/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/input/index.tsx @@ -147,7 +147,7 @@ export default function HealthInput() { const input = BP_INDICATORS.includes(currentIndicator) ? { indicator_type: currentIndicator as 'blood_pressure' | 'blood_pressure_evening', value: parseFloat(systolic), extra: { systolic: parseFloat(systolic), diastolic: parseFloat(diastolic) } } - : { indicator_type: currentIndicator as any, value: parseFloat(value) }; + : { indicator_type: currentIndicator as 'heart_rate' | 'blood_sugar_fasting' | 'blood_sugar_postprandial' | 'weight' | 'temperature', value: parseFloat(value) }; const valueResult = valueCheck.safeParse(input.value); if (!valueResult.ok) { diff --git a/apps/miniprogram/src/pages/pkg-profile/events/index.tsx b/apps/miniprogram/src/pages/pkg-profile/events/index.tsx index 93e725c..8c29e21 100644 --- a/apps/miniprogram/src/pages/pkg-profile/events/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/events/index.tsx @@ -43,8 +43,8 @@ export default function EventsPage() { await pointsApi.registerEvent(event.id); Taro.showToast({ title: '报名成功', icon: 'success' }); loadEvents(); - } catch (err: any) { - const msg = err?.message || '报名失败'; + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : '报名失败'; Taro.showToast({ title: msg.substring(0, 20), icon: 'none' }); } finally { setRegistering(null); diff --git a/apps/miniprogram/src/pages/pkg-profile/followups/detail/index.tsx b/apps/miniprogram/src/pages/pkg-profile/followups/detail/index.tsx index b51d7d9..a9881ee 100644 --- a/apps/miniprogram/src/pages/pkg-profile/followups/detail/index.tsx +++ b/apps/miniprogram/src/pages/pkg-profile/followups/detail/index.tsx @@ -55,7 +55,7 @@ export default function FollowUpDetail() { trackEvent('followup_submit', { task_id: id }); const tmplId = TEMPLATE_IDS.FOLLOWUP_REMINDER; if (tmplId) { - try { await (Taro.requestSubscribeMessage as any)({ tmplIds: [tmplId] }); } catch { /* 用户拒绝 */ } + try { await (Taro as { requestSubscribeMessage: (opts: { tmplIds: string[] }) => Promise }).requestSubscribeMessage({ tmplIds: [tmplId] }); } catch { /* 用户拒绝 */ } } setContent(''); } catch (err) { diff --git a/apps/miniprogram/src/services/ble/BLEManager.ts b/apps/miniprogram/src/services/ble/BLEManager.ts index 6d19104..6e6a13e 100644 --- a/apps/miniprogram/src/services/ble/BLEManager.ts +++ b/apps/miniprogram/src/services/ble/BLEManager.ts @@ -7,6 +7,10 @@ import type { NormalizedReading, SyncResult, BLEManagerConfig, + BLEScanResult, + BLEConnectionChangeResult, + BLECharacteristicChangeResult, + BLEServiceItem, } from './types'; import { DataBuffer } from './DataBuffer'; @@ -27,8 +31,8 @@ export class BLEManager { private scanTimer: ReturnType | null = null; private onConnectionChange?: (state: BLEConnectionState) => void; private onReadings?: (readings: NormalizedReading[]) => void; - private connChangeHandler: ((res: any) => void) | null = null; - private charChangeHandler: ((res: any) => void) | null = null; + private connChangeHandler: ((res: BLEConnectionChangeResult) => void) | null = null; + private charChangeHandler: ((res: BLECharacteristicChangeResult) => void) | null = null; constructor(config?: Partial) { this.config = { ...DEFAULT_CONFIG, ...config }; @@ -70,8 +74,9 @@ export class BLEManager { async initialize(): Promise { try { await Taro.openBluetoothAdapter(); - } catch (e: any) { - throw new Error(e.errMsg || '蓝牙初始化失败,请检查蓝牙是否开启'); + } catch (e: unknown) { + const errMsg = e instanceof Error ? e.message : (e as { errMsg?: string })?.errMsg || '蓝牙初始化失败,请检查蓝牙是否开启'; + throw new Error(errMsg); } } @@ -81,7 +86,7 @@ export class BLEManager { const discovered = new Map(); - const onFound = (res: any) => { + const onFound = (res: BLEScanResult) => { for (const device of res.devices || []) { const name = device.name || device.localName || ''; if (!name) continue; @@ -90,7 +95,7 @@ export class BLEManager { discovered.set(device.deviceId, { deviceId: device.deviceId, name, - RSSI: device.RSSI, + RSSI: device.RSSI ?? 0, localName: device.localName, advertisData: device.advertisData, adapter, @@ -156,7 +161,7 @@ export class BLEManager { } // 监听断连 - this.connChangeHandler = (res: any) => { + this.connChangeHandler = (res: BLEConnectionChangeResult) => { if (res.deviceId === device.deviceId && !res.connected) { this.updateState('disconnected', '设备断开连接'); this.connection = null; @@ -170,7 +175,7 @@ export class BLEManager { // 启用通知 for (const { service: svcUUID, characteristic: charUUID } of device.adapter.notifyCharacteristics) { - const svc = services.find((s: any) => s.uuid.toUpperCase().includes(svcUUID.toUpperCase())); + const svc = services.find((s: BLEServiceItem) => s.uuid.toUpperCase().includes(svcUUID.toUpperCase())); if (!svc) continue; await Taro.getBLEDeviceCharacteristics({ @@ -187,7 +192,7 @@ export class BLEManager { } // 监听数据通知 - this.charChangeHandler = (res: any) => { + this.charChangeHandler = (res: BLECharacteristicChangeResult) => { if (res.deviceId !== device.deviceId) return; const newReadings = device.adapter!.parseNotification( res.serviceId, @@ -207,10 +212,12 @@ export class BLEManager { this.connection = { ...this.connection, state: 'connected', connectedAt: Date.now() }; this.updateState('connected'); - } catch (e: any) { - this.updateState('error', e.errMsg || e.message || '连接失败'); + } catch (e: unknown) { + const errMsg = (e as { errMsg?: string })?.errMsg; + const msg = errMsg || (e instanceof Error ? e.message : '') || '连接失败'; + this.updateState('error', msg); this.connection = null; - throw new Error(e.errMsg || '蓝牙连接失败'); + throw new Error(errMsg || '蓝牙连接失败'); } } @@ -227,7 +234,7 @@ export class BLEManager { const services = servicesRes.services || []; for (const { service: svcUUID, characteristic: charUUID } of adapter.readCharacteristics) { - const svc = services.find((s: any) => s.uuid.toUpperCase().includes(svcUUID.toUpperCase())); + const svc = services.find((s: BLEServiceItem) => s.uuid.toUpperCase().includes(svcUUID.toUpperCase())); if (!svc) continue; try { @@ -274,8 +281,8 @@ export class BLEManager { readingsCount: batch.length, uploadedCount: uploaded, }; - } catch (e: any) { - lastError = e.message || '上传失败'; + } catch (e: unknown) { + lastError = e instanceof Error ? e.message : '上传失败'; // flush 已取出,失败时需要放回 this.dataBuffer.push(batch); } diff --git a/apps/miniprogram/src/services/ble/types.ts b/apps/miniprogram/src/services/ble/types.ts index b02079b..9f456fb 100644 --- a/apps/miniprogram/src/services/ble/types.ts +++ b/apps/miniprogram/src/services/ble/types.ts @@ -97,3 +97,34 @@ export type GenericBLEProfile = | 'heart_rate' // Heart Rate Service (0x180D) | 'health_thermometer' // Health Thermometer Service (0x1809) | 'blood_pressure'; // Blood Pressure Service (0x1810) + +/** 微信 BLE 扫描回调结果 */ +export interface BLEScanResult { + devices: Array<{ + deviceId: string; + name?: string; + RSSI?: number; + localName?: string; + advertisData?: ArrayBuffer; + }>; +} + +/** 微信 BLE 连接状态变更回调结果 */ +export interface BLEConnectionChangeResult { + deviceId: string; + connected: boolean; +} + +/** 微信 BLE 特征值变更回调结果 */ +export interface BLECharacteristicChangeResult { + deviceId: string; + serviceId: string; + characteristicId: string; + value: ArrayBuffer; +} + +/** 微信 BLE 服务描述(getBLEDeviceServices 返回项) */ +export interface BLEServiceItem { + uuid: string; + isPrimary?: boolean; +} diff --git a/apps/miniprogram/src/services/request.ts b/apps/miniprogram/src/services/request.ts index 1789df8..b6f6624 100644 --- a/apps/miniprogram/src/services/request.ts +++ b/apps/miniprogram/src/services/request.ts @@ -243,10 +243,10 @@ async function request(method: string, path: string, data?: unknown, timeout? const url = `${BASE_URL}${path}`; let res: Taro.request.SuccessCallbackResult; try { - res = await Taro.request({ url, method: method as any, data, header: headers, timeout: timeout || 15000 }); - } catch (err: any) { + res = await Taro.request({ url, method: method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS', data, header: headers, timeout: timeout || 15000 }); + } catch (err: unknown) { if (signal?.aborted) throw new Error('请求已取消'); - const msg = err?.errMsg || ''; + const msg = (err as { errMsg?: string })?.errMsg || ''; if (msg.includes('timeout')) { Taro.showToast({ title: '网络超时,请重试', icon: 'none' }); throw new Error('网络超时');