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

@@ -29,7 +29,7 @@
.vital-tab {
flex: 1;
height: 40px;
height: 48px;
border-radius: $r-sm;
background: $surface-alt;
@include flex-center;
@@ -273,8 +273,8 @@
}
.device-icon {
width: 44px;
height: 44px;
width: 48px;
height: 48px;
border-radius: $r-sm;
background: $pri-l;
@include flex-center;

View File

@@ -1,9 +1,10 @@
import { useState, useEffect } from 'react';
import { View, Text, Input } from '@tarojs/components';
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
import Taro, { usePullDownRefresh } from '@tarojs/taro';
import { useHealthStore } from '../../stores/health';
import { useAuthStore } from '../../stores/auth';
import { useElderClass } from '../../hooks/useElderClass';
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
import { inputVitalSign, getTrend, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../services/health';
import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis';
import Loading from '../../components/Loading';
@@ -41,8 +42,12 @@ interface TrendPoint {
}
export default function Health() {
const { todaySummary, loading, refreshToday, getTrend: fetchTrend } = useHealthStore();
const { user, currentPatient } = useAuthStore();
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 user = useAuthStore((s) => s.user);
const currentPatient = useAuthStore((s) => s.currentPatient);
const modeClass = useElderClass();
const [activeTab, setActiveTab] = useState<VitalType>('blood_pressure');
const [systolic, setSystolic] = useState('');
@@ -57,13 +62,16 @@ export default function Health() {
const [aiSuggestions, setAiSuggestions] = useState<AiSuggestionItem[]>([]);
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
useDidShow(() => {
useThrottledDidShow(() => {
if (!user) return;
refreshToday();
loadTrend(activeTab);
loadAiSuggestions();
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); });
});
// 批量发起请求,避免串行 setState 级联重渲染
Promise.allSettled([
refreshToday(),
loadTrend(activeTab),
loadAiSuggestions(),
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); }),
]);
}, 5000);
usePullDownRefresh(() => {
if (!user) return;
@@ -202,7 +210,7 @@ export default function Health() {
}
};
const maxTrendValue = Math.max(...trendData.map((d) => d.value), 1);
const maxTrendValue = trendData.reduce((max, d) => Math.max(max, d.value), 1);
const getThresholdValue = (type: VitalType, th: HealthThreshold[]): number | null => {
if (type === 'blood_pressure') return findThreshold(th, 'systolic_bp', 'high')?.threshold_value ?? 140;
@@ -228,7 +236,7 @@ export default function Health() {
} else if (first?.suggestion_type === 'followup') {
Taro.navigateTo({ url: '/pages/pkg-profile/followups/index' });
} else {
Taro.navigateTo({ url: '/pages/health/index' });
Taro.switchTab({ url: '/pages/health/index' });
}
}}>
<View className='ai-card-header'>