# 插件开发 Skill 基于 CRM 客户管理插件的开发经验,提炼可复用的插件开发流程和模式。 ## 触发场景 - 用户说"开发一个新插件"、"新建行业模块"、"创建插件" - 用户提到需要在 ERP 平台上扩展新的业务模块 ## 插件开发流程 ### 第一步:需求分析 → 数据模型 1. 确定插件 ID(如 `erp-crm`、`erp-inventory`) 2. 列出实体及其字段,为每个字段标注: - `field_type`: String/Integer/Float/Boolean/Date/DateTime/Uuid/Decimal/Json - `required` / `unique` / `searchable` / `filterable` / `sortable` - `visible_when`: 条件显示表达式(如 `type == 'enterprise'`) - `ui_widget`: 表单控件(input/select/textarea/datepicker) - `options`: select 类型的选项列表 ### 第二步:编写 plugin.toml manifest ```toml [metadata] id = "erp-xxx" name = "模块名称" version = "0.1.0" description = "描述" author = "ERP Team" min_platform_version = "0.1.0" # 权限:{entity}.{list|manage} [[permissions]] code = "entity.list" name = "查看 XX" [[permissions]] code = "entity.manage" name = "管理 XX" # 实体定义 [[schema.entities]] name = "entity" display_name = "实体名" [[schema.entities.fields]] name = "field_name" field_type = "String" required = true display_name = "字段名" searchable = true # 页面声明 [[ui.pages]] type = "crud" entity = "entity" label = "页面标题" icon = "icon-name" enable_search = true ``` ### 第三步:创建 Rust crate ```bash mkdir -p crates/erp-plugin-xxx/src ``` **Cargo.toml**: ```toml [package] name = "erp-plugin-xxx" version = "0.1.0" edition = "2024" [lib] crate-type = ["cdylib"] [dependencies] wit-bindgen = "0.55" serde = { workspace = true } serde_json = { workspace = true } ``` **src/lib.rs**: ```rust wit_bindgen::generate!({ path: "../erp-plugin-prototype/wit/plugin.wit", world: "plugin-world", }); use crate::exports::erp::plugin::plugin_api::Guest; struct XxxPlugin; impl Guest for XxxPlugin { fn init() -> Result<(), String> { Ok(()) } fn on_tenant_created(_tenant_id: String) -> Result<(), String> { Ok(()) } fn handle_event(_event_type: String, _payload: Vec) -> Result<(), String> { Ok(()) } } export!(XxxPlugin); ``` ### 第四步:注册到 workspace 根 `Cargo.toml` 的 `[workspace] members` 添加 `"crates/erp-plugin-xxx"`。 ### 第五步:编译和转换 ```bash cargo build -p erp-plugin-xxx --target wasm32-unknown-unknown --release wasm-tools component new target/wasm32-unknown-unknown/release/erp_plugin_xxx.wasm -o target/erp_plugin_xxx.component.wasm ``` ### 第六步:上传和测试 PluginAdmin 页面上传 `.component.wasm` + `plugin.toml`:上传 → 安装 → 启用。 ## 可用页面类型 | 类型 | 说明 | 必填配置 | |------|------|----------| | `crud` | 增删改查表格 | `entity`, `label` | | `tree` | 树形展示 | `entity`, `label`, `id_field`, `parent_field`, `label_field` | | `detail` | 详情 Drawer | `entity`, `label`, `sections` | | `tabs` | 标签页容器 | `label`, `tabs`(子页面列表) | ## detail section 类型 | 类型 | 说明 | 配置 | |------|------|------| | `fields` | 字段描述列表 | `label`, `fields`(字段名数组) | | `crud` | 嵌套 CRUD 表格 | `label`, `entity`, `filter_field` | ## 字段属性速查 | 属性 | 说明 | |------|------| | `searchable` | 可搜索,自动创建 B-tree 索引 | | `filterable` | 可筛选,前端渲染 Select | | `sortable` | 可排序,表格列头排序图标 | | `visible_when` | 条件显示,格式 `field == 'value'` | | `unique` | 唯一约束,CREATE UNIQUE INDEX | | `ui_widget` | 控件:select / textarea | | `options` | select 选项 `[{label, value}]` | ## 权限规则 - 格式:`{entity}.{list|manage}` - 安装时自动加 manifest_id 前缀 - REST API 动态检查,无精细权限时回退 `plugin.list` / `plugin.admin` ## REST API | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/v1/plugins/{id}/{entity}` | 列表(filter/search/sort) | | POST | `/api/v1/plugins/{id}/{entity}` | 创建(required 校验) | | GET | `/api/v1/plugins/{id}/{entity}/{rid}` | 详情 | | PUT | `/api/v1/plugins/{id}/{entity}/{rid}` | 更新(乐观锁) | | DELETE | `/api/v1/plugins/{id}/{entity}/{rid}` | 软删除 | | GET | `/api/v1/plugins/{id}/{entity}/count` | 统计 | | GET | `/api/v1/plugins/{id}/{entity}/aggregate` | 聚合 | ## 测试检查清单 - [ ] `cargo check --workspace` 通过 - [ ] `cargo test --workspace` 通过 - [ ] WASM 编译 + Component 转换成功 - [ ] 上传 → 安装 → 启用流程正常 - [ ] CRUD 完整可用 - [ ] 唯一字段重复插入返回冲突 - [ ] filter/search/sort 查询正常 - [ ] visible_when 条件字段动态显示 - [ ] 侧边栏菜单正确生成 ## 常见陷阱 1. 表名格式:`plugin_{sanitized_id}_{sanitized_entity}`,连字符变下划线 2. edition 必须是 "2024" 3. WIT 路径:`../erp-plugin-prototype/wit/plugin.wit`,不是 `erp-plugin` 4. JSONB 无外键约束,Uuid 字段不自动校验引用完整性 5. Fuel 限制 1000 万,简单逻辑足够,避免重计算循环 6. manifest 中只写 `entity.action`,安装时自动加 manifest_id 前缀