fix(mp): T40 UI 审计修复 — 28 项设计系统合规 + 安全加固 + 讨论记录

T40 UI 审计修复(60 页面全覆盖):
- 新增 $acc-d/$wrn-d 渐变中间色变量,修复首页轮播渐变硬编码
- 替换 8 处裸 white 为 $white 设计变量(5 个 SCSS 文件)
- 修复 7 处触摸目标 40/44px → 48px(健康/消息/咨询/预约/首页)
- 3 页面新增 Loading 状态(体征录入/个人中心/就诊人添加)
- statusTag 移除硬编码布局值,改用 SCSS mixin 控制
- 医生端 14 页面架构 Hook 层补充(useThrottledDidShow 替换 useEffect)
- 移除 action-inbox 未使用 import

安全 P0 修复:
- JWT 中间件加固:token 类型校验 + 过期预检 + 类型别名简化
- 速率限制增强:滑动窗口 + 暴力破解防护
- analytics handler 错误处理完善

文档:
- T40 审计报告(24 PASS / 36 PASS_WITH_ISSUES / 0 NEEDS_WORK)
- 5 份 DevTools/性能审计讨论记录
- wiki 症状导航 + 小程序章节更新
This commit is contained in:
iven
2026-05-14 23:12:54 +08:00
parent 447126b6c5
commit 8f353946e1
90 changed files with 2089 additions and 830 deletions

View File

@@ -1,6 +1,7 @@
import React, { useState, useCallback, useRef } from 'react';
import { View, Text } from '@tarojs/components';
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
import Taro, { usePullDownRefresh } from '@tarojs/taro';
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
import { listPatientAlerts, type Alert } from '@/services/alert';
import { useAuthStore } from '@/stores/auth';
import Loading from '@/components/Loading';
@@ -23,7 +24,7 @@ const STATUS_TABS = [
export default function PatientAlerts() {
const modeClass = useElderClass();
const { currentPatient } = useAuthStore();
const currentPatient = useAuthStore((s) => s.currentPatient);
const [alerts, setAlerts] = useState<Alert[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
@@ -60,10 +61,10 @@ export default function PatientAlerts() {
[currentPatient],
);
useDidShow(() => {
useThrottledDidShow(() => {
Taro.setNavigationBarTitle({ title: '健康告警' });
fetchAlerts(1, status, true);
});
}, 10000);
usePullDownRefresh(() => {
fetchAlerts(1, status, true).finally(() => Taro.stopPullDownRefresh());

View File

@@ -270,7 +270,7 @@
.dm-submit-text {
font-size: var(--tk-font-num);
color: white;
color: $white;
font-weight: bold;
letter-spacing: 2px;
}

View File

@@ -60,7 +60,7 @@ const FIELD_LABELS: Record<string, string> = {
export default function DailyMonitoring() {
const modeClass = useElderClass();
const { currentPatient } = useAuthStore();
const currentPatient = useAuthStore((s) => s.currentPatient);
const today = formatDate(new Date());
const [dateIdx, setDateIdx] = useState(0);

View File

@@ -226,7 +226,7 @@
.input-submit-text {
font-size: var(--tk-font-num);
color: white;
color: $white;
font-weight: bold;
letter-spacing: 2px;
}

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { View, Text, Input, Picker } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro';
import Taro from '@tarojs/taro';
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
import { num, validateStr } from '@/utils/validate';
import { inputVitalSign, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../../services/health';
import { useAuthStore } from '../../../stores/auth';
@@ -9,6 +10,7 @@ import { usePointsStore } from '@/stores/points';
import { clearRequestCache } from '@/services/request';
import { trackEvent } from '@/services/analytics';
import { useElderClass } from '../../../hooks/useElderClass';
import Loading from '../../../components/Loading';
import './index.scss';
const INDICATORS = [
@@ -59,12 +61,14 @@ export default function HealthInput() {
const [diastolic, setDiastolic] = useState('');
const [note, setNote] = useState('');
const [submitting, setSubmitting] = useState(false);
const { currentPatient } = useAuthStore();
const { clearCache } = useHealthStore();
const [loadingThresholds, setLoadingThresholds] = useState(true);
const currentPatient = useAuthStore((s) => s.currentPatient);
const clearCache = useHealthStore((s) => s.clearCache);
/** 从 storage 中读取设备同步回传的数据并自动填充表单 */
useDidShow(() => {
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); });
useThrottledDidShow(() => {
setLoadingThresholds(true);
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); }).finally(() => setLoadingThresholds(false));
try {
const raw = Taro.getStorageSync('device_sync_result');
if (!raw) return;
@@ -88,7 +92,7 @@ export default function HealthInput() {
} catch {
// 解析失败则忽略,不影响正常使用
}
});
}, 10000);
const handleSubmit = async () => {
if (!currentPatient) {
@@ -164,6 +168,10 @@ export default function HealthInput() {
return (
<View className={`input-page ${modeClass}`}>
{loadingThresholds && <Loading />}
{!loadingThresholds && (
<>
{/* 页面标题 */}
<View className='input-hero'>
<View className='input-hero-icon'>
@@ -267,6 +275,8 @@ export default function HealthInput() {
>
<Text className='input-submit-text'>{submitting ? '提交中...' : '提交录入'}</Text>
</View>
</>
)}
</View>
);
}

View File

@@ -65,7 +65,7 @@
}
.trange-tab-text-active {
color: white;
color: $white;
}
/* ── chart card ── */

View File

@@ -31,7 +31,7 @@ export default function Trend() {
const [range, setRange] = useState('7d');
const [points, setPoints] = useState<{ date: string; value: number }[]>([]);
const [loading, setLoading] = useState(true);
const { getTrend } = useHealthStore();
const getTrend = useHealthStore((s) => s.getTrend);
useEffect(() => {
setLoading(true);