# mydraw-studio **Repository Path**: sharpglasses/mydraw-studio ## Basic Information - **Project Name**: mydraw-studio - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-30 - **Last Updated**: 2026-09-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MyDraw Studio 一个**完全离线、可商业分发**的图表编辑器桌面客户端,使用 **Rust + Tauri** 构建, 以内嵌方式承载 [draw.io](https://github.com/jgraph/drawio)(Apache 2.0)的编辑引擎。 > 当前为最小可跑骨架(方案 C:本机 loopback HTTP 内嵌 draw.io 静态资源)。 > 商用前请按文末清单逐项处理。 --- ## 架构 ``` 前端 (Vite + 原生 JS) Rust (Tauri v2) ┌─────────────────────────┐ ┌──────────────────────────┐ │ index.html │ invoke │ commands.rs │ │ └ #editor iframe(全屏) │ ───────▶ │ get_drawio_url() │ │ │ │ │ save_xml()/read_xml() │ │ │ postMessage(proto=json) │ │ │ ▼ │ │ asset_server.rs │ │ http://127.0.0.1:PORT/ │ ◀─────── │ 127.0.0.1 随机端口服务 │ │ draw.io embed │ HTTP │ 读 drawio-assets/ │ │ └ plugins/mydraw-host.js └──────────────────────────┘ │ 在 draw.io「文件」菜单注入 新建/打开/保存/另存为 └─────────────────────────┘ ``` 文件操作入口在 **draw.io 自己的「文件」菜单**里,宿主不再渲染顶部按钮栏: 菜单点击 → 插件 `postMessage` → 宿主 `invoke` Rust 命令读写本地磁盘。 关键点:draw.io 的所有相对路径资源(stencils / images / xml / mxgraph)通过本机 `127.0.0.1` 的 HTTP 服务加载,**无需改造 draw.io 源码**即可完全离线运行。 --- ## 快速开始 ### 前置依赖 - Node.js ≥ 18 - Rust 工具链(`rustup`,含 stable) - 平台系统库: - **Windows**:Visual Studio Build Tools(C++ 工作负载)+ WebView2(Win10/11 自带) - **macOS**:Xcode Command Line Tools(`xcode-select --install`) - **Linux**:`libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev` ### 步骤 ```bash # 1. 安装前端依赖 npm install # 2. 拉取并本地化 draw.io 静态资源(输出到 src-tauri/drawio-assets/,约 100MB) npm run bundle:drawio # 3. 生成应用图标(默认用内置设计生成,或从自有 PNG 生成全套) python3 scripts/gen-icon.py # 内置设计:渐变圆角方块 + 节点连线图 # npm run tauri icon path/to/logo.png # 换成贵司真实 Logo # 4. 开发模式启动 npm run tauri dev # 5. 打包发布安装包(Windows msi/exe、macOS dmg、Linux deb/rpm) npm run tauri build ``` > 若 `invoke` 报权限错误,运行 `npm run tauri permissions`(v2 CLI)自动补全 > `capabilities/default.json` 中的命令权限名,或直接按报错提示补权限。 --- ## 商业分发前置清单(必做) 1. **改名换标**:把 `tauri.conf.json` 的 `productName`、窗口标题、品牌文本改为自有品牌; 替换 `icons/` 为自有 Logo(运行 `npm run tauri icon your-logo.png`); 默认图标由 `scripts/gen-icon.py` 生成,改配色/布局改脚本顶部的常量即可。 2. **改 identifier**:`com.example.mydrawstudio` → 贵司反向域名。 3. **保留许可证**:根目录 `LICENSE`(Apache 2.0 全文)、`drawio-assets/LICENSE`、 `THIRD_PARTY_NOTICES.md` 随安装包一并分发(已配置 `bundle.resources`)。 4. **代码签名(否则企业客户装不上)**: - Windows:购买代码签名证书(DigiCert / Sectigo 等),在 `tauri.conf.json` 的 `bundle.windows.certificateThumbprint` 填入指纹,或用 `tauri signer` 签名。 - macOS:申请 **Developer ID Application** 证书并对产物**公证(notarize)**, 否则 Gatekeeper 拦截。建议用 `tauri-plugin-apple-notarization` 或 CI 完成。 5. **离线自检**:断网后启动,确认图形库、模板、语言包均可用(资源已在 `drawio-assets/`)。 6. **去除云同步入口**:`scripts/bundle-drawio.mjs` 已做基础本地化;若自行升级 draw.io, 复查 `index.html` 有无新的云端/统计引用。 --- ## 目录结构 ``` mydraw-studio/ ├── index.html # 前端入口 ├── src/ # 前端(全屏 iframe + embed 桥接) │ ├── main.js # 新建/打开/保存逻辑 │ ├── drawio-bridge.js # draw.io embed 协议封装 │ └── style.css ├── drawio-plugin/ # 注入 draw.io 的宿主菜单插件(版本控制) │ └── mydraw-host.js ├── src-tauri/ # Rust 后端 │ ├── Cargo.toml │ ├── tauri.conf.json │ ├── capabilities/default.json │ ├── icons/ │ └── src/ │ ├── main.rs │ ├── lib.rs │ ├── asset_server.rs # 127.0.0.1 静态服务(离线核心) │ └── commands.rs # get_drawio_url / save_xml / read_xml ├── scripts/ │ ├── bundle-drawio.mjs # 拉取 + 本地化 draw.io │ └── gen-icon.py # 图标生成(Pillow,3 倍超采样) ├── src-tauri/drawio-assets/ # 由 bundle 脚本生成(git 忽略内容,保留 .gitkeep) ├── LICENSE # Apache 2.0 全文(含 draw.io 版权) ├── THIRD_PARTY_NOTICES.md # 第三方组件与商标合规说明 └── package.json ``` --- ## 文件菜单与保存的实现要点 - **为什么不用 draw.io 自带的插件机制**: - `App.loadPlugins()` 只认 `App.pluginRegistry` 里的内置短名,URL 参数(实际是 `p`,分号分隔) 对自定义路径无效,控制台只会打印 "Unknown plugin"; - `window.Draw` 由 `App.initPluginCallback()` 创建,而它在 `App.main()` **内部**调用, `App.DrawPlugins` 又在 App 实例构造时遍历 → 注册必须早于 `App.main()`; - `js/PostConfig.js` 异步加载,可能落在 `App.main()` 之后,不可靠;改 `js/app.min.js` 又要动第三方大文件,出问题难回滚。 - **挂载方式**:`bundle:drawio` 把插件复制到 `plugins/mydraw-host.js`,并在 `index.html` 的 `` 前插入挂载脚本(带 `MYDRAW_HOST_BRIDGE_BEGIN/END` 标记,可重复执行)。 插件只导出 `window.MyDrawHostPlugin`,挂载脚本**包一层 `App.main`**, 在真正启动前调用 `Draw.loadPlugin`,无论脚本加载时序如何都不会错过。 **第三方文件(`js/app.min.js`、`js/PreConfig.js`)保持原样**。 - **保存为什么走 export**:draw.io 的 embed 协议**不支持**宿主主动发 `{action:'save'}`, 只有 `load / merge / export`。所以保存前必须发 `{action:'export', format:'xmlsvg'}`, 用返回的 `{event:'export', data}` 作为文件内容——**不能复用缓存的旧 XML 变量**, 否则保存出来的文件是空的。 - **Ctrl+S**:沿用 draw.io 内部保存流程,事件里自带 XML,直接落盘。 - **保存内容**:取 export 消息里的 **`xml` 字段**(`getFileData()` 的完整 mxfile,干净无内嵌 SVG); 该字段缺失时才回退用 `data`(带内嵌 SVG,体积偏大,但 draw.io 打开时会自动重建,不影响使用)。 - **菜单位置**:宿主项(新建/打开/保存/另存为)放在「文件」菜单**最顶部**。 原生「文件」菜单本身是 `new open - save saveAs - import export - pageSetup print`, 含同名项(走 draw.io 自带的浏览器存储),所以插件会把宿主项搬到顶部后, **隐藏重名的原生项**并按 `mxResources` 文案匹配清理残留分隔线。 实现:`menu.tbody` 上做行搬迁(`mxPopupMenu` 每次 popup 都会清空行再调 `funct`,故幂等)。 不需要隐藏时把插件里的 `HIDE_DUPLICATE_NATIVES` 置 `false`。 - **embed 底部按钮**:URL 用 `noSaveBtn=1&saveAndExit=0&noExitBtn=1`。 注意 drawio 的判断是 `"1"==noSaveBtn ? "0"!=saveAndExit && 加「保存并退出」 : ...`, **`saveAndExit` 缺省时该条件仍为真**,必须显式写 `0` 才能去掉该按钮。 ## 已知限制 - draw.io 编辑器本身是浏览器内运行的 SVG/DOM 应用,超大图(数万节点)性能受 WebView 限制。 - 本骨架仅演示 load/save/export 基础链路;AI 生成、协同、版本历史等需自行扩展。