第二章:数据模型与持久化存储
在自动化系统中,清晰的数据模型是逻辑复用与系统扩展的基础。本章将介绍:
- 如何使用 TypeScript 定义核心数据结构
- 如何利用
electron-store实现数据的本地持久化
2.1 类型定义(TypeScript Interfaces)
我们使用 TypeScript 接口来严格定义数据结构,确保代码在开发与运行阶段都具备良好的类型安全性。
🧩 基础工作流模型
首先定义通用的工作流结构。
每个工作流包含一系列步骤(WorkflowStep)。
动作类型枚举
ts
// 动作类型枚举
export type ActionType =
| 'input'
| 'click'
| 'wait'
| 'scroll'
| 'select'
| 'date';
动作模式(ActionMode)
例如 input 类型可以是直接赋值(set),也可以模拟打字(type)。
ts
// 动作模式:定义更细粒度的操作行为
export type ActionMode =
| 'set'
| 'type'
| 'inner_text' // 输入相关
| 'value'
| 'text'
| 'index'; // select 相关
WorkflowStep
ts
export interface WorkflowStep {
id: string;
type: ActionType;
desc?: string; // 步骤描述
selector?: string; // CSS 选择器 (公共工作流可能为空)
value?: string; // 输入/选择的值
mode?: ActionMode; // 动作模式
delay?: number; // 执行后的延时(ms)
}
Workflow
ts
export interface Workflow {
id: string;
title: string;
desc?: string;
steps: WorkflowStep[];
createdAt: number;
updatedAt: number;
}
🧩 页面工作流模型
页面工作流(PageWorkflow)是公共工作流的具体化:
- 继承
Workflow - 绑定了具体 URL
- 步骤包含对页面定制的 selector 或参数
ts
export interface PageWorkflow extends Workflow {
workflowId: string; // 公共工作流的 ID
url: string; // 绑定的目标 URL
}
2.2 本地存储实现(electron-store)
为了保存用户创建的工作流,我们使用 electron-store。
它基于 JSON 文件进行持久化,并存储于用户的应用数据目录中。
⚠️ Store 初始化与兼容性踩坑
在集成新版 electron-store (v9+) 时,会出现典型的 ESM vs CommonJS 兼容性问题。
❌ 问题复现
报错:
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported.
📌 原因分析
- Electron 主进程默认运行在 CommonJS 环境
electron-storev9+ 强制使用 ESM- 导致 require/import 不兼容问题
✅ 解决方案(实战经验)
为了保持项目稳定并避免复杂的构建配置,我们采用:
- 降级到
electron-store@8.1.0(最后一个支持 CJS 的版本) - 使用:
ts
import Store from 'electron-store'; // 配合 tsconfig: esModuleInterop=true
Store 初始化
ts
import Store from 'electron-store';
// 定义 Store 的 Schema
interface StoreSchema {
workflows: Workflow[];
pageWorkflows: PageWorkflow[];
}
// 初始化 Store 实例
const store = new Store<StoreSchema>({
defaults: {
workflows: [],
pageWorkflows: [],
},
});
export default store;
2.3 封装 CRUD 操作(数据访问层 DAO)
为了保持代码整洁,不应直接在 IPC Handler 中操作 Store。
我们创建一个 数据访问层 (DAO) 来封装存储逻辑。
ts
export class WorkflowStore {
// 获取所有工作流
static getWorkflows(): Workflow[] {
return store.get('workflows') || [];
}
// 根据 ID 获取
static getWorkflowById(id: string): Workflow | undefined {
return this.getWorkflows().find((w) => w.id === id);
}
// 保存或更新工作流
static saveWorkflow(workflow: Workflow): void {
const workflows = this.getWorkflows();
const index = workflows.findIndex((w) => w.id === workflow.id);
if (index > -1) {
workflows[index] = workflow; // 更新
} else {
workflows.push(workflow); // 新增
}
store.set('workflows', workflows);
}
// 删除工作流
static deleteWorkflow(id: string): void {
const workflows = this.getWorkflows();
store.set(
'workflows',
workflows.filter((w) => w.id !== id)
);
}
}
2.4 暴露 IPC 接口
通过 IPC 让渲染进程能够访问工作流数据。
ts
import { ipcMain } from 'electron';
// 获取所有工作流
ipcMain.handle('workflow:get-all', () => {
return WorkflowStore.getWorkflows();
});
// 保存工作流
ipcMain.handle('workflow:save', (_event, workflow) => {
WorkflowStore.saveWorkflow(workflow);
return { success: true };
});
📝 教学笔记(深入理解)
在 Electron 开发中,追求依赖最新版并不总是最佳选择。
-
Electron 环境由 Node.js、Chromium 混合组成
-
模块规范(CJS vs ESM)兼容性问题非常常见
-
遇到问题时:
- 检查依赖包
package.json是否包含"type": "module" - 选择回退版本往往是最快捷且稳定的解决方式
- 检查依赖包