Files
erp/.claude/skills/plugin-development/SKILL.md
iven b482230a07 docs(crm): 更新架构快照 + 提炼插件开发 Skill
- CLAUDE.md §12 新增 CRM 插件完成记录和 erp-plugin-crm 模块
- §13 新增动态表 SQL 注入防护和插件权限注册反模式
- §10 scope 表补充 plugin/crm 范围
- §11 设计文档索引补充 CRM 插件设计和实施计划
- 新建 .claude/skills/plugin-development/SKILL.md 可复用插件开发流程
2026-04-16 19:23:54 +08:00

5.1 KiB
Raw Blame History

插件开发 Skill

基于 CRM 客户管理插件的开发经验,提炼可复用的插件开发流程和模式。

触发场景

  • 用户说"开发一个新插件"、"新建行业模块"、"创建插件"
  • 用户提到需要在 ERP 平台上扩展新的业务模块

插件开发流程

第一步:需求分析 → 数据模型

  1. 确定插件 IDerp-crmerp-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

[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

mkdir -p crates/erp-plugin-xxx/src

Cargo.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

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<u8>) -> Result<(), String> { Ok(()) }
}

export!(XxxPlugin);

第四步:注册到 workspace

Cargo.toml[workspace] members 添加 "crates/erp-plugin-xxx"

第五步:编译和转换

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 前缀