|
@@ -783,3 +783,567 @@ try {
|
|
|
- ✅ **可观测性**:完整的状态追踪和监控
|
|
- ✅ **可观测性**:完整的状态追踪和监控
|
|
|
|
|
|
|
|
这个初始化管道系统是**现代 React 应用的最佳实践**,特别适用于需要复杂初始化流程的企业级应用。
|
|
这个初始化管道系统是**现代 React 应用的最佳实践**,特别适用于需要复杂初始化流程的企业级应用。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 🔍 **旧的启动逻辑分析**
|
|
|
|
|
+
|
|
|
|
|
+以下是对现有程序从启动到进入登录页面的完整流程分析。
|
|
|
|
|
+
|
|
|
|
|
+### **程序启动流程概览**
|
|
|
|
|
+
|
|
|
|
|
+从程序开始运行到显示登录页面,经历了**两个主要层面**的初始化:
|
|
|
|
|
+
|
|
|
|
|
+#### **两层架构说明**
|
|
|
|
|
+
|
|
|
|
|
+程序的启动流程分为两个独立的层面:
|
|
|
|
|
+
|
|
|
|
|
+**第一层:环境层初始化(Environment Layer)**
|
|
|
|
|
+- **职责**:处理特定运行环境的底层初始化
|
|
|
|
|
+- **范围**:根据不同环境有不同的实现
|
|
|
|
|
+- **生命周期**:在 React 应用启动之前完成
|
|
|
|
|
+
|
|
|
|
|
+**第二层:React 应用层初始化(Application Layer)**
|
|
|
|
|
+- **职责**:处理应用业务逻辑的初始化
|
|
|
|
|
+- **范围**:所有环境共享同一套逻辑
|
|
|
|
|
+- **生命周期**:在环境层初始化完成后开始
|
|
|
|
|
+
|
|
|
|
|
+#### **不同环境的初始化差异**
|
|
|
|
|
+
|
|
|
|
|
+| 环境类型 | 环境层初始化 | 应用层初始化 |
|
|
|
|
|
+|---------|------------|------------|
|
|
|
|
|
+| **Electron** | ✅ 单实例检查<br>✅ 创建 BrowserWindow<br>✅ 加载 h5/index.html<br>✅ 设置 IPC 处理器<br>✅ 服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
|
|
|
|
|
+| **浏览器** | ❌ 无单实例检查<br>❌ 无窗口创建<br>❌ 无 IPC 处理器<br>❌ 无服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
|
|
|
|
|
+| **Cordova** | ✅ Cordova 插件初始化<br>✅ 设备就绪检查<br>✅ 服务器连接检查 | ✅ 产品初始化<br>✅ 多语言加载<br>✅ 注释管理器初始化 |
|
|
|
|
|
+
|
|
|
|
|
+#### **分层启动流程图**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 第一层:环境层初始化(Environment Layer) │
|
|
|
|
|
+├─────────────────────────────────────────────────────────────┤
|
|
|
|
|
+│ │
|
|
|
|
|
+│ Electron 环境: │
|
|
|
|
|
+│ ├─ 单实例检查(app.requestSingleInstanceLock) │
|
|
|
|
|
+│ ├─ 等待应用就绪(app.whenReady) │
|
|
|
|
|
+│ ├─ 创建 BrowserWindow │
|
|
|
|
|
+│ ├─ 设置 IPC 处理器 │
|
|
|
|
|
+│ └─ 加载 h5/index.html │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 浏览器环境: │
|
|
|
|
|
+│ └─ 直接加载 index.html(无需额外初始化) │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ Cordova 环境: │
|
|
|
|
|
+│ ├─ Cordova 插件初始化 │
|
|
|
|
|
+│ ├─ 设备就绪检查(deviceready 事件) │
|
|
|
|
|
+│ └─ 加载应用主页面 │
|
|
|
|
|
+│ │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 第二层:React 应用层初始化(Application Layer) │
|
|
|
|
|
+├─────────────────────────────────────────────────────────────┤
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 所有环境共享: │
|
|
|
|
|
+│ ├─ React App 渲染 │
|
|
|
|
|
+│ ├─ Redux Provider 初始化 │
|
|
|
|
|
+│ ├─ 【并发执行阶段】 │
|
|
|
|
|
+│ │ ├─ 【线程1】平台检测与服务器连接(app.tsx) │
|
|
|
|
|
+│ │ │ ├─ 平台检测(platform.isBrowser/isElectron/isCordova) │
|
|
|
|
|
+│ │ │ ├─ 服务器连接检查(仅非浏览器环境) │
|
|
|
|
|
+│ │ │ └─ 如果连接失败 → 显示服务器配置对话框 │
|
|
|
|
|
+│ │ └─ 【线程2】Taro路由加载云同步页面 │
|
|
|
|
|
+│ │ ├─ 加载 cloud_sync_expired 页面 │
|
|
|
|
|
+│ │ ├─ 调用 getQuota() API │
|
|
|
|
|
+│ │ ├─ 检查 overdue 状态 │
|
|
|
|
|
+│ │ ├─ 如果过期 → 显示提示并重试(最多10次) │
|
|
|
|
|
+│ │ └─ 如果未过期 → 跳转到 pages/index/index │
|
|
|
|
|
+│ ├─ 【阶段 2】应用状态初始化 │
|
|
|
|
|
+│ │ ├─ 初始化产品状态(initializeProductState) │
|
|
|
|
|
+│ │ ├─ 加载多语言资源(loadI18nMessages) │
|
|
|
|
|
+│ │ ├─ 初始化注释管理器(initializeAnnotationManager) │
|
|
|
|
|
+│ │ └─ 标记应用就绪(isI18nReady = true) │
|
|
|
|
|
+│ │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 页面渲染层(Presentation Layer) │
|
|
|
|
|
+├─────────────────────────────────────────────────────────────┤
|
|
|
|
|
+│ │
|
|
|
|
|
+│ ├─ 检查登录状态 │
|
|
|
|
|
+│ ├─ 未登录 → 显示登录页面 │
|
|
|
|
|
+│ ├─ 已登录但未初始化 → 显示初始化组件 │
|
|
|
|
|
+│ └─ 已登录且已初始化 → 显示主应用界面 │
|
|
|
|
|
+│ │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### **详细阶段分析**
|
|
|
|
|
+
|
|
|
|
|
+从程序开始运行到显示登录页面,经历了以下几个主要阶段:
|
|
|
|
|
+
|
|
|
|
|
+#### **1. Electron 主进程启动** (main.js)
|
|
|
|
|
+
|
|
|
|
|
+**入口文件**:`main.js`
|
|
|
|
|
+
|
|
|
|
|
+**主要步骤**:
|
|
|
|
|
+- 应用启动时执行 `main.js`
|
|
|
|
|
+- 进行单实例检查(`app.requestSingleInstanceLock()`)
|
|
|
|
|
+ - 如果抢锁失败,立即退出应用
|
|
|
|
|
+ - 如果抢锁成功,监听后续启动事件
|
|
|
|
|
+- 等待应用就绪(`app.whenReady()`)
|
|
|
|
|
+- 调用 `createWindow()` 创建浏览器窗口
|
|
|
|
|
+ - 创建隐藏的无边框窗口
|
|
|
|
|
+ - 配置 webPreferences(禁用 nodeIntegration,启用 contextIsolation)
|
|
|
|
|
+ - 加载预加载脚本 `preload.js`
|
|
|
|
|
+ - 移除应用菜单栏
|
|
|
|
|
+ - 窗口最大化
|
|
|
|
|
+- 加载页面:`win.loadFile(join(process.cwd(), 'h5/index.html'))`
|
|
|
|
|
+- 窗口准备就绪后显示:`win.once('ready-to-show', () => win.show())`
|
|
|
|
|
+- 设置各种 IPC 处理器:
|
|
|
|
|
+ - 存储操作(storage-get, storage-set, storage-remove)
|
|
|
|
|
+ - 日志功能(write-log)
|
|
|
|
|
+ - 应用控制(exit-close, exit-shutdown)
|
|
|
|
|
+ - 打印功能(print-film, print-film-to-file, print-to-pdf)
|
|
|
|
|
+
|
|
|
|
|
+**关键代码**:
|
|
|
|
|
+```javascript
|
|
|
|
|
+function createWindow() {
|
|
|
|
|
+ win = new BrowserWindow({
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ frame: false,
|
|
|
|
|
+ titleBarStyle: 'hidden',
|
|
|
|
|
+ webPreferences: {
|
|
|
|
|
+ nodeIntegration: false,
|
|
|
|
|
+ contextIsolation: true,
|
|
|
|
|
+ preload: join(__dirname, 'preload.js'),
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ Menu.setApplicationMenu(null);
|
|
|
|
|
+ win.maximize();
|
|
|
|
|
+ win.loadFile(join(process.cwd(), 'h5/index.html'));
|
|
|
|
|
+ win.once('ready-to-show', () => win.show());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+app.whenReady().then(createWindow);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+#### **2. React 应用初始化** (src/app.tsx)
|
|
|
|
|
+
|
|
|
|
|
+**入口组件**:`App` 组件
|
|
|
|
|
+
|
|
|
|
|
+**主要逻辑**:
|
|
|
|
|
+- 渲染 `App` 组件,包裹 Redux Provider
|
|
|
|
|
+- `AppContent` 组件开始执行初始化逻辑
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+#### **3. 云同步过期检查** (src/pages/security/cloud_sync_expired.tsx)
|
|
|
|
|
+
|
|
|
|
|
+**触发时机**:Taro 路由加载的第一个页面
|
|
|
|
|
+
|
|
|
|
|
+**配置位置**:在 `src/app.config.ts` 中配置为第一个页面
|
|
|
|
|
+```typescript
|
|
|
|
|
+export default defineAppConfig({
|
|
|
|
|
+ pages: [
|
|
|
|
|
+ 'pages/security/cloud_sync_expired', // 第一个页面
|
|
|
|
|
+ 'pages/index/index',
|
|
|
|
|
+ ],
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**检查流程**:
|
|
|
|
|
+1. **调用配额检查 API**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const response = await getQuota();
|
|
|
|
|
+ const isOverdue = response.data.overdue;
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+2. **判断过期状态**
|
|
|
|
|
+ - 如果 `overdue === false`:授权有效,跳转到主入口页面
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ Taro.redirectTo({ url: '/pages/index/index' });
|
|
|
|
|
+ ```
|
|
|
|
|
+ - 如果 `overdue === true`:显示过期提示
|
|
|
|
|
+
|
|
|
|
|
+3. **重试机制**
|
|
|
|
|
+ - 自动重试:最多重试 10 次,每次间隔 1 秒
|
|
|
|
|
+ - 手动重试:用户可以点击"手动重试"按钮
|
|
|
|
|
+ - 错误处理:API 调用失败也会触发重试
|
|
|
|
|
+
|
|
|
|
|
+**显示状态**:
|
|
|
|
|
+- 检查中:"正在检查授权状态..."
|
|
|
|
|
+- 过期:"请与云端同步以继续使用"
|
|
|
|
|
+- 有效:"授权有效,正在跳转..."
|
|
|
|
|
+
|
|
|
|
|
+**关键代码**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+const checkoverdue = async (currentRetry = 0) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await getQuota();
|
|
|
|
|
+ setoverdueStatus(response.data.overdue);
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.data.overdue) {
|
|
|
|
|
+ // 未过期,跳转到主页面
|
|
|
|
|
+ Taro.redirectTo({ url: '/pages/index/index' });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 过期,需要重试
|
|
|
|
|
+ if (currentRetry < maxRetries) {
|
|
|
|
|
+ setTimeout(() => checkoverdue(currentRetry + 1), 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ // 错误处理和重试逻辑
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**重要性**:
|
|
|
|
|
+- 这是应用启动后的第一道检查
|
|
|
|
|
+- 只有通过云同步检查,才能进入后续的初始化流程
|
|
|
|
|
+- 保证了应用的授权合法性
|
|
|
|
|
+
|
|
|
|
|
+**环境检测与初始化**:
|
|
|
|
|
+- **浏览器环境**:
|
|
|
|
|
+ - 检测到 `platform.isBrowser` 为 true
|
|
|
|
|
+ - 跳过服务器连接检查
|
|
|
|
|
+ - 直接调用 `initializeApp()`
|
|
|
|
|
+
|
|
|
|
|
+- **Electron/Cordova 环境**:
|
|
|
|
|
+ - 执行 `dispatch(checkServerConnection())`
|
|
|
|
|
+ - 根据检查结果决定:
|
|
|
|
|
+ - 如果 `result.needsConfig` 为 true:显示服务器配置对话框
|
|
|
|
|
+ - 如果连接正常:继续调用 `initializeApp()`
|
|
|
|
|
+ - 如果检查失败:显示配置对话框
|
|
|
|
|
+
|
|
|
|
|
+**关键代码**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+useEffect(() => {
|
|
|
|
|
+ // 浏览器环境不需要服务器连接检查,直接初始化应用
|
|
|
|
|
+ if (platform.isBrowser) {
|
|
|
|
|
+ console.log('浏览器环境,跳过服务器连接检查,直接初始化应用');
|
|
|
|
|
+ initializeApp();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Electron/Cordova 环境:检查服务器连接
|
|
|
|
|
+ dispatch(checkServerConnection())
|
|
|
|
|
+ .unwrap()
|
|
|
|
|
+ .then((result) => {
|
|
|
|
|
+ setConnectionChecked(true);
|
|
|
|
|
+ if (result.needsConfig) {
|
|
|
|
|
+ console.log('检测到需要服务器配置,显示配置对话框');
|
|
|
|
|
+ setShowConfigModal(true);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log('服务器连接正常,开始应用初始化');
|
|
|
|
|
+ return initializeApp();
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ console.error('连接检查失败:', error);
|
|
|
|
|
+ setConnectionChecked(true);
|
|
|
|
|
+ setShowConfigModal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+}, [dispatch]);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### **4. 应用初始化流程** (initializeApp 函数)
|
|
|
|
|
+
|
|
|
|
|
+**执行步骤**:
|
|
|
|
|
+
|
|
|
|
|
+1. **初始化产品状态**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const productState = await dispatch(initializeProductState()).unwrap();
|
|
|
|
|
+ ```
|
|
|
|
|
+ - 获取产品配置信息
|
|
|
|
|
+ - 包含语言设置等配置
|
|
|
|
|
+
|
|
|
|
|
+2. **加载国际化资源**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ const languageCode = productState.language;
|
|
|
|
|
+ await dispatch(loadI18nMessages(languageCode)).unwrap();
|
|
|
|
|
+ ```
|
|
|
|
|
+ - 根据产品配置的语言代码加载对应的多语言文件
|
|
|
|
|
+ - 加载完成后设置到 Redux store
|
|
|
|
|
+
|
|
|
|
|
+3. **初始化注释管理器**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ await initializeAnnotationManager();
|
|
|
|
|
+ ```
|
|
|
|
|
+ - 初始化图像注释相关功能
|
|
|
|
|
+
|
|
|
|
|
+4. **标记初始化完成**
|
|
|
|
|
+ ```typescript
|
|
|
|
|
+ setIsI18nReady(true);
|
|
|
|
|
+ ```
|
|
|
|
|
+ - 设置国际化就绪状态
|
|
|
|
|
+ - 允许渲染后续界面
|
|
|
|
|
+
|
|
|
|
|
+**错误处理**:
|
|
|
|
|
+- 如果初始化失败,显示服务器配置对话框
|
|
|
|
|
+- 允许用户重新配置后重试
|
|
|
|
|
+
|
|
|
|
|
+#### **5. 页面路由加载** (src/pages/index/index.tsx)
|
|
|
|
|
+
|
|
|
|
|
+**路由配置** (src/app.config.ts):
|
|
|
|
|
+```typescript
|
|
|
|
|
+export default defineAppConfig({
|
|
|
|
|
+ pages: [
|
|
|
|
|
+ 'pages/security/cloud_sync_expired',
|
|
|
|
|
+ 'pages/index/index',
|
|
|
|
|
+ ],
|
|
|
|
|
+ // ...
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**入口页面渲染逻辑**:
|
|
|
|
|
+
|
|
|
|
|
+`AppContent` 组件根据状态决定渲染内容:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+const AppContent: React.FC = () => {
|
|
|
|
|
+ const [initialized, setInitialized] = React.useState(false);
|
|
|
|
|
+ const userInfo = useSelector((state: RootState) => state.userInfo);
|
|
|
|
|
+ const loggedIn = isLoggedIn(userInfo);
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 未登录 → 显示登录页面
|
|
|
|
|
+ if (!loggedIn) {
|
|
|
|
|
+ return <Login />;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 已登录但未初始化 → 显示初始化组件
|
|
|
|
|
+ if (!initialized) {
|
|
|
|
|
+ return <AppInitializer onInitialized={() => setInitialized(true)} />;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 已登录且已初始化 → 显示主布局
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Router>
|
|
|
|
|
+ <BasicLayout children={undefined}></BasicLayout>
|
|
|
|
|
+ </Router>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**状态判断逻辑**:
|
|
|
|
|
+- **第一优先级**:检查用户是否登录(`isLoggedIn(userInfo)`)
|
|
|
|
|
+- **第二优先级**:检查应用是否初始化(`initialized` 状态)
|
|
|
|
|
+- **第三优先级**:渲染主应用布局
|
|
|
|
|
+
|
|
|
|
|
+**token 过期处理**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+useEffect(() => {
|
|
|
|
|
+ const handleTokenExpired = () => {
|
|
|
|
|
+ dispatch(clearUserInfo());
|
|
|
|
|
+ message.error('Your session has expired. Please log in again.');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ emitter.on('tokenExpired', handleTokenExpired);
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ emitter.off('tokenExpired', handleTokenExpired);
|
|
|
|
|
+ };
|
|
|
|
|
+}, [dispatch]);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### **6. 登录页面显示** (src/pages/security/Login.tsx)
|
|
|
|
|
+
|
|
|
|
|
+**显示条件**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 急诊模式下不显示登录页面
|
|
|
|
|
+if (systemMode === SystemMode.Emergency) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 正常模式但已登录,不显示登录页面
|
|
|
|
|
+if (systemMode === SystemMode.Normal && !!userInfo && userInfo.uid !== 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**登录页面功能**:
|
|
|
|
|
+
|
|
|
|
|
+1. **用户名密码登录**
|
|
|
|
|
+ - 输入用户名和密码
|
|
|
|
|
+ - 调用登录 API:`loginApi(username, password)`
|
|
|
|
|
+ - 登录成功后:
|
|
|
|
|
+ - 保存用户信息到 Redux:`dispatch(setUserInfo(userInfo))`
|
|
|
|
|
+ - 设置系统模式为正常:`dispatch(setSystemMode(SystemMode.Normal))`
|
|
|
|
|
+ - 显示成功消息
|
|
|
|
|
+
|
|
|
|
|
+2. **急诊模式**
|
|
|
|
|
+ - 点击"急诊"按钮
|
|
|
|
|
+ - 执行急诊操作:`handleEmergencyOperation()`
|
|
|
|
|
+ - 进入急诊工作流
|
|
|
|
|
+
|
|
|
|
|
+**自动聚焦**:
|
|
|
|
|
+```typescript
|
|
|
|
|
+useEffect(() => {
|
|
|
|
|
+ if (usernameInputRef.current) {
|
|
|
|
|
+ usernameInputRef.current.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+}, []);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### **完整启动时序图**
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 1. Electron 启动 │
|
|
|
|
|
+│ └─ main.js 执行 │
|
|
|
|
|
+│ ├─ 单实例检查 │
|
|
|
|
|
+│ ├─ 等待应用就绪 (app.whenReady) │
|
|
|
|
|
+│ ├─ 创建 BrowserWindow │
|
|
|
|
|
+│ └─ 加载 h5/index.html │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 2. React App 渲染 (app.tsx) │
|
|
|
|
|
+│ ├─ Redux Provider 初始化 │
|
|
|
|
|
+│ └─ AppContent 组件渲染 │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 3. 【并发执行】平台检测 + 页面路由加载 │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 【线程1: 平台检测与服务器连接】 │
|
|
|
|
|
+│ ├─ 平台检测(platform.isBrowser/isElectron/isCordova) │
|
|
|
|
|
+│ ├─ 服务器连接检查(仅非浏览器环境,异步API调用) │
|
|
|
|
|
+│ └─ 如果连接失败 → 显示服务器配置对话框(IP输入框) │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 【线程2: Taro路由加载云同步页面】 │
|
|
|
|
|
+│ ├─ 加载 cloud_sync_expired 页面 │
|
|
|
|
|
+│ ├─ 调用 getQuota() API │
|
|
|
|
|
+│ ├─ 检查 overdue 状态 │
|
|
|
|
|
+│ ├─ 如果过期 → 显示提示并支持重试(重试提示) │
|
|
|
|
|
+│ └─ 如果未过期 → 跳转到 pages/index/index │
|
|
|
|
|
+│ │
|
|
|
|
|
+│ 【结果】两个UI可能同时显示: │
|
|
|
|
|
+│ ├─ 服务器配置对话框(IP输入框) │
|
|
|
|
|
+│ └─ 云同步过期重试提示 │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 4. 进入主页面后的应用初始化 │
|
|
|
|
|
+│ ├─ initializeProductState() - 获取产品配置 │
|
|
|
|
|
+│ ├─ loadI18nMessages() - 加载多语言资源 │
|
|
|
|
|
+│ ├─ initializeAnnotationManager() - 初始化注释管理器 │
|
|
|
|
|
+│ └─ setIsI18nReady(true) - 标记初始化完成 │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 5. 主页面状态判断 (pages/index/index.tsx) │
|
|
|
|
|
+│ └─ AppContent 状态判断 │
|
|
|
|
|
+│ ├─ !loggedIn → 渲染 <Login /> │
|
|
|
|
|
+│ ├─ !initialized → 渲染 <AppInitializer /> │
|
|
|
|
|
+│ └─ 已登录且已初始化 → 渲染 <BasicLayout /> │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+ ↓
|
|
|
|
|
+┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
+│ 6. 显示登录页面 (Login.tsx) │
|
|
|
|
|
+│ ├─ 检查系统模式 (不在急诊模式) │
|
|
|
|
|
+│ ├─ 检查登录状态 (未登录) │
|
|
|
|
|
+│ └─ 渲染登录表单 │
|
|
|
|
|
+│ ├─ 用户名输入框 (自动聚焦) │
|
|
|
|
|
+│ ├─ 密码输入框 │
|
|
|
|
|
+│ ├─ 登录按钮 │
|
|
|
|
|
+│ └─ 急诊按钮 │
|
|
|
|
|
+└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### **关键状态判断**
|
|
|
|
|
+
|
|
|
|
|
+登录页面是否显示取决于以下条件:
|
|
|
|
|
+
|
|
|
|
|
+| 条件 | 结果 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| `systemMode === Emergency` | 不显示登录页面 |
|
|
|
|
|
+| `systemMode === Normal && isLoggedIn(userInfo)` | 不显示登录页面 |
|
|
|
|
|
+| `systemMode === Normal && !isLoggedIn(userInfo)` | **显示登录页面** |
|
|
|
|
|
+
|
|
|
|
|
+### **数据流向**
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ Start([应用启动]) --> ElectronMain[Electron 主进程]
|
|
|
|
|
+ ElectronMain --> LoadHTML[加载 h5/index.html]
|
|
|
|
|
+ LoadHTML --> ReactApp[React App 初始化]
|
|
|
|
|
+
|
|
|
|
|
+ ReactApp --> PlatformCheck{平台检测}
|
|
|
|
|
+
|
|
|
|
|
+ PlatformCheck -->|浏览器| DirectInit[直接初始化应用]
|
|
|
|
|
+ PlatformCheck -->|Electron/Cordova| ServerCheck[检查服务器连接]
|
|
|
|
|
+
|
|
|
|
|
+ ServerCheck --> ServerOK{连接正常?}
|
|
|
|
|
+ ServerOK -->|否| ShowConfig[显示配置对话框]
|
|
|
|
|
+ ShowConfig --> UserConfig[用户输入配置]
|
|
|
|
|
+ UserConfig --> ServerCheck
|
|
|
|
|
+ ServerOK -->|是| InitApp[initializeApp]
|
|
|
|
|
+
|
|
|
|
|
+ DirectInit --> InitApp
|
|
|
|
|
+
|
|
|
|
|
+ InitApp --> ProductInit[初始化产品状态]
|
|
|
|
|
+ ProductInit --> I18nLoad[加载多语言资源]
|
|
|
|
|
+ I18nLoad --> AnnotationInit[初始化注释管理器]
|
|
|
|
|
+ AnnotationInit --> Ready[设置 isI18nReady = true]
|
|
|
|
|
+
|
|
|
|
|
+ Ready --> LoadPage[加载 pages/index/index]
|
|
|
|
|
+
|
|
|
|
|
+ LoadPage --> LoginCheck{用户已登录?}
|
|
|
|
|
+ LoginCheck -->|否| ShowLogin[显示登录页面]
|
|
|
|
|
+ LoginCheck -->|是| InitCheck{应用已初始化?}
|
|
|
|
|
+
|
|
|
|
|
+ InitCheck -->|否| ShowInitializer[显示初始化组件]
|
|
|
|
|
+ InitCheck -->|是| ShowMainUI[显示主应用界面]
|
|
|
|
|
+
|
|
|
|
|
+ ShowLogin --> LoginForm[登录表单]
|
|
|
|
|
+ LoginForm --> LoginAction[用户登录]
|
|
|
|
|
+ LoginAction --> SaveUserInfo[保存用户信息]
|
|
|
|
|
+ SaveUserInfo --> ShowMainUI
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### **涉及的关键文件**
|
|
|
|
|
+
|
|
|
|
|
+| 文件路径 | 作用 | 关键函数/组件 |
|
|
|
|
|
+|---------|------|--------------|
|
|
|
|
|
+| `src/app.config.ts` | Taro 页面路由配置 | 页面顺序配置 |
|
|
|
|
|
+| `src/pages/security/cloud_sync_expired.tsx` | 云同步过期检查页面 | `checkoverdue()`, `getQuota()` |
|
|
|
|
|
+| `main.js` | Electron 主进程入口 | `createWindow()`, `app.whenReady()` |
|
|
|
|
|
+| `src/app.tsx` | React 应用入口 | `App`, `AppContent`, `initializeApp()` |
|
|
|
|
|
+| `src/pages/index/index.tsx` | 应用主入口页面 | `AppContent`, 登录状态判断 |
|
|
|
|
|
+| `src/pages/security/Login.tsx` | 登录页面 | `Login`, `handleFinish()`, `handleEmergencyClick()` |
|
|
|
|
|
+| `src/states/user_info.ts` | 用户信息状态管理 | `setUserInfo`, `isLoggedIn` |
|
|
|
|
|
+| `src/states/productSlice.ts` | 产品状态管理 | `initializeProductState` |
|
|
|
|
|
+| `src/states/i18nSlice.ts` | 国际化状态管理 | `loadI18nMessages` |
|
|
|
|
|
+| `src/features/serverConfig/` | 服务器配置功能 | `checkServerConnection`, `ServerConfigModal` |
|
|
|
|
|
+
|
|
|
|
|
+### **现有启动流程的特点**
|
|
|
|
|
+
|
|
|
|
|
+**优点**:
|
|
|
|
|
+- ✅ 支持多平台(浏览器、Electron、Cordova)
|
|
|
|
|
+- ✅ 有基本的错误处理和配置引导
|
|
|
|
|
+- ✅ 国际化支持完善
|
|
|
|
|
+- ✅ 状态管理清晰(Redux)
|
|
|
|
|
+
|
|
|
|
|
+**存在的问题**:
|
|
|
|
|
+- ❌ 初始化逻辑分散在多个文件中,不易维护
|
|
|
|
|
+- ❌ 缺乏统一的进度显示和状态追踪
|
|
|
|
|
+- ❌ 错误处理不够精细,难以针对不同错误提供不同的恢复策略
|
|
|
|
|
+- ❌ 初始化步骤之间的依赖关系不够清晰
|
|
|
|
|
+- ❌ 难以扩展新的初始化步骤
|
|
|
|
|
+- ❌ 调试困难,缺乏详细的日志和状态追踪
|
|
|
|
|
+
|
|
|
|
|
+**改进方向**:
|
|
|
|
|
+- 引入初始化管道系统,将初始化流程标准化
|
|
|
|
|
+- 提供统一的进度显示和状态管理
|
|
|
|
|
+- 实现更精细的错误处理和恢复机制
|
|
|
|
|
+- 使用状态机管理初始化流程
|
|
|
|
|
+- 支持节点级别的重试和用户交互
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+**📝 备注**:以上分析基于当前代码库的实际情况,为设计新的初始化管道系统提供参考。
|