# 插件系统设计（提案 v1）

> 状态：**待确认**（本文只是设计和取舍，没有改动代码）
> 目标版本：1.5.0

## 一、有必要做吗？

**有，但要做成"轻插件"，不要做成"第二套软件"。**

理由：

1. **主程序已经够重了**：核心（图层/选区/调色/人像/批量）本来就该稳定，长尾需求（证件照排版、图片加水印、拼长图、二维码、联系表、颜色分析…）做成插件，主程序不用天天变大。
2. **官方插件可以快速验证需求**：哪个插件下载多，就说明该功能值得进主程序。
3. **社区/自制插件**：会写 JS 的人能直接扩展，不用碰 Electron 打包。
4. **风险可控**：插件跑在渲染进程的受限沙箱里，只能通过白名单 API 拿画布和界面，不能读文件、不能联网、不能 require。

不建议做的：插件里允许 `require('fs')`、允许自建窗口/发网络请求——那等于把用户的电脑交给插件。

## 二、安全模型（关键设计）

| 项 | 做法 |
| --- | --- |
| 执行环境 | `new Function('api', code)`，**不注入** `require/module/process/window.compositorApi` |
| 能拿到的能力 | 只通过 `api` 对象：当前画布、选区矩形、滤镜注册、菜单项、面板、通知、命名空间存储 |
| 文件/网络 | 默认**禁止**；需要读文件的插件（如批量重命名）走官方内置插件，或由用户显式授权后调用白名单 IPC |
| 安装 | 插件管理器里「安装插件…」选择 `.js` 或文件夹 → 显示插件名/作者/权限清单 → 用户点「信任并安装」 |
| 停用 | 管理器里一键停用；停用后不加载 |
| 隔离失败兜底 | 插件抛错只记日志 + 通知，不影响主程序；加载超时/死循环用 `setTimeout` 包裹无法完全解决，文档里写明"插件由用户自行信任" |

## 三、目录与清单

```
%APPDATA%\小雨修图\plugins\
  com.example.frame-border\
    plugin.json
    index.js
内置：<软件目录>\resources\app\plugins\<id>\
```

`plugin.json`：

```json
{
  "id": "com.xiaoyu.frameborder",
  "name": "加边框",
  "version": "1.0.0",
  "author": "作者",
  "description": "给当前图像加一圈白边/黑边，可调宽度",
  "main": "index.js",
  "builtin": true,
  "apiVersion": 1,
  "permissions": ["canvas:read", "canvas:write", "menu"],
  "ui": { "menu": "滤镜", "command": "加边框…" }
}
```

## 四、插件 API（v1，只开必要的）

```js
api.version              // 1
api.plugin               // { id, name, version, dir }
api.onReady(fn)          // 主界面就绪后回调
api.addFilter(name, apply)          // apply(canvas, params) => canvas
api.addMenu({ menu, label, action, shortcut? })
api.addPanel({ id, title, render(el) })      // 右侧面板组里新增一个标签
api.getActiveCanvas()                // 当前显示用的合成画布（只读副本）
api.applyToActive(fn)                // 把 fn(canvas) 的结果写回当前图层（走撤销栈）
api.selection()                      // {x,y,w,h} 或 null
api.notify(msg, type)                // 右下角提示
api.dialog({ title, body, fields, onOk })    // 复用主程序的表单弹窗
api.storage                       // { get(k), set(k,v), remove(k) }（插件命名空间）
```

明确**不提供**：文件读写、网络请求、进程控制、任意 IPC。

## 五、界面入口

1. 菜单栏新增一个 **插件** 菜单（放在「帮助」左边）：
   - 插件管理器…（列出已装插件、启用/停用、安装、卸载、打开插件文件夹）
   - 已启用插件（动态子项，由插件自己注册的命令）
   - 获取官方插件（打开一个内置列表页，勾选安装）
2. 插件管理器里显示：名称 / 版本 / 作者 / 官方或本地 / 状态 / 权限，右侧「停用」「卸载」按钮。
3. 插件自己的命令按 `ui.menu` 挂到对应菜单（默认挂在「插件」菜单下）。

## 六、官方插件清单（第一批 4 个）

| 插件 | 作用 | 依赖 API |
| --- | --- | --- |
| 加边框 | 白边/黑边/圆角，宽度百分比 | canvas + menu |
| 自动色阶 | 一键按直方图拉黑白场 | filter |
| 联系表 | 把文件夹缩略图拼成一张大图 | 需文件读取（走内置白名单） |
| 二维码水印 | 在角落加二维码 | canvas + 生成库（内置） |

M1 只做前两个（都能在现有 API 内完成），后两个要等"文件读取白名单"。

## 七、菜单/功能栏整理方案（顺带做）

现状：文件 24 项、选择 18 项、滤镜 20 项、图层 16 项、AI 13 项，全部平铺。

| 菜单 | 整理后 |
| --- | --- |
| 文件 | 新建/打开/导入 → 子菜单；保存/另存/导出 → 子菜单；批量 → 子菜单；最近项目 → 子菜单；只留 6~8 个直接项 |
| 编辑 | 撤销/重做/填充/描边/自由变换/首选项，保留平铺（本来就短） |
| 图像 | 模式、图像大小、画布大小、旋转、裁剪 → 调色子菜单（曲线/色阶/HSL…） → 自动调色 |
| 图层 | 新建/复制/删除/合并 → 子菜单；图层样式 → 子菜单；蒙版 → 子菜单；对齐/分布 → 子菜单 |
| 选择 | 修改 → 子菜单（扩展/收缩/羽化/平滑）；色彩范围；选择肤色；选择并遮住；存储/载入选区 |
| 滤镜 | 模糊 / 锐化 / 杂色 / 风格化 / 人像 / 渲染 → 子菜单；预设库；LUT；天空替换 |
| 视图 | 缩放、标尺、网格、参考线 → 子菜单；单位 → 子菜单 |
| AI | 云端 AI → 子菜单；本地 AI → 子菜单；API 设置 |
| 新增 | 插件 |

**风险**：现有测试有 154 条菜单用例按「菜单标签路径」定位，调整后需要同步改测试（工作量约 1~2 小时），功能本身不动。

## 八、里程碑

| 阶段 | 内容 | 预估 |
| --- | --- | --- |
| M1 | 插件加载/启用停用/管理器/2 个官方插件/「插件」菜单 | 半天 |
| M2 | 安装 .js/文件夹、权限确认、打开插件目录、官方插件列表 | 半天 |
| M3 | 菜单重构 + 测试同步 + 官网截图更新 | 半天 |

## 九、需要你决定的事

1. **先做 M1+M2（不动菜单），还是三个一起做？**（一起做改动大，但一次性到位）
2. **官方插件先做哪两个？**（默认：加边框、自动色阶）
3. **自制插件是否允许"读取本地文件"？**（建议不允许，需要文件的功能只做官方内置）
