安全修复: - 移除硬编码管理员凭据 admin/Admin@2026,改用环境变量注入 - 移除 forceSetAuth 全局 bridge 方法,减少攻击面 - sanitizeHtml 从黑名单正则升级为白名单方式 - secure-storage 实现 XOR+Base64 加密存储,不再明文 - 添加旧数据迁移逻辑 migrateLegacyStorage 功能修复: - 新增咨询创建页(consultation/create),修复"发起咨询"按钮导航失败 - 修复咨询详情页长轮询可能永远不启动(dataLoadedRef → useState) - 新增 createSession service API - 预约页面从主包移至分包,配置 commonChunks 优化主包体积 UX 修复: - 65 处硬编码字号 → var(--tk-font-*) token 替换 - AI 聊天页 13 处、咨询详情页 14 处、医生端核心页 38 处 - StatusTag 色值对齐设计系统色板 - Loading 文字从 --tk-font-h1(28px) 修正为 --tk-font-body-sm - EmptyState 文字从 --tk-font-num(30px)/--tk-font-h2(22px) 修正 - 医生端 5 处硬编码颜色 → SCSS 变量
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import { useEffect, useRef, PropsWithChildren } from 'react';
|
|
import Taro, { useDidShow, useDidHide } from '@tarojs/taro';
|
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
import { flushEvents } from './services/analytics';
|
|
import { useAuthStore } from './stores/auth';
|
|
import { useUIStore } from './stores/ui';
|
|
import { migrateLegacyStorage } from './utils/secure-storage';
|
|
import './app.scss';
|
|
|
|
function App({ children }: PropsWithChildren<Record<string, unknown>>) {
|
|
const restoreAuth = useAuthStore((s) => s.restore);
|
|
const restoreUI = useUIStore((s) => s.restore);
|
|
|
|
// useDidShow 在首次 mount 时也会触发,不需要 useEffect 重复调用
|
|
useDidShow(() => {
|
|
migrateLegacyStorage();
|
|
restoreAuth();
|
|
restoreUI();
|
|
});
|
|
|
|
// 暴露全局 bridge 供 MCP/自动化测试调用(仅 dev 模式)
|
|
useEffect(() => {
|
|
if (process.env.NODE_ENV === 'production') return;
|
|
(globalThis as any).__hms = {
|
|
restoreAuth: () => { restoreAuth(); return useAuthStore.getState(); },
|
|
restoreUI,
|
|
getAuthState: () => useAuthStore.getState(),
|
|
};
|
|
return () => { delete (globalThis as any).__hms; };
|
|
}, []);
|
|
|
|
// Analytics 定时器:仅在页面可见时运行,后台时暂停以节省资源
|
|
const analyticsTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
const startAnalyticsTimer = () => {
|
|
if (analyticsTimerRef.current) return;
|
|
analyticsTimerRef.current = setInterval(flushEvents, 30000);
|
|
};
|
|
|
|
const stopAnalyticsTimer = () => {
|
|
if (analyticsTimerRef.current) {
|
|
clearInterval(analyticsTimerRef.current);
|
|
analyticsTimerRef.current = null;
|
|
}
|
|
};
|
|
|
|
useDidShow(() => {
|
|
startAnalyticsTimer();
|
|
});
|
|
|
|
useDidHide(() => {
|
|
stopAnalyticsTimer();
|
|
flushEvents();
|
|
});
|
|
|
|
useEffect(() => {
|
|
return () => { stopAnalyticsTimer(); };
|
|
}, []);
|
|
|
|
return <ErrorBoundary>{children}</ErrorBoundary>;
|
|
}
|
|
|
|
export default App;
|