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:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listConsents, revokeConsent } from '@/services/consent';
|
||||
import type { Consent } from '@/services/consent';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
@@ -29,13 +30,16 @@ export default function ConsentList() {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [revoking, setRevoking] = useState<string | null>(null);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setConsents([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listConsents(patientId, { page: p, page_size: 20 });
|
||||
@@ -50,7 +54,7 @@ export default function ConsentList() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => { fetchData(1); });
|
||||
useThrottledDidShow(() => { fetchData(1); }, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => Taro.stopPullDownRefresh());
|
||||
@@ -118,7 +122,7 @@ export default function ConsentList() {
|
||||
</View>
|
||||
|
||||
{consents.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无知情同意记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无知情同意记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listDiagnoses, Diagnosis } from '../../../services/health-record';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
@@ -25,13 +26,16 @@ export default function Diagnoses() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setRecords([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listDiagnoses(patientId, { page: p, page_size: 20 });
|
||||
@@ -46,9 +50,9 @@ export default function Diagnoses() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
useThrottledDidShow(() => {
|
||||
fetchData(1);
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => {
|
||||
@@ -94,7 +98,7 @@ export default function Diagnoses() {
|
||||
</View>
|
||||
|
||||
{records.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无诊断记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无诊断记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listDialysisPrescriptions } from '@/services/dialysis';
|
||||
import type { DialysisPrescription } from '@/services/dialysis';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
@@ -20,13 +21,16 @@ export default function DialysisPrescriptionList() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setPrescriptions([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listDialysisPrescriptions({ patient_id: patientId, page: p, page_size: 20 });
|
||||
@@ -41,7 +45,7 @@ export default function DialysisPrescriptionList() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => { fetchData(1); });
|
||||
useThrottledDidShow(() => { fetchData(1); }, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => Taro.stopPullDownRefresh());
|
||||
@@ -91,7 +95,7 @@ export default function DialysisPrescriptionList() {
|
||||
</View>
|
||||
|
||||
{prescriptions.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无透析处方' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无透析处方' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listDialysisRecords } from '@/services/dialysis';
|
||||
import type { DialysisRecord } from '@/services/dialysis';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
@@ -26,13 +27,16 @@ export default function DialysisRecordList() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setRecords([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listDialysisRecords(patientId, { page: p, page_size: 20 });
|
||||
@@ -47,7 +51,7 @@ export default function DialysisRecordList() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => { fetchData(1); });
|
||||
useThrottledDidShow(() => { fetchData(1); }, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => Taro.stopPullDownRefresh());
|
||||
@@ -96,7 +100,7 @@ export default function DialysisRecordList() {
|
||||
</View>
|
||||
|
||||
{records.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无透析记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无透析记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function FamilyAdd() {
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
Taro.showLoading({ title: '提交中...' });
|
||||
try {
|
||||
if (editId && editData) {
|
||||
await updatePatient(editId, {
|
||||
@@ -42,6 +43,7 @@ export default function FamilyAdd() {
|
||||
birth_date: birthDate || undefined,
|
||||
relation: RELATION_OPTIONS[relationIdx],
|
||||
}, editData.version);
|
||||
Taro.hideLoading();
|
||||
Taro.showToast({ title: '修改成功', icon: 'success' });
|
||||
} else {
|
||||
await createPatient({
|
||||
@@ -49,10 +51,12 @@ export default function FamilyAdd() {
|
||||
gender: GENDER_OPTIONS[genderIdx] === '男' ? 'male' : 'female',
|
||||
birth_date: birthDate || undefined,
|
||||
});
|
||||
Taro.hideLoading();
|
||||
Taro.showToast({ title: '添加成功', icon: 'success' });
|
||||
}
|
||||
setTimeout(() => Taro.navigateBack(), 1000);
|
||||
} catch {
|
||||
Taro.hideLoading();
|
||||
Taro.showToast({ title: editId ? '修改失败' : '添加失败', icon: 'none' });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listPatients, Patient } from '../../../services/patient';
|
||||
import { useAuthStore } from '../../../stores/auth';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
@@ -11,7 +12,8 @@ export default function FamilyList() {
|
||||
const modeClass = useElderClass();
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { currentPatient, setCurrentPatient } = useAuthStore();
|
||||
const currentPatient = useAuthStore((s) => s.currentPatient);
|
||||
const setCurrentPatient = useAuthStore((s) => s.setCurrentPatient);
|
||||
|
||||
const fetchPatients = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -25,9 +27,9 @@ export default function FamilyList() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
useThrottledDidShow(() => {
|
||||
fetchPatients();
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
const handleSelect = (patient: Patient) => {
|
||||
setCurrentPatient({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listTasks, FollowUpTask } from '../../../services/followup';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
@@ -31,9 +32,9 @@ export default function MyFollowUps() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
useThrottledDidShow(() => {
|
||||
fetchTasks(activeTab);
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
setActiveTab(key);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listHealthRecords, HealthRecord } from '../../../services/health-record';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
@@ -19,13 +20,16 @@ export default function HealthRecords() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setRecords([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listHealthRecords(patientId, { page: p, page_size: 20 });
|
||||
@@ -40,9 +44,9 @@ export default function HealthRecords() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
useThrottledDidShow(() => {
|
||||
fetchData(1);
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => {
|
||||
@@ -83,7 +87,7 @@ export default function HealthRecords() {
|
||||
</View>
|
||||
|
||||
{records.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无健康记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无健康记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && <Loading />}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
|
||||
import { listReports, LabReport } from '../../../services/report';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
@@ -13,13 +14,16 @@ export default function MyReports() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [hasPatient, setHasPatient] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setReports([]);
|
||||
setHasPatient(false);
|
||||
return;
|
||||
}
|
||||
setHasPatient(true);
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listReports(patientId, p);
|
||||
@@ -34,9 +38,9 @@ export default function MyReports() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
useThrottledDidShow(() => {
|
||||
fetchData(1);
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => {
|
||||
@@ -97,7 +101,7 @@ export default function MyReports() {
|
||||
</View>
|
||||
|
||||
{reports.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无报告记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
<EmptyState text={hasPatient ? '暂无报告记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
|
||||
@@ -7,30 +7,35 @@ import './index.scss';
|
||||
|
||||
export default function Settings() {
|
||||
const modeClass = useElderClass();
|
||||
const { logout } = useAuthStore();
|
||||
const logout = useAuthStore((s) => s.logout);
|
||||
|
||||
const handleClearCache = () => {
|
||||
Taro.showModal({
|
||||
const handleClearCache = async () => {
|
||||
const { confirm } = await Taro.showModal({
|
||||
title: '清除缓存',
|
||||
content: '确定要清除本地缓存数据吗?不会影响账号信息。',
|
||||
}).then((res) => {
|
||||
if (res.confirm) {
|
||||
const preservedKeys = ['access_token', 'refresh_token', 'user_data', 'user_roles', 'tenant_id', 'wechat_openid', 'current_patient', 'current_patient_id'];
|
||||
const preservedData: Record<string, unknown> = {};
|
||||
for (const key of preservedKeys) {
|
||||
const val = Taro.getStorageSync(key);
|
||||
if (val) preservedData[key] = val;
|
||||
}
|
||||
|
||||
Taro.clearStorageSync();
|
||||
|
||||
for (const [key, val] of Object.entries(preservedData)) {
|
||||
Taro.setStorageSync(key, val);
|
||||
}
|
||||
|
||||
Taro.showToast({ title: '缓存已清除', icon: 'success' });
|
||||
}
|
||||
});
|
||||
if (!confirm) return;
|
||||
|
||||
const preservedKeys = ['access_token', 'refresh_token', 'user_data', 'user_roles', 'tenant_id', 'wechat_openid', 'current_patient', 'current_patient_id'];
|
||||
const preserved: Record<string, unknown> = {};
|
||||
await Promise.all(
|
||||
preservedKeys.map(async (key) => {
|
||||
try {
|
||||
const val = await Taro.getStorage({ key });
|
||||
if (val.data) preserved[key] = val.data;
|
||||
} catch { /* key not found */ }
|
||||
}),
|
||||
);
|
||||
|
||||
await Taro.clearStorage();
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(preserved).map(([key, val]) =>
|
||||
Taro.setStorage({ key, data: val }),
|
||||
),
|
||||
);
|
||||
|
||||
Taro.showToast({ title: '缓存已清除', icon: 'success' });
|
||||
};
|
||||
|
||||
const handleAbout = () => {
|
||||
|
||||
Reference in New Issue
Block a user