docs: add UI flow analysis documentation and update gitignore
Add documentation for extractor start button and settings save button flows with detailed Mermaid diagrams. Also add logs directory to gitignore. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -22,4 +22,7 @@ tests/debug/
|
|||||||
tests/manual/
|
tests/manual/
|
||||||
test-*.mjs
|
test-*.mjs
|
||||||
test-*.js
|
test-*.js
|
||||||
compare_*.js
|
compare_*.js
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
657
docs/extractor-start-button-flow.md
Normal file
657
docs/extractor-start-button-flow.md
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
# 数据提取界面 - 开始按钮工作流程详解
|
||||||
|
|
||||||
|
> **文档版本**: 1.0
|
||||||
|
> **创建日期**: 2026-03-03
|
||||||
|
> **适用范围**: ERPAuto v1.0+
|
||||||
|
> **相关文件**:
|
||||||
|
> - `src/renderer/src/pages/ExtractorPage.tsx` (UI层)
|
||||||
|
> - `src/main/ipc/extractor-handler.ts` (IPC处理层)
|
||||||
|
> - `src/main/services/erp/extractor.ts` (业务逻辑层)
|
||||||
|
> - `src/main/services/erp/order-resolver.ts` (订单号解析服务)
|
||||||
|
> - `src/main/services/erp/erp-auth.ts` (ERP认证服务)
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [系统架构概览](#系统架构概览)
|
||||||
|
2. [完整执行流程](#完整执行流程)
|
||||||
|
3. [状态管理流程](#状态管理流程)
|
||||||
|
4. [错误处理机制](#错误处理机制)
|
||||||
|
5. [数据流转过程](#数据流转过程)
|
||||||
|
6. [关键代码引用](#关键代码引用)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 系统架构概览
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "Renderer Process (UI层)"
|
||||||
|
A[ExtractorPage.tsx]
|
||||||
|
B[React State Management]
|
||||||
|
C[用户输入: 订单号列表]
|
||||||
|
D[开始按钮]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "IPC Bridge (通信桥梁)"
|
||||||
|
E[electron.extractor.runExtractor]
|
||||||
|
F[extractor:run Channel]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Main Process (主进程)"
|
||||||
|
G[extractor-handler.ts]
|
||||||
|
H[OrderNumberResolver]
|
||||||
|
I[ErpAuthService]
|
||||||
|
J[ExtractorService]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "External Services (外部服务)"
|
||||||
|
K[(MySQL Database)]
|
||||||
|
L[ERP Web System]
|
||||||
|
M[Playwright Browser]
|
||||||
|
end
|
||||||
|
|
||||||
|
A -->|用户点击| D
|
||||||
|
D -->|调用API| E
|
||||||
|
E -->|IPC通信| F
|
||||||
|
F -->|接收请求| G
|
||||||
|
G -->|解析订单号| H
|
||||||
|
H -->|查询数据| K
|
||||||
|
G -->|登录认证| I
|
||||||
|
I -->|自动化操作| M
|
||||||
|
M -->|访问页面| L
|
||||||
|
G -->|执行提取| J
|
||||||
|
J -->|使用| I
|
||||||
|
J -->|返回结果| G
|
||||||
|
G -->|IPC响应| F
|
||||||
|
F -->|更新UI| A
|
||||||
|
B -->|管理状态| A
|
||||||
|
|
||||||
|
style A fill:#e1f5ff
|
||||||
|
style G fill:#fff4e1
|
||||||
|
style K fill:#e8f5e9
|
||||||
|
style L fill:#f3e5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 架构说明
|
||||||
|
|
||||||
|
- **Renderer Process**: 负责UI展示和用户交互,使用React管理状态
|
||||||
|
- **IPC Bridge**: 安全的进程间通信桥梁,通过preload脚本暴露
|
||||||
|
- **Main Process**: 处理业务逻辑、数据库操作、浏览器自动化
|
||||||
|
- **External Services**: MySQL数据库和ERP Web系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整执行流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
autonumber
|
||||||
|
participant User as 👤 用户
|
||||||
|
participant UI as ExtractorPage.tsx
|
||||||
|
participant IPC as electron API
|
||||||
|
participant Handler as extractor-handler.ts
|
||||||
|
participant Resolver as OrderNumberResolver
|
||||||
|
participant MySQL as MySQL Database
|
||||||
|
participant Auth as ErpAuthService
|
||||||
|
participant Extractor as ExtractorService
|
||||||
|
participant Browser as Playwright Browser
|
||||||
|
participant ERP as ERP Web System
|
||||||
|
|
||||||
|
User->>UI: 1. 输入订单号列表<br/>(每行一个)
|
||||||
|
User->>UI: 2. 点击"开始提取"按钮
|
||||||
|
|
||||||
|
Note over UI: 前端验证与准备
|
||||||
|
UI->>UI: 3. 验证订单号非空
|
||||||
|
UI->>UI: 4. 设置isRunning=true
|
||||||
|
UI->>UI: 5. 清空之前的结果和错误
|
||||||
|
UI->>UI: 6. 存储Production IDs到共享状态
|
||||||
|
UI->>IPC: 7. 调用electron.extractor.runExtractor()
|
||||||
|
|
||||||
|
Note over IPC,Handler: IPC通信
|
||||||
|
IPC->>Handler: 8. 发送IPC消息 'extractor:run'
|
||||||
|
|
||||||
|
Note over Handler: 环境配置检查
|
||||||
|
Handler->>Handler: 9. 读取.env配置<br/>(ERP_URL, USERNAME, PASSWORD)
|
||||||
|
Handler->>Handler: 10. 验证配置完整性
|
||||||
|
alt 配置不完整
|
||||||
|
Handler-->>UI: 返回ValidationError
|
||||||
|
UI->>UI: 显示错误提示
|
||||||
|
UI->>UI: 设置isRunning=false
|
||||||
|
end
|
||||||
|
|
||||||
|
Note over Handler,MySQL: 订单号解析阶段
|
||||||
|
Handler->>MySQL: 11. 连接MySQL数据库
|
||||||
|
alt MySQL连接失败
|
||||||
|
Handler-->>UI: 返回DatabaseQueryError
|
||||||
|
end
|
||||||
|
|
||||||
|
Handler->>Resolver: 12. 创建OrderNumberResolver
|
||||||
|
Handler->>Resolver: 13. 调用resolve(orderNumbers)
|
||||||
|
Resolver->>MySQL: 14. 查询生产订单号映射
|
||||||
|
MySQL-->>Resolver: 15. 返回映射结果
|
||||||
|
Resolver-->>Handler: 16. 返回映射结果<br/>(包含有效订单号和警告)
|
||||||
|
|
||||||
|
alt 没有有效订单号
|
||||||
|
Handler-->>UI: 返回ValidationError
|
||||||
|
UI->>UI: 显示错误: "没有有效的生产订单号"
|
||||||
|
end
|
||||||
|
|
||||||
|
Note over Handler,Browser: ERP认证阶段
|
||||||
|
Handler->>Auth: 17. 创建ErpAuthService
|
||||||
|
Handler->>Auth: 18. 调用login()
|
||||||
|
Auth->>Browser: 19. 启动Playwright浏览器
|
||||||
|
Browser->>ERP: 20. 访问ERP登录页面
|
||||||
|
Browser->>ERP: 21. 填写用户名密码
|
||||||
|
Browser->>ERP: 22. 点击登录按钮
|
||||||
|
ERP-->>Browser: 23. 登录成功
|
||||||
|
Browser-->>Auth: 24. 返回session对象
|
||||||
|
Auth-->>Handler: 25. 登录成功
|
||||||
|
alt 登录失败
|
||||||
|
Auth-->>Handler: 抛出异常
|
||||||
|
Handler-->>UI: 返回ErpConnectionError
|
||||||
|
end
|
||||||
|
|
||||||
|
Note over Extractor,ERP: 数据提取阶段
|
||||||
|
Handler->>Extractor: 26. 创建ExtractorService
|
||||||
|
Handler->>Extractor: 27. 调用extract()<br/>传入有效订单号
|
||||||
|
Extractor->>Browser: 28. 使用已有session
|
||||||
|
Extractor->>ERP: 29. 导航到离散备料计划维护页面
|
||||||
|
Extractor->>ERP: 30. 设置查询界面<br/>(订单号查询, 全部标签, 限制5000)
|
||||||
|
|
||||||
|
loop 批处理循环 (每批最多100个订单)
|
||||||
|
Extractor->>Extractor: 31. 创建批次<br/>(按batchSize分组)
|
||||||
|
Extractor->>UI: 32. 发送进度更新<br/>onProgress(message, progress%)
|
||||||
|
UI->>UI: 33. 更新进度条和日志
|
||||||
|
|
||||||
|
Extractor->>ERP: 34. 填充订单号到搜索框
|
||||||
|
Extractor->>ERP: 35. 点击搜索按钮
|
||||||
|
Extractor->>ERP: 36. 等待加载完成
|
||||||
|
Extractor->>ERP: 37. 点击第一行复选框
|
||||||
|
Extractor->>ERP: 38. 悬停并点击"更多"
|
||||||
|
Extractor->>ERP: 39. 点击"输出"
|
||||||
|
Extractor->>ERP: 40. 设置行数阈值为300000
|
||||||
|
Extractor->>ERP: 41. 点击"确定(Y)"
|
||||||
|
|
||||||
|
Browser->>Browser: 42. 监听下载事件
|
||||||
|
ERP->>Browser: 43. 触发文件下载
|
||||||
|
Browser->>Browser: 44. 保存文件到downloads目录
|
||||||
|
Browser-->>Extractor: 45. 返回文件路径
|
||||||
|
Extractor->>Extractor: 46. 记录下载文件路径
|
||||||
|
end
|
||||||
|
|
||||||
|
Extractor->>Extractor: 47. 汇总结果<br/>(文件列表, 记录数, 错误)
|
||||||
|
Extractor-->>Handler: 48. 返回ExtractorResult
|
||||||
|
Handler->>Handler: 49. 添加解析警告到错误列表
|
||||||
|
|
||||||
|
Note over Handler,IPC: 清理阶段
|
||||||
|
Handler->>Browser: 50. 关闭浏览器
|
||||||
|
Handler->>MySQL: 51. 断开数据库连接
|
||||||
|
|
||||||
|
Note over Handler,UI: 响应阶段
|
||||||
|
Handler-->>IPC: 52. 返回IPC响应<br/>(success: true, data: result)
|
||||||
|
IPC-->>UI: 53. 返回response
|
||||||
|
UI->>UI: 54. 设置result状态
|
||||||
|
UI->>UI: 55. 设置isRunning=false
|
||||||
|
UI->>UI: 56. 清空进度状态
|
||||||
|
UI->>User: 57. 显示提取结果<br/>(文件数, 记录数, 错误数)
|
||||||
|
|
||||||
|
alt 发生任何错误
|
||||||
|
Handler-->>UI: 返回error响应
|
||||||
|
UI->>UI: 设置error状态
|
||||||
|
UI->>UI: 设置isRunning=false
|
||||||
|
UI->>User: 显示错误信息
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 状态管理流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Idle: 初始状态
|
||||||
|
|
||||||
|
Idle --> Validating: 用户点击开始按钮
|
||||||
|
Validating --> Idle: 验证失败<br/>(订单号为空)
|
||||||
|
Validating --> Running: 验证通过
|
||||||
|
|
||||||
|
Running --> Processing: 调用IPC API
|
||||||
|
Processing --> Progress: 收到进度更新
|
||||||
|
Progress --> Processing: 继续处理
|
||||||
|
|
||||||
|
Processing --> Success: 提取成功
|
||||||
|
Processing --> Error: 提取失败
|
||||||
|
|
||||||
|
Success --> Idle: 用户清空或重新输入
|
||||||
|
Error --> Idle: 用户修正后重试
|
||||||
|
|
||||||
|
note right of Validating
|
||||||
|
前端验证阶段:
|
||||||
|
- 检查orderNumbers非空
|
||||||
|
- 解析订单号列表
|
||||||
|
- 存储到sessionStorage
|
||||||
|
- 存储到共享状态
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Processing
|
||||||
|
后端处理阶段:
|
||||||
|
- 环境配置检查
|
||||||
|
- 订单号解析
|
||||||
|
- ERP登录
|
||||||
|
- 批量数据提取
|
||||||
|
- 资源清理
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Progress
|
||||||
|
进度更新:
|
||||||
|
- 更新进度条百分比
|
||||||
|
- 添加日志到控制台
|
||||||
|
- 保持isRunning=true
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Success
|
||||||
|
成功状态:
|
||||||
|
- 显示下载文件数
|
||||||
|
- 显示记录总数
|
||||||
|
- 显示错误数
|
||||||
|
- isRunning=false
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Error
|
||||||
|
错误状态:
|
||||||
|
- 显示错误信息
|
||||||
|
- isRunning=false
|
||||||
|
- 保留用户输入
|
||||||
|
end note
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态变量说明
|
||||||
|
|
||||||
|
| 状态变量 | 类型 | 说明 | 持久化 |
|
||||||
|
|---------|------|------|--------|
|
||||||
|
| `orderNumbers` | string | 用户输入的订单号列表 | ✅ sessionStorage |
|
||||||
|
| `batchSize` | number | 每批处理的订单数量 (默认100) | ✅ sessionStorage |
|
||||||
|
| `isRunning` | boolean | 是否正在执行提取 | ❌ 内存状态 |
|
||||||
|
| `progress` | ExtractorProgress \| null | 当前进度信息 | ❌ 内存状态 |
|
||||||
|
| `result` | ExtractorResult \| null | 提取结果 | ❌ 内存状态 |
|
||||||
|
| `error` | string \| null | 错误信息 | ❌ 内存状态 |
|
||||||
|
| `logs` | string[] | 执行日志列表 | ❌ 内存状态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误处理机制
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Start([用户点击开始]) --> Validate{前端验证}
|
||||||
|
Validate -->|订单号为空| ShowEmptyError[显示错误:<br/>请输入至少一个订单号]
|
||||||
|
Validate -->|验证通过| CallIPC[调用IPC API]
|
||||||
|
|
||||||
|
CallIPC --> ConfigCheck{环境配置检查}
|
||||||
|
ConfigCheck -->|配置不完整| ConfigError[返回ValidationError:<br/>ERP配置不完整]
|
||||||
|
ConfigCheck -->|配置完整| ConnectMySQL[连接MySQL]
|
||||||
|
|
||||||
|
ConnectMySQL --> MySQLCheck{连接成功?}
|
||||||
|
MySQLCheck -->|失败| MySQLError[返回DatabaseQueryError:<br/>MySQL连接失败]
|
||||||
|
MySQLCheck -->|成功| ResolveOrders[解析订单号]
|
||||||
|
|
||||||
|
ResolveOrders --> ValidOrders{有有效订单号?}
|
||||||
|
ValidOrders -->|无| NoOrdersError[返回ValidationError:<br/>没有有效的生产订单号]
|
||||||
|
ValidOrders -->|有| LoginERP[ERP登录]
|
||||||
|
|
||||||
|
LoginERP --> LoginCheck{登录成功?}
|
||||||
|
LoginCheck -->|失败| LoginError[返回ErpConnectionError:<br/>ERP登录失败]
|
||||||
|
LoginCheck -->|成功| ExtractData[执行数据提取]
|
||||||
|
|
||||||
|
ExtractData --> BatchLoop[批处理循环]
|
||||||
|
BatchLoop --> BatchError{批次成功?}
|
||||||
|
BatchError -->|失败| RecordError[记录错误到result.errors]
|
||||||
|
BatchError -->|成功| SaveFile[保存文件]
|
||||||
|
RecordError --> NextBatch{还有批次?}
|
||||||
|
SaveFile --> NextBatch
|
||||||
|
|
||||||
|
NextBatch -->|是| BatchLoop
|
||||||
|
NextBatch -->|否| Cleanup[清理资源]
|
||||||
|
|
||||||
|
Cleanup --> CheckWarnings{有警告?}
|
||||||
|
CheckWarnings -->|是| AddWarnings[添加警告到errors]
|
||||||
|
CheckWarnings -->|否| ReturnSuccess[返回成功结果]
|
||||||
|
AddWarnings --> ReturnSuccess
|
||||||
|
|
||||||
|
ShowEmptyError --> ResetState1[设置isRunning=false]
|
||||||
|
ConfigError --> ResetState2[设置isRunning=false]
|
||||||
|
MySQLError --> ResetState3[设置isRunning=false]
|
||||||
|
NoOrdersError --> ResetState4[设置isRunning=false]
|
||||||
|
LoginError --> ResetState5[设置isRunning=false]
|
||||||
|
|
||||||
|
ResetState1 --> End1([结束])
|
||||||
|
ResetState2 --> End2([结束])
|
||||||
|
ResetState3 --> End3([结束])
|
||||||
|
ResetState4 --> End4([结束])
|
||||||
|
ResetState5 --> End5([结束])
|
||||||
|
ReturnSuccess --> End6([显示结果])
|
||||||
|
|
||||||
|
style ShowEmptyError fill:#ffcccc
|
||||||
|
style ConfigError fill:#ffcccc
|
||||||
|
style MySQLError fill:#ffcccc
|
||||||
|
style NoOrdersError fill:#ffcccc
|
||||||
|
style LoginError fill:#ffcccc
|
||||||
|
style RecordError fill:#fff4cc
|
||||||
|
style ReturnSuccess fill:#ccffcc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误类型与处理策略
|
||||||
|
|
||||||
|
| 错误类型 | 触发条件 | 用户反馈 | 恢复策略 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| `ValidationError` | 订单号为空、配置不完整、无有效订单号 | 显示红色错误消息 | 修正输入后重试 |
|
||||||
|
| `DatabaseQueryError` | MySQL连接失败 | 显示数据库连接错误 | 检查数据库配置 |
|
||||||
|
| `ErpConnectionError` | ERP登录失败 | 显示ERP登录错误 | 检查ERP凭据 |
|
||||||
|
| `BatchError` | 单个批次处理失败 | 记录到错误列表,继续处理 | 查看错误详情 |
|
||||||
|
| `SystemError` | 未知系统错误 | 显示通用错误消息 | 查看日志 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据流转过程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph "Input (用户输入)"
|
||||||
|
A1[原始输入<br/>订单号列表]
|
||||||
|
A2[批次大小<br/>batchSize=100]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Transformation (数据转换)"
|
||||||
|
B1[行解析<br/>按换行符分割]
|
||||||
|
B2[去空白<br/>trim每行]
|
||||||
|
B3[过滤空行<br/>移除空字符串]
|
||||||
|
B4[存储共享状态<br/>Production IDs]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Resolution (订单号解析)"
|
||||||
|
C1[查询MySQL<br/>查找映射关系]
|
||||||
|
C2[提取生产订单号<br/>获取有效值]
|
||||||
|
C3[收集警告<br/>记录未映射项]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Processing (批量处理)"
|
||||||
|
D1[批次分组<br/>按batchSize切分]
|
||||||
|
D2[批次迭代<br/>逐批处理]
|
||||||
|
D3[订单拼接<br/>逗号连接]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Output (结果输出)"
|
||||||
|
E1[下载文件列表<br/>downloadedFiles数组]
|
||||||
|
E2[合并文件<br/>mergedFile TODO]
|
||||||
|
E3[记录总数<br/>recordCount]
|
||||||
|
E4[错误列表<br/>errors数组]
|
||||||
|
end
|
||||||
|
|
||||||
|
A1 --> B1
|
||||||
|
B1 --> B2
|
||||||
|
B2 --> B3
|
||||||
|
B3 --> B4
|
||||||
|
B4 --> C1
|
||||||
|
A2 --> D1
|
||||||
|
C1 --> C2
|
||||||
|
C2 --> D1
|
||||||
|
C3 --> E4
|
||||||
|
D1 --> D2
|
||||||
|
D2 --> D3
|
||||||
|
D3 --> E1
|
||||||
|
E1 --> E3
|
||||||
|
|
||||||
|
style A1 fill:#e3f2fd
|
||||||
|
style A2 fill:#e3f2fd
|
||||||
|
style E1 fill:#e8f5e9
|
||||||
|
style E2 fill:#e8f5e9
|
||||||
|
style E3 fill:#e8f5e9
|
||||||
|
style E4 fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据转换详情
|
||||||
|
|
||||||
|
**阶段1: 用户输入 → Production IDs**
|
||||||
|
```
|
||||||
|
输入: "PO-20231024-001\nPO-20231024-002\nPO-20231024-003"
|
||||||
|
↓ 分割 + trim + 过滤
|
||||||
|
结果: ["PO-20231024-001", "PO-20231024-002", "PO-20231024-003"]
|
||||||
|
↓ 存储到共享状态
|
||||||
|
共享状态: Production IDs (供清理模块使用)
|
||||||
|
```
|
||||||
|
|
||||||
|
**阶段2: Production IDs → 生产订单号**
|
||||||
|
```
|
||||||
|
输入: ["PO-20231024-001", "PO-20231024-002", "INVALID"]
|
||||||
|
↓ MySQL查询 (production_order表)
|
||||||
|
映射结果: {
|
||||||
|
"PO-20231024-001": "MO-20231024-001",
|
||||||
|
"PO-20231024-002": "MO-20231024-002",
|
||||||
|
"INVALID": null
|
||||||
|
}
|
||||||
|
↓ 提取有效值
|
||||||
|
有效订单号: ["MO-20231024-001", "MO-20231024-002"]
|
||||||
|
警告: ["INVALID: 未找到对应的生产订单号"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**阶段3: 生产订单号 → 批次**
|
||||||
|
```
|
||||||
|
输入: ["MO-001", "MO-002", ..., "MO-250"] (250个)
|
||||||
|
批次大小: 100
|
||||||
|
↓ 分组
|
||||||
|
批次1: ["MO-001", ..., "MO-100"]
|
||||||
|
批次2: ["MO-101", ..., "MO-200"]
|
||||||
|
批次3: ["MO-201", ..., "MO-250"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**阶段4: 批次 → ERP查询字符串**
|
||||||
|
```
|
||||||
|
批次: ["MO-001", "MO-002", "MO-003"]
|
||||||
|
↓ 逗号连接
|
||||||
|
查询字符串: "MO-001,MO-002,MO-003"
|
||||||
|
↓ 填充到ERP搜索框
|
||||||
|
ERP操作: 填入搜索框并点击搜索
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键代码引用
|
||||||
|
|
||||||
|
### 1. 前端开始按钮处理 (ExtractorPage.tsx:52-90)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const handleExtract = async () => {
|
||||||
|
// 1. 前端验证
|
||||||
|
if (!orderNumbers.trim()) {
|
||||||
|
setError('请输入至少一个订单号')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 设置运行状态
|
||||||
|
setIsRunning(true)
|
||||||
|
setProgress(null)
|
||||||
|
setResult(null)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 3. 解析订单号列表
|
||||||
|
const orderNumberList = orderNumbers
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0)
|
||||||
|
|
||||||
|
// 4. 存储到共享状态
|
||||||
|
await window.electron.validation.setSharedProductionIds(orderNumberList)
|
||||||
|
|
||||||
|
// 5. 调用后端API
|
||||||
|
const response = await window.electron.extractor.runExtractor({
|
||||||
|
orderNumbers: orderNumberList,
|
||||||
|
batchSize
|
||||||
|
})
|
||||||
|
|
||||||
|
// 6. 处理响应
|
||||||
|
if (response.success && response.data) {
|
||||||
|
setResult(response.data)
|
||||||
|
} else {
|
||||||
|
setError(response.error || '提取失败')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : '发生未知错误')
|
||||||
|
} finally {
|
||||||
|
// 7. 重置状态
|
||||||
|
setIsRunning(false)
|
||||||
|
setProgress(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. IPC处理器核心逻辑 (extractor-handler.ts:17-154)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
ipcMain.handle('extractor:run', async (_event, input: ExtractorInput) => {
|
||||||
|
return withErrorHandling(async () => {
|
||||||
|
// 1. 环境配置检查
|
||||||
|
const erpUrl = process.env.ERP_URL || ''
|
||||||
|
const erpUsername = process.env.ERP_USERNAME || ''
|
||||||
|
const erpPassword = process.env.ERP_PASSWORD || ''
|
||||||
|
|
||||||
|
if (!erpUrl || !erpUsername || !erpPassword) {
|
||||||
|
throw new ValidationError('ERP 配置不完整')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 订单号解析
|
||||||
|
const mysqlService = new MySqlService(mysqlConfig)
|
||||||
|
await mysqlService.connect()
|
||||||
|
|
||||||
|
const resolver = new OrderNumberResolver(mysqlService)
|
||||||
|
const mappings = await resolver.resolve(input.orderNumbers)
|
||||||
|
const validOrderNumbers = resolver.getValidOrderNumbers(mappings)
|
||||||
|
|
||||||
|
if (validOrderNumbers.length === 0) {
|
||||||
|
throw new ValidationError('没有有效的生产订单号可处理')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ERP登录
|
||||||
|
const authService = new ErpAuthService({...})
|
||||||
|
await authService.login()
|
||||||
|
|
||||||
|
// 4. 执行提取
|
||||||
|
const extractor = new ExtractorService(authService)
|
||||||
|
const result = await extractor.extract({
|
||||||
|
...input,
|
||||||
|
orderNumbers: validOrderNumbers
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. 资源清理
|
||||||
|
await authService.close()
|
||||||
|
await mysqlService.disconnect()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}, 'extractor:run')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 提取服务批处理逻辑 (extractor.ts:43-67)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 批处理循环
|
||||||
|
const batches = this.createBatches(input.orderNumbers, batchSize)
|
||||||
|
|
||||||
|
for (let i = 0; i < batches.length; i++) {
|
||||||
|
const batch = batches[i]
|
||||||
|
const progress = ((i + 1) / batches.length) * 100
|
||||||
|
|
||||||
|
// 发送进度更新
|
||||||
|
input.onProgress?.(`Processing batch ${i + 1}/${batches.length}`, progress)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filePath = await this.downloadBatch(
|
||||||
|
session, popupPage, workFrame, batch, i, batches.length
|
||||||
|
)
|
||||||
|
result.downloadedFiles.push(filePath)
|
||||||
|
} catch (error) {
|
||||||
|
// 记录错误但继续处理
|
||||||
|
result.errors.push(`Batch ${i + 1}: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 浏览器自动化单批次处理 (extractor.ts:151-194)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private async downloadBatch(...): Promise<string> {
|
||||||
|
// 1. 填充订单号
|
||||||
|
const textbox = workFrame.getByRole('textbox', { name: '来源生产订单号' })
|
||||||
|
await textbox.fill(orderNumbers.join(','))
|
||||||
|
|
||||||
|
// 2. 点击搜索
|
||||||
|
await workFrame.locator('.search-component-searchBtn').click()
|
||||||
|
|
||||||
|
// 3. 等待加载
|
||||||
|
await this.waitForLoading(workFrame)
|
||||||
|
|
||||||
|
// 4. 选择第一行
|
||||||
|
await workFrame.getByRole('row', { name: '序号' }).getByLabel('').click()
|
||||||
|
|
||||||
|
// 5. 点击更多 -> 输出
|
||||||
|
await workFrame.getByRole('button', { name: '更多' }).hover()
|
||||||
|
await workFrame.getByText('输出', { exact: true }).click()
|
||||||
|
|
||||||
|
// 6. 设置阈值
|
||||||
|
await thresholdBox.fill('300000')
|
||||||
|
|
||||||
|
// 7. 等待下载
|
||||||
|
const downloadPromise = popupPage.waitForEvent('download')
|
||||||
|
await workFrame.getByRole('button', { name: '确定(Y)' }).click()
|
||||||
|
const download = await downloadPromise
|
||||||
|
|
||||||
|
// 8. 保存文件
|
||||||
|
const downloadPath = path.join(this.downloadDir, `temp_batch_${batchIndex + 1}.xlsx`)
|
||||||
|
await download.saveAs(downloadPath)
|
||||||
|
|
||||||
|
return downloadPath
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
### 流程关键点
|
||||||
|
|
||||||
|
1. **三层验证机制**:
|
||||||
|
- 前端验证: 非空检查
|
||||||
|
- 配置验证: 环境变量完整性
|
||||||
|
- 数据验证: 订单号有效性
|
||||||
|
|
||||||
|
2. **资源管理策略**:
|
||||||
|
- 使用try-finally确保资源清理
|
||||||
|
- 浏览器在使用后立即关闭
|
||||||
|
- 数据库连接在使用后断开
|
||||||
|
|
||||||
|
3. **错误容错设计**:
|
||||||
|
- 单个批次失败不影响其他批次
|
||||||
|
- 警告信息独立收集,不影响主流程
|
||||||
|
- 详细错误信息返回给前端展示
|
||||||
|
|
||||||
|
4. **用户体验优化**:
|
||||||
|
- sessionStorage持久化用户输入
|
||||||
|
- 实时进度反馈
|
||||||
|
- 共享状态支持跨页面数据传递
|
||||||
|
- 详细的日志记录
|
||||||
|
|
||||||
|
### 性能考虑
|
||||||
|
|
||||||
|
- **批处理**: 默认每批100个订单,平衡性能与稳定性
|
||||||
|
- **异步并发**: 使用async/await处理异步操作
|
||||||
|
- **进度反馈**: 避免长时间无响应的用户体验
|
||||||
|
|
||||||
|
### 扩展性
|
||||||
|
|
||||||
|
- **配置化**: batchSize可配置
|
||||||
|
- **模块化**: 服务独立,易于测试和维护
|
||||||
|
- **错误类型化**: 使用自定义错误类型便于精确处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 如代码逻辑变更,请及时更新本文档和相关流程图。
|
||||||
913
docs/settings-save-button-flow.md
Normal file
913
docs/settings-save-button-flow.md
Normal file
@@ -0,0 +1,913 @@
|
|||||||
|
# 系统设置保存按钮工作流程分析
|
||||||
|
# System Settings Save Button Workflow Analysis
|
||||||
|
|
||||||
|
## 文档概述 / Document Overview
|
||||||
|
|
||||||
|
本文档详细分析了 ERPAuto 系统设置界面中保存按钮的完整工作流程,包括架构设计、数据流转、技术实现细节以及错误处理机制。
|
||||||
|
|
||||||
|
This document provides a comprehensive analysis of the save button workflow in the ERPAuto system settings interface, including architecture design, data flow, technical implementation details, and error handling mechanisms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录 / Table of Contents
|
||||||
|
|
||||||
|
1. [架构概览](#架构概览)
|
||||||
|
2. [数据流程图](#数据流程图)
|
||||||
|
3. [组件详解](#组件详解)
|
||||||
|
4. [数据结构](#数据结构)
|
||||||
|
5. [错误处理机制](#错误处理机制)
|
||||||
|
6. [安全考虑](#安全考虑)
|
||||||
|
7. [技术实现细节](#技术实现细节)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构概览 / Architecture Overview
|
||||||
|
|
||||||
|
### 系统架构 / System Architecture
|
||||||
|
|
||||||
|
系统设置保存功能采用典型的 Electron 三层架构模式:
|
||||||
|
|
||||||
|
The system settings save functionality follows the classic Electron three-tier architecture pattern:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "Renderer Process 渲染进程"
|
||||||
|
UI[SettingsPage.tsx<br/>UI Component]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Preload Script 预加载脚本"
|
||||||
|
BRIDGE[contextBridge API<br/>Security Boundary]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Main Process 主进程"
|
||||||
|
IPC[settings-handler.ts<br/>IPC Handler]
|
||||||
|
SERVICE[ConfigManager.ts<br/>Configuration Service]
|
||||||
|
FILE[.env File<br/>Persistent Storage]
|
||||||
|
end
|
||||||
|
|
||||||
|
UI -->|IPC Invoke| BRIDGE
|
||||||
|
BRIDGE -->|Secure Channel| IPC
|
||||||
|
IPC -->|Business Logic| SERVICE
|
||||||
|
SERVICE -->|Write| FILE
|
||||||
|
FILE -->|Confirm| SERVICE
|
||||||
|
SERVICE -->|Result| IPC
|
||||||
|
IPC -->|Response| BRIDGE
|
||||||
|
BRIDGE -->|Promise Resolve| UI
|
||||||
|
|
||||||
|
style UI fill:#e1f5ff
|
||||||
|
style BRIDGE fill:#fff4e1
|
||||||
|
style IPC fill:#ffe1f5
|
||||||
|
style SERVICE fill:#e1ffe1
|
||||||
|
style FILE fill:#f5f5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 核心设计模式 / Core Design Patterns
|
||||||
|
|
||||||
|
1. **单向数据流**:数据从 UI → Main Process → File,响应沿相反路径返回
|
||||||
|
2. **安全隔离**:Preload 脚本作为安全桥梁,通过 `contextBridge` 暴露受限 API
|
||||||
|
3. **单例模式**:ConfigManager 使用单例确保配置一致性
|
||||||
|
4. **缓存优先**:配置读取优先从内存缓存获取,写入时同步到磁盘
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据流程图 / Data Flow Diagrams
|
||||||
|
|
||||||
|
### 完整保存流程 / Complete Save Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor User as 用户 User
|
||||||
|
participant UI as SettingsPage.tsx
|
||||||
|
participant Preload as preload/index.ts
|
||||||
|
participant IPC as settings-handler.ts
|
||||||
|
participant Config as ConfigManager.ts
|
||||||
|
participant File as .env File
|
||||||
|
|
||||||
|
User->>UI: 点击保存按钮<br/>Click Save Button
|
||||||
|
activate UI
|
||||||
|
|
||||||
|
UI->>UI: handleSaveSettings()
|
||||||
|
Note over UI: 检查是否修改<br/>Check isModified
|
||||||
|
|
||||||
|
UI->>Preload: window.electron.settings<br/>.saveSettings(settings)
|
||||||
|
activate Preload
|
||||||
|
|
||||||
|
Preload->>IPC: ipcRenderer.invoke<br/>('settings:saveSettings', settings)
|
||||||
|
activate IPC
|
||||||
|
|
||||||
|
IPC->>IPC: 验证用户类型<br/>Validate User Type
|
||||||
|
IPC->>Config: configManager<br/>.saveAllSettings(settings)
|
||||||
|
activate Config
|
||||||
|
|
||||||
|
Config->>Config: 更新内存缓存<br/>Update Cache
|
||||||
|
Note over Config: set('erp.url', value)<br/>set('erp.username', value)<br/>... (40+ fields)
|
||||||
|
|
||||||
|
Config->>File: fs.writeFileSync<br/>(.env, content)
|
||||||
|
activate File
|
||||||
|
File-->>Config: true/false
|
||||||
|
deactivate File
|
||||||
|
|
||||||
|
Config-->>IPC: Promise<boolean>
|
||||||
|
deactivate Config
|
||||||
|
|
||||||
|
IPC-->>Preload: {success, error?}
|
||||||
|
deactivate IPC
|
||||||
|
|
||||||
|
Preload-->>UI: Promise resolve
|
||||||
|
deactivate Preload
|
||||||
|
|
||||||
|
alt 保存成功 / Save Success
|
||||||
|
UI->>UI: setIsModified(false)
|
||||||
|
UI->>User: 显示成功消息<br/>Show Success Message
|
||||||
|
else 保存失败 / Save Failed
|
||||||
|
UI->>User: 显示错误消息<br/>Show Error Message
|
||||||
|
end
|
||||||
|
|
||||||
|
deactivate UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据转换流程 / Data Transformation Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph "UI State"
|
||||||
|
STATE[Settings Interface<br/>settings.erp.url = 'https://...']
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Type Conversion"
|
||||||
|
T1[SettingsData Object<br/>TypeScript Interface]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "IPC Transport"
|
||||||
|
JSON[JSON Serialization<br/>String Transfer]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Service Layer"
|
||||||
|
CACHE[Config Cache<br/>Map<string, string>]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "File System"
|
||||||
|
ENV[.env File Format<br/>KEY=VALUE]
|
||||||
|
end
|
||||||
|
|
||||||
|
STATE -->|Object| T1
|
||||||
|
T1 -->|JSON.stringify| JSON
|
||||||
|
JSON -->|Deserialize| T1
|
||||||
|
T1 -->|set key-value| CACHE
|
||||||
|
CACHE -->|Format| ENV
|
||||||
|
|
||||||
|
style STATE fill:#e1f5ff
|
||||||
|
style JSON fill:#fff4e1
|
||||||
|
style CACHE fill:#e1ffe1
|
||||||
|
style ENV fill:#f5f5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 组件详解 / Component Details
|
||||||
|
|
||||||
|
### 1. 渲染进程 / Renderer Process
|
||||||
|
|
||||||
|
#### SettingsPage.tsx (`src/renderer/src/pages/SettingsPage.tsx`)
|
||||||
|
|
||||||
|
**主要职责 / Main Responsibilities:**
|
||||||
|
- 用户界面渲染和交互
|
||||||
|
- 本地状态管理(settings, isModified, message)
|
||||||
|
- 调用 IPC 通信
|
||||||
|
|
||||||
|
**关键函数 / Key Functions:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 第 61-73 行 / Lines 61-73
|
||||||
|
const handleSaveSettings = async () => {
|
||||||
|
try {
|
||||||
|
const result = await window.electron.settings.saveSettings(settings as any)
|
||||||
|
if (result.success) {
|
||||||
|
setIsModified(false) // 清除修改标记
|
||||||
|
showMessage('success', '设置保存成功')
|
||||||
|
} else {
|
||||||
|
showMessage('error', result.error || '保存失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('error', '保存设置时发生错误')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**状态管理 / State Management:**
|
||||||
|
|
||||||
|
| 状态变量 | 类型 | 用途 |
|
||||||
|
|---------|------|------|
|
||||||
|
| `settings` | `Settings` | 当前配置数据,结构为 `{ erp: { url, username, password } }` |
|
||||||
|
| `isModified` | `boolean` | 标记配置是否已修改,控制保存按钮启用状态 |
|
||||||
|
| `isLoading` | `boolean` | 加载状态,显示加载动画 |
|
||||||
|
| `message` | `object \| null` | 临时消息,3秒后自动消失 |
|
||||||
|
|
||||||
|
**UI 交互逻辑 / UI Interaction Logic:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Loading: 组件挂载
|
||||||
|
Loading --> Ready: loadSettings()
|
||||||
|
Ready --> Modified: updateSettings()
|
||||||
|
Modified --> Modified: 继续修改
|
||||||
|
Modified --> Ready: 保存成功
|
||||||
|
Modified --> Error: 保存失败
|
||||||
|
Error --> Modified: 用户继续操作
|
||||||
|
Ready --> [*]: 组件卸载
|
||||||
|
|
||||||
|
note right of Modified
|
||||||
|
保存按钮启用
|
||||||
|
Save Button Enabled
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of Ready
|
||||||
|
保存按钮禁用
|
||||||
|
Save Button Disabled
|
||||||
|
end note
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 预加载脚本 / Preload Script
|
||||||
|
|
||||||
|
#### preload/index.ts (`src/preload/index.ts`)
|
||||||
|
|
||||||
|
**主要职责 / Main Responsibilities:**
|
||||||
|
- 安全桥梁,暴露受限 API 到渲染进程
|
||||||
|
- 类型安全的 IPC 通道定义
|
||||||
|
|
||||||
|
**关键代码 / Key Code:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 第 89-97 行 / Lines 89-97
|
||||||
|
settings: {
|
||||||
|
getUserType: () => ipcRenderer.invoke('settings:getUserType'),
|
||||||
|
getSettings: () => ipcRenderer.invoke('settings:getSettings'),
|
||||||
|
saveSettings: (settings: SettingsData) =>
|
||||||
|
ipcRenderer.invoke('settings:saveSettings', settings),
|
||||||
|
resetDefaults: () => ipcRenderer.invoke('settings:resetDefaults'),
|
||||||
|
testErpConnection: () => ipcRenderer.invoke('settings:testErpConnection'),
|
||||||
|
testDbConnection: () => ipcRenderer.invoke('settings:testDbConnection')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**安全隔离机制 / Security Isolation:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
Renderer[Renderer Process<br/>Untrusted Context]
|
||||||
|
Preload[Preload Script<br/>Trusted Context]
|
||||||
|
Main[Main Process<br/>Trusted Context]
|
||||||
|
|
||||||
|
Renderer -->|window.electron| Preload
|
||||||
|
Preload -->|ipcRenderer.invoke| Main
|
||||||
|
Main -->|Validation| Preload
|
||||||
|
Preload -->|Return Promise| Renderer
|
||||||
|
|
||||||
|
style Renderer fill:#ffe1e1
|
||||||
|
style Preload fill:#e1ffe1
|
||||||
|
style Main fill:#e1e1ff
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 主进程 / Main Process
|
||||||
|
|
||||||
|
#### settings-handler.ts (`src/main/ipc/settings-handler.ts`)
|
||||||
|
|
||||||
|
**主要职责 / Main Responsibilities:**
|
||||||
|
- IPC 通道注册和处理
|
||||||
|
- 权限验证(基于用户类型)
|
||||||
|
- 业务逻辑协调
|
||||||
|
|
||||||
|
**保存设置处理函数 / Save Settings Handler:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 第 83-102 行 / Lines 83-102
|
||||||
|
ipcMain.handle(
|
||||||
|
'settings:saveSettings',
|
||||||
|
async (_event, settings: SettingsData): Promise<SaveSettingsResult> => {
|
||||||
|
try {
|
||||||
|
log.info('Saving settings')
|
||||||
|
const success = await configManager.saveAllSettings(settings)
|
||||||
|
if (success) {
|
||||||
|
log.info('Settings saved successfully')
|
||||||
|
return { success: true }
|
||||||
|
} else {
|
||||||
|
log.warn('Failed to save settings')
|
||||||
|
return { success: false, error: '保存设置失败' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
log.error('Error saving settings', { error: message })
|
||||||
|
return { success: false, error: `保存设置失败:${message}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**用户类型过滤 / User Type Filtering:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 第 31-54 行 / Lines 31-54
|
||||||
|
function filterSettingsByUserType(settings: SettingsData, userType: UserType): SettingsData {
|
||||||
|
if (userType === 'Admin') {
|
||||||
|
return settings // Admin 获取完整配置
|
||||||
|
}
|
||||||
|
|
||||||
|
// User 用户获取受限配置
|
||||||
|
return {
|
||||||
|
erp: {
|
||||||
|
username: settings.erp.username,
|
||||||
|
password: settings.erp.password,
|
||||||
|
headless: settings.erp.headless,
|
||||||
|
url: settings.erp.url,
|
||||||
|
ignoreHttpsErrors: settings.erp.ignoreHttpsErrors,
|
||||||
|
autoCloseBrowser: settings.erp.autoCloseBrowser
|
||||||
|
},
|
||||||
|
paths: settings.paths,
|
||||||
|
execution: settings.execution,
|
||||||
|
database: settings.database,
|
||||||
|
extraction: settings.extraction,
|
||||||
|
validation: settings.validation,
|
||||||
|
ui: settings.ui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**权限控制矩阵 / Permission Control Matrix:**
|
||||||
|
|
||||||
|
| 功能 / Feature | Admin | User | Guest |
|
||||||
|
|---------------|-------|------|-------|
|
||||||
|
| 查看所有设置 | ✅ | ⚠️ 部分 | ❌ |
|
||||||
|
| 保存设置 | ✅ | ✅ | ❌ |
|
||||||
|
| 恢复默认值 | ✅ | ❌ | ❌ |
|
||||||
|
| 测试 ERP 连接 | ✅ | ✅ | ❌ |
|
||||||
|
| 测试数据库连接 | ✅ | ✅ | ❌ |
|
||||||
|
|
||||||
|
### 4. 配置管理服务 / Configuration Manager Service
|
||||||
|
|
||||||
|
#### config-manager.ts (`src/main/services/config/config-manager.ts`)
|
||||||
|
|
||||||
|
**主要职责 / Main Responsibilities:**
|
||||||
|
- .env 文件读写
|
||||||
|
- 配置缓存管理
|
||||||
|
- 默认值管理
|
||||||
|
- 类型转换和验证
|
||||||
|
|
||||||
|
**类结构 / Class Structure:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class ConfigManager {
|
||||||
|
private static instance: ConfigManager | null = null // 单例模式
|
||||||
|
private envPath: string // .env 文件路径
|
||||||
|
private configCache: Map<string, string> // 内存缓存
|
||||||
|
private initialized: boolean = false // 初始化标记
|
||||||
|
|
||||||
|
// 单例获取方法
|
||||||
|
public static getInstance(): ConfigManager
|
||||||
|
|
||||||
|
// 配置读取
|
||||||
|
public get(key: string, defaultValue?: string): string | undefined
|
||||||
|
public getBoolean(key: string, defaultValue?: boolean): boolean
|
||||||
|
public getNumber(key: string, defaultValue?: number): number
|
||||||
|
|
||||||
|
// 配置写入
|
||||||
|
public set(key: string, value: string | number | boolean): void
|
||||||
|
|
||||||
|
// 持久化
|
||||||
|
public async save(): Promise<boolean>
|
||||||
|
|
||||||
|
// 高级操作
|
||||||
|
public getAllSettings(): SettingsData
|
||||||
|
public async saveAllSettings(settings: SettingsData): Promise<boolean>
|
||||||
|
public resetToDefaults(): SettingsData
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**保存详细流程 / Save Detailed Flow:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
START[saveAllSettings] --> STEP1[更新 ERP 配置 6 字段]
|
||||||
|
STEP1 --> STEP2[更新数据库配置 7 字段]
|
||||||
|
STEP2 --> STEP3[更新路径配置 3 字段]
|
||||||
|
STEP3 --> STEP4[更新提取配置 5 字段]
|
||||||
|
STEP4 --> STEP5[更新校验配置 5 字段]
|
||||||
|
STEP5 --> STEP6[更新 UI 配置 3 字段]
|
||||||
|
STEP6 --> STEP7[更新执行配置 1 字段]
|
||||||
|
STEP7 --> SAVE[调用 save 方法]
|
||||||
|
SAVE --> BUILD[构建 .env 内容]
|
||||||
|
BUILD --> WRITE[写入文件系统]
|
||||||
|
WRITE --> CHECK{检查结果}
|
||||||
|
CHECK -->|成功| SUCCESS[返回 true]
|
||||||
|
CHECK -->|失败| FAILURE[返回 false]
|
||||||
|
```
|
||||||
|
|
||||||
|
**.env 文件格式 / .env File Format:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ===========================
|
||||||
|
# ERP 系统配置
|
||||||
|
# ===========================
|
||||||
|
ERP_URL=https://68.11.34.30:8082/
|
||||||
|
ERP_USERNAME=
|
||||||
|
ERP_PASSWORD=
|
||||||
|
ERP_HEADLESS=true
|
||||||
|
ERP_IGNORE_HTTPS_ERRORS=true
|
||||||
|
ERP_AUTO_CLOSE_BROWSER=true
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# 数据库配置 - MySQL
|
||||||
|
# ===========================
|
||||||
|
DB_TYPE=mysql
|
||||||
|
DB_NAME=BLD_DB
|
||||||
|
DB_USERNAME=remote_user
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_MYSQL_HOST=192.168.31.83
|
||||||
|
DB_MYSQL_PORT=3306
|
||||||
|
DB_MYSQL_CHARSET=utf8mb4
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# 路径配置
|
||||||
|
# ===========================
|
||||||
|
PATH_DATA_DIR=D:/python/playwrite/data/
|
||||||
|
PATH_DEFAULT_OUTPUT=离散备料计划维护_合并.xlsx
|
||||||
|
PATH_VALIDATION_OUTPUT=物料状态校验结果.xlsx
|
||||||
|
|
||||||
|
# ... 更多配置节
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据结构 / Data Structures
|
||||||
|
|
||||||
|
### SettingsData 接口 / Interface Definition
|
||||||
|
|
||||||
|
**类型定义位置 / Type Definition Location:**
|
||||||
|
`src/main/types/settings.types.ts` (第 136-151 行)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface SettingsData {
|
||||||
|
erp: ErpConfig
|
||||||
|
database: DatabaseConfig
|
||||||
|
paths: PathsConfig
|
||||||
|
extraction: ExtractionConfig
|
||||||
|
validation: ValidationConfig
|
||||||
|
ui: UiConfig
|
||||||
|
execution: ExecutionConfig
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整数据结构树 / Complete Data Structure Tree
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
Settings[SettingsData]
|
||||||
|
|
||||||
|
Settings --> Erp[ErpConfig]
|
||||||
|
Erp --> Erp1[url: string]
|
||||||
|
Erp --> Erp2[username: string]
|
||||||
|
Erp --> Erp3[password: string]
|
||||||
|
Erp --> Erp4[headless: boolean]
|
||||||
|
Erp --> Erp5[ignoreHttpsErrors: boolean]
|
||||||
|
Erp --> Erp6[autoCloseBrowser: boolean]
|
||||||
|
|
||||||
|
Settings --> DB[DatabaseConfig]
|
||||||
|
DB --> DB1[dbType: 'mysql'|'sqlserver']
|
||||||
|
DB --> DB2[server: string]
|
||||||
|
DB --> DB3[mysqlHost: string]
|
||||||
|
DB --> DB4[mysqlPort: number]
|
||||||
|
DB --> DB5[database: string]
|
||||||
|
DB --> DB6[username: string]
|
||||||
|
DB --> DB7[password: string]
|
||||||
|
|
||||||
|
Settings --> Paths[PathsConfig]
|
||||||
|
Paths --> Paths1[dataDir: string]
|
||||||
|
Paths --> Paths2[defaultOutput: string]
|
||||||
|
Paths --> Paths3[validationOutput: string]
|
||||||
|
|
||||||
|
Settings --> Extract[ExtractionConfig]
|
||||||
|
Extract --> Extract1[batchSize: number]
|
||||||
|
Extract --> Extract2[verbose: boolean]
|
||||||
|
Extract --> Extract3[autoConvert: boolean]
|
||||||
|
Extract --> Extract4[mergeBatches: boolean]
|
||||||
|
Extract --> Extract5[enableDbPersistence: boolean]
|
||||||
|
|
||||||
|
Settings --> Valid[ValidationConfig]
|
||||||
|
Valid --> Valid1[dataSource: ValidationDataSource]
|
||||||
|
Valid --> Valid2[batchSize: number]
|
||||||
|
Valid --> Valid3[matchMode: MatchMode]
|
||||||
|
Valid --> Valid4[enableCrud: boolean]
|
||||||
|
Valid --> Valid5[defaultManager: string]
|
||||||
|
|
||||||
|
Settings --> UI[UiConfig]
|
||||||
|
UI --> UI1[fontFamily: string]
|
||||||
|
UI --> UI2[fontSize: number]
|
||||||
|
UI --> UI3[productionIdInputWidth: number]
|
||||||
|
|
||||||
|
Settings --> Exec[ExecutionConfig]
|
||||||
|
Exec --> Exec1[dryRun: boolean]
|
||||||
|
|
||||||
|
style Settings fill:#e1f5ff
|
||||||
|
style Erp fill:#ffe1f5
|
||||||
|
style DB fill:#e1ffe1
|
||||||
|
style Paths fill:#fff4e1
|
||||||
|
style Extract fill:#f5e1ff
|
||||||
|
style Valid fill:#ffe1e1
|
||||||
|
style UI fill:#e1f5ff
|
||||||
|
style Exec fill:#f5f5f5
|
||||||
|
```
|
||||||
|
|
||||||
|
### IPC 通信数据格式 / IPC Communication Data Format
|
||||||
|
|
||||||
|
**请求格式 / Request Format:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"erp": {
|
||||||
|
"url": "https://68.11.34.30:8082/",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "password123",
|
||||||
|
"headless": true,
|
||||||
|
"ignoreHttpsErrors": true,
|
||||||
|
"autoCloseBrowser": true
|
||||||
|
},
|
||||||
|
"database": { ... },
|
||||||
|
"paths": { ... },
|
||||||
|
"extraction": { ... },
|
||||||
|
"validation": { ... },
|
||||||
|
"ui": { ... },
|
||||||
|
"execution": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应格式 / Response Format:**
|
||||||
|
```json
|
||||||
|
// 成功 / Success
|
||||||
|
{
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 失败 / Failure
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "保存设置失败:Access denied"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误处理机制 / Error Handling Mechanism
|
||||||
|
|
||||||
|
### 错误处理层次 / Error Handling Layers
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph "UI Layer"
|
||||||
|
UI_TRY[try-catch in handleSaveSettings]
|
||||||
|
UI_MSG[showMessage display]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "IPC Layer"
|
||||||
|
IPC_TRY[try-catch in handler]
|
||||||
|
IPC_LOG[Structured logging]
|
||||||
|
IPC_RETURN[Return error object]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "Service Layer"
|
||||||
|
SVC_TRY[try-catch in save]
|
||||||
|
SVC_LOG[Console error log]
|
||||||
|
SVC_RETURN[Return false]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph "File System"
|
||||||
|
FS_CHECK[File exists check]
|
||||||
|
FS_WRITE[Write with error handling]
|
||||||
|
end
|
||||||
|
|
||||||
|
UI_TRY -->|Catch| UI_MSG
|
||||||
|
IPC_TRY -->|Catch| IPC_LOG --> IPC_RETURN
|
||||||
|
SVC_TRY -->|Catch| SVC_LOG --> SVC_RETURN
|
||||||
|
FS_WRITE -->|Error| SVC_TRY
|
||||||
|
|
||||||
|
style UI_TRY fill:#ffe1e1
|
||||||
|
style IPC_TRY fill:#ffe1e1
|
||||||
|
style SVC_TRY fill:#ffe1e1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误场景分析 / Error Scenario Analysis
|
||||||
|
|
||||||
|
| 错误场景 / Error Scenario | 触发位置 / Location | 处理方式 / Handling | 用户反馈 / User Feedback |
|
||||||
|
|--------------------------|-------------------|-------------------|----------------------|
|
||||||
|
| IPC 通信失败 | Renderer | try-catch | 显示"保存设置时发生错误" |
|
||||||
|
| 权限不足 | Main Process | 检查 UserType | 返回权限错误信息 |
|
||||||
|
| 文件写入失败 | ConfigManager | fs.writeFileSync 捕获 | 返回"保存设置失败" |
|
||||||
|
| 无效数据类型 | IPC Handler | TypeScript 类型检查 | 返回验证错误 |
|
||||||
|
| 磁盘空间不足 | File System | OS 异常捕获 | 返回系统错误信息 |
|
||||||
|
|
||||||
|
### 日志记录策略 / Logging Strategy
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Main Process 结构化日志 / Structured Logging
|
||||||
|
log.info('Saving settings')
|
||||||
|
log.info('Settings saved successfully')
|
||||||
|
log.warn('Failed to save settings')
|
||||||
|
log.error('Error saving settings', { error: message })
|
||||||
|
```
|
||||||
|
|
||||||
|
**日志级别使用 / Log Level Usage:**
|
||||||
|
- `info`: 正常操作流程
|
||||||
|
- `warn`: 潜在问题(如保存失败但未崩溃)
|
||||||
|
- `error`: 严重错误(如异常抛出)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安全考虑 / Security Considerations
|
||||||
|
|
||||||
|
### 安全机制层级 / Security Layers
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
L1[Layer 1: Context Isolation<br/>渲染进程隔离]
|
||||||
|
L2[Layer 2: contextBridge<br/>受限 API 暴露]
|
||||||
|
L3[Layer 3: User Type Filtering<br/>基于角色的访问控制]
|
||||||
|
L4[Layer 4: File System Permissions<br/>.env 文件保护]
|
||||||
|
|
||||||
|
L1 --> L2 --> L3 --> L4
|
||||||
|
|
||||||
|
style L1 fill:#e1f5ff
|
||||||
|
style L2 fill:#fff4e1
|
||||||
|
style L3 fill:#e1ffe1
|
||||||
|
style L4 fill:#ffe1f5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键安全措施 / Key Security Measures
|
||||||
|
|
||||||
|
1. **密码明文存储风险 / Password Storage Risk**
|
||||||
|
- ⚠️ 当前:密码以明文形式存储在 .env 文件中
|
||||||
|
- 🔒 建议:实现加密存储机制
|
||||||
|
|
||||||
|
2. **用户权限隔离 / User Permission Isolation**
|
||||||
|
- ✅ 实现:基于用户类型过滤可见配置
|
||||||
|
- ✅ 实现:Guest 用户无法访问设置页面
|
||||||
|
|
||||||
|
3. **IPC 通信安全 / IPC Communication Security**
|
||||||
|
- ✅ 实现:使用 `contextBridge` 而非直接暴露
|
||||||
|
- ✅ 实现:类型安全的 TypeScript 接口
|
||||||
|
|
||||||
|
4. **文件系统访问 / File System Access**
|
||||||
|
- ✅ 实现:.env 文件仅主进程可访问
|
||||||
|
- ⚠️ 风险:文件权限取决于操作系统
|
||||||
|
|
||||||
|
### 敏感数据流向 / Sensitive Data Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User as 用户输入
|
||||||
|
participant UI as UI State (内存)
|
||||||
|
participant IPC as IPC Channel
|
||||||
|
participant Cache as Config Cache
|
||||||
|
participant File as .env File
|
||||||
|
|
||||||
|
User->>UI: password = "secret123"
|
||||||
|
UI->>IPC: JSON 传输 (未加密)
|
||||||
|
IPC->>Cache: Map.set('erp.password', 'secret123')
|
||||||
|
Cache->>File: 写入明文到磁盘
|
||||||
|
|
||||||
|
Note over File: ⚠️ 安全风险:<br/>密码以明文形式持久化
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术实现细节 / Technical Implementation Details
|
||||||
|
|
||||||
|
### 文件位置索引 / File Location Index
|
||||||
|
|
||||||
|
| 组件 / Component | 文件路径 / File Path | 关键行数 / Key Lines |
|
||||||
|
|-----------------|---------------------|-------------------|
|
||||||
|
| UI 组件 | `src/renderer/src/pages/SettingsPage.tsx` | 61-73 (保存处理) |
|
||||||
|
| 预加载脚本 | `src/preload/index.ts` | 89-97 (API 定义) |
|
||||||
|
| IPC 处理器 | `src/main/ipc/settings-handler.ts` | 83-102 (保存处理) |
|
||||||
|
| 配置管理器 | `src/main/services/config/config-manager.ts` | 437-483 (保存方法) |
|
||||||
|
| 类型定义 | `src/main/types/settings.types.ts` | 136-171 (接口定义) |
|
||||||
|
| IPC 注册 | `src/main/ipc/index.ts` | 导入 settings-handler |
|
||||||
|
|
||||||
|
### 性能特性 / Performance Characteristics
|
||||||
|
|
||||||
|
1. **异步操作 / Async Operations**
|
||||||
|
- 所有 IPC 调用使用 `async/await` 模式
|
||||||
|
- 避免阻塞主进程事件循环
|
||||||
|
|
||||||
|
2. **内存优化 / Memory Optimization**
|
||||||
|
- 使用 Map 缓存配置,减少文件读取
|
||||||
|
- 按需加载配置项
|
||||||
|
|
||||||
|
3. **写入策略 / Write Strategy**
|
||||||
|
- 每次保存完整重写 .env 文件
|
||||||
|
- 原子写入(writeFileSync)
|
||||||
|
|
||||||
|
### 依赖关系图 / Dependency Graph
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[SettingsPage.tsx] -->|imports| B[lucide-react]
|
||||||
|
A -->|uses| C[window.electron.settings]
|
||||||
|
|
||||||
|
C -->|exposed by| D[preload/index.ts]
|
||||||
|
D -->|imports| E[electron API]
|
||||||
|
D -->|imports| F[SettingsData Type]
|
||||||
|
|
||||||
|
G[settings-handler.ts] -->|imports| H[ipcMain]
|
||||||
|
G -->|imports| I[ConfigManager]
|
||||||
|
G -->|imports| J[SessionManager]
|
||||||
|
G -->|imports| K[Logger]
|
||||||
|
|
||||||
|
I -->|imports| L[fs/path]
|
||||||
|
I -->|imports| M[SettingsData Type]
|
||||||
|
I -->|imports| N[DEFAULT_SETTINGS]
|
||||||
|
|
||||||
|
style A fill:#e1f5ff
|
||||||
|
style D fill:#fff4e1
|
||||||
|
style G fill:#ffe1f5
|
||||||
|
style I fill:#e1ffe1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键代码片段分析 / Key Code Snippet Analysis
|
||||||
|
|
||||||
|
**1. 状态更新逻辑 / State Update Logic**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// SettingsPage.tsx 第 50-59 行
|
||||||
|
const updateSettings = (category: string, key: string, value: any) => {
|
||||||
|
setSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[category]: {
|
||||||
|
...(prev as any)[category],
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
setIsModified(true) // 标记为已修改
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计要点 / Design Points:**
|
||||||
|
- 不可变更新模式(Immutable Update Pattern)
|
||||||
|
- 使用展开运算符保持对象引用
|
||||||
|
- 自动启用保存按钮
|
||||||
|
|
||||||
|
**2. 配置保存逻辑 / Configuration Save Logic**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// config-manager.ts 第 437-483 行
|
||||||
|
public async saveAllSettings(settings: SettingsData): Promise<boolean> {
|
||||||
|
// 批量更新缓存 (40+ 字段)
|
||||||
|
this.set('erp.url', settings.erp.url)
|
||||||
|
this.set('erp.username', settings.erp.username)
|
||||||
|
// ... 更多字段
|
||||||
|
|
||||||
|
// 同步写入文件
|
||||||
|
return this.save()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计要点 / Design Points:**
|
||||||
|
- 先更新内存,后写入磁盘
|
||||||
|
- 失败时缓存保持不变
|
||||||
|
- 返回布尔值表示成功/失败
|
||||||
|
|
||||||
|
**3. .env 文件生成逻辑 / .env File Generation**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// config-manager.ts 第 179-345 行
|
||||||
|
public async save(): Promise<boolean> {
|
||||||
|
const lines: string[] = []
|
||||||
|
|
||||||
|
// 构建格式化的 .env 内容
|
||||||
|
lines.push('# ===========================')
|
||||||
|
lines.push('# ERP 系统配置')
|
||||||
|
lines.push('# ===========================')
|
||||||
|
lines.push(`ERP_URL=${this.configCache.get('erp.url') || DEFAULT_SETTINGS.erp.url}`)
|
||||||
|
|
||||||
|
const content = lines.join('\n')
|
||||||
|
fs.writeFileSync(this.envPath, content, 'utf-8')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计要点 / Design Points:**
|
||||||
|
- 添加注释分隔符提高可读性
|
||||||
|
- 使用默认值作为后备
|
||||||
|
- 同步写入确保一致性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 扩展与改进建议 / Extension and Improvement Suggestions
|
||||||
|
|
||||||
|
### 短期改进 / Short-term Improvements
|
||||||
|
|
||||||
|
1. **输入验证 / Input Validation**
|
||||||
|
- 添加 URL 格式验证
|
||||||
|
- 密码强度检查
|
||||||
|
- 端口号范围验证
|
||||||
|
|
||||||
|
2. **用户体验 / User Experience**
|
||||||
|
- 添加保存进度指示器
|
||||||
|
- 实现自动保存功能
|
||||||
|
- 添加配置导入/导出
|
||||||
|
|
||||||
|
3. **错误处理 / Error Handling**
|
||||||
|
- 更详细的错误消息
|
||||||
|
- 错误恢复建议
|
||||||
|
- 错误日志导出
|
||||||
|
|
||||||
|
### 长期改进 / Long-term Improvements
|
||||||
|
|
||||||
|
1. **安全性增强 / Security Enhancement**
|
||||||
|
```typescript
|
||||||
|
// 建议实现密码加密
|
||||||
|
interface SecureSettingsData extends SettingsData {
|
||||||
|
erp: {
|
||||||
|
...ErpConfig
|
||||||
|
encryptedPassword: string // 替代明文密码
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **配置版本控制 / Configuration Versioning**
|
||||||
|
- 实现配置历史记录
|
||||||
|
- 支持回滚到之前版本
|
||||||
|
- 配置变更审计日志
|
||||||
|
|
||||||
|
3. **实时配置重载 / Live Config Reload**
|
||||||
|
- 监听 .env 文件变化
|
||||||
|
- 自动重载配置
|
||||||
|
- 通知相关服务更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试建议 / Testing Recommendations
|
||||||
|
|
||||||
|
### 单元测试 / Unit Tests
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 测试用例示例
|
||||||
|
describe('ConfigManager', () => {
|
||||||
|
it('should save settings successfully', async () => {
|
||||||
|
const manager = ConfigManager.getInstance()
|
||||||
|
const settings: SettingsData = { /* mock data */ }
|
||||||
|
const result = await manager.saveAllSettings(settings)
|
||||||
|
expect(result).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle file write errors', async () => {
|
||||||
|
// Mock fs.writeFileSync to throw error
|
||||||
|
const result = await manager.saveAllSettings(settings)
|
||||||
|
expect(result).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 集成测试 / Integration Tests
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
describe('Settings Save Flow', () => {
|
||||||
|
it('should complete full save cycle', async () => {
|
||||||
|
// 1. User modifies settings
|
||||||
|
// 2. Clicks save button
|
||||||
|
// 3. Verifies .env file updated
|
||||||
|
// 4. Confirms UI feedback
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录 / Appendix
|
||||||
|
|
||||||
|
### 完整配置字段列表 / Complete Configuration Field List
|
||||||
|
|
||||||
|
| 类别 / Category | 字段数 / Field Count | 字段列表 / Field List |
|
||||||
|
|---------------|---------------------|-------------------|
|
||||||
|
| ERP | 6 | url, username, password, headless, ignoreHttpsErrors, autoCloseBrowser |
|
||||||
|
| Database | 7 | dbType, server, mysqlHost, mysqlPort, database, username, password |
|
||||||
|
| Paths | 3 | dataDir, defaultOutput, validationOutput |
|
||||||
|
| Extraction | 5 | batchSize, verbose, autoConvert, mergeBatches, enableDbPersistence |
|
||||||
|
| Validation | 5 | dataSource, batchSize, matchMode, enableCrud, defaultManager |
|
||||||
|
| UI | 3 | fontFamily, fontSize, productionIdInputWidth |
|
||||||
|
| Execution | 1 | dryRun |
|
||||||
|
| **总计 / Total** | **30** | |
|
||||||
|
|
||||||
|
### 相关文档 / Related Documentation
|
||||||
|
|
||||||
|
- [Electron Security Guidelines](https://www.electronjs.org/docs/latest/tutorial/security)
|
||||||
|
- [IPC 通信最佳实践](https://www.electronjs.org/docs/latest/tutorial/ipc)
|
||||||
|
- [环境变量管理规范](.env.example)
|
||||||
|
|
||||||
|
### 版本历史 / Version History
|
||||||
|
|
||||||
|
| 版本 / Version | 日期 / Date | 变更 / Changes |
|
||||||
|
|---------------|------------|--------------|
|
||||||
|
| 1.0 | 2025-03-03 | 初始版本 / Initial version |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档生成时间 / Document Generated:** 2025-03-03
|
||||||
|
**最后更新 / Last Updated:** 2025-03-03
|
||||||
|
**维护者 / Maintainer:** ERPAuto Development Team
|
||||||
Reference in New Issue
Block a user