From 2e6578247e4248c7c916eff1fe81cf2023a4f98e Mon Sep 17 00:00:00 2001 From: 24kycj <1637269896@qq.com> Date: Sat, 9 May 2026 18:57:53 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BD=91=E9=A1=B5=E7=AB=AF=EF=BC=9A=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E6=A1=A5=E4=B8=8E=E9=83=A8=E7=BD=B2=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=88=E5=AE=9D=E5=A1=94/Nginx=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/platform:网页 Electron 统一 bridge/web/electron,子目录 API 基路径、返回首页跳站点根、路径末尾斜杠兼容 - index/design*.web.html:网页入口,web.js 加缓存参数避免旧脚本缓存 - api:PHP 读写 soon;文档说明目录权限与 Nginx - lib/design*、lib/index:与 platformBridge 对接及网页侧逻辑 - 公共资源 JsBarcode/jr-qrcode;文档与 package/README/.gitignore 等同步更新 Co-authored-by: Cursor --- .gitignore | 23 +- README.md | 521 +++- api/README.md | 97 + api/read_file.php | 75 + api/save_file.php | 88 + design1.html | 4 +- design1.web.html | 878 ++++++ design2.html | 4 +- design2.web.html | 668 +++++ docs/网页端功能清单.md | 83 + index.html | 6 +- index.web.html | 166 ++ lib/design1.js | 71 +- lib/design1/core.js | 68 +- lib/design1/output.js | 545 ++-- lib/design1/ui.js | 126 +- lib/design2.js | 70 +- lib/design2/core.js | 5105 ++++++++++++++++---------------- lib/design2/output.js | 298 +- lib/design2/ui.js | 112 +- lib/index.js | 570 ++-- lib/platform/bridge.js | 44 + lib/platform/electron.js | 145 + lib/platform/web.js | 341 +++ package.json | 37 +- public/js/JsBarcode.all.min.js | 2 + public/js/jr-qrcode.js | 1 + 27 files changed, 6606 insertions(+), 3542 deletions(-) create mode 100644 api/README.md create mode 100644 api/read_file.php create mode 100644 api/save_file.php create mode 100644 design1.web.html create mode 100644 design2.web.html create mode 100644 docs/网页端功能清单.md create mode 100644 index.web.html create mode 100644 lib/platform/bridge.js create mode 100644 lib/platform/electron.js create mode 100644 lib/platform/web.js create mode 100644 public/js/JsBarcode.all.min.js create mode 100644 public/js/jr-qrcode.js diff --git a/.gitignore b/.gitignore index 7e9c18f..e3597fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ -.DS_Store -dist/electron/* -dist/web/* -build/ -node_modules/ -npm-debug.log -npm-debug.log.* -thumbs.db -!.gitkeep -.backup/ \ No newline at end of file +.DS_Store +dist/electron/* +dist/web/* +build/ +node_modules/ +npm-debug.log +npm-debug.log.* +thumbs.db +!.gitkeep +.backup/ +# 服务器文件存储目录(用户上传的文件) +api/files/ +design/files/ \ No newline at end of file diff --git a/README.md b/README.md index c212fc8..a4b1328 100644 --- a/README.md +++ b/README.md @@ -1,160 +1,361 @@ -# SoonDesign 设计工具 - -简单易用的桌面设计软件,支持光盘和卡片模板设计。 - ---- - -## 🚀 快速开始 - -### 第一次使用 - -```bash -# 1. 安装依赖(只需一次) -yarn install - -# 2. 启动程序 -yarn start -``` - -> **前置要求**:需要安装 [Node.js](https://nodejs.org/)(推荐 16 或更高版本) - ---- - -## 📦 发布流程(傻瓜式操作) - -### 方式一:一键发布 ⭐ 推荐 - -```bash -# Windows 系统 -yarn release:win - -# Mac 系统 -yarn release:mac - -# Linux 系统 -yarn release:linux -``` - -✅ **自动完成**:加密 → 测试 → 清理 → 打包 -✅ **安装包位置**:`build` 文件夹 - ---- - -### 方式二:分步操作(手动控制) - -#### 步骤 1:加密代码 - -```bash -yarn encrypt:win # Windows -yarn encrypt:linux # Mac/Linux -``` - -✅ 生成 `.jsc` 加密文件(自动覆盖旧文件) - -#### 步骤 2:测试 - -```bash -yarn start -``` - -✅ 确认程序正常运行,无报错 - -#### 步骤 3:清理源文件 - -```bash -yarn cleanup -``` - -✅ 删除 `.js` 源文件,自动备份到 `.backup` 文件夹 - -#### 步骤 4:打包 - -```bash -yarn build:win # Windows -yarn build:mac # Mac -yarn build:linux # Linux -``` - -✅ 生成安装包到 `build` 文件夹 - ---- - -## 🔧 其他常用命令 - -### 恢复源文件 - -```bash -yarn restore -``` - -> 从 `.backup` 文件夹恢复所有源文件(误删除时使用) - -### 清理加密文件 - -```bash -yarn cleanup:jsc -``` - -> 删除所有 `.jsc` 文件(重新加密前使用) - -### 修改文件名映射 - -编辑 `lib/module-map.json`: - -```json -{ - "mappings": { - "design1": { - "core": "core1", // 修改这里 - "output": "output1", - "ui": "ui1" - } - } -} -``` - -然后重新运行 `yarn encrypt:win` - ---- - -## ⚠️ 注意事项 - -- **开发时**:保留 `.js` 源文件,不要运行 `cleanup` -- **发布时**:必须先加密、再清理、最后打包 -- **备份在**:`.backup` 文件夹(不会被打包进应用) - ---- - -## 📁 项目结构 - -``` -lib/ -├── module-map.json - 文件名映射配置 -├── index.js - 首页入口 -├── design1.js - 光盘模板入口 -├── design2.js - 卡片模板入口 -├── design1/ - 光盘模板功能模块 -│ ├── core.js - 核心功能 -│ ├── output.js - 导出/保存 -│ └── ui.js - 界面交互 -└── design2/ - 卡片模板功能模块 - ├── core.js - ├── output.js - └── ui.js - -scripts/encrypt/ - 加密脚本(不会被打包) -.backup/ - 源文件备份(不会被打包) -``` - ---- - -## 🔧 调试模式 - -按 `F12` 或 `Ctrl+Shift+I` 打开开发者工具查看错误信息。 - ---- - -## 📚 更多文档 - -- [加密详细说明](scripts/encrypt/README.md) -- 当前版本:v2.3.017 +# SoonDesign 设计工具 + +专业的卡片和光盘设计软件,支持单面/双面模板设计、二维码/条形码生成、图片编辑等功能。提供**桌面端(Electron)**和**网页端**两种使用方式。 + +--- + +## ✨ 主要功能 + +- 🎨 **模板设计**:支持单面(光盘)和双面(卡片)模板设计 +- 📱 **二维码/条形码**:内置二维码和条形码生成器 +- 🖼️ **图片编辑**:支持图片添加、裁剪、旋转、缩放等操作 +- 📝 **文本编辑**:丰富的文本样式设置(字体、大小、颜色、对齐等) +- 💾 **文件管理**:支持 `.soon` 格式项目文件的保存和打开 +- 🌐 **多端支持**:桌面端(Windows/Mac/Linux)和网页端(现代浏览器) +- 🌍 **多语言**:支持中文简体、中文繁体、英文 + +--- + +## 🚀 快速开始 + +### 系统要求 + +- **Node.js**: >= 16.0.0 +- **npm**: >= 7.0.0 或 **yarn** +- **操作系统**: Windows 7+, macOS 10.12+, Linux (Ubuntu 18.04+) + +### 安装依赖 + +```bash +# 使用 yarn(推荐) +yarn install + +# 或使用 npm +npm install +``` + +### 启动应用 + +```bash +# 桌面端(Electron) +yarn start + +# 或 +npm start +``` + +### 网页端使用 + +#### 方式一:使用本地服务器(推荐)⭐ + +```bash +# 启动本地服务器(自动打开浏览器) +yarn web + +# 或使用 npm +npm run web + +# 开发模式(禁用缓存) +yarn web:dev +``` + +服务器会在 `http://localhost:8080` 启动,并自动打开首页。 + +> 💡 **提示**:使用本地服务器可以避免 CORS 问题,确保所有资源正确加载。 + +#### 方式二:使用 Python 内置服务器 + +```bash +# Python 3 +python -m http.server 8080 + +# Python 2 +python -m SimpleHTTPServer 8080 +``` + +然后在浏览器中访问 `http://localhost:8080/index.web.html` + +#### 方式三:使用其他静态服务器 + +```bash +# 使用 serve(需要先安装: npm install -g serve) +serve -p 8080 + +# 使用 live-server(需要先安装: npm install -g live-server) +live-server --port=8080 +``` + +#### 方式四:直接打开文件 + +直接在浏览器中打开以下文件: + +- **首页**: `index.web.html` +- **单面设计页**: `design1.web.html` +- **双面设计页**: `design2.web.html` + +> ⚠️ **注意**:直接打开文件时,某些浏览器可能因为安全策略限制文件访问,建议使用方式一。 + +#### 网页端数据存储 + +- **sessionStorage**: 存储当前会话的项目数据(关闭标签页后清空) +- **localStorage**: 存储历史记录和用户设置(持久化) +- **文件格式**: 使用 `soondesign_session:文件名.soon` 作为 session key + +#### 服务器文件保存功能 ⭐ 新增 + +网页端默认将文件保存在浏览器缓存中。如果部署在服务器上,可以启用服务器文件保存功能,实现真正的文件持久化: + +1. **自动检测**:程序会自动检测是否在服务器环境(非 localhost) +2. **自动保存**:保存文件时自动上传到服务器 +3. **自动读取**:打开文件时自动从服务器读取 +4. **降级处理**:如果服务器保存失败,自动降级为浏览器下载 + +**部署步骤**: +- 将 `api` 目录下的 PHP 文件上传到服务器的 `/api/` 目录 +- 创建文件存储目录:`/design/files/` +- 详细说明请参考:[api/README.md](api/README.md) + +--- + +## 📦 构建和发布 + +### 方式一:一键发布 ⭐ 推荐 + +```bash +# Windows 系统 +yarn release:win + +# Mac 系统 +yarn release:mac + +# Linux 系统 +yarn release:linux +``` + +✅ **自动完成**:加密 → 测试 → 清理 → 打包 +✅ **安装包位置**:`build` 文件夹 + +### 方式二:分步操作(手动控制) + +#### 步骤 1:加密代码 + +```bash +yarn encrypt:win # Windows +yarn encrypt:linux # Mac/Linux +``` + +✅ 生成 `.jsc` 加密文件(自动覆盖旧文件) + +#### 步骤 2:测试 + +```bash +yarn start +``` + +✅ 确认程序正常运行,无报错 + +#### 步骤 3:清理源文件 + +```bash +yarn cleanup +``` + +✅ 删除 `.js` 源文件,自动备份到 `.backup` 文件夹 + +#### 步骤 4:打包 + +```bash +yarn build:win # Windows +yarn build:mac # Mac +yarn build:linux # Linux +``` + +✅ 生成安装包到 `build` 文件夹 + +--- + +## 🔧 其他常用命令 + +### 开发相关 + +```bash +# 启动开发模式 +yarn start + +# 构建 Web 资源 +yarn dist + +# 使用 electron-packager 打包(旧方式) +yarn package +``` + +### 文件管理 + +```bash +# 恢复源文件 +yarn restore + +# 清理加密文件 +yarn cleanup:jsc +``` + +> 从 `.backup` 文件夹恢复所有源文件(误删除时使用) + +### 修改文件名映射 + +编辑 `lib/module-map.json`: + +```json +{ + "mappings": { + "design1": { + "core": "core1", // 修改这里 + "output": "output1", + "ui": "ui1" + } + } +} +``` + +然后重新运行 `yarn encrypt:win` + +--- + +## 📁 项目结构 + +``` +soonDesign/ +├── lib/ # 核心代码库 +│ ├── platform/ # 平台抽象层 +│ │ ├── web.js # 网页端实现 +│ │ ├── electron.js # Electron 桌面端实现 +│ │ └── bridge.js # 平台桥接层 +│ ├── common/ # 公共工具 +│ ├── design1/ # 单面模板(光盘) +│ │ ├── core.js # 核心功能 +│ │ ├── output.js # 导出/保存 +│ │ └── ui.js # 界面交互 +│ ├── design2/ # 双面模板(卡片) +│ │ ├── core.js +│ │ ├── output.js +│ │ └── ui.js +│ ├── index.js # 首页入口 +│ ├── design1.js # 单面模板入口 +│ └── design2.js # 双面模板入口 +├── public/ # 静态资源 +│ ├── images/ # 图片资源 +│ └── css/ # 样式文件 +├── layui/ # LayUI 框架 +├── scripts/ # 构建脚本 +│ └── encrypt/ # 加密脚本 +├── help/ # 帮助文档 +├── main.js # Electron 主进程入口 +├── index.web.html # 网页端首页 +├── design1.web.html # 网页端单面设计页 +├── design2.web.html # 网页端双面设计页 +├── package.json # 项目配置 +└── README.md # 本文档 +``` + +--- + +## 🛠️ 技术栈 + +- **框架**: Electron 15.x +- **UI 库**: LayUI +- **画布库**: Fabric.js +- **二维码**: jr-qrcode +- **条形码**: jsbarcode +- **构建工具**: electron-builder, webpack +- **语言**: JavaScript (ES5+) + +--- + +## 🌐 网页端特性 + +### 存储机制 + +- **sessionStorage**: 存储当前会话的项目数据(关闭标签页后清空) +- **localStorage**: 存储历史记录和用户设置(持久化) +- **文件格式**: 使用 `soondesign_session:文件名.soon` 作为 session key + +### 功能限制 + +- 文件保存会触发浏览器下载(使用 File System Access API 时除外) +- 关闭标签页后 session 数据会丢失,但历史记录会保留 +- 部分浏览器可能限制脚本关闭页签的功能 + +### 浏览器兼容性 + +- Chrome/Edge 90+ +- Firefox 88+ +- Safari 14+ + +--- + +## ⚠️ 注意事项 + +### 开发时 + +- **保留 `.js` 源文件**,不要运行 `cleanup` +- 使用 `yarn start` 启动开发模式 +- 按 `F12` 打开开发者工具调试 + +### 发布时 + +- **必须先加密、再清理、最后打包** +- 备份文件在 `.backup` 文件夹(不会被打包进应用) +- 确保所有依赖已正确安装 + +### 网页端 + +- 数据存储在浏览器本地,清除浏览器数据会丢失历史记录 +- 建议定期导出 `.soon` 文件作为备份 +- 不同域名下的数据不共享 + +--- + +## 🔧 调试模式 + +### 桌面端 + +按 `F12` 或 `Ctrl+Shift+I`(Mac: `Cmd+Option+I`)打开开发者工具查看错误信息。 + +### 网页端 + +同样使用浏览器开发者工具(F12),查看 Console 面板的日志和错误信息。 + +--- + +## 📚 更多文档 + +- [加密详细说明](scripts/encrypt/README.md) +- [网页端功能清单](docs/网页端功能清单.md) + +--- + +## 📝 版本信息 + +- **当前版本**: v3.2.101 +- **更新日期**: 2026-02-07 + +--- + +## 📄 许可证 + +MIT License + +Copyright © 2023 [CardSoon](https://www.cardsoon.com) + +--- + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +--- + +## 📞 联系方式 + +- **官网**: [CardSoon](https://www.cardsoon.com/index.php/cn/products/cardsoonsoft.html) +- **邮箱**: jerry79.xu@gmail.com diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..3d405cc --- /dev/null +++ b/api/README.md @@ -0,0 +1,97 @@ +# SoonDesign 网页端服务器文件保存 API + +## 功能说明 + +网页端默认将文件保存在浏览器缓存(localStorage/sessionStorage)中。通过部署这些 API 接口,可以实现将文件保存到服务器,实现真正的文件持久化。 + +## 部署步骤 + +### 1. 上传 API 文件 + +将 `api` 目录下的 PHP 文件上传到服务器的 `/api/` 目录(相对于网站根目录)。 + +例如,如果网站根目录是 `/www/wwwroot/soonWebsite/`,则: +- `save_file.php` → `/www/wwwroot/soonWebsite/api/save_file.php` +- `read_file.php` → `/www/wwwroot/soonWebsite/api/read_file.php` + +### 2. 创建文件存储目录 + +创建用于存储 `.soon` 文件的目录: + +```bash +mkdir -p /www/wwwroot/soonWebsite/design/files +chmod 755 /www/wwwroot/soonWebsite/design/files +``` + +### 3. 配置 Nginx(如果需要) + +确保 Nginx 配置允许访问 `/api/` 和 `/design/files/` 目录: + +```nginx +location /api/ { + try_files $uri $uri/ =404; +} + +location /design/files/ { + alias /www/wwwroot/soonWebsite/design/files/; + # 允许列出文件(可选) + autoindex off; +} +``` + +### 4. 测试 API + +访问以下 URL 测试 API 是否正常工作: + +- 保存文件:`http://114.55.133.123:8888/api/save_file.php` +- 读取文件:`http://114.55.133.123:8888/api/read_file.php?fileName=test.soon` + +## 工作原理 + +1. **自动检测服务器环境**:网页端会自动检测是否在服务器环境(非 localhost/127.0.0.1) +2. **保存文件**:当用户保存文件时,如果是服务器环境,会自动调用 `save_file.php` 将文件上传到服务器 +3. **读取文件**:当打开文件时,如果是服务器文件路径,会自动调用 `read_file.php` 从服务器读取 +4. **降级处理**:如果服务器保存失败,会自动降级为浏览器下载 + +## 文件存储位置 + +所有保存的 `.soon` 文件存储在: +``` +/design/files/ +``` + +文件访问路径为: +``` +http://114.55.133.123:8888/design/files/文件名.soon +``` + +## 安全说明 + +- API 接口已包含文件名清理,防止路径遍历攻击 +- 建议在生产环境中添加身份验证(如 Token 验证) +- 建议限制文件大小和类型 +- 建议定期清理旧文件 + +## 故障排除 + +### 问题:保存时提示"保存失败" + +**可能原因:** +1. API 文件路径不正确 +2. 文件存储目录权限不足 +3. PHP 配置问题 + +**解决方法:** +1. 检查 API 文件是否在正确位置 +2. 检查目录权限:`chmod 755 /www/wwwroot/soonWebsite/design/files` +3. 检查 PHP 错误日志 + +### 问题:文件保存后找不到 + +**可能原因:** +1. 文件保存路径配置错误 +2. Nginx 配置问题 + +**解决方法:** +1. 检查 `save_file.php` 中的 `$saveDir` 路径 +2. 检查 Nginx 配置是否正确指向文件目录 diff --git a/api/read_file.php b/api/read_file.php new file mode 100644 index 0000000..b655b4d --- /dev/null +++ b/api/read_file.php @@ -0,0 +1,75 @@ + false, 'error' => 'Method not allowed']); + exit; +} + +// 获取文件名 +$fileName = isset($_GET['fileName']) ? $_GET['fileName'] : ''; + +// 验证文件名 +if (empty($fileName)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => '文件名不能为空']); + exit; +} + +// 清理文件名,防止路径遍历攻击 +$fileName = basename($fileName); +if (strpos($fileName, '..') !== false) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => '无效的文件名']); + exit; +} + +// 获取文件路径 +$saveDir = dirname(__DIR__) . '/design/files/'; +$filePath = $saveDir . $fileName; + +// 检查文件是否存在 +if (!file_exists($filePath)) { + http_response_code(404); + echo json_encode(['success' => false, 'error' => '文件不存在']); + exit; +} + +// 读取文件内容 +try { + $content = file_get_contents($filePath); + if ($content === false) { + throw new Exception('文件读取失败'); + } + + // 尝试解析 JSON 验证格式 + $json = json_decode($content, true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new Exception('文件格式错误: ' . json_last_error_msg()); + } + + echo $content; +} catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => '读取失败: ' . $e->getMessage() + ]); +} +?> diff --git a/api/save_file.php b/api/save_file.php new file mode 100644 index 0000000..c22af92 --- /dev/null +++ b/api/save_file.php @@ -0,0 +1,88 @@ + false, 'error' => 'Method not allowed']); + exit; +} + +// 获取保存目录(相对于网站根目录) +// 如果 API 在 /api/ 目录,文件存储在 /design/files/ +$saveDir = dirname(__DIR__) . '/design/files/'; +// 如果上述路径不正确,请根据实际部署情况修改,例如: +// $saveDir = '/www/wwwroot/soonWebsite/design/files/'; + +// 确保目录存在 +if (!is_dir($saveDir)) { + mkdir($saveDir, 0755, true); +} + +// 获取文件名和内容 +$fileName = isset($_POST['fileName']) ? $_POST['fileName'] : ''; +$fileContent = isset($_POST['fileContent']) ? $_POST['fileContent'] : ''; + +// 验证文件名 +if (empty($fileName)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => '文件名不能为空']); + exit; +} + +// 清理文件名,防止路径遍历攻击 +$fileName = basename($fileName); +if (strpos($fileName, '..') !== false) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => '无效的文件名']); + exit; +} + +// 确保文件名以 .soon 结尾 +if (substr($fileName, -5) !== '.soon') { + $fileName = preg_replace('/\.soon$/i', '', $fileName) . '.soon'; +} + +// 验证内容 +if (empty($fileContent)) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => '文件内容不能为空']); + exit; +} + +// 保存文件 +$filePath = $saveDir . $fileName; +try { + $result = file_put_contents($filePath, $fileContent); + if ($result === false) { + throw new Exception('文件写入失败'); + } + + echo json_encode([ + 'success' => true, + 'message' => '文件保存成功', + 'filePath' => '/design/files/' . $fileName, + 'fileName' => $fileName + ]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode([ + 'success' => false, + 'error' => '保存失败: ' . $e->getMessage() + ]); +} +?> diff --git a/design1.html b/design1.html index 701d990..4fdca93 100644 --- a/design1.html +++ b/design1.html @@ -873,10 +873,8 @@ (function() { const path = require('path'), fs = require('fs'), remote = require('@electron/remote'); const appPath = (() => { try { return remote.app.getAppPath(); } catch(e) { return __dirname || process.cwd(); } })(); - + try { require(path.join(appPath, 'lib', 'platform', 'electron.js')); } catch (e) { console.warn('platform/electron:', e); } const jsPath = path.join(appPath, 'lib', 'design1.js'); - - // 加载 .js 源文件 if (fs.existsSync(jsPath)) { try { require(jsPath); diff --git a/design1.web.html b/design1.web.html new file mode 100644 index 0000000..9d6077b --- /dev/null +++ b/design1.web.html @@ -0,0 +1,878 @@ + + + + + + + Soon Design + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design2.html b/design2.html index c518300..41514a6 100644 --- a/design2.html +++ b/design2.html @@ -662,10 +662,8 @@ (function() { const path = require('path'), fs = require('fs'), remote = require('@electron/remote'); const appPath = (() => { try { return remote.app.getAppPath(); } catch(e) { return __dirname || process.cwd(); } })(); - + try { require(path.join(appPath, 'lib', 'platform', 'electron.js')); } catch (e) { console.warn('platform/electron:', e); } const jsPath = path.join(appPath, 'lib', 'design2.js'); - - // 加载 .js 源文件 if (fs.existsSync(jsPath)) { try { require(jsPath); diff --git a/design2.web.html b/design2.web.html new file mode 100644 index 0000000..12ad3a9 --- /dev/null +++ b/design2.web.html @@ -0,0 +1,668 @@ + + + + + + + Soon Design + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/网页端功能清单.md b/docs/网页端功能清单.md new file mode 100644 index 0000000..ca41258 --- /dev/null +++ b/docs/网页端功能清单.md @@ -0,0 +1,83 @@ +# SoonDesign 网页端功能清单 + +本文档列出 SoonDesign 各功能在**桌面端(Electron)**与**网页端**的实现情况,以及网页端不可用时的替代方案。 + +--- + +## 一、首页(index) + +| 功能 | 桌面端 | 网页端 | 替代/说明 | +|------|--------|--------|----------| +| 打开文件 | ✅ 系统对话框选文件,打开设计页 | ✅ 使用 `` 选文件,项目 JSON 存入 `sessionStorage`,通过 `soondesign_session:文件名` 传给设计页 | 网页端选文件后在新页打开,关闭后需重新选择 | +| 新建 1(单面) | ✅ 通过 ipcRenderer 打开设计页 | ✅ 通过 `platformBridge.openDesignPage("", 1)` 跳转 design1.web.html | 一致 | +| 新建 2(双面) | ✅ 通过 ipcRenderer 打开设计页 | ✅ 通过 `platformBridge.openDesignPage("", 2)` 跳转 design2.web.html | 一致 | +| 历史记录 | ✅ 从本地文件读取并展示 | ✅ 使用 `platformBridge.readHistory()`(localStorage) | 网页端历史仅限当前域名,且为路径/名称列表,点击打开需支持 sessionKey | +| 语言切换 | ✅ 正常 | ✅ 使用 localStorage 存语言,无系统 API 时沿用本地存储 | 一致 | +| 关于 | ✅ 正常 | ✅ 可显示版本等信息(若未注入 sysAPI 则仅显示界面) | 一致 | +| 关闭 | ✅ 通过 ipcRenderer 关闭窗口 | ⚠️ 使用 `window.close()` 或隐藏;部分浏览器限制脚本关闭页签 | 替代:用户手动关闭页签 | + +--- + +## 二、设计页(design1 / design2 通用) + +### 2.1 文件操作 + +| 功能 | 桌面端 | 网页端 | 替代/说明 | +|------|--------|--------|----------| +| 打开项目 | ✅ `fs.readFileSync` 读 .soon 后加载画布 | ✅ `platformBridge.readJsonFile(file)` 支持路径或 `soondesign_session:xxx`,异步加载 | 从首页“打开文件”传入的 sessionKey 在设计页通过 readJsonFile 从 sessionStorage 读取 | +| 保存 | ✅ `dialog.showSaveDialog` + `fs.writeFileSync` | ✅ 使用 `platformBridge.writeFile`(fileHandle 或默认文件名)+ 异步写入 | 网页端无 fileHandle 时触发下载 | +| 另存为 | ✅ 同上 | ✅ 同上,通过 showSaveDialog 选保存位置或文件名 | 一致 | +| 导出 PNG | ✅ 对话框选路径 + fs 写入 | ✅ Blob + `fileHandle.createWritable()` 或 `` + `URL.createObjectURL(blob)` 下载 | 一致 | +| 导出 PDF(仅 design2) | ✅ `dialog.showSaveDialog` + `fs.writeFile` | ✅ 使用桥 `showSaveDialog` + `writeFile(Blob)`,或 Blob 下载 | 一致 | +| 写历史(saveHistory) | ✅ fs 读写本地历史文件 | ✅ `platformBridge.readHistory` / `writeHistory`(localStorage) | 一致 | + +### 2.2 预览与打印 + +| 功能 | 桌面端 | 网页端 | 替代/说明 | +|------|--------|--------|----------| +| 预览 | ✅ 生成临时文件 + printJS 等 | ✅ 使用画布导出 Blob → base64 → Uint8Array → Blob URL 显示预览图 | 一致 | +| 打印 | ✅ 同上,调用打印 | ✅ 使用 `platformBridge.printPdf(blob)` 或 Blob URL 交给浏览器打印 | 一致(依赖桥提供 printPdf) | + +### 2.3 界面与编辑 + +| 功能 | 桌面端 | 网页端 | 替代/说明 | +|------|--------|--------|----------| +| 帮助 | ✅ 打开本地或在线帮助 | ✅ 新窗口打开帮助链接或 PDF | 一致 | +| 返回首页 | ✅ ipcRenderer 跳转首页 | ✅ `location.href` 跳转 index.web.html | 一致 | +| 字体 / 剪贴板 / 撤销重做 | ✅ 不依赖 Node | ✅ 同桌面端 | 一致 | +| 关闭前提示(未保存) | ✅ 通过 IPC 或 beforeunload | ✅ beforeunload 提示 | 一致 | + +--- + +## 三、design2 额外说明 + +- **保存 / 另存为**:design2 的 `output.js`、`ui.js` 已统一为:有 `platformBridge.writeFile` 时异步写入,否则使用 `fs.writeFileSync`,网页端走桥或下载。 +- **导出 PDF**:`design2/core.js` 的 `savePdf` 已接桥:网页端使用 `showSaveDialog` + `writeFile(blob)` 或 Blob 下载;预览/打印在无 fs 时使用 data URL,打印可走 `platformBridge.printPdf(pdfBlob)`。 +- **打开项目**:design2 已支持 `platformBridge.readJsonFile` 与 `openFile(file, jAlready)`;`ui.js` 打开对话框在网页端使用 `showOpenDialog` 与 `readJsonFile(file)` 后调用 `openFile(name, j)`。 + +--- + +## 四、平台桥接口(网页端 lib/platform/web.js) + +网页端通过 `window.platformBridge` 提供: + +- `openDesignPage(file, type)`:跳转设计页(file 可为空或 sessionKey) +- `openFirstPage()`:跳转首页 +- `openHelp()`:新窗口打开帮助 +- `runClose()`:尝试关闭当前页(受浏览器限制) +- `onClose(callback)`:beforeunload 时回调(关闭前确认) +- `readJsonFile(pathOrHandle)`:支持 `soondesign_session:xxx` 从 sessionStorage 读 JSON +- `readHistory` / `writeHistory`:历史记录(localStorage) +- `showOpenDialog` / `showSaveDialog`:文件选择(input 或 File System Access) +- `writeFile(content, fileHandleOrFilename)`:写字符串或 Blob +- `printPdf(blob)`:打印 PDF Blob(若未实现则可不提供) + +网页端 `ipcRenderer` 桩会响应:`get-sys-language`、`open-first-page`、`open-design-page`、`open-help-file`、`run-close`,并转发到上述桥方法。 + +--- + +## 五、总结 + +- **首页**:打开、新建、历史、语言、关于在网页端均可用;关闭为浏览器限制,无替代实现则用户手动关页。 +- **设计页**:打开(含 sessionKey)、保存、另存为、导出 PNG、预览、打印、历史、帮助、返回首页、编辑相关功能在网页端可用;design2 导出 PDF 建议后续接桥或改为下载。 +- **无法在网页实现的**:无;仅“关闭窗口”受浏览器限制,其余均有实现或替代方案。 diff --git a/index.html b/index.html index 262b13c..f5ec59a 100644 --- a/index.html +++ b/index.html @@ -219,10 +219,10 @@ (function() { const path = require('path'), fs = require('fs'), remote = require('@electron/remote'); const appPath = (() => { try { return remote.app.getAppPath(); } catch(e) { return __dirname || process.cwd(); } })(); - + try { + require(path.join(appPath, 'lib', 'platform', 'electron.js')); + } catch (e) { console.warn('platform/electron load:', e); } const jsPath = path.join(appPath, 'lib', 'index.js'); - - // 加载 .js 源文件 if (fs.existsSync(jsPath)) { try { require(jsPath); diff --git a/index.web.html b/index.web.html new file mode 100644 index 0000000..7879b0e --- /dev/null +++ b/index.web.html @@ -0,0 +1,166 @@ + + + + + + + SoonDesign + + + + + + +
+
+
+
+

+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ + + + + + diff --git a/lib/design1.js b/lib/design1.js index ff25174..6be4ac2 100644 --- a/lib/design1.js +++ b/lib/design1.js @@ -1,6 +1,6 @@ // design1.js - 主入口文件 -// 导入公共模块(不依赖jQuery的部分) -require('./common/fabric-ext.js'); +// 导入公共模块(不依赖jQuery的部分);网页端由页面 script 标签提前加载 +if (typeof require !== 'undefined') { try { require('./common/fabric-ext.js'); } catch (e) {} } // 过滤 Canvas2D willReadFrequently 警告(不影响功能,只是性能提示) if (typeof console !== 'undefined' && console.warn) { @@ -15,20 +15,24 @@ if (typeof console !== 'undefined' && console.warn) { }; } -// Electron相关导入 -const remote = require('@electron/remote'); - var path = require('path'); -const exePath = remote.app.getPath('userData'); -const { ipcRenderer } = require('electron'); -const { dialog } = require('@electron/remote'); - var jrQrcode = require('jr-qrcode'); - var JsBarcode = require('jsbarcode'); -const { clipboard } = require('electron'); -const dpi = 600; +// 使用平台桥(桌面端由 design1.html 先加载 lib/platform/electron.js 注入) +var remote = typeof window !== 'undefined' ? window.remote : null; +var path = typeof window !== 'undefined' ? window.path : null; +var exePath = typeof window !== 'undefined' ? window.exePath : ''; +var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null; +var dialog = typeof window !== 'undefined' ? window.dialog : null; +var clipboard = typeof window !== 'undefined' ? window.clipboard : null; +var jrQrcode, JsBarcode; +if (typeof require !== 'undefined') { + try { jrQrcode = require('jr-qrcode'); } catch (e) {} + try { JsBarcode = require('jsbarcode'); } catch (e) {} +} +var dpi = 600; -// 发送IPC消息 - ipcRenderer.send('get-sys-fonts'); - ipcRenderer.send('get-scale-rate'); +if (ipcRenderer) { + ipcRenderer.send('get-sys-fonts'); + ipcRenderer.send('get-scale-rate'); +} // 使用layui layui.use(['layer', 'slider', 'form', 'colorpicker'], function () { @@ -41,7 +45,7 @@ const dpi = 600; if (typeof global !== 'undefined') { global.s_lan = s_lan; } - ipcRenderer.send('get-sys-language'); + if (ipcRenderer) ipcRenderer.send('get-sys-language'); var $ = layui.$; var layer = layui.layer; @@ -90,7 +94,7 @@ const dpi = 600; var colorpicker = layui.colorpicker; // 历史记录文件路径(供 output.js 使用) - var fullPath = path.join(exePath, 'data.json'); + var fullPath = (path && exePath) ? path.join(exePath, 'data.json') : (typeof window !== 'undefined' ? window.fullPath : ''); // 确保 fullPath 在全局作用域中可用(用于 .jsc 文件加载) if (typeof window !== 'undefined') { window.fullPath = fullPath; @@ -489,25 +493,40 @@ const dpi = 600; var ctx2 = window.ctx2; let is_bgi_add = window.is_bgi_add; - // 加载design1模块 - const fs = require('fs'); - const appPath = (() => { try { return remote.app.getAppPath(); } catch (e) { return __dirname || process.cwd(); } })(); + // 加载 design1 模块:网页端在回调内动态加载(保证 $ 已存在),桌面端用 require+eval + var fs = typeof window !== 'undefined' ? window.fs : null; + var appPath = (remote && remote.app && remote.app.getAppPath) ? remote.app.getAppPath() : (typeof __dirname !== 'undefined' ? __dirname : '.'); + + if (typeof require === 'undefined') { + // 网页端:按顺序动态插入 script,确保 core/ui 执行时 window.$ 已存在 + function loadScript(src, next) { + var s = document.createElement('script'); + s.src = src; + s.onload = s.onerror = function () { if (typeof next === 'function') next(); }; + document.body.appendChild(s); + } + loadScript('./lib/design1/output.js', function () { + loadScript('./lib/design1/core.js', function () { + loadScript('./lib/design1/ui.js', function () {}); + }); + }); + return; + } const loadModule = (moduleName) => { - const jsPath = path.join(appPath, 'lib', 'design1', `${moduleName}.js`); - - // 加载 .js 源文件 + if (!path || !fs) return; + const jsPath = path.join(appPath, 'lib', 'design1', moduleName + '.js'); if (fs.existsSync(jsPath)) { try { eval(fs.readFileSync(jsPath, 'utf8')); } catch (e) { - alert(`加载 ${moduleName} 失败!\n\n错误: ${e.message}`); + alert('加载 ' + moduleName + ' 失败!\n\n错误: ' + e.message); } } else { - alert(`文件不存在!\n\n请检查:${jsPath}`); + alert('文件不存在!\n\n请检查:' + jsPath); } }; - loadModule('output'); // 先加载 output,因为 core 中的 addBackground() 需要调用 output 中的 open() + loadModule('output'); loadModule('core'); loadModule('ui'); }); diff --git a/lib/design1/core.js b/lib/design1/core.js index 9853d21..0a6939d 100644 --- a/lib/design1/core.js +++ b/lib/design1/core.js @@ -81,25 +81,31 @@ canvas2.preserveObjectStacking = true; var front_list = []; -ipcRenderer.on('font-list', (event, data) => { - const newFontList = []; - data.map(item => { +function applyFontList(data) { + var newFontList = []; + (data || []).map(function(item) { if (item.indexOf('"') === 0) { newFontList.push(item.replace(/^"|"$/g, '')); } else { newFontList.push(item); } - }) + }); front_list = newFontList; - for (let item of front_list) { + for (var i = 0; i < front_list.length; i++) { + var item = front_list[i]; $("#text_font_family").append(""); $("#circle_text_font_family").append(""); $("#out_text_font_family").append(""); $("#barcode_font_family").append(""); $("#count_font_family").append(""); } -}); -ipcRenderer.on('sys-lan', (event, data) => { +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('font-list', function(event, data) { applyFontList(data); }); +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) { + window.platformBridge.getSystemFonts().then(applyFontList); +} +function applySysLan(data) { let lang = localStorage.getItem("lang"); if (lang) { s_lan = lang; @@ -246,23 +252,33 @@ ipcRenderer.on('sys-lan', (event, data) => { localStorage.setItem("lang", "en"); break; } -}); -ipcRenderer.on('close', (event, message) => { - layer.confirm('' + language_str("whetherSave") + '', {//是否保存当前文件? - btn: [language_str("save"), language_str("noSave"), language_str("cancel")] //['保存', '不保存', '取消'] - , btn3: function (index, layero) { - - } +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('sys-lan', function(event, data) { applySysLan(data); }); +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getLocale) { + applySysLan(window.platformBridge.getLocale()); +} +function onCloseConfirm() { + layer.confirm('' + language_str("whetherSave") + '', { + btn: [language_str("save"), language_str("noSave"), language_str("cancel")] + , btn3: function (index, layero) {} }, function (index, layero) { - if (typeof window.output === 'function') { - window.output(() => ipcRenderer.send('run-close')); + window.output(function() { + if (ipcRenderer) ipcRenderer.send('run-close'); + else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose(); + }); } }, function (index) { - ipcRenderer.send('run-close'); + if (ipcRenderer) ipcRenderer.send('run-close'); + else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose(); }); - -}) +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('close', function(event, message) { onCloseConfirm(); }); +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.onClose) { + window.platformBridge.onClose(onCloseConfirm); +} addBackground();//添加背景 白色卡片 initAligningGuidelines(canvas1); initAligningGuidelines(canvas2); @@ -402,9 +418,19 @@ function addBackground() { recordObjs2.push(JSON.stringify(objs2)); recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES)); - let file = GetFile().get('file') + let file = GetFile().get('file'); + if (!file && typeof sessionStorage !== 'undefined') { + try { + file = sessionStorage.getItem('soondesign_open_file'); + if (file) { + sessionStorage.removeItem('soondesign_open_file'); + sessionStorage.removeItem('soondesign_open_type'); + } + } catch (e) {} + } if (file && file != 'empty') { - window.openFile(file) + var p = window.openFile(file) + if (p && p.then) p.then(function() {}, function() {}) } }); }); diff --git a/lib/design1/output.js b/lib/design1/output.js index 35d7501..94c948a 100644 --- a/lib/design1/output.js +++ b/lib/design1/output.js @@ -14,27 +14,41 @@ var openAs = { } }; -// 导出文件 +// 导出文件(兼容桌面 fs 与网页 bridge 下载) async function saveImageAsPNG(buffer) { - let fs = require('fs'); - // 弹出保存对话框 - const { canceled, filePath } = await dialog.showSaveDialog({ + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + var pathJoin = (path && path.join) ? path.join.bind(path) : function() { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); }; + var defaultPath = exePath ? pathJoin(exePath, 'output.png') : 'output.png'; + const result = await dialogApi.showSaveDialog({ title: '保存图片', - defaultPath: path.join(exePath, 'output.png'), // 默认路径 - filters: [{ name: 'PNG Images', extensions: ['png'] }] // 文件过滤器 + defaultPath: defaultPath, + filters: [{ name: 'PNG Images', extensions: ['png'] }] }); - - if (canceled) { + var canceled = result.canceled, filePath = result.filePath || result.fileHandle; + if (canceled || !filePath) return; + if (window.platformBridge && window.platformBridge.writeFile) { + var blob = buffer instanceof Uint8Array ? new Blob([buffer]) : (buffer && buffer.buffer ? new Blob([buffer]) : new Blob([buffer])); + var name = (typeof filePath === 'string' ? filePath.split(/[/\\]/).pop() : 'output.png') || 'output.png'; + if (!/\.png$/i.test(name)) name = name + '.png'; + if (result.fileHandle && result.fileHandle.createWritable) { + result.fileHandle.createWritable().then(function(w) { w.write(blob); return w.close(); }).then(function() { layer.msg(language_str("saveSucc") + name); }); + } else { + var a = document.createElement('a'); + a.download = name; + a.href = URL.createObjectURL(blob); + a.click(); + URL.revokeObjectURL(a.href); + layer.msg(language_str("saveSucc") + name); + } return; } - - // 将Buffer写入PNG文件到用户选择的路径 - fs.writeFile(filePath, buffer, (err) => { - if (err) { - } else { - layer.msg(language_str("saveSucc") + filePath);//'保存成功至' - } - }); + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFile(filePath, buffer, (err) => { + if (!err) layer.msg(language_str("saveSucc") + filePath); + }); + } } // 将 display_func 附加到 window 对象,确保全局可访问 @@ -368,11 +382,22 @@ window.display_func = function display_func(img1, img2, img3) { w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 } - let fs = require('fs'); - const Buffer = require('buffer').Buffer; - const buffer = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); - const printPath = path.join(exePath, 'print.png'); - fs.writeFileSync(printPath, buffer); + var printPath, printBlobUrl, buffer; + if (typeof require !== 'undefined' && require('fs')) { + var fs = require('fs'); + var Buffer = require('buffer').Buffer; + var buf = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + printPath = path.join(exePath, 'print.png'); + fs.writeFileSync(printPath, buf); + buffer = buf; + } else { + var bin = atob(url3.replace(/^data:image\/\w+;base64,/, "")); + var arr = new Uint8Array(bin.length); + for (var i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i); + buffer = arr; + printBlobUrl = URL.createObjectURL(new Blob([arr], { type: 'image/png' })); + printPath = printBlobUrl; + } layer.open({ type: 1, @@ -392,14 +417,18 @@ window.display_func = function display_func(img1, img2, img3) { `, btn: [language_str("output"), "打印"],//'导出' btn1: function (index, layero) { - // 只处理导出(保存PNG) - saveImageAsPNG(buffer) + saveImageAsPNG(buffer); }, btn2: function () { - // 只处理打印 - printJS({ printable: printPath, type: 'image', style: 'img { width: 100%; height: auto; }' }) + if (window.platformBridge && window.platformBridge.printPdf) { + var blob = buffer instanceof Uint8Array ? new Blob([buffer], { type: 'image/png' }) : new Blob([buffer], { type: 'image/png' }); + window.platformBridge.printPdf(blob); + } else if (typeof printJS === 'function') { + printJS({ printable: printPath, type: 'image', style: 'img { width: 100%; height: auto; }' }); + } }, end: function () { + if (printBlobUrl) try { URL.revokeObjectURL(printBlobUrl); } catch (e) {} // 预览窗口关闭后,恢复所有对象的 selectable 和 evented 状态 canvas1.getObjects().forEach((obj, index) => { if (!obj.isGuideLine && objStates1[index]) { @@ -1047,157 +1076,111 @@ window.regenerateQrCodesAndBarcodes = function regenerateQrCodesAndBarcodes(canv // open() 函数在 output.js 中定义,因为 output.js 先于 core.js 加载 // 使用 window.openFile 避免与浏览器原生的 window.open 冲突 -window.openFile = function open(file) { - try { - let fs = require('fs'); - if (!fs.existsSync(file)) { - return; - } - var fsData = fs.readFileSync(file); - openAs.name = file; - let j; - try { - j = JSON.parse(fsData.toString()); - } catch (e) { - return; - } - - if (!background_image) { - return; - } - if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) { - return; - } +window.openFile = function open(file, jAlready) { + if (!file) return; + function doOpenWithJson(j) { + if (!j || !background_image) return; + if (!j.f || !j.f.objects || !Array.isArray(j.f.objects) || j.f.objects.length === 0) return; if (!j.b || !j.b.objects || !Array.isArray(j.b.objects) || j.b.objects.length === 0) { - j.b = { - version: '4.6.0', - objects: [], - hoverCursor: 'move' - }; + j.b = { version: '4.6.0', objects: [], hoverCursor: 'move' }; } - - let left = background_image.left; - let top = background_image.top; - let new_left = j.f.objects[0].left; - let new_top = j.f.objects[0].top; - for (let o of j.f.objects) { - o.left = o.left - new_left + left; - o.top = o.top - new_top + top; + openAs.name = file; + var left = background_image.left; + var top = background_image.top; + var new_left = j.f.objects[0].left; + var new_top = j.f.objects[0].top; + for (var i = 0; i < j.f.objects.length; i++) { + j.f.objects[i].left = j.f.objects[i].left - new_left + left; + j.f.objects[i].top = j.f.objects[i].top - new_top + top; } - if (j.f.objects[0] && !j.f.objects[0].src) { - j.f.objects[0].src = './public/images/bg_front_1.png' - } - if (j.b.objects && j.b.objects.length > 0 && j.b.objects[0] && !j.b.objects[0].src) { - j.b.objects[0].src = './public/images/bg_back.png' - } - - const fo = j.fo || []; - const bo = j.bo || []; - const placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; - - fo.forEach((objMeta, idx) => { - if ((objMeta.type === 8 || objMeta.type === 9) && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) { - j.f.objects[idx + 1].src = placeholder; - } else if (objMeta.type === 2 && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) { - // 合成图片类型,恢复默认图片 - j.f.objects[idx + 1].src = './public/images/outpic_extra.png'; - } + if (j.f.objects[0] && !j.f.objects[0].src) j.f.objects[0].src = './public/images/bg_front_1.png'; + if (j.b.objects && j.b.objects[0] && !j.b.objects[0].src) j.b.objects[0].src = './public/images/bg_back.png'; + var fo = j.fo || [], bo = j.bo || []; + var placeholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; + fo.forEach(function(objMeta, idx) { + if ((objMeta.type === 8 || objMeta.type === 9) && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) j.f.objects[idx + 1].src = placeholder; + else if (objMeta.type === 2 && j.f.objects[idx + 1] && !j.f.objects[idx + 1].src) j.f.objects[idx + 1].src = './public/images/outpic_extra.png'; }); - - bo.forEach((objMeta, idx) => { - if ((objMeta.type === 8 || objMeta.type === 9) && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) { - j.b.objects[idx + 1].src = placeholder; - } else if (objMeta.type === 2 && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) { - // 合成图片类型,恢复默认图片 - j.b.objects[idx + 1].src = './public/images/outpic_extra.png'; - } + bo.forEach(function(objMeta, idx) { + if ((objMeta.type === 8 || objMeta.type === 9) && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) j.b.objects[idx + 1].src = placeholder; + else if (objMeta.type === 2 && j.b.objects && j.b.objects[idx + 1] && !j.b.objects[idx + 1].src) j.b.objects[idx + 1].src = './public/images/outpic_extra.png'; }); - - canvas1.loadFromJSON(j.f, function () { - // 加载后解锁所有对象(背景除外) - if (typeof window.unlockAllObjects === 'function') { - window.unlockAllObjects(canvas1); - } - if (typeof window.regenerateQrCodesAndBarcodes === 'function') { - window.regenerateQrCodesAndBarcodes(canvas1, fo); - } - // 加载后更新列表和图标显示 - if (typeof window.updateList === 'function') { - window.updateList(); - } + canvas1.loadFromJSON(j.f, function() { + if (typeof window.unlockAllObjects === 'function') window.unlockAllObjects(canvas1); + if (typeof window.regenerateQrCodesAndBarcodes === 'function') window.regenerateQrCodesAndBarcodes(canvas1, fo); + if (typeof window.updateList === 'function') window.updateList(); canvas1.renderAll(); }); - canvas2.loadFromJSON(j.b, function () { - // 加载后解锁所有对象(背景除外) - if (typeof window.unlockAllObjects === 'function') { - window.unlockAllObjects(canvas2); - } - if (typeof window.regenerateQrCodesAndBarcodes === 'function') { - window.regenerateQrCodesAndBarcodes(canvas2, bo); - } - // 加载后更新列表和图标显示 - if (typeof window.updateList === 'function') { - window.updateList(); - } + canvas2.loadFromJSON(j.b, function() { + if (typeof window.unlockAllObjects === 'function') window.unlockAllObjects(canvas2); + if (typeof window.regenerateQrCodesAndBarcodes === 'function') window.regenerateQrCodesAndBarcodes(canvas2, bo); + if (typeof window.updateList === 'function') window.updateList(); canvas2.renderAll(); - }); - - let objects1 = canvas1.getObjects(); - let objects2 = canvas2.getObjects(); - if (!objects1 || objects1.length === 0) { - return; - } - background_image1 = objects1[0]; - background_image2 = objects2 && objects2.length > 0 ? objects2[0] : null; - - objs2 = j.bo || []; - objs1 = j.fo || []; - step1.val = 0; - step2.val = 0; - recordJson1 = []; - recordJson2 = []; - recordObjs1 = []; - recordObjs2 = []; - if ($("#front_side").hasClass("ui-button-active")) { - objs = objs1; - recordObjs = recordObjs1; - recordJson = recordJson1; - step = step1; - background_image = background_image1; - } else if ($("#back_side").hasClass("ui-button-active")) { - objs = objs2; - recordObjs = recordObjs2; - recordJson = recordJson2; - background_image = background_image2 || background_image1; - step = step2; - } - canvas1.renderAll(); - canvas2.renderAll(); - // 调用 updateList,现在它已附加到 window 对象 - if (typeof window.updateList === 'function') { - window.updateList(); - } else { - setTimeout(() => { - if (typeof window.updateList === 'function') { - window.updateList(); + var objects1 = canvas1.getObjects(), objects2 = canvas2.getObjects(); + if (objects1 && objects1.length > 0) { + background_image1 = objects1[0]; + background_image2 = objects2 && objects2.length > 0 ? objects2[0] : null; + objs2 = j.bo || []; + objs1 = j.fo || []; + step1.val = 0; + step2.val = 0; + recordJson1 = []; + recordJson2 = []; + recordObjs1 = []; + recordObjs2 = []; + if ($("#front_side").hasClass("ui-button-active")) { + objs = objs1; + recordObjs = recordObjs1; + recordJson = recordJson1; + step = step1; + background_image = background_image1; + } else if ($("#back_side").hasClass("ui-button-active")) { + objs = objs2; + recordObjs = recordObjs2; + recordJson = recordJson2; + background_image = background_image2 || background_image1; + step = step2; } - }, 100); - } - setTimeout(() => { - recordObjs1.push(JSON.stringify(objs1)) - const props = window.TO_JSON_PROPERTIES || ["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"]; - let j1 = canvas1.toJSON(props); - j1.objects[0].hoverCursor = "default"; - recordJson1.push(j1); - - recordObjs2.push(JSON.stringify(objs2)) - let j2 = canvas2.toJSON(props); - recordJson2.push(j2); - }, 0); - } catch (e) { - // 静默处理错误 + canvas1.renderAll(); + canvas2.renderAll(); + if (typeof window.updateList === 'function') window.updateList(); + else setTimeout(function() { if (typeof window.updateList === 'function') window.updateList(); }, 100); + setTimeout(function() { + recordObjs1.push(JSON.stringify(objs1)); + var props = window.TO_JSON_PROPERTIES || ["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"]; + var j1 = canvas1.toJSON(props); + if (j1.objects && j1.objects[0]) j1.objects[0].hoverCursor = "default"; + recordJson1.push(j1); + recordObjs2.push(JSON.stringify(objs2)); + var j2 = canvas2.toJSON(props); + recordJson2.push(j2); + }, 0); + } + }); } -} + if (arguments.length >= 2 && jAlready) { + doOpenWithJson(jAlready); + openAs.name = file; + return; + } + if (window.platformBridge && window.platformBridge.readJsonFile) { + return window.platformBridge.readJsonFile(file).then(function(j) { + if (j) { + doOpenWithJson(j); + openAs.name = file; + } + }).catch(function() {}); + } + try { + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (!fs || file.indexOf('soondesign_session:') === 0) return; + if (!fs.existsSync(file)) return; + var fsData = fs.readFileSync(file); + var j; + try { j = JSON.parse(fsData.toString()); } catch (e) { return; } + doOpenWithJson(j); + } catch (e) {} +}; // 清理不需要的 src 字段(保留自定义图片的 src) // 将 cleanupSrcFields 附加到 window 对象,确保在 ui.js 中的 saveAs 函数也能访问 @@ -1266,34 +1249,37 @@ function saveAs(op1, callback) { let o = { f: j, b: {}, fo: objs1, bo: objs2 }; let con_o = Object.assign(o, op1); - dialog.showSaveDialog({ - title: language_str("saveFile"),//'保存文件' - filters: [ - { name: 'Soon File Type', extensions: ['soon'] }, - ], + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ + title: language_str("saveFile"), + filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined - }).then(result => { - if (result.filePath == "") { return; } - if (result.filePath == null || result.filePath == undefined) { return; } - // 检查文件扩展名,确保以.soon结尾 - const path = require('path'); - let filePath = result.filePath; - const ext = path.extname(filePath).toLowerCase(); - if (ext !== '.soon') { - filePath = filePath + '.soon'; + }).then(async function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : ''); + if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon'; + var content = JSON.stringify(con_o); + con_o.soonType = 1; + if (window.platformBridge && window.platformBridge.writeFile) { + await window.platformBridge.writeFile(result.fileHandle || fp, content); + openAs.name = 'soondesign_session:' + fp; + webSessionStore(content, fp); + layer.msg(language_str("saveSucc") + fp); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + return; } - result.filePath = filePath; - let fs = require('fs') - con_o.soonType = 1 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath; - layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' - saveHistory(); - if (callback && typeof callback === 'function') { - callback(); + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(result.filePath || fp, content, 'utf8'); + openAs.name = result.filePath || fp; + layer.msg(language_str("saveSucc") + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); } - }).catch(err => { - }) + }).catch(function(err) {}); } function save(op1, callback) { @@ -1330,95 +1316,122 @@ function save(op1, callback) { let o = { f: j, b: {}, fo: objs1, bo: objs2 }; let con_o = Object.assign(o, op1); if (openAs.name != "") { - //打开的文件 - let fs = require('fs') - con_o.soonType = 1 - fs.writeFileSync(openAs.name, JSON.stringify(con_o), 'utf8') - layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' - saveHistory(); - if (callback && typeof callback === 'function') { - callback(); + con_o.soonType = 1; + var content = JSON.stringify(con_o); + if (openAs.name.indexOf('soondesign_session:') === 0) { + webSessionStore(content, openAs.name); + // 服务器环境:同时保存到服务器 + var fileName = openAs.name.replace(/^soondesign_session:/, ''); + if (window.platformBridge && window.platformBridge.writeFile && fileName) { + window.platformBridge.writeFile(fileName, content).catch(function() {}); + } + layer.msg(language_str("saveSucc") + fileName); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + return; + } + if (window.platformBridge && window.platformBridge.writeFile) { + webSessionStore(content, openAs.name); + window.platformBridge.writeFile(openAs.name, content).then(function() { + layer.msg(language_str("saveSucc") + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + }); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(openAs.name, content, 'utf8'); + layer.msg(language_str("saveSucc") + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); } return; } - dialog.showSaveDialog({ - title: language_str("saveFile"),//'保存文件' - filters: [ - { name: 'Soon File Type', extensions: ['soon'] }, - ], + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ + title: language_str("saveFile"), + filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined - }).then(result => { - if (result.filePath == "") { return; } - if (result.filePath == null || result.filePath == undefined) { return; } - // 检查文件扩展名,确保以.soon结尾 - const path = require('path'); - let filePath = result.filePath; - const ext = path.extname(filePath).toLowerCase(); - if (ext !== '.soon') { - filePath = filePath + '.soon'; + }).then(async function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + var pathExt = (typeof window !== 'undefined' && window.path && window.path.extname) ? window.path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : ''); + if (pathExt !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon'; + var content = JSON.stringify(con_o); + con_o.soonType = 1; + if (window.platformBridge && window.platformBridge.writeFile) { + await window.platformBridge.writeFile(result.fileHandle || fp, content); + openAs.name = 'soondesign_session:' + fp; + webSessionStore(content, fp); + layer.msg(language_str("saveSucc") + fp); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + return; } - result.filePath = filePath; - let fs = require('fs') - con_o.soonType = 1 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath; - layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' - saveHistory(); - if (callback && typeof callback === 'function') { - callback(); + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(result.filePath || fp, content, 'utf8'); + openAs.name = result.filePath || fp; + layer.msg(language_str("saveSucc") + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); } - }).catch(err => { - }) + }).catch(function(err) {}); +} + +// 网页端:保存前把当前内容写入 sessionStorage 与 localStorage,供首页历史读取(localStorage 跨标签/重开可用) +function webSessionStore(content, fileName) { + if (!window.platformBridge || typeof sessionStorage === 'undefined') return; + var name = fileName || openAs.name || 'design.soon'; + var key = (name && name.indexOf('soondesign_session:') === 0) ? name : ('soondesign_session:' + name); + try { + sessionStorage.setItem(key, content); + if (typeof localStorage !== 'undefined') localStorage.setItem(key, content); + } catch (e) {} } // 将 saveHistory 附加到 window 对象,确保在 ui.js 中也能访问 window.saveHistory = function saveHistory() { - let fs = require('fs'); - let j = { history: [] }; // 默认结构必须是数组 - - try { - // 1. 读取并解析现有文件 - if (fs.existsSync(fullPath)) { - let content = fs.readFileSync(fullPath).toString(); - // 防止空文件报错 - if (content.trim()) { - j = JSON.parse(content); - } + function doWrite(j) { + var currentPath = openAs.name; + var pathForHistory = currentPath; + if (window.platformBridge && (currentPath.indexOf('soondesign_session:') !== 0)) { + pathForHistory = 'soondesign_session:' + (currentPath || 'design.soon'); + } + var newList = (j.history || []).filter(function(item) { return item.path !== pathForHistory && item.path !== currentPath; }); + newList.unshift({ time: getDate(), path: pathForHistory, type: 1 }); + if (newList.length > 20) newList = newList.slice(0, 20); + j.history = newList; + if (window.platformBridge && window.platformBridge.writeHistory) { + window.platformBridge.writeHistory(j); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs && fullPath) { + try { fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8'); } catch (e) {} } - } catch (e) { - // 出错时使用默认空数组,不中断流程 - j = { history: [] }; } - - // 确保 j.history 是数组 (防御性编程) - if (!Array.isArray(j.history)) { - j.history = []; + if (window.platformBridge && window.platformBridge.readHistory) { + window.platformBridge.readHistory().then(function(j) { + if (!j || !Array.isArray(j.history)) j = { history: [] }; + doWrite(j); + }).catch(function() { doWrite({ history: [] }); }); + return; } - - // 2. 【核心优化】先过滤掉当前文件(如果已存在),然后加到最前面 - // 这样可以实现“最近使用的文件排在第一位”的效果 - let currentPath = openAs.name; - let newList = j.history.filter(item => item.path !== currentPath); - - // 3. 插入到头部 (Unshift) - newList.unshift({ - time: getDate(), - path: currentPath, - // type: 1 // 如果需要记录文件类型也可以加在这里 - }); - - // 4. (可选) 限制历史记录数量,比如只保留最近20条,防止文件无限膨胀 - if (newList.length > 20) { - newList = newList.slice(0, 20); - } - - j.history = newList; - - // 5. 统一写入 - try { - fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8'); - } catch (e) { + var fs = typeof require !== 'undefined' ? require('fs') : null; + var j = { history: [] }; + if (fs && fullPath) { + try { + if (fs.existsSync(fullPath)) { + var content = fs.readFileSync(fullPath).toString(); + if (content.trim()) j = JSON.parse(content); + } + } catch (e) { j = { history: [] }; } } + if (!Array.isArray(j.history)) j.history = []; + doWrite(j); }; // 向后兼容 function saveHistory() { diff --git a/lib/design1/ui.js b/lib/design1/ui.js index bc0320e..961d4b7 100644 --- a/lib/design1/ui.js +++ b/lib/design1/ui.js @@ -241,16 +241,17 @@ $("body").keydown(function (event) { $("#addPic").click(function () { OpenDialog(); function OpenDialog() { - dialog.showOpenDialog({ - title: language_str("selectPic"),//请选择图片 - buttonLabel: language_str("comf"),//"确认" - filters: [ - { name: 'Image File Type', extensions: ['png', 'jpg', 'jpeg'] }, - ] - }).then(result => { + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showOpenDialog({ + title: language_str("selectPic"), + buttonLabel: language_str("comf"), + filters: [{ name: 'Image File Type', extensions: ['png', 'jpg', 'jpeg'] }] + }).then(function(result) { if (result.canceled) return; - //console.log(result.filePaths) - fabric.Image.fromURL(result.filePaths[0], function (image) { + var url = (result.files && result.files[0]) ? URL.createObjectURL(result.files[0]) : (result.filePaths && result.filePaths[0]); + if (!url) return; + fabric.Image.fromURL(url, function (image) { //image.left = pointer.x; image.setSrc(image.toDataURL(), function (image) { pre_add_image = image; @@ -1332,29 +1333,35 @@ function saveAs(op1, callback) { let o = { f: j, b: {}, fo: objs1, bo: objs2 }; let con_o = Object.assign(o, op1); - dialog.showSaveDialog({ - title: language_str("saveFile"),//'保存文件' - filters: [ - { name: 'Soon File Type', extensions: ['soon'] }, - ] - }).then(result => { - if (result.filePath == "") { return; } - if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { - result.filePath += '.soon'; + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ + title: language_str("saveFile"), + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }).then(function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + if (typeof fp === 'string' && fp.substring(fp.length - 5).indexOf('.') == -1) fp += '.soon'; + con_o.soonType = 1; + var content = JSON.stringify(con_o); + if (window.platformBridge && window.platformBridge.writeFile) { + window.platformBridge.writeFile(result.fileHandle || fp, content).then(function() { + openAs.name = fp; + layer.msg(language_str("saveSucc") + openAs.name); + if (typeof window.saveHistory === 'function') window.saveHistory(); + if (callback && typeof callback === 'function') callback(); + }).catch(function() {}); + return; } - let fs = require('fs') - con_o.soonType = 1 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath; - layer.msg(language_str("saveSucc") + openAs.name);//'保存成功�? - if (typeof window.saveHistory === 'function') { - window.saveHistory(); + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(result.filePath || fp, content, 'utf8'); + openAs.name = result.filePath || fp; + layer.msg(language_str("saveSucc") + openAs.name); + if (typeof window.saveHistory === 'function') window.saveHistory(); + if (callback && typeof callback === 'function') callback(); } - if (callback && typeof callback === 'function') { - callback(); - } - }).catch(err => { - }) + }).catch(function(err) {}); } // save 函数已在 output.js 中定义 $("#copy").click(function () { @@ -1400,31 +1407,39 @@ $("#open").click(function () { OpenDialog(); }); function OpenDialog() { - dialog.showOpenDialog({ + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showOpenDialog({ title: "请选择文件", buttonLabel: language_str("comf"), - filters: [ - { name: 'Soon File Type', extensions: ['soon'] }, - ] - }).then(result => { + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }).then(function(result) { if (result.canceled) return; - let filePath = result.filePaths[0]; - let fs = require('fs'); - let fsData = fs.readFileSync(filePath); - let j = JSON.parse(fsData.toString()); - let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; - - if (type == 2) { - ipcRenderer.send('open-design-page', filePath, type); + var filePath = result.filePaths && result.filePaths[0]; + var file = (result.files && result.files[0]) || result.file; + function onData(j) { + var nameOrPath = (file && file.name) ? file.name : filePath; + var type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; + if (type == 2) { + if (ipcRenderer) ipcRenderer.send('open-design-page', nameOrPath, type); + else if (window.platformBridge) window.platformBridge.openDesignPage(nameOrPath, type); + return; + } + if (typeof window.openFile === 'function') { + window.openFile(nameOrPath, j); + } + } + if (file && file.text) { + file.text().then(function(t) { onData(JSON.parse(t)); }).catch(function() {}); return; } - - // 调用 output.js 中的 openFile 函数 - if (typeof window.openFile === 'function') { - window.openFile(filePath); + if (!filePath) return; + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + var fsData = fs.readFileSync(filePath); + onData(JSON.parse(fsData.toString())); } - }).catch(err => { - }) + }).catch(function(err) {}); } }); //let blank = "{\"f\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":2787,\"height\":2787,\"fill\":\"#000000\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAX2YPDVmXlAHjVBgAABAgQIECBAgAABAgQIlAh8bQ8Gt83MQ0oWtiYBAgQIECBAgAABAgQIECCQBW7fg8GtM/OoPGuCAAECBAgQIECAAAECBAgQKBG4dQ8GN8zMdSULW5MAAQIECBAgQIAAAQIECBDIAjfuweADM/OsPGuCAAECBAgQIECAAAECBAgQKBH44B4M3jIzLy9Z2JoECBAgQIAAAQIECBAgQIBAFnjrHgxeMDPvzrMmCBAgQIAAAQIECBAgQIAAgRKBn9uDwYmZ+UTJwtYkQIAAAQIECBAgQIAAAQIEssCJPRg8cGa+ODNX5XkTBAgQIECAAAECBAgQIECAwGUucMfMPGztS27b9qGZedplvrD1CBAgQIAAAQIECBAgQIAAgSzw4bXW008Gg/1LD/cvP/QgQIAAAQIECBAgQIAAAQIEugVevtb6zZPB4JqZuanbw/YECBAgQIAAAQIECBAgQIDAzFy71rr568Fgf2zbdv3MPBENAQIECBAgQIAAAQIECBAgUCtw/VrrSfv2pwaDF8/Mb9eSWJwAAQIECBAgQIAAAQIECBB48VrrHXcPBg+dmc/OzLfwIUCAAAECBAgQIECAAAECBOoE/ntmrl5r3fYNwWD/H9u2vW1mXlJHYmECBAgQIECAAAECBAgQIEDg7Wutl55kuOufJByCwWNm5p9n5kGcCBAgQIAAAQIECBAgQIAAgRqB/5uZ711r3XKPweAQDd41Mz9fQ2JRAgQIECBAgAABAgQIECBA4HfXWi88leEbPmFwCAaPPHzK4CpeBAgQIECAAAECBAgQIECAwGUvcMfMPHattX+v4V2P04LBIRq8embecNmTWJAAAQIECBAgQIAAAQIECBB47VrrtAZwpmCwf4fBDTPzeG4ECBAgQIAAAQIECBAgQIDAZSvwqZk5sdb6yt03vMdgsA9t23ZiZj42M1dctiwWI0CAAAECBAgQIECAAAECvQJ3zsxT1lr7BwZOe5wxGByiwS/OzK/32tmcAAECBAgQIECAAAECBAhctgKvWmv92pm2S8Fg//M/nplnX7Y8FiNAgAABAgQIECBAgAABAn0C75uZ56y1tnMKBvsPbdv2rTPz5zPz5D4/GxMgQIAAAQIECBAgQIAAgctO4OMz87S11u1n2+ysnzA4+YPbtj388H0G1152TBYiQIAAAQIECBAgQIAAAQI9AjcdvrfgC2nlIwWD/Um2bXvMzHx0Zh6VntSfEyBAgAABAgQIECBAgAABAvc7gc/MzFPXWjcf5Z0dORgcosE1M/ORmfmuozy5GQIECBAgQIAAAQIECBAgQOB+IfDpmfnxtdb+CYMjPY4VDA7RYP+EwZ/MzBOO9AqGCBAgQIAAAQIECBAgQIAAgftS4JMz88y11v4JgyM/jh0MDtHgwTPzhzPzk0d+JYMECBAgQIAAAQIECBAgQIDAxRZ4/8w8f631peO+8DkFg0M02H/2lTPzKzNzxXFf2DwBAgQIECBAgAABAgQIECBwwQTunJlfnpk3ne1XJ57t1c85GJx80m3bThw+bfC4C7amJyZAgAABAgQIECBAgAABAgSOKvCPM/O8tdaNR/2Be5q718Fgf9Jt2/ZPGOyfNnj1zFx1b96QnyVAgAABAgQIECBAgAABAgTOSeCOmXnj4VMF+ycM7tXjvASDk+9g27ZHzMzrZuZnZ+ab7tU788MECBAgQIAAAQIECBAgQIDAUQT+d2Z+b2Zev9b6/FF+4Cgz5zUYnBIOrp6ZV8zMi2bmyqO8ETMECBAgQIAAAQIECBAgQIDAsQS+PDPvnJk3r7U+d6yfPMLwBQkGp4SDh87Mc/d/OzEzP3iE92OEAAECBAgQIECAAAECBAgQOLvA9TPznpl571rrtguFdUGDwalvetu2x8zMM2bmR2bmqTPzsAu1lOclQIAAAQIECBAgQIAAAQKXkcAXZ+avZ+avZubP1lo3X4zdLlowuFs8eMDMPGFmrpuZ75uZa2bmO2fm2w9fmrh/MsGDAAECBAgQIECAAAECBAi0COyfFNi/tPDfZ+bWmbllZvbfdrD/poNPrrW+drEh/h+5oaPdsxLhuQAAAABJRU5ErkJggg==\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"b\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":2787,\"height\":2787,\"fill\":\"rgb(0,0,0)\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"fo\":[],\"bo\":[]}"; @@ -1463,15 +1478,17 @@ if (typeof window.updateList === 'function') { } $("#new").click(function () { if (step.val == 0) { - ipcRenderer.send('open-first-page'); + if (ipcRenderer) ipcRenderer.send('open-first-page'); + else if (window.platformBridge) window.platformBridge.openFirstPage(); return; } layer.confirm('' + language_str("whetherSave") + '', function (index) {//confirm 是否保存当前文件�? if (typeof window.output === 'function') { - window.output(() => ipcRenderer.send('open-first-page')); + window.output(function() { if (ipcRenderer) ipcRenderer.send('open-first-page'); else if (window.platformBridge) window.platformBridge.openFirstPage(); }); } }, function (index) { - ipcRenderer.send('open-first-page'); + if (ipcRenderer) ipcRenderer.send('open-first-page'); + else if (window.platformBridge) window.platformBridge.openFirstPage(); }); }); @@ -2386,8 +2403,9 @@ $("#about").click(async function () { '' }); }) -$("#help").click(() => { - ipcRenderer.send('open-help-file'); +$("#help").click(function() { + if (window.platformBridge && window.platformBridge.openHelp) window.platformBridge.openHelp(); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-help-file'); }); // =========================================================== diff --git a/lib/design2.js b/lib/design2.js index a9b5e66..048bc30 100644 --- a/lib/design2.js +++ b/lib/design2.js @@ -1,6 +1,6 @@ // design2.js - 主入口文件 -// 导入公共模块(不依赖jQuery的部分) -require('./common/fabric-ext.js'); +// 导入公共模块(不依赖jQuery的部分);网页端由页面 script 标签提前加载 +if (typeof require !== 'undefined') { try { require('./common/fabric-ext.js'); } catch (e) {} } // 过滤 Canvas2D willReadFrequently 警告(不影响功能,只是性能提示) if (typeof console !== 'undefined' && console.warn) { @@ -15,25 +15,29 @@ if (typeof console !== 'undefined' && console.warn) { }; } -// Electron相关导入 -const remote = require('@electron/remote'); - var path = require('path'); -const exePath = remote.app.getPath('userData'); -const { ipcRenderer } = require('electron'); -const { dialog } = require('@electron/remote'); -var jrQrcode = require('jr-qrcode'); -var JsBarcode = require('jsbarcode'); -const { clipboard } = require('electron'); +// 使用平台桥(桌面端由 design2.html 先加载 lib/platform/electron.js 注入) +var remote = typeof window !== 'undefined' ? window.remote : null; +var path = typeof window !== 'undefined' ? window.path : null; +var exePath = typeof window !== 'undefined' ? window.exePath : ''; +var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null; +var dialog = typeof window !== 'undefined' ? window.dialog : null; +var clipboard = typeof window !== 'undefined' ? window.clipboard : null; +var jrQrcode, JsBarcode; +if (typeof require !== 'undefined') { + try { jrQrcode = require('jr-qrcode'); } catch (e) {} + try { JsBarcode = require('jsbarcode'); } catch (e) {} +} -// 发送IPC消息 -ipcRenderer.send('get-sys-fonts'); -ipcRenderer.send('get-scale-rate'); +if (ipcRenderer) { + ipcRenderer.send('get-sys-fonts'); + ipcRenderer.send('get-scale-rate'); +} // 使用layui layui.use(['layer', 'slider', 'form', 'colorpicker'], function () { let myDate = new Date(); let s_lan = ""; - ipcRenderer.send('get-sys-language'); + if (ipcRenderer) ipcRenderer.send('get-sys-language'); var $ = layui.$; var layer = layui.layer; @@ -81,7 +85,7 @@ ipcRenderer.send('get-scale-rate'); var colorpicker = layui.colorpicker; // 历史记录文件路径(供 output.js 使用) - var fullPath = path.join(exePath, 'data.json'); + var fullPath = (path && exePath) ? path.join(exePath, 'data.json') : (typeof window !== 'undefined' ? window.fullPath : ''); // 确保 fullPath 在全局作用域中可用(用于 .jsc 文件加载) if (typeof window !== 'undefined') { window.fullPath = fullPath; @@ -482,25 +486,39 @@ ipcRenderer.send('get-scale-rate'); var ctx2 = window.ctx2; let is_bgi_add = window.is_bgi_add; - // 加载design2模块 - const fs = require('fs'); - const appPath = (() => { try { return remote.app.getAppPath(); } catch(e) { return __dirname || process.cwd(); } })(); - + // 加载 design2 模块:网页端在回调内动态加载(保证 $ 已存在),桌面端用 require+eval + var fs = typeof window !== 'undefined' ? window.fs : null; + var appPath = (remote && remote.app && remote.app.getAppPath) ? remote.app.getAppPath() : (typeof __dirname !== 'undefined' ? __dirname : '.'); + + if (typeof require === 'undefined') { + function loadScript(src, next) { + var s = document.createElement('script'); + s.src = src; + s.onload = s.onerror = function () { if (typeof next === 'function') next(); }; + document.body.appendChild(s); + } + loadScript('./lib/design2/output.js', function () { + loadScript('./lib/design2/core.js', function () { + loadScript('./lib/design2/ui.js', function () {}); + }); + }); + return; + } + const loadModule = (moduleName) => { - const jsPath = path.join(appPath, 'lib', 'design2', `${moduleName}.js`); - - // 加载 .js 源文件 + if (!path || !fs) return; + const jsPath = path.join(appPath, 'lib', 'design2', moduleName + '.js'); if (fs.existsSync(jsPath)) { try { eval(fs.readFileSync(jsPath, 'utf8')); } catch (e) { - alert(`加载 ${moduleName} 失败!\n\n错误: ${e.message}`); + alert('加载 ' + moduleName + ' 失败!\n\n错误: ' + e.message); } } else { - alert(`文件不存在!\n\n请检查:${jsPath}`); + alert('文件不存在!\n\n请检查:' + jsPath); } }; - loadModule('output'); // 先加载 output,因为 core 中的 addBackground() 需要调用 output 中的 open() + loadModule('output'); loadModule('core'); loadModule('ui'); }); diff --git a/lib/design2/core.js b/lib/design2/core.js index 8e711a1..dfaaaf1 100644 --- a/lib/design2/core.js +++ b/lib/design2/core.js @@ -1,2534 +1,2571 @@ -// design2核心功能模块 -// 注意:此代码在layui.use回调函数内部执行 -// 全局变量已在主入口文件中定义,这里只需要初始化 - -$('#canvas1').attr('height', document.body.clientHeight - 100) -$('#canvas2').attr('height', document.body.clientHeight - 100) -$('#object_attribute').css('height', '470px') -$('#obj_list').css('height', document.body.clientHeight - 520) -$('.obj_list').css('height', $('#obj_list').height() - 32) - -$('.main').show() -$('#canvas1').attr('width', $('#canvas-div').width()) -$('#canvas2').attr('width', $('#canvas-div').width()) - -// 初始化Canvas对象 -// 优化性能:在创建 Fabric Canvas 之前,先设置 canvas context 的 willReadFrequently -try { - const canvas1El = document.getElementById('canvas1'); - const canvas2El = document.getElementById('canvas2'); - if (canvas1El) { - canvas1El.getContext('2d', { willReadFrequently: true }); - } - if (canvas2El) { - canvas2El.getContext('2d', { willReadFrequently: true }); - } -} catch (e) { - // 如果浏览器不支持 willReadFrequently,忽略错误 -} - -canvas1 = new fabric.Canvas('canvas1') -canvas2 = new fabric.Canvas('canvas2') -// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.canvas1 = canvas1; -window.canvas2 = canvas2; -// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史 -window._isApplyingHistory = false; -fabric.Object.prototype.objectCaching = false -ctx1 = canvas1.getSelectionContext() -ctx2 = canvas2.getSelectionContext() -is_bgi_add = false -// 同步到 window 对象 -window.ctx1 = ctx1; -window.ctx2 = ctx2; -window.is_bgi_add = is_bgi_add; -canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) -//console.log(canvas1.width / 2, canvas1.height / 2); -//console.log($("#canvas-div").width() / 1200 > 1 ? 1 : $("#canvas-div").width() / 1200); -canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) -zoom = $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200 - -// 初始化标尺 -function initRulers() { - // dpi已在主入口文件中定义为全局变量 - const mmToPx = dpi / 25.4; // 1mm = 11.811px (300 DPI) - const rulerHeight = 20; - const rulerWidth = 20; - - // 获取实际画布的像素尺寸(px) - // design2.html 的实际画布尺寸是 1012px x 648px - const actualCanvasWidth = canvas1.width || 1012; // 实际画布宽度(px) - const actualCanvasHeight = canvas1.height || 648; // 实际画布高度(px) - - // 获取显示区域的尺寸(包括 margin) - const canvasDiv = $('#canvas-div'); - const canvasWrapper = $('.canvas-wrapper'); - // 使用 canvas-wrapper 的宽度,如果没有则使用 canvas_div 的宽度 - let canvasWidth = canvasWrapper.width(); - let canvasHeight = canvasWrapper.height(); - if (!canvasWidth || canvasWidth === 0) { - const canvas1Width = $('#canvas1').width() || canvasDiv.width() - 20; - canvasWidth = canvas1Width + 20; // 加上 margin-left: 20px - } - if (!canvasHeight || canvasHeight === 0) { - const canvas1Height = $('#canvas1').height() || canvasDiv.height() - 20; - canvasHeight = canvas1Height + 20; // 加上 margin-top: 20px - } - - // 设置标尺尺寸(使用显示区域的尺寸) - $('#ruler-h').attr('width', canvasWidth).attr('height', rulerHeight); - $('#ruler-v').attr('width', rulerWidth).attr('height', canvasHeight); - $('.ruler-horizontal').css('width', canvasWidth + 'px'); - $('.ruler-vertical').css('height', canvasHeight + 'px'); - - // 绘制水平标尺 - function drawHorizontalRuler() { - const ctx = document.getElementById('ruler-h').getContext('2d', { willReadFrequently: true }); - ctx.clearRect(0, 0, canvasWidth, rulerHeight); - - // 背景 - ctx.fillStyle = '#2C2C2C'; - ctx.fillRect(0, 0, canvasWidth, rulerHeight); - - // 边框 - ctx.strokeStyle = '#1E1E1E'; - ctx.lineWidth = 1; - ctx.strokeRect(0, 0, canvasWidth, rulerHeight); - ctx.beginPath(); - ctx.moveTo(0, rulerHeight - 1); - ctx.lineTo(canvasWidth, rulerHeight - 1); - ctx.stroke(); - - // 刻度线 - ctx.strokeStyle = '#888888'; - ctx.fillStyle = '#CCCCCC'; - ctx.font = '10px Arial'; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - - // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 - // 实际画布物理宽度 = actualCanvasWidth / dpi * 25.4 (mm) - // 显示区域宽度 = actualCanvasWidth * zoom (px) - // 所以:1mm 在显示区域中 = (actualCanvasWidth * zoom) / (actualCanvasWidth / dpi * 25.4) = zoom * dpi / 25.4 - const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 - const marginOffset = 20; // canvas 的 margin-left: 20px - // 标尺0点应该在background_image的左上角位置 - // background_image在画布坐标系中的left值,需要转换到标尺坐标系 - // 画布从标尺的marginOffset位置开始,所以:标尺0点 = marginOffset + background_image.left (如果存在) - let originX = marginOffset; - if (typeof background_image !== 'undefined' && background_image) { - originX = marginOffset + background_image.left; - } - const startX = 0; - const endX = canvasWidth; - - // 绘制主要刻度(每10mm) - // 从0点向左绘制负值刻度 - for (let mm = -10; originX + mm * pxPerMm >= startX; mm -= 10) { - const x = originX + mm * pxPerMm; - if (x < startX) break; - - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, rulerHeight); - ctx.stroke(); - - // 数字(负值) - ctx.fillText(mm + 'mm', x + 2, 2); - } - // 从0点向右绘制正值刻度 - for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 10) { - const x = originX + mm * pxPerMm; - if (x > endX) break; - - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, rulerHeight); - ctx.stroke(); - - // 数字 - ctx.fillText(mm + 'mm', x + 2, 2); - } - - // 绘制次要刻度(每1mm) - // 从0点向左绘制负值刻度 - for (let mm = -1; originX + mm * pxPerMm >= startX; mm -= 1) { - const x = originX + mm * pxPerMm; - if (x < startX) break; - if (mm % 10 === 0) continue; // 跳过主要刻度 - - ctx.beginPath(); - ctx.moveTo(x, rulerHeight - 8); - ctx.lineTo(x, rulerHeight); - ctx.stroke(); - } - // 从0点向右绘制正值刻度 - for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 1) { - const x = originX + mm * pxPerMm; - if (x > endX) break; - if (mm % 10 === 0) continue; // 跳过主要刻度 - - ctx.beginPath(); - ctx.moveTo(x, rulerHeight - 8); - ctx.lineTo(x, rulerHeight); - ctx.stroke(); - } - } - - // 绘制垂直标尺 - function drawVerticalRuler() { - const ctx = document.getElementById('ruler-v').getContext('2d', { willReadFrequently: true }); - ctx.clearRect(0, 0, rulerWidth, canvasHeight); - - // 背景 - ctx.fillStyle = '#2C2C2C'; - ctx.fillRect(0, 0, rulerWidth, canvasHeight); - - // 边框 - ctx.strokeStyle = '#1E1E1E'; - ctx.lineWidth = 1; - ctx.strokeRect(0, 0, rulerWidth, canvasHeight); - ctx.beginPath(); - ctx.moveTo(rulerWidth - 1, 0); - ctx.lineTo(rulerWidth - 1, canvasHeight); - ctx.stroke(); - - // 刻度线 - ctx.strokeStyle = '#888888'; - ctx.fillStyle = '#CCCCCC'; - ctx.font = '10px Arial'; - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - - // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 - const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 - const marginOffset = 20; // canvas 的 margin-top: 20px (但还要加上ruler的高度) - const rulerHeightOffset = 20; // ruler 的高度 - // 标尺0点应该在background_image的左上角位置 - // background_image在画布坐标系中的top值,需要转换到标尺坐标系 - // 画布从标尺的marginOffset + rulerHeightOffset位置开始,所以:标尺0点 = marginOffset + rulerHeightOffset + background_image.top (如果存在) - let originY = marginOffset + rulerHeightOffset; - if (typeof background_image !== 'undefined' && background_image) { - originY = marginOffset + rulerHeightOffset + background_image.top; - } - const startY = 0; - const endY = canvasHeight; - - // 绘制主要刻度(每10mm) - // 从0点向上绘制负值刻度 - for (let mm = -10; originY + mm * pxPerMm >= startY; mm -= 10) { - const y = originY + mm * pxPerMm; - if (y < startY) break; - - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(rulerWidth, y); - ctx.stroke(); - - // 数字(旋转90度,负值) - ctx.save(); - ctx.translate(2, y + 8); - ctx.rotate(-Math.PI / 2); - ctx.fillText(mm + 'mm', 0, 0); - ctx.restore(); - } - // 从0点向下绘制正值刻度 - for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 10) { - const y = originY + mm * pxPerMm; - if (y > endY) break; - - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(rulerWidth, y); - ctx.stroke(); - - // 数字(旋转90度) - ctx.save(); - ctx.translate(2, y + 8); - ctx.rotate(-Math.PI / 2); - ctx.fillText(mm + 'mm', 0, 0); - ctx.restore(); - } - - // 绘制次要刻度(每1mm) - // 从0点向上绘制负值刻度 - for (let mm = -1; originY + mm * pxPerMm >= startY; mm -= 1) { - const y = originY + mm * pxPerMm; - if (y < startY) break; - if (mm % 10 === 0) continue; // 跳过主要刻度 - - ctx.beginPath(); - ctx.moveTo(rulerWidth - 8, y); - ctx.lineTo(rulerWidth, y); - ctx.stroke(); - } - // 从0点向下绘制正值刻度 - for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 1) { - const y = originY + mm * pxPerMm; - if (y > endY) break; - if (mm % 10 === 0) continue; // 跳过主要刻度 - - ctx.beginPath(); - ctx.moveTo(rulerWidth - 8, y); - ctx.lineTo(rulerWidth, y); - ctx.stroke(); - } - } - - // 初始绘制 - drawHorizontalRuler(); - drawVerticalRuler(); - - // 监听窗口大小变化 - $(window).on('resize', function () { - setTimeout(function () { - const canvasWrapper = $('.canvas-wrapper'); - let newWidth = canvasWrapper.width(); - let newHeight = canvasWrapper.height(); - if (!newWidth || newWidth === 0) { - const canvas1Width = $('#canvas1').width() || $('#canvas-div').width() - 20; - newWidth = canvas1Width + 20; // 加上 margin-left: 20px - } - if (!newHeight || newHeight === 0) { - const canvas1Height = $('#canvas1').height() || $('#canvas-div').height() - 20; - newHeight = canvas1Height + 20; // 加上 margin-top: 20px - } - if (newWidth !== canvasWidth || newHeight !== canvasHeight) { - canvasWidth = newWidth; - canvasHeight = newHeight; - $('#ruler-h').attr('width', newWidth); - $('#ruler-v').attr('height', newHeight); - $('.ruler-horizontal').css('width', newWidth + 'px'); - $('.ruler-vertical').css('height', newHeight + 'px'); - drawHorizontalRuler(); - drawVerticalRuler(); - } - }, 100); - }); - - // 监听画布缩放/渲染变化,仅用于重绘标尺 - // 辅助线交给 Fabric 自己渲染,否则会出现“重影 / 变两三根”的现象 - canvas1.on('after:render', function () { - drawHorizontalRuler(); - drawVerticalRuler(); - }); - canvas2.on('after:render', function () { - drawHorizontalRuler(); - drawVerticalRuler(); - }); - - // --- 辅助线功能 (参考 design1) --- - // canvasWrapper 已在上面声明,直接使用 - const canvasBox = $('#canvas-div'); // design2 使用 canvas-div 作为容器 - - // 确保容器定位 - if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative'); - - // 定义临时拖拽线 DOM - let $dragLine = null; - - // 通用拖拽开始函数 - const startDragGuide = (type, e) => { - e.preventDefault(); - e.stopPropagation(); - - // 防止重复调用:如果正在拖拽,直接返回 - if ($dragLine && $dragLine.length > 0) { - return; - } - - // 创建临时显示的线 - $dragLine = $('
'); - canvasWrapper.append($dragLine); - - if (type === 'horizontal') { - // 从上往下拖 (水平线) - $dragLine.css({ - left: 0, width: '100%', height: '1px', top: e.offsetY - }); - } else { - // 从左往右拖 (垂直线) - $dragLine.css({ - top: 0, height: '100%', width: '1px', left: e.offsetX - }); - } - - // 绑定 document 移动事件 - $(document).on('mousemove.ruler', function (moveEvent) { - let wrapperOffset = canvasWrapper.offset(); - if (type === 'horizontal') { - let relY = moveEvent.pageY - wrapperOffset.top; - $dragLine.css('top', relY + 'px'); - } else { - let relX = moveEvent.pageX - wrapperOffset.left; - $dragLine.css('left', relX + 'px'); - } - }); - - // 绑定松开事件 (创建真实的 Fabric 线) - $(document).on('mouseup.ruler', function (upEvent) { - $(document).off('mousemove.ruler mouseup.ruler'); - - if (!$dragLine) return; - - // 获取最终位置 - let wrapperOffset = canvasWrapper.offset(); - let finalX = upEvent.pageX - wrapperOffset.left; - let finalY = upEvent.pageY - wrapperOffset.top; - - // 移除临时线 - $dragLine.remove(); - $dragLine = null; - - // 只有拖入画布区域才创建 - if (finalX > rulerWidth && finalY > rulerHeight) { - createFabricGuide(type, finalX, finalY); - } - }); - }; - - // 绑定到标尺 DOM - $('#ruler-h').off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e)); - $('#ruler-v').off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e)); - - // --- 在画布中创建辅助线 --- - function createFabricGuide(type, domX, domY) { - // domX 和 domY 是相对于 canvas-wrapper 的坐标 - // 需要转换为相对于 canvas 的坐标,然后再转换为 Fabric 画布坐标 - - // 获取 canvas 相对于 canvas-wrapper 的位置 - let canvas1El = $('#canvas1'); - let canvasPosition = canvas1El.position(); - let canvasMarginLeft = parseFloat(canvas1El.css('margin-left')) || 0; - let canvasMarginTop = parseFloat(canvas1El.css('margin-top')) || 0; - - // 计算相对于 canvas 的坐标(减去 canvas 在 wrapper 中的位置和 margin) - let relX = domX - canvasPosition.left - canvasMarginLeft; - let relY = domY - canvasPosition.top - canvasMarginTop; - - // 转为 Fabric 坐标 (除以 zoom) - let canvasX = relX / zoom; - let canvasY = relY / zoom; - - // 使用与画布尺寸相关的长度,避免“整块区域都像辅助线” - let lineExtent = Math.max(canvas1.width, canvas1.height) * 2; - - // 构造线对象 - let linePoints = []; - if (type === 'horizontal') { - linePoints = [-lineExtent, canvasY, lineExtent, canvasY]; - } else { - linePoints = [canvasX, -lineExtent, canvasX, lineExtent]; - } - - let guideLine = new fabric.Line(linePoints, { - stroke: '#0066FF', - strokeWidth: 1 / zoom, - opacity: 0.9, - shadow: { - color: '#0066FF', - blur: 6, - offsetX: 0, - offsetY: 0, - affectStroke: true - }, - selectable: true, - evented: true, - hasControls: false, - hasBorders: false, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize', - // 只在靠近线条时触发命中,避免“大块区域都像辅助线” - // 同时适当放宽 tolerance,保证易于点中拖拽 - perPixelTargetFind: true, - targetFindTolerance: 6, - padding: 2, - isGuideLine: true, - ignoreSave: true - }); - - // 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉) - guideLine.on('moving', function () { - if (type === 'horizontal') { - // 水平线:根据当前中心点的 Y,重算 x1/x2,使其横穿画布 - const cy = this.getCenterPoint().y; - this.set({ - x1: -lineExtent, - y1: cy, - x2: lineExtent, - y2: cy - }); - } else { - // 垂直线:根据当前中心点的 X,重算 y1/y2,使其竖穿画布 - const cx = this.getCenterPoint().x; - this.set({ - x1: cx, - y1: -lineExtent, - x2: cx, - y2: lineExtent - }); - } - }); - - // 确定应该添加到哪个 canvas(使用当前活动的 canvas) - let targetCanvas = canvas; - if (!targetCanvas || (targetCanvas !== canvas1 && targetCanvas !== canvas2)) { - // 如果没有活动的 canvas,默认使用 canvas1 - targetCanvas = canvas1; - } - - targetCanvas.add(guideLine); - targetCanvas.setActiveObject(guideLine); - targetCanvas.renderAll(); - } - -} -initRulers(); -$('#source_front').width(zoom * $('#source_front').width()) -$('#source_back').width(zoom * $('#source_back').width()) -$('.canvas-container:eq(1)').hide() -canvas = canvas1 -window.canvas = canvas -fabric.Object.prototype.set({ - borderColor: '#9013FE', - cornerColor: '#9013FE', //激活状态角落图标的填充颜色 - cornerStrokeColor: '#9013FE', //激活状态角落图标的边框颜色 - borderOpacityWhenMoving: 1, - borderScaleFactor: 1, - cornerSize: 6, - cornerStyle: 'circle', //rect,circle - centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点 - centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转 - transparentCorners: false, //激活状态角落的图标是否透明 - rotatingPointOffset: 20, //旋转距旋转体的距离 - padding: 0 -}) -canvas1.preserveObjectStacking = true -canvas2.preserveObjectStacking = true - -// ========================================== -// 对象选择和属性更新函数 -// ========================================== -// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.updateControls = function updateControls() { - // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问 - const currentCanvas = window.canvas || canvas; - const currentBackgroundImage = window.background_image || background_image; - if (!currentCanvas || !currentCanvas.getActiveObject()) return; - $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0)); - $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0)); - $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0)); - $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0)); - $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0)); - // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性) - if (currentCanvas.getActiveObject().get("lockMovementX")) { - $("#lock_img").attr("src", "public/images/lock20.png")//已锁定 - } else { - $("#lock_img").attr("src", "public/images/unlock20.png")//未锁定 - } -}; -// 向后兼容 -function updateControls() { - return window.updateControls(); -} -function selectObject(target, index) { - updateControls(); - $("#base_control").show(); -} - -function selectPic(target, index) { - // 检查是否有选中对象,如果没有则直接返回 - if (!canvas || !canvas.getActiveObject()) return; - - $("#pic_control").show(); - $("#set_bg").show(); - let activeObj = canvas.getActiveObject(); - - // 检查对象是否被锁定: - // - 从画布点击时(_selectingFromList 为 false):不允许选中,被锁定的对象保持不可选状态 - // - 从对象列表点击时(_selectingFromList 为 true):允许选中,但对象在画布上仍然保持锁定 - if (!window._selectingFromList && isObjectLocked(activeObj)) { - canvas.discardActiveObject(); - canvas.renderAll(); - return; - } - - if (typeof pic_op !== 'undefined' && pic_op && typeof pic_op.setValue === 'function') { - pic_op.setValue(100 - (activeObj.get("opacity") * 100)); - } else { - $("#pic_op").next().children(".layui-slider-bar").css("width", (1 - activeObj.get("opacity")) * 100 + "%"); - $("#pic_op").next().children(".layui-slider-handle").css("left", (1 - activeObj.get("opacity")) * 100 + "%"); - } - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectOutPic(target, index) { - $("#out_pic_control").show(); - if (typeof out_pic_op !== 'undefined' && out_pic_op.setValue) { - out_pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); - } else { - $("#out_pic_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - $("#out_pic_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - } - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#out_pic_field_name").val(objs[idx - 1].name); - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectText(target, index) { - $("#text_control").show(); - $("#text_color").val(canvas.getActiveObject().get("fill")); - $("#text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); - let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi - if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 - $("#text_size").val(val); - $("#text_text").val(canvas.getActiveObject().get("text")); - $("#text_font_family").val(canvas.getActiveObject().get("fontFamily")); - - if (canvas.getActiveObject().get("underline") == true) { - $("#text_underline").addClass("ui-radio-acitve") - } else { - $("#text_underline").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("fontWeight") == "bold") { - $("#text_bold").addClass("ui-radio-acitve") - } else { - $("#text_bold").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("fontStyle") == "italic") { - $("#text_italics").addClass("ui-radio-acitve") - } else { - $("#text_italics").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("linethrough") == true) { - $("#text_linethrough").addClass("ui-radio-acitve") - } else { - $("#text_linethrough").removeClass("ui-radio-acitve") - } - $("#circle_text_control").hide(); - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectCircleText(target, index) { - const activeObj = target || canvas.getActiveObject(); - if (!activeObj) return; - - let idx = getIndex(activeObj); - $("#circle_text_control").show(); - $("#circle_text_color").val(activeObj.get("fill")); - $("#circle_text_back_color").val(activeObj.get("textBackgroundColor") == "" ? '#ffffff' : activeObj.get("textBackgroundColor")); - - if (typeof circle_text_size !== 'undefined' && circle_text_size && typeof circle_text_size.setValue === 'function') { - if (typeof circle_text_size_updating !== 'undefined') { - circle_text_size_updating = true; - } - circle_text_size.setValue(activeObj.get("fontSize") - 10); - if (typeof circle_text_size_updating !== 'undefined') { - circle_text_size_updating = false; - } - } - - if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter && typeof circle_text_diameter.setValue === 'function') { - circle_text_diameter.setValue(activeObj.get("diameter") - 50); - } - - $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : ''); - $("#circle_text_text").val(activeObj.get("text")); - $("#circle_text_font_family").val(activeObj.get("fontFamily")); - - if (activeObj.get("underline") == true) { - $("#circle_text_underline").addClass("ui-radio-acitve") - } else { - $("#circle_text_underline").removeClass("ui-radio-acitve") - } - if (activeObj.get("fontWeight") == "bold") { - $("#circle_text_bold").addClass("ui-radio-acitve") - } else { - $("#circle_text_bold").removeClass("ui-radio-acitve") - } - if (activeObj.get("fontStyle") == "italic") { - $("#circle_text_italics").addClass("ui-radio-acitve") - } else { - $("#circle_text_italics").removeClass("ui-radio-acitve") - } - if (activeObj.get("linethrough") == true) { - $("#circle_text_linethrough").addClass("ui-radio-acitve") - } else { - $("#circle_text_linethrough").removeClass("ui-radio-acitve") - } - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#rotate").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectOutText(target, index) { - const align = canvas.getActiveObject().get('textAlign') - if (align === 'left') { - $('#out_left_alignment').parent().addClass('tool1') - } else if (align === 'right') { - $('#out_right_alignment').parent().addClass('tool1') - } else { - $('#out_center_alignment').parent().addClass('tool1') - } - $("#out_text_control").show(); - $("#out_text_color").val(canvas.getActiveObject().get("fill")); - $("#out_text_text").val(canvas.getActiveObject().get("text")); - let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi - if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 - $("#out_text_size").val(val); - $("#out_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#out_text_field_name").val(objs[idx - 1].name); - $("#out_text_font_family").val(canvas.getActiveObject().get("fontFamily")); - $("#exceed").prop("checked", objs[idx - 1].exceed ? 'checked' : ''); - $("#autoWrap").prop("checked", objs[idx - 1].autoWrap ? 'checked' : ''); - $("#fixSize").prop("checked", objs[idx - 1].fixSize ? 'checked' : ''); - if (canvas.getActiveObject().get("underline") == true) { - $("#out_text_underline").addClass("ui-radio-acitve") - } else { - $("#out_text_underline").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("fontWeight") == "bold") { - $("#out_text_bold").addClass("ui-radio-acitve") - } else { - $("#out_text_bold").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("fontStyle") == "italic") { - $("#out_text_italics").addClass("ui-radio-acitve") - } else { - $("#out_text_italics").removeClass("ui-radio-acitve") - } - if (canvas.getActiveObject().get("linethrough") == true) { - $("#out_text_linethrough").addClass("ui-radio-acitve") - } else { - $("#out_text_linethrough").removeClass("ui-radio-acitve") - } - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectRect(target, index) { - $("#rect_control").show(); - $("#rect_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); - $("#rect_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); - $("#rect_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); - $("#rect_radius").val(canvas.getActiveObject().get("rx")); - if (typeof rect_op !== 'undefined' && rect_op.setValue) { - rect_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); - } else { - $("#rect_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - $("#rect_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - } - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#circle_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectCircle(target, index) { - $("#circle_control").show(); - $("#circle_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); - $("#circle_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); - $("#circle_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); - if (typeof circle_op !== 'undefined' && circle_op.setValue) { - circle_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); - } else { - $("#circle_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - $("#circle_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); - } - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#line_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectLine(target, index) { - $("#line_control").show(); - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#line_color").val(canvas.getActiveObject().get("stroke")); - $("#line_dist").val(canvas.getActiveObject().get("strokeDashArray")[0]); - $("#text_h").val(canvas.getActiveObject().get("strokeWidth")) - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function selectQrCode(target, index) { - $("#qrcode_Controll").show(); - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#qrcode_field_name").val(objs[idx - 1].name); - $("#qrcode_color").val(objs[idx - 1].color); - $("#qrcode_val").val(objs[idx - 1].val); - $("#line_control").hide(); - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#counter_control").hide(); - $("#barcode_controll").hide(); -} - -function selectBarCode(target, index) { - $("#barcode_controll").show(); - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#barcode_field_name").val(objs[idx - 1].name); - $("#barcode_font_family").val(objs[idx - 1].font); - let val = parseFloat(objs[idx - 1].fontSize) * 72 / dpi - if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 - $("#barcode_size").val(val); - $("#barcode_val").val(objs[idx - 1].val); - $("#barcode_show").prop("checked", objs[idx - 1].show ? 'checked' : ''); - $("#line_control").hide(); - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#counter_control").hide(); - $("#qrcode_Controll").hide(); -} - -function selectCounter(target, index) { - $("#counter_control").show(); - let idx = getIndex(canvas.getActiveObject(), canvas); - $("#counter_start").val(objs[idx - 1].start); - $("#counter_add").val(objs[idx - 1].add); - $("#counter_color").val(canvas.getActiveObject().get("fill")); - $("#count_font_family").val(canvas.getActiveObject().get("fontFamily")); - let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi - if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 - $("#count_size").val(val); - $("#line_control").hide(); - $("#pic_control").hide(); - $("#set_bg").hide(); - $("#out_pic_control").hide(); - $("#text_control").hide(); - $("#circle_text_control").hide(); - $("#out_text_control").hide(); - $("#rect_control").hide(); - $("#circle_control").hide(); - $("#qrcode_Controll").hide(); - $("#barcode_controll").hide(); -} - -function handleObjectSelected(e) { - // 确定当前活动的 canvas(从事件参数或全局变量) - let currentCanvas = (e && e.target && (e.target.canvas === canvas1 || e.target.canvas === canvas2)) - ? (e.target.canvas === canvas1 ? canvas1 : canvas2) - : canvas; - - // 如果事件来自 canvas,更新全局 canvas 变量 - if (currentCanvas === canvas1 || currentCanvas === canvas2) { - canvas = currentCanvas; - objs = (currentCanvas === canvas1) ? objs1 : objs2; - window.canvas = canvas; - window.objs = objs; - } - - let select_obj = canvas.getActiveObject(); - - // 1. 未选中任何对象 - if (!select_obj) { - $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); - $("#component_type").text(language_str("bg")); - selectObj(-1, true); // 传 true,只更新UI - return; - } - - // 2. 如果是辅助线,直接返回,不处理(在获取索引之前检查) - if (select_obj.isGuideLine) { - return; - } - - // 3. 获取索引 - let i = getIndex(select_obj, canvas); - - // 如果不是通过对象列表选中的,且对象被锁定,则取消选中 - if (!window._selectingFromList && isObjectLocked(select_obj)) { - canvas.discardActiveObject(); - canvas.renderAll(); - $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); - $("#component_type").text(language_str("bg")); - selectObj(-1, true); - return; - } - - if (i != 0) { - // --- 选中了普通对象 --- - --i; // 转换为 objs 数组下标 - - // 安全检查:确保 objs[i] 存在且有 type 属性 - if (objs[i] && typeof objs[i].type !== 'undefined') { - switch (objs[i].type) { - case 1: // 图片 - $("#component_type").text(language_str("img")); - selectObject(select_obj, i); selectPic(select_obj, i); break; - case 2: // 合成图片 - $("#component_type").text(language_str("simg")); - selectObject(select_obj, i); selectOutPic(select_obj, i); break; - case 3: // 文本 - $("#component_type").text(language_str("text")); - selectObject(select_obj, i); selectText(select_obj, i); break; - case 4: // 合成文本 - $("#component_type").text(language_str("stext")); - selectObject(select_obj, i); selectOutText(select_obj, i); break; - case 5: // 矩形 - $("#component_type").text(language_str("rect")); - selectObject(select_obj, i); selectRect(select_obj, i); break; - case 6: // 圆形 - $("#component_type").text(language_str("circ")); - selectObject(select_obj, i); selectCircle(select_obj, i); break; - case 7: // 直线 - $("#component_type").text(language_str("line")); - selectObject(select_obj, i); selectLine(select_obj, i); break; - case 8: // 二维码 - $("#component_type").text(language_str("qrc")); - selectObject(select_obj, i); selectQrCode(select_obj, i); break; - case 9: // 条形码 - $("#component_type").text(language_str("barc")); - selectObject(select_obj, i); selectBarCode(select_obj, i); break; - case 10: // 计数器 - $("#component_type").text(language_str("counter")); - selectObject(select_obj, i); selectCounter(select_obj, i); break; - case 11: // 圆形文本 - $("#component_type").text(language_str("ctext")); - selectObject(select_obj, i); selectCircleText(select_obj, i); break; - } - } - - // 【关键】这里传入 true! - // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了 - selectObj(i, true); - - } else { - // --- 选中了背景 --- - $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); - selectObj(-1, true); - $("#component_type").text(language_str("bg")); - } -} - -function handleObjectMoving(e) { - let obj = e.target; - // 跳过辅助线 - if (obj.isGuideLine) { - updateControls(); - return; - } - let idx = getIndex(obj, canvas); - // 圆形文本特殊处理 - // 安全检查:确保 objs[idx - 1] 存在且有 type 属性 - if (idx > 0 && idx <= objs.length && objs[idx - 1] && typeof objs[idx - 1].type !== 'undefined' && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { - let pointer = { x: obj.left, y: obj.top }; - let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2); - let angleDeg = getDeg(pointer); - let flipped = pointer.y > canvas.height / 2 ? true : false; - - if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter.setValue) { - circle_text_diameter.setValue(distance * 2 - 50); - } - obj.set("diameter", distance * 2); - obj.set("angle", angleDeg); - obj.set("flipped", flipped); - } - updateControls(); -} - -// 辅助函数:检查对象是否被锁定 -// 只有当 lockMovementX 为 true 时才认为对象被锁定 -// selectable 为 false 可能是其他原因(如背景图),所以只检查 lockMovementX -function isObjectLocked(obj) { - if (!obj) return false; - // 主要检查 lockMovementX,这是锁定的主要标志 - return obj.get('lockMovementX') === true; -} -// 暴露到全局作用域,确保其他模块可以访问 -window.isObjectLocked = isObjectLocked; - -// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存) -// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped) -const TO_JSON_PROPERTIES = [ - "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", - "evented", "linethrough", "textBackgroundColor", "diameter", "flipped" -]; -// 暴露到全局作用域 -window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES; - -// 辅助函数:解锁所有对象(加载时使用) -function unlockAllObjects(canvas) { - if (!canvas) return; - const objects = canvas.getObjects(); - objects.forEach((obj, index) => { - // 背景对象(索引0)保持锁定状态 - if (index === 0) { - obj.set({ - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - lockSkewingX: true, - lockSkewingY: true, - selectable: false, - evented: false - }); - } else { - // 其他对象默认解锁 - obj.set({ - lockMovementX: false, - lockMovementY: false, - lockRotation: false, - lockScalingX: false, - lockScalingY: false, - lockSkewingX: false, - lockSkewingY: false, - selectable: true, - evented: true - }); - } - }); -} -// 暴露到全局作用域 -window.unlockAllObjects = unlockAllObjects; - -// 将 handleObjectSelected 附加到 window 对象,确保全局可访问 -window.handleObjectSelected = handleObjectSelected; - -// 防止重复初始化事件监听器 -var _canvasEventsInitialized = false; - -function initCanvasEvents() { - // 如果已经初始化过,直接返回 - if (_canvasEventsInitialized) { - return; - } - _canvasEventsInitialized = true; - - [canvas1, canvas2].forEach(currentCanvas => { - // 1. 鼠标移动 - currentCanvas.on("mouse:move", (e) => { - let offsetX = 0, offsetY = 0; - if (currentCanvas === canvas2 && typeof background_image !== 'undefined') { - offsetX = background_image.left; - offsetY = background_image.top; - } - if (e.pointer) { - $("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0)); - } - }); - - // 2. 鼠标移出 - currentCanvas.on("mouse:out", () => { - // 可以在这里隐藏标尺辅助线 - }); - - // 3. 鼠标松开 (添加对象) - currentCanvas.on("mouse:up", (e) => { - if (addState === 0) return; - const ptr = e.pointer; - switch (addState) { - case 1: addPic(ptr); break; - case 2: addOutPic(ptr); break; - case 3: addText(ptr); break; - case 4: addOutText(ptr); break; - case 5: addRect(ptr); break; - case 6: addCircle(ptr); break; - case 7: addLine(ptr); break; - case 8: addQrCode(ptr); break; - case 9: addBarCode(ptr); break; - case 10: addCounter(ptr); break; - case 11: addCircleText(ptr); break; - } - addState = 0; - if (typeof background_image !== 'undefined') background_image.hoverCursor = "default"; - currentCanvas.renderAll(); - $("#addPic, #addOutPic, #addText, #addOutText, #addRect, #addCircle, #addLine, #addQrCode, #addBarCode, #addCounter, #addCircleText").css("background-color", ""); - }); - - // 4. 选中处理 - currentCanvas.on('mouse:down', (e) => { - // 更新全局 canvas 变量 - canvas = currentCanvas; - objs = (currentCanvas === canvas1) ? objs1 : objs2; - handleObjectSelected(e); - }); - // 阻止被锁定的对象被框选选中,同时彻底排除辅助线 - currentCanvas.on('before:selection:created', (e) => { - if (!e.targets || e.targets.length === 0) return; - - // 调试日志:原始选区 - console.log('[Guide] before:selection:created raw targets', e.targets.map(t => ({ - isGuideLine: !!t.isGuideLine, - type: t.type, - left: t.left, - top: t.top - }))); - - // 选区里彻底排除辅助线:不允许辅助线进入 ActiveSelection - // 只保留未锁定的普通对象 - e.targets = e.targets.filter(target => { - if (target.isGuideLine) return false; // 完全移除辅助线 - return !isObjectLocked(target); // 过滤掉被锁定的普通对象 - }); - - // 调试日志:过滤后的选区 - console.log('[Guide] before:selection:created filtered targets', e.targets.map(t => ({ - isGuideLine: !!t.isGuideLine, - type: t.type, - left: t.left, - top: t.top - }))); - }); - - // 添加 selection 事件以确保对象选中时正确显示属性面板 - // 注意:真正排除辅助线的逻辑已经在 before:selection:created 里完成, - // 这里不再对 ActiveSelection 做任何重建,避免 Fabric 内部递归导致栈溢出。 - currentCanvas.on('selection:created', (e) => { - canvas = currentCanvas; - objs = (currentCanvas === canvas1) ? objs1 : objs2; - handleObjectSelected(e); - }); - currentCanvas.on('selection:updated', (e) => { - canvas = currentCanvas; - objs = (currentCanvas === canvas1) ? objs1 : objs2; - handleObjectSelected(e); - }); - currentCanvas.on('object:selected', (e) => { - canvas = currentCanvas; - objs = (currentCanvas === canvas1) ? objs1 : objs2; - - // 如果不是通过对象列表选中的,且对象被锁定,则取消选中 - // 注意:只有真正被锁定的对象才会被取消选中,未锁定的对象应该正常选中 - if (!window._selectingFromList && e.target && isObjectLocked(e.target)) { - currentCanvas.discardActiveObject(); - currentCanvas.renderAll(); - return; - } - - handleObjectSelected(e); - // 更新对象列表以同步锁状态 - if (typeof window.updateList === 'function') { - window.updateList(); - } - }); - - // 5. 对象移动 - currentCanvas.on("object:moving", (e) => { - // 阻止背景图(索引0)被移动(如果已锁定) - let movingObj = e.target; - let idx = getIndex(movingObj, currentCanvas); - if (idx === 0) { - let bgObj = currentCanvas.getObjects()[0]; - if (bgObj && bgObj.get('lockMovementX') === true) { - e.e.preventDefault(); - e.e.stopPropagation(); - return false; - } - } - handleObjectMoving(e); - }); - - // 6. 变换事件 - currentCanvas.on("object:rotating", updateControls); - currentCanvas.on("object:scaling", updateControls); - currentCanvas.on("object:rotated", () => { recordState(); }); - currentCanvas.on('text:editing:entered', () => { _textEdit = true; }); - currentCanvas.on('text:editing:exited', () => { _textEdit = false; }); - currentCanvas.on("text:changed", (e) => { $("#text_text").val(e.target.get("text")); }); - - // 7. 对象修改后 - currentCanvas.on('object:modified', function (event) { - if (currentCanvas.getActiveObjects().length > 1) return; - // 跳过辅助线 - if (event.target.isGuideLine) { - return; - } - let idx = getIndex(event.target, currentCanvas); - if (idx <= 0) return; - let objData = objs[idx - 1]; - // 安全检查:确保 objData 存在且有 type 属性 - if (!objData || typeof objData.type === 'undefined') { - return; - } - let target = event.target; - - if ([3, 4, 10].includes(objData.type)) { - target.fontSize = (target.fontSize * target.scaleX).toFixed(0); - target.scaleX = 1; target.scaleY = 1; - target._clearCache(); - if (objData.type == 3) $("#text_size").val(target.fontSize); - if (objData.type == 4) $("#out_text_size").val(target.fontSize); - if (objData.type == 10) $("#count_size").val(target.fontSize); - currentCanvas.renderAll(); - updateControls(); - } else if (objData.type == 5) { - target.width *= target.scaleX; target.height *= target.scaleY; - target.scaleX = 1; target.scaleY = 1; - updateControls(); - } - recordState(); - // 更新对象列表以同步锁状态 - if (typeof window.updateList === 'function') { - window.updateList(); - } - }); - }); -} - -// 将 updateList 附加到 window 对象,确保全局可访问 -window.updateList = function updateList() { - // 确保使用正确的 objs(优先使用 window.objs,因为它可能被撤回/重做更新) - const currentObjs = window.objs || objs; - const currentCanvas = window.canvas || canvas; - - let str = ""; - let i = 0; - - // 定义行样式:Flex布局,两端对齐,相对定位防止溢出 - const rowStyle = "display:flex; justify-content:space-between; align-items:center; height:32px; padding:0 5px; cursor:pointer; border-bottom:1px solid #444; color:#ccc; font-size:12px; position:relative;"; - // 定义左侧内容样式 - const leftStyle = "display:flex; align-items:center; overflow:hidden; white-space:nowrap; max-width:180px;"; - // 定义序号样式:靠右,有左边框 - const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;"; - - for (let item of currentObjs) { - let displayIndex = i + 1; // 序号底层为1 - - // 图标逻辑 - let iconName = "rect.svg"; - switch (item.type) { - case 1: iconName = "images.svg"; break; - case 2: iconName = "images_add.svg"; break; - case 3: iconName = "text.svg"; break; - case 4: iconName = "text2.svg"; break; - case 5: iconName = "rect.svg"; break; - case 6: iconName = "round.svg"; break; - case 7: iconName = "line.svg"; break; - case 8: iconName = "qrcode.svg"; break; - case 9: iconName = "barcode.svg"; break; - case 10: iconName = "counter.svg"; break; - case 11: iconName = "text3.svg"; break; - } - - // 名称逻辑 - let nameText = item.name ? item.name : - (item.type === 3 ? "TEXT_" + displayIndex : - (item.type === 1 ? "IMG_" + displayIndex : "OBJ_" + displayIndex)); - - // 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性) - // 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1 - let isLocked = false; - if (currentCanvas.getObjects()[i + 1]) { - let canvasObj = currentCanvas.getObjects()[i + 1]; - isLocked = canvasObj.get('lockMovementX') === true; - } - - // 锁定状态图标 - const lockIcon = isLocked ? - '' : - ''; - - // 组装 HTML - str = `
-
- - ${nameText} - ${lockIcon} -
- ${displayIndex} -
` + str; - i++; - } - $(".obj_list").html(str); - - // 重新绑定点击事件(防止 updateList 后点击失效) - $(".obj_list > div").on("click", function () { - let imgId = $(this).find("img").attr("id"); - if (imgId) { - let idx = parseInt(imgId.replace("obj", "")); - selectObj(idx); - } - }); -} - -// 标记是否通过对象列表选中(用于区分鼠标点击和列表点击) -window._selectingFromList = false; - -// 将 selectObj 附加到 window 对象,确保全局可访问 -window.selectObj = function selectObj(objIndices, isFromCanvas = false) { - let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []); - - // 清除所有高亮 - $('.obj_list > div').css("background-color", "transparent"); - - // 遍历列表查找并高亮 - $('.obj_list > div').each(function () { - let $img = $(this).find("img"); - if ($img.length === 0) return; - - let idIndex = parseInt($img.attr("id").replace("obj", "")); - - if (indicesArray.includes(idIndex)) { - $(this).css("background-color", "#6F7A84"); - - // 自动滚动到可视区域 - if (isFromCanvas && indicesArray[0] === idIndex) { - this.scrollIntoView({ behavior: "smooth", block: "nearest" }); - } - } - }); - - // 如果是点击列表,触发画布选中 - if (!isFromCanvas && indicesArray.length === 1) { - let target = canvas.getObjects()[indicesArray[0] + 1]; - if (target) { - // 检查对象是否被锁定 - let isLocked = isObjectLocked(target); - - // 设置标志位,表示这是通过对象列表选中的(无论是否锁定) - window._selectingFromList = true; - - // 如果被锁定,临时设置 selectable 和 evented 为 true,以便能够选中 - if (isLocked) { - target.set({ - selectable: true, - evented: true - }); - } - - canvas.setActiveObject(target); - canvas.requestRenderAll(); - - // 如果对象被锁定,选中后立即恢复锁定状态(但保持选中状态) - if (isLocked) { - setTimeout(() => { - target.set({ - selectable: false, - evented: false - }); - canvas.requestRenderAll(); - }, 0); - } - - // 清除标志位(延迟清除,确保事件处理完成) - setTimeout(() => { - window._selectingFromList = false; - }, 10); - - handleObjectSelected(); - } - } -} - -// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.recordState = function recordState() { - // 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期 - if (window._isApplyingHistory) { - return; - } - // 确定当前是正面还是背面,直接使用对应的数组 - const isFront = $('#front_side').hasClass('ui-button-active'); - const currentStep = isFront ? (window.step1 || step1) : (window.step2 || step2); - const currentRecordObjs = isFront ? (window.recordObjs1 || recordObjs1) : (window.recordObjs2 || recordObjs2); - const currentRecordJson = isFront ? (window.recordJson1 || recordJson1) : (window.recordJson2 || recordJson2); - const currentObjs = window.objs || objs; - const currentCanvas = window.canvas || canvas; - - ++currentStep.val; - for (let i = currentStep.val; i < currentRecordObjs.length; i++) { - currentRecordObjs.pop(); - currentRecordJson.pop(); - } - currentRecordObjs.push(JSON.stringify(currentObjs)) - // 保存所有锁定相关的属性,确保锁定状态可以正确恢复 - let j = currentCanvas.toJSON(TO_JSON_PROPERTIES); - j.objects[0].hoverCursor = "default"; - currentRecordJson.push(j); - - // 同步更新 window 变量和局部变量 - if (isFront) { - window.step1 = currentStep; - window.recordObjs1 = currentRecordObjs; - window.recordJson1 = currentRecordJson; - window.step = currentStep; - window.recordObjs = currentRecordObjs; - window.recordJson = currentRecordJson; - step1 = currentStep; - recordObjs1 = currentRecordObjs; - recordJson1 = currentRecordJson; - step = currentStep; - recordObjs = currentRecordObjs; - recordJson = currentRecordJson; - } else { - window.step2 = currentStep; - window.recordObjs2 = currentRecordObjs; - window.recordJson2 = currentRecordJson; - window.step = currentStep; - window.recordObjs = currentRecordObjs; - window.recordJson = currentRecordJson; - step2 = currentStep; - recordObjs2 = currentRecordObjs; - recordJson2 = currentRecordJson; - step = currentStep; - recordObjs = currentRecordObjs; - recordJson = currentRecordJson; - } -}; -// 向后兼容 -function recordState() { - return window.recordState(); -} - -// 将 getIndex 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 -window.getIndex = function getIndex(target, _canvas = null) { - let temp_objs; - // 使用 window.canvas 作为默认值,确保在 eval() 执行时也能访问 - const currentCanvas = _canvas || window.canvas || canvas; - if (!currentCanvas) { - return 0; - } - temp_objs = currentCanvas.getObjects(); - - let i = 0; - for (let res of temp_objs) { - if (target == res) { - return i; - } - i++; - } - return 0; -}; -// 向后兼容 -function getIndex(target, _canvas = null) { - return window.getIndex(target, _canvas); -} - -function getType(target) { - // 如果是辅助线,返回 null - if (target.isGuideLine) { - return null; - } - let temp_objs = canvas.getObjects(); - let i = 0; - for (let res of temp_objs) { - if (target == res) { - // 安全检查:确保 objs[i] 存在且有 type 属性 - if (!objs[i] || typeof objs[i].type === 'undefined') { - return null; - } - return objs[i].type; - } - i++; - } - return null; -} - -function checkName(name) { - //检查字段名是否重复了 - for (let item of objs1) { - if (item.name == name) { - return false; - } - } - for (let item of objs2) { - if (item.name == name) { - return false; - } - } - return true; -} - -function resName(pre_name) { - //返回的是不重复的字段名 - let num = (objs1.length + objs2.length) - 1; - do { - num++; - } - while (!checkName(pre_name + num));//重复了就再加1 - return pre_name + num; -} - -function getDisdance(x1, y1, x2, y2) { - var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值 - var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值 - var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离 - return distance; -} - -function getDeg(pointer) { - // 计算点击位置与画布中心的坐标差 - var centerX = canvas.width / 2; - var centerY = canvas.height / 2; - var mouseX = pointer.x; - var mouseY = pointer.y; - var dx = mouseX - centerX; - var dy = mouseY - centerY; - // 计算旋转角度(弧度) - var angleRad = Math.atan2(dy, dx); - // 将角度转换为度数 - var angleDeg = angleRad * (180 / Math.PI); - if (angleDeg < 0) { - angleDeg += 360; - } - angleDeg += 90 - return angleDeg -} - -function getCoordsMinX(acoords) { - x = acoords[0].x; - for (let item of acoords) { - if (item.x < x) { - x = item.x - } - } - return x; -} - -function getCoordsMaxX(acoords) { - x = acoords[0].x; - for (let item of acoords) { - if (item.x > x) { - x = item.x - } - } - return x; -} - -function getCoordsMinY(acoords) { - y = acoords[0].y; - for (let item of acoords) { - if (item.y < y) { - y = item.y - } - } - return y; -} - -function getCoordsMaxY(acoords) { - y = acoords[0].y; - for (let item of acoords) { - if (item.y > y) { - y = item.y - } - } - return y; -} - -function addBackground() { - // 类型改变 - fabric.Image.fromURL('./public/images/bg_front_2.png', function (image) { - image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2 - image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2 - - image.selectable = false - image.hoverable = false - image.hoverCursor = 'default' - image.setSrc(image.toDataURL(), function (image) { - background_image1 = image - background_image = background_image1 - window.background_image1 = background_image1; - window.background_image = background_image; - canvas1.add(image) - canvas1.renderAll() - - recordObjs1.push(JSON.stringify(objs1)) - const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped']; - recordJson1.push(canvas1.toJSON(props)) - }) - // 类型改变 - fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) { - image.left = ($('#canvas2').width() - image.width) / 2 - image.top = ($('#canvas2').height() - image.height) / 2 - - image.selectable = false - image.hoverable = false - image.hoverCursor = 'default' - - image.setSrc(image.toDataURL(), function (image) { - background_image2 = image - window.background_image2 = background_image2; - canvas2.add(image) - canvas2.renderAll() - - recordObjs2.push(JSON.stringify(objs2)) - recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES)) - - let file = GetFile().get('file') - if (file && file != 'empty') { - window.openFile(file); - } - }) - }) - is_bgi_add = true - }) -} - -function addPic(pointer) { - pre_add_image.left = pointer.x - pre_add_image.top = pointer.y - - canvas.add(pre_add_image) - - objs.push({ type: 1, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -function addOutPic(pointer) { - fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) { - let width = canvas.getWidth() - let height = canvas.getHeight() - image.left = pointer.x - image.top = pointer.y - image.opacity = 1 - image.setSrc(image.toDataURL(), function (image) { - canvas.add(image) - objs.push({ type: 2, name: resName('OUT_PIC_'), black: false }) - canvas.renderAll() - updateList() - recordState() - }) - }) -} - -function addText(pointer) { - // 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt - let text = new fabric.IText('TEXT', { - left: pointer.x, - top: pointer.y, - fill: '#000000', - textBackgroundColor: '', - fontStyle: 'normal', - fontWeight: 'normal', - fontSize: 30, - underline: false, - linethrough: false, - fontFamily: 'Arial' - }) - text.setControlVisible('ml', false) - text.setControlVisible('mt', false) - text.setControlVisible('mr', false) - text.setControlVisible('mb', false) - canvas.add(text) - objs.push({ type: 3, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -function addCircleText(pointer) { - let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2) - let angleDeg = getDeg(pointer) - let text = new fabric.CurvedText('Circle TEXT', { - left: pointer.x, - top: pointer.y, - fill: "#000000", - textBackgroundColor: "", - fontStyle: "normal", - fontWeight: "normal", - diameter: distance * 2, - fontSize: 30, - underline: false, - linethrough: false, - lockScalingX: true, - lockScalingY: true, - fontFamily: 'Arial', - originX: 'center', - originY: 'center', - angle: angleDeg, - flipped: pointer.y > canvas.height / 2 ? true : false - }); - text.setControlVisible("ml", false); - text.setControlVisible("mt", false); - text.setControlVisible("mr", false); - text.setControlVisible("mb", false); - canvas.add(text); - objs.push({ type: 11, black: false, circleCenter: 0 }); - canvas.renderAll(); - updateList(); - recordState(); -} - -function addOutText(pointer) { - let text = new fabric.Text('MERGE TEXT', { - left: pointer.x, - top: pointer.y, - fill: '#000000', - fontStyle: 'normal', - fontWeight: 'normal', - textBackgroundColor: '', - underline: false, - linethrough: false, - fontFamily: 'Arial', - textAlign: 'center' - }) - text.setControlVisible('ml', false) - text.setControlVisible('mt', false) - text.setControlVisible('mr', false) - text.setControlVisible('mb', false) - - canvas.add(text) - objs.push({ type: 4, name: resName('OUT_TEXT_'), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' }) - canvas.renderAll() - updateList() - recordState() -} - -function addRect(pointer) { - let rect = new fabric.Rect({ - left: pointer.x, - top: pointer.y, - fill: '', - stroke: "#ff0000", - strokeWidth: 3, - width: 100, - height: 100 - }) - canvas.add(rect) - objs.push({ type: 5, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -function addCircle(pointer) { - let circle = new fabric.Circle({ - left: pointer.x, - top: pointer.y, - fill: '', - stroke: '#ff0000', - strokeWidth: 3, - radius: 100, - strokeUniform: true - }) - canvas.add(circle) - objs.push({ type: 6, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -function addLine(pointer) { - let line = new fabric.Line([50, 100, 200, 100], { - left: pointer.x, - top: pointer.y, - stroke: '#ff0000', - strokeWidth: 3, - strokeDashArray: [0, 0], - }) - - line.setControlVisible('tl', false) - line.setControlVisible('tr', false) - line.setControlVisible('br', false) - line.setControlVisible('bl', false) - line.setControlVisible('mt', false) - line.setControlVisible('mb', false) - canvas.add(line) - objs.push({ type: 7, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -function addQrCode(pointer) { - let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 }) - fabric.Image.fromURL(qr, function (image) { - image.left = pointer.x - image.top = pointer.y - canvas.add(image) - objs.push({ type: 8, name: resName('QR_CODE_'), color: '#000000', val: 'http://www.cardsoon.com', black: false }) - canvas.renderAll() - updateList() - recordState() - }) -} - -function addBarCode(pointer) { - JsBarcode('#barcode', '123456789', { margin: 0, lineColor: '#000000', fontSize: 18, font: 'Arial' }) - let barcode = document.getElementById('barcode') - let bar = barcode.toDataURL('image/png') - fabric.Image.fromURL(bar, function (image) { - image.left = pointer.x - image.top = pointer.y - - canvas.add(image) - objs.push({ type: 9, name: resName('BAR_CODE_'), val: '123456789', color: '#000000', fontSize: 18, font: 'Arial', show: false, black: false }) - canvas.renderAll() - updateList() - recordState() - }) -} - -function addCounter(pointer) { - let text = new fabric.Text('0', { - left: pointer.x, - top: pointer.y, - fill: '#000000' - }) - canvas.add(text) - objs.push({ type: 10, name: resName('COUNTER_'), start: 0, add: 1, black: false }) - canvas.renderAll() - updateList() - recordState() -} - -var front_list = [] -ipcRenderer.on('font-list', (event, data) => { - const newFontList = [] - data.map((item) => { - if (item.indexOf('"') === 0) { - newFontList.push(item.replace(/^"|"$/g, '')) - } else { - newFontList.push(item) - } - }) - front_list = newFontList - for (let item of front_list) { - $('#text_font_family').append("') - $("#circle_text_font_family").append(""); - $('#out_text_font_family').append("') - $('#barcode_font_family').append("') - $('#count_font_family').append("') - } -}) -ipcRenderer.on('sys-lan', (event, data) => { - let lang = localStorage.getItem('lang') - if (lang) { - s_lan = lang - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge(lang) - $('#language_select').val(lang) - return - } - switch (data) { - case 'zh-CN': - s_lan = 'zh' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('zh') - $('#language_select').val('zh') - localStorage.setItem('lang', 'zh') - break - case 'zh': - s_lan = 'zh' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('zh') - $('#language_select').val('zh') - localStorage.setItem('lang', 'zh') - break - case 'zh-TW': - s_lan = 'ozh' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('ozh') - $('#language_select').val('ozh') - localStorage.setItem('lang', 'ozh') - break - case 'en': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-AU': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-CA': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-GB': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-NZ': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-US': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - case 'en-ZA': - s_lan = 'en' - // 同步到全局作用域(用于 .jsc 文件加载) - if (typeof window !== 'undefined') { - window.s_lan = s_lan; - } - if (typeof global !== 'undefined') { - global.s_lan = s_lan; - } - langua_ge('en') - $('#language_select').val('en') - localStorage.setItem('lang', 'en') - break - } -}) -ipcRenderer.on('close', (event, message) => { - layer.confirm( - '' + language_str('whetherSave') + '', - { - //是否保存当前文件? - btn: [language_str('save'), language_str('noSave'), language_str('cancel')], //['保存', '不保存', '取消'] - btn3: function (index, layero) { } - }, - function (index, layero) { - if (typeof window.output === 'function') { - window.output(() => ipcRenderer.send('run-close')); - } - }, - function (index) { - ipcRenderer.send('run-close') - } - ) -}) - -addBackground() //添加背景 白色卡片 - -initAligningGuidelines(canvas1) -initAligningGuidelines(canvas2) - -// 初始化 Canvas 事件监听 -initCanvasEvents(); - -// 初始化所有 Slider -var circle_text_size_updating = false; // 标志位,防止循环更新 -var circle_text_size = slider.render({ - elem: '#circle_text_size', - min: 10, - max: 120, - value: 50, - change: function (value) { - if (circle_text_size_updating) return; - if (canvas && canvas.getActiveObject && canvas.getActiveObject()) { - canvas.getActiveObject().set('fontSize', value); - canvas.renderAll(); - if (typeof updateControls === 'function') { - updateControls(); - } - if (typeof recordState === 'function') { - recordState(); - } - } - } -}); - -var circle_text_diameter = slider.render({ - elem: '#circle_text_diameter', - min: 50, - max: 1100, - value: 500, - change: function (value) { - if (canvas && canvas.getActiveObject && canvas.getActiveObject()) { - canvas.getActiveObject().set('diameter', value); - canvas.renderAll(); - if (typeof updateControls === 'function') { - updateControls(); - } - if (typeof recordState === 'function') { - recordState(); - } - } - } -}); - -var out_pic_op = slider.render({ - elem: '#out_pic_op', - min: 0, - max: 100, - value: 0, - change: function (value) { - if (canvas.getActiveObject()) { - canvas.getActiveObject().set('opacity', 1 - value / 100); - canvas.renderAll(); - updateControls(); - recordState(); - } - } -}); - -var pic_op = slider.render({ - elem: '#pic_op', - min: 0, - max: 100, - value: 0, - change: function (value) { - if (canvas.getActiveObject()) { - canvas.getActiveObject().set('opacity', 1 - value / 100); - canvas.renderAll(); - updateControls(); - recordState(); - } - } -}); - -var rect_op = slider.render({ - elem: '#rect_op', - min: 0, - max: 100, - value: 0, - change: function (value) { - if (canvas.getActiveObject()) { - canvas.getActiveObject().set('opacity', 1 - value / 100); - canvas.renderAll(); - updateControls(); - recordState(); - } - } -}); - -var circle_op = slider.render({ - elem: '#circle_op', - min: 0, - max: 100, - value: 0, - change: function (value) { - if (canvas.getActiveObject()) { - canvas.getActiveObject().set('opacity', 1 - value / 100); - canvas.renderAll(); - updateControls(); - recordState(); - } - } -}); - -//canvas.setZoom(0.5) -//recordObjs1.push(JSON.stringify(objs1)); -//recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); -//recordObjs2.push(JSON.stringify(objs2)); -//recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); -canvas.renderAll() //重新渲染画布 -var zoomSlider = slider.render({ - elem: '#zoomSlide', //绑定元素 - min: 25, - max: 500, - value: 100, - change(val) { - canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), val / 100) - zoom = val / 100 - //$(".canvas-container").css("transform","scale(" + val / 100 + ")") - } -}) -window.rotate = function (id) { - let angle = $(`#${id}`).attr('angle') - 0 - angle += 90 - $(`#${id}`).css('transform', `rotate(${angle}deg)`) - $(`#${id}`).attr('angle', angle) -} -// 注意:#display 事件绑定已在 ui.js 中定义(第2633行),此处已删除重复定义 -// 导出文件 -// 将 savePdf 附加到 window 对象,确保在 output.js 中也能访问 -window.savePdf = async function savePdf(buffer) { - let fs = require('fs'); - // 弹出保存对话框 - const { canceled, filePath } = await dialog.showSaveDialog({ - title: '保存文件', - defaultPath: path.join(exePath, 'output.pdf'), // 默认路径 - filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] // 文件过滤器 - }); - - if (canceled) { - return; - } - - // 将Buffer写入PDF文件到用户选择的路径 - fs.writeFile(filePath, buffer, (err) => { - if (err) { - } else { - layer.msg(language_str("saveSucc") + filePath);//'保存成功至' - } - }); -}; -// 向后兼容 -async function savePdf(buffer) { - return window.savePdf(buffer); -} - -// display_func 函数已移至 output.js,避免重复定义 -// function display_func(img1, img2, img3) { ... } - -// output 函数已移至 output.js,避免重复定义 -// function output(callback = null, _save = save) { ... } - -var _keyctrl = false, - _keyc = false, - _textEdit = false, - _inputEdit = false -$('input').focus(function () { - _inputEdit = true -}) -$('input').blur(function () { - _inputEdit = false -}) -$('textarea').focus(function () { - _inputEdit = true -}) -$('textarea').blur(function () { - _inputEdit = false -}) -$('body').keyup(function (event) { - // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) - if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放 - _keyctrl = false - } - if (event.keyCode == 67) { - _keyc = false - } -}) -$('body').keydown(function (event) { - // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta) - const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端 - - // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的) - if (!_inputEdit && !_textEdit) { - // CTRL/CMD+N - 新建 - if (isModifierKey && event.keyCode == 78) { - event.preventDefault(); - $('#new').click(); - return; - } - // CTRL/CMD+O - 打开 - if (isModifierKey && event.keyCode == 79) { - event.preventDefault(); - $('#open').click(); - return; - } - // CTRL/CMD+S - 保存 - if (isModifierKey && !event.shiftKey && event.keyCode == 83) { - event.preventDefault(); - $('#save').click(); - return; - } - // CTRL/CMD+SHIFT+S - 保存副本 - if (isModifierKey && event.shiftKey && event.keyCode == 83) { - event.preventDefault(); - if (typeof window.output === 'function') { - window.output(null, saveAs); - } - return; - } - // CTRL/CMD+Z - 还原 - if (isModifierKey && !event.shiftKey && event.keyCode == 90) { - event.preventDefault(); - $('#previous').click(); - return; - } - // CTRL/CMD+Y - 重做 - if (isModifierKey && !event.shiftKey && event.keyCode == 89) { - event.preventDefault(); - $('#next').click(); - return; - } - // CTRL/CMD+C - 复制 - if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') { - event.preventDefault(); - window.copySelection(); - return; - } - // CTRL/CMD+V - 粘贴 - if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') { - event.preventDefault(); - window.pasteFromClipboard(); - return; - } - // CTRL/CMD+SHIFT+DEL - 清空 - if (isModifierKey && event.shiftKey && event.keyCode == 46) { - event.preventDefault(); - $('#clean').click(); - return; - } - // CTRL/CMD+DEL - 删除 - if (isModifierKey && !event.shiftKey && event.keyCode == 46) { - event.preventDefault(); - $('#delete').click(); - return; - } - } - // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) - if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd) - _keyctrl = true - if (_keyc && _textEdit) { - //同时按下且在编辑状态 - setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) - //clipboard.writeText((clipboard.readText()+' ')) - } - } - if (event.keyCode == 67) { - _keyc = true - if (_keyctrl & _textEdit) { - //同时按下且在编辑状态 - setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) - //clipboard.writeText((clipboard.readText()+' ')) - } - } - if (event.keyCode == 37) { - //left - let v = -1 - if (_keyctrl || event.ctrlKey || event.metaKey) { - v = -5 - } - if (canvas.getActiveObject()) { - let x = canvas.getActiveObject().get('left') - x += v - canvas.getActiveObject().set('left', x) - canvas.renderAll() - recordState() - updateControls() - } - } - if (event.keyCode == 38) { - //up - let v = -1 - if (_keyctrl || event.ctrlKey || event.metaKey) { - v = -5 - } - if (canvas.getActiveObject()) { - let y = canvas.getActiveObject().get('top') - y += v - canvas.getActiveObject().set('top', y) - canvas.renderAll() - recordState() - updateControls() - } - } - if (event.keyCode == 39) { - //right - let v = 1 - if (_keyctrl || event.ctrlKey || event.metaKey) { - v = 5 - } - if (canvas.getActiveObject()) { - let x = canvas.getActiveObject().get('left') - x += v - canvas.getActiveObject().set('left', x) - canvas.renderAll() - recordState() - updateControls() - } - } - if (event.keyCode == 40) { - //down - let v = +1 - if (_keyctrl || event.ctrlKey || event.metaKey) { - v = 5 - } - if (canvas.getActiveObject()) { - let y = canvas.getActiveObject().get('top') - y += v - canvas.getActiveObject().set('top', y) - canvas.renderAll() - recordState() - updateControls() - } - } - // 普通删除键(DEL键,没有按CTRL/CMD) - if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) { - let actObjs = canvas.getActiveObjects() - let allObjs = canvas.getObjects() - let flag = 0 - for (let item of actObjs) { - if (item != allObjs[0]) { - let i = getIndex(item, canvas) //0是背景,所以从1开始,但objs不包含背景图片,故要-1 - objs.splice(i - 1, 1) - canvas.remove(item) - flag = 1 - } - } - updateList() - canvas.discardActiveObject() - if (flag == 1) { - recordState() - canvas.renderAll() - handleObjectSelected() - } - } -}) - -// 复制选中对象 -window.copySelection = function copySelection() { - const activeObjects = canvas.getActiveObjects(); - - if (!activeObjects || activeObjects.length === 0) { - layer.msg("未选中任何对象") - return; - } - - // 【核心修复】遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据 - clipboardData.data = activeObjects.map(obj => { - let idx = getIndex(obj, canvas); // 获取对象在画布中的索引 - - // 获取业务属性 (objs数组中对应的那一项) - // 注意:idx=0 是背景图,objs数组是从下标0开始存第1个对象的,所以是 idx-1 - let appData = (idx > 0 && objs[idx - 1]) ? objs[idx - 1] : { type: 0 }; - - return { - // 1. 保存画面样式 (坐标、颜色、字号等,包含图片的 src) - fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data', 'src']), - // 2. 保存业务属性 (深拷贝,防止引用冲突) - appData: JSON.parse(JSON.stringify(appData)) - }; - }); - - clipboardData.offset = 10; // 重置偏移量 - layer.msg("已复制"); -} - -// 粘贴对象 -window.pasteFromClipboard = function pasteFromClipboard() { - // 检查是否有数据 - if (!clipboardData.data || clipboardData.data.length === 0) { - layer.msg("剪贴板为空") - return; - } - - // 防止重复调用:如果正在粘贴,直接返回 - if (window._isPasting) { - return; - } - window._isPasting = true; - - // 1. 提取 Fabric JSON 准备恢复画面 - const jsonToEnliven = clipboardData.data.map(item => item.fabricJson); - - // 2. 恢复对象 (异步过程) - fabric.util.enlivenObjects(jsonToEnliven, function (objects) { - canvas.discardActiveObject(); - - objects.forEach((clone, index) => { - // --- A. 处理画面位置 --- - clone.set({ - left: clone.left + clipboardData.offset, - top: clone.top + clipboardData.offset, - evented: true - }); - - // 如果是组内对象,需要特殊处理坐标 - if (clone.group) { - clone.set({ - left: clone.group.left + clipboardData.offset, - top: clone.group.top + clipboardData.offset - }); - } - - // --- B. 处理业务数据 --- - // 取出复制时保存的 appData - let savedAppData = clipboardData.data[index].appData; - let newAppData = JSON.parse(JSON.stringify(savedAppData)); // 再次深拷贝 - - // 修改名称,防止 ID 冲突 (例如 TEXT_1 -> TEXT_1_copy) - if (newAppData.name) { - newAppData.name = newAppData.name + "_copy"; - } - - // --- C. 添加到画布和数据数组 --- - canvas.add(clone); - objs.push(newAppData); // 将新业务数据推入当前页面的 objs 数组 - }); - - // 3. 选中新粘贴出来的对象 - let sel = new fabric.ActiveSelection(objects, { canvas: canvas }); - canvas.setActiveObject(sel); - canvas.requestRenderAll(); - - // 4. 刷新列表和历史记录 - if (typeof window.updateList === 'function') { - window.updateList(); - } - recordState(); - - // 5. 高亮左侧列表最后一项 (即新粘贴的项) - if (typeof window.selectObj === 'function') { - if (objs.length > 0) { - window.selectObj(objs.length - 1); - } - } - - // 6. 增加下次粘贴的偏移量 (让用户知道粘贴了新东西) - clipboardData.offset += 10; - - // 清除粘贴标志 - window._isPasting = false; - }); -} - -// 将 onStateRestored 附加到 window 对象,确保全局可访问 -// 此函数在撤回/重做后调用,用于恢复画布状态 -window.onStateRestored = function onStateRestored() { - // 1. 重新获取背景图引用 (因为旧对象被销毁了) - // 注意:canvas 是全局变量,指向当前激活的画板 - if (canvas && canvas.getObjects().length > 0) { - background_image = canvas.getObjects()[0]; - background_image.selectable = false; // 确保背景不可选 - window.background_image = background_image; - background_image.evented = false; - } - - // 2. 渲染画布 - canvas.requestRenderAll(); - - // 3. 取消选中状态 - if (typeof window.selectObj === 'function') { - window.selectObj(-1); - } - - // 4. 隐藏所有属性面板 - $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); - - // 5. 刷新左侧列表 - if (typeof window.updateList === 'function') { - window.updateList(); - } -} +// design2核心功能模块 +// 注意:此代码在layui.use回调函数内部执行 +// 全局变量已在主入口文件中定义,这里只需要初始化 + +$('#canvas1').attr('height', document.body.clientHeight - 100) +$('#canvas2').attr('height', document.body.clientHeight - 100) +$('#object_attribute').css('height', '470px') +$('#obj_list').css('height', document.body.clientHeight - 520) +$('.obj_list').css('height', $('#obj_list').height() - 32) + +$('.main').show() +$('#canvas1').attr('width', $('#canvas-div').width()) +$('#canvas2').attr('width', $('#canvas-div').width()) + +// 初始化Canvas对象 +// 优化性能:在创建 Fabric Canvas 之前,先设置 canvas context 的 willReadFrequently +try { + const canvas1El = document.getElementById('canvas1'); + const canvas2El = document.getElementById('canvas2'); + if (canvas1El) { + canvas1El.getContext('2d', { willReadFrequently: true }); + } + if (canvas2El) { + canvas2El.getContext('2d', { willReadFrequently: true }); + } +} catch (e) { + // 如果浏览器不支持 willReadFrequently,忽略错误 +} + +canvas1 = new fabric.Canvas('canvas1') +canvas2 = new fabric.Canvas('canvas2') +// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 +window.canvas1 = canvas1; +window.canvas2 = canvas2; +// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史 +window._isApplyingHistory = false; +fabric.Object.prototype.objectCaching = false +ctx1 = canvas1.getSelectionContext() +ctx2 = canvas2.getSelectionContext() +is_bgi_add = false +// 同步到 window 对象 +window.ctx1 = ctx1; +window.ctx2 = ctx2; +window.is_bgi_add = is_bgi_add; +canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) +//console.log(canvas1.width / 2, canvas1.height / 2); +//console.log($("#canvas-div").width() / 1200 > 1 ? 1 : $("#canvas-div").width() / 1200); +canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) +zoom = $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200 + +// 初始化标尺 +function initRulers() { + // dpi已在主入口文件中定义为全局变量 + const mmToPx = dpi / 25.4; // 1mm = 11.811px (300 DPI) + const rulerHeight = 20; + const rulerWidth = 20; + + // 获取实际画布的像素尺寸(px) + // design2.html 的实际画布尺寸是 1012px x 648px + const actualCanvasWidth = canvas1.width || 1012; // 实际画布宽度(px) + const actualCanvasHeight = canvas1.height || 648; // 实际画布高度(px) + + // 获取显示区域的尺寸(包括 margin) + const canvasDiv = $('#canvas-div'); + const canvasWrapper = $('.canvas-wrapper'); + // 使用 canvas-wrapper 的宽度,如果没有则使用 canvas_div 的宽度 + let canvasWidth = canvasWrapper.width(); + let canvasHeight = canvasWrapper.height(); + if (!canvasWidth || canvasWidth === 0) { + const canvas1Width = $('#canvas1').width() || canvasDiv.width() - 20; + canvasWidth = canvas1Width + 20; // 加上 margin-left: 20px + } + if (!canvasHeight || canvasHeight === 0) { + const canvas1Height = $('#canvas1').height() || canvasDiv.height() - 20; + canvasHeight = canvas1Height + 20; // 加上 margin-top: 20px + } + + // 设置标尺尺寸(使用显示区域的尺寸) + $('#ruler-h').attr('width', canvasWidth).attr('height', rulerHeight); + $('#ruler-v').attr('width', rulerWidth).attr('height', canvasHeight); + $('.ruler-horizontal').css('width', canvasWidth + 'px'); + $('.ruler-vertical').css('height', canvasHeight + 'px'); + + // 绘制水平标尺 + function drawHorizontalRuler() { + const ctx = document.getElementById('ruler-h').getContext('2d', { willReadFrequently: true }); + ctx.clearRect(0, 0, canvasWidth, rulerHeight); + + // 背景 + ctx.fillStyle = '#2C2C2C'; + ctx.fillRect(0, 0, canvasWidth, rulerHeight); + + // 边框 + ctx.strokeStyle = '#1E1E1E'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, canvasWidth, rulerHeight); + ctx.beginPath(); + ctx.moveTo(0, rulerHeight - 1); + ctx.lineTo(canvasWidth, rulerHeight - 1); + ctx.stroke(); + + // 刻度线 + ctx.strokeStyle = '#888888'; + ctx.fillStyle = '#CCCCCC'; + ctx.font = '10px Arial'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 + // 实际画布物理宽度 = actualCanvasWidth / dpi * 25.4 (mm) + // 显示区域宽度 = actualCanvasWidth * zoom (px) + // 所以:1mm 在显示区域中 = (actualCanvasWidth * zoom) / (actualCanvasWidth / dpi * 25.4) = zoom * dpi / 25.4 + const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 + const marginOffset = 20; // canvas 的 margin-left: 20px + // 标尺0点应该在background_image的左上角位置 + // background_image在画布坐标系中的left值,需要转换到标尺坐标系 + // 画布从标尺的marginOffset位置开始,所以:标尺0点 = marginOffset + background_image.left (如果存在) + let originX = marginOffset; + if (typeof background_image !== 'undefined' && background_image) { + originX = marginOffset + background_image.left; + } + const startX = 0; + const endX = canvasWidth; + + // 绘制主要刻度(每10mm) + // 从0点向左绘制负值刻度 + for (let mm = -10; originX + mm * pxPerMm >= startX; mm -= 10) { + const x = originX + mm * pxPerMm; + if (x < startX) break; + + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + + // 数字(负值) + ctx.fillText(mm + 'mm', x + 2, 2); + } + // 从0点向右绘制正值刻度 + for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 10) { + const x = originX + mm * pxPerMm; + if (x > endX) break; + + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + + // 数字 + ctx.fillText(mm + 'mm', x + 2, 2); + } + + // 绘制次要刻度(每1mm) + // 从0点向左绘制负值刻度 + for (let mm = -1; originX + mm * pxPerMm >= startX; mm -= 1) { + const x = originX + mm * pxPerMm; + if (x < startX) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(x, rulerHeight - 8); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + } + // 从0点向右绘制正值刻度 + for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 1) { + const x = originX + mm * pxPerMm; + if (x > endX) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(x, rulerHeight - 8); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + } + } + + // 绘制垂直标尺 + function drawVerticalRuler() { + const ctx = document.getElementById('ruler-v').getContext('2d', { willReadFrequently: true }); + ctx.clearRect(0, 0, rulerWidth, canvasHeight); + + // 背景 + ctx.fillStyle = '#2C2C2C'; + ctx.fillRect(0, 0, rulerWidth, canvasHeight); + + // 边框 + ctx.strokeStyle = '#1E1E1E'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, rulerWidth, canvasHeight); + ctx.beginPath(); + ctx.moveTo(rulerWidth - 1, 0); + ctx.lineTo(rulerWidth - 1, canvasHeight); + ctx.stroke(); + + // 刻度线 + ctx.strokeStyle = '#888888'; + ctx.fillStyle = '#CCCCCC'; + ctx.font = '10px Arial'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 + const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 + const marginOffset = 20; // canvas 的 margin-top: 20px (但还要加上ruler的高度) + const rulerHeightOffset = 20; // ruler 的高度 + // 标尺0点应该在background_image的左上角位置 + // background_image在画布坐标系中的top值,需要转换到标尺坐标系 + // 画布从标尺的marginOffset + rulerHeightOffset位置开始,所以:标尺0点 = marginOffset + rulerHeightOffset + background_image.top (如果存在) + let originY = marginOffset + rulerHeightOffset; + if (typeof background_image !== 'undefined' && background_image) { + originY = marginOffset + rulerHeightOffset + background_image.top; + } + const startY = 0; + const endY = canvasHeight; + + // 绘制主要刻度(每10mm) + // 从0点向上绘制负值刻度 + for (let mm = -10; originY + mm * pxPerMm >= startY; mm -= 10) { + const y = originY + mm * pxPerMm; + if (y < startY) break; + + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + + // 数字(旋转90度,负值) + ctx.save(); + ctx.translate(2, y + 8); + ctx.rotate(-Math.PI / 2); + ctx.fillText(mm + 'mm', 0, 0); + ctx.restore(); + } + // 从0点向下绘制正值刻度 + for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 10) { + const y = originY + mm * pxPerMm; + if (y > endY) break; + + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + + // 数字(旋转90度) + ctx.save(); + ctx.translate(2, y + 8); + ctx.rotate(-Math.PI / 2); + ctx.fillText(mm + 'mm', 0, 0); + ctx.restore(); + } + + // 绘制次要刻度(每1mm) + // 从0点向上绘制负值刻度 + for (let mm = -1; originY + mm * pxPerMm >= startY; mm -= 1) { + const y = originY + mm * pxPerMm; + if (y < startY) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(rulerWidth - 8, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + } + // 从0点向下绘制正值刻度 + for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 1) { + const y = originY + mm * pxPerMm; + if (y > endY) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(rulerWidth - 8, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + } + } + + // 初始绘制 + drawHorizontalRuler(); + drawVerticalRuler(); + + // 监听窗口大小变化 + $(window).on('resize', function () { + setTimeout(function () { + const canvasWrapper = $('.canvas-wrapper'); + let newWidth = canvasWrapper.width(); + let newHeight = canvasWrapper.height(); + if (!newWidth || newWidth === 0) { + const canvas1Width = $('#canvas1').width() || $('#canvas-div').width() - 20; + newWidth = canvas1Width + 20; // 加上 margin-left: 20px + } + if (!newHeight || newHeight === 0) { + const canvas1Height = $('#canvas1').height() || $('#canvas-div').height() - 20; + newHeight = canvas1Height + 20; // 加上 margin-top: 20px + } + if (newWidth !== canvasWidth || newHeight !== canvasHeight) { + canvasWidth = newWidth; + canvasHeight = newHeight; + $('#ruler-h').attr('width', newWidth); + $('#ruler-v').attr('height', newHeight); + $('.ruler-horizontal').css('width', newWidth + 'px'); + $('.ruler-vertical').css('height', newHeight + 'px'); + drawHorizontalRuler(); + drawVerticalRuler(); + } + }, 100); + }); + + // 监听画布缩放/渲染变化,仅用于重绘标尺 + // 辅助线交给 Fabric 自己渲染,否则会出现“重影 / 变两三根”的现象 + canvas1.on('after:render', function () { + drawHorizontalRuler(); + drawVerticalRuler(); + }); + canvas2.on('after:render', function () { + drawHorizontalRuler(); + drawVerticalRuler(); + }); + + // --- 辅助线功能 (参考 design1) --- + // canvasWrapper 已在上面声明,直接使用 + const canvasBox = $('#canvas-div'); // design2 使用 canvas-div 作为容器 + + // 确保容器定位 + if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative'); + + // 定义临时拖拽线 DOM + let $dragLine = null; + + // 通用拖拽开始函数 + const startDragGuide = (type, e) => { + e.preventDefault(); + e.stopPropagation(); + + // 防止重复调用:如果正在拖拽,直接返回 + if ($dragLine && $dragLine.length > 0) { + return; + } + + // 创建临时显示的线 + $dragLine = $('
'); + canvasWrapper.append($dragLine); + + if (type === 'horizontal') { + // 从上往下拖 (水平线) + $dragLine.css({ + left: 0, width: '100%', height: '1px', top: e.offsetY + }); + } else { + // 从左往右拖 (垂直线) + $dragLine.css({ + top: 0, height: '100%', width: '1px', left: e.offsetX + }); + } + + // 绑定 document 移动事件 + $(document).on('mousemove.ruler', function (moveEvent) { + let wrapperOffset = canvasWrapper.offset(); + if (type === 'horizontal') { + let relY = moveEvent.pageY - wrapperOffset.top; + $dragLine.css('top', relY + 'px'); + } else { + let relX = moveEvent.pageX - wrapperOffset.left; + $dragLine.css('left', relX + 'px'); + } + }); + + // 绑定松开事件 (创建真实的 Fabric 线) + $(document).on('mouseup.ruler', function (upEvent) { + $(document).off('mousemove.ruler mouseup.ruler'); + + if (!$dragLine) return; + + // 获取最终位置 + let wrapperOffset = canvasWrapper.offset(); + let finalX = upEvent.pageX - wrapperOffset.left; + let finalY = upEvent.pageY - wrapperOffset.top; + + // 移除临时线 + $dragLine.remove(); + $dragLine = null; + + // 只有拖入画布区域才创建 + if (finalX > rulerWidth && finalY > rulerHeight) { + createFabricGuide(type, finalX, finalY); + } + }); + }; + + // 绑定到标尺 DOM + $('#ruler-h').off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e)); + $('#ruler-v').off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e)); + + // --- 在画布中创建辅助线 --- + function createFabricGuide(type, domX, domY) { + // domX 和 domY 是相对于 canvas-wrapper 的坐标 + // 需要转换为相对于 canvas 的坐标,然后再转换为 Fabric 画布坐标 + + // 获取 canvas 相对于 canvas-wrapper 的位置 + let canvas1El = $('#canvas1'); + let canvasPosition = canvas1El.position(); + let canvasMarginLeft = parseFloat(canvas1El.css('margin-left')) || 0; + let canvasMarginTop = parseFloat(canvas1El.css('margin-top')) || 0; + + // 计算相对于 canvas 的坐标(减去 canvas 在 wrapper 中的位置和 margin) + let relX = domX - canvasPosition.left - canvasMarginLeft; + let relY = domY - canvasPosition.top - canvasMarginTop; + + // 转为 Fabric 坐标 (除以 zoom) + let canvasX = relX / zoom; + let canvasY = relY / zoom; + + // 使用与画布尺寸相关的长度,避免“整块区域都像辅助线” + let lineExtent = Math.max(canvas1.width, canvas1.height) * 2; + + // 构造线对象 + let linePoints = []; + if (type === 'horizontal') { + linePoints = [-lineExtent, canvasY, lineExtent, canvasY]; + } else { + linePoints = [canvasX, -lineExtent, canvasX, lineExtent]; + } + + let guideLine = new fabric.Line(linePoints, { + stroke: '#0066FF', + strokeWidth: 1 / zoom, + opacity: 0.9, + shadow: { + color: '#0066FF', + blur: 6, + offsetX: 0, + offsetY: 0, + affectStroke: true + }, + selectable: true, + evented: true, + hasControls: false, + hasBorders: false, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize', + // 只在靠近线条时触发命中,避免“大块区域都像辅助线” + // 同时适当放宽 tolerance,保证易于点中拖拽 + perPixelTargetFind: true, + targetFindTolerance: 6, + padding: 2, + isGuideLine: true, + ignoreSave: true + }); + + // 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉) + guideLine.on('moving', function () { + if (type === 'horizontal') { + // 水平线:根据当前中心点的 Y,重算 x1/x2,使其横穿画布 + const cy = this.getCenterPoint().y; + this.set({ + x1: -lineExtent, + y1: cy, + x2: lineExtent, + y2: cy + }); + } else { + // 垂直线:根据当前中心点的 X,重算 y1/y2,使其竖穿画布 + const cx = this.getCenterPoint().x; + this.set({ + x1: cx, + y1: -lineExtent, + x2: cx, + y2: lineExtent + }); + } + }); + + // 确定应该添加到哪个 canvas(使用当前活动的 canvas) + let targetCanvas = canvas; + if (!targetCanvas || (targetCanvas !== canvas1 && targetCanvas !== canvas2)) { + // 如果没有活动的 canvas,默认使用 canvas1 + targetCanvas = canvas1; + } + + targetCanvas.add(guideLine); + targetCanvas.setActiveObject(guideLine); + targetCanvas.renderAll(); + } + +} +initRulers(); +$('#source_front').width(zoom * $('#source_front').width()) +$('#source_back').width(zoom * $('#source_back').width()) +$('.canvas-container:eq(1)').hide() +canvas = canvas1 +window.canvas = canvas +fabric.Object.prototype.set({ + borderColor: '#9013FE', + cornerColor: '#9013FE', //激活状态角落图标的填充颜色 + cornerStrokeColor: '#9013FE', //激活状态角落图标的边框颜色 + borderOpacityWhenMoving: 1, + borderScaleFactor: 1, + cornerSize: 6, + cornerStyle: 'circle', //rect,circle + centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点 + centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转 + transparentCorners: false, //激活状态角落的图标是否透明 + rotatingPointOffset: 20, //旋转距旋转体的距离 + padding: 0 +}) +canvas1.preserveObjectStacking = true +canvas2.preserveObjectStacking = true + +// ========================================== +// 对象选择和属性更新函数 +// ========================================== +// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 +window.updateControls = function updateControls() { + // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问 + const currentCanvas = window.canvas || canvas; + const currentBackgroundImage = window.background_image || background_image; + if (!currentCanvas || !currentCanvas.getActiveObject()) return; + $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0)); + $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0)); + $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0)); + $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0)); + // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性) + if (currentCanvas.getActiveObject().get("lockMovementX")) { + $("#lock_img").attr("src", "public/images/lock20.png")//已锁定 + } else { + $("#lock_img").attr("src", "public/images/unlock20.png")//未锁定 + } +}; +// 向后兼容 +function updateControls() { + return window.updateControls(); +} +function selectObject(target, index) { + updateControls(); + $("#base_control").show(); +} + +function selectPic(target, index) { + // 检查是否有选中对象,如果没有则直接返回 + if (!canvas || !canvas.getActiveObject()) return; + + $("#pic_control").show(); + $("#set_bg").show(); + let activeObj = canvas.getActiveObject(); + + // 检查对象是否被锁定: + // - 从画布点击时(_selectingFromList 为 false):不允许选中,被锁定的对象保持不可选状态 + // - 从对象列表点击时(_selectingFromList 为 true):允许选中,但对象在画布上仍然保持锁定 + if (!window._selectingFromList && isObjectLocked(activeObj)) { + canvas.discardActiveObject(); + canvas.renderAll(); + return; + } + + if (typeof pic_op !== 'undefined' && pic_op && typeof pic_op.setValue === 'function') { + pic_op.setValue(100 - (activeObj.get("opacity") * 100)); + } else { + $("#pic_op").next().children(".layui-slider-bar").css("width", (1 - activeObj.get("opacity")) * 100 + "%"); + $("#pic_op").next().children(".layui-slider-handle").css("left", (1 - activeObj.get("opacity")) * 100 + "%"); + } + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectOutPic(target, index) { + $("#out_pic_control").show(); + if (typeof out_pic_op !== 'undefined' && out_pic_op.setValue) { + out_pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + } else { + $("#out_pic_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + $("#out_pic_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + } + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#out_pic_field_name").val(objs[idx - 1].name); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectText(target, index) { + $("#text_control").show(); + $("#text_color").val(canvas.getActiveObject().get("fill")); + $("#text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi + if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 + $("#text_size").val(val); + $("#text_text").val(canvas.getActiveObject().get("text")); + $("#text_font_family").val(canvas.getActiveObject().get("fontFamily")); + + if (canvas.getActiveObject().get("underline") == true) { + $("#text_underline").addClass("ui-radio-acitve") + } else { + $("#text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#text_bold").addClass("ui-radio-acitve") + } else { + $("#text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#text_italics").addClass("ui-radio-acitve") + } else { + $("#text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#text_linethrough").addClass("ui-radio-acitve") + } else { + $("#text_linethrough").removeClass("ui-radio-acitve") + } + $("#circle_text_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectCircleText(target, index) { + const activeObj = target || canvas.getActiveObject(); + if (!activeObj) return; + + let idx = getIndex(activeObj); + $("#circle_text_control").show(); + $("#circle_text_color").val(activeObj.get("fill")); + $("#circle_text_back_color").val(activeObj.get("textBackgroundColor") == "" ? '#ffffff' : activeObj.get("textBackgroundColor")); + + if (typeof circle_text_size !== 'undefined' && circle_text_size && typeof circle_text_size.setValue === 'function') { + if (typeof circle_text_size_updating !== 'undefined') { + circle_text_size_updating = true; + } + circle_text_size.setValue(activeObj.get("fontSize") - 10); + if (typeof circle_text_size_updating !== 'undefined') { + circle_text_size_updating = false; + } + } + + if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter && typeof circle_text_diameter.setValue === 'function') { + circle_text_diameter.setValue(activeObj.get("diameter") - 50); + } + + $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : ''); + $("#circle_text_text").val(activeObj.get("text")); + $("#circle_text_font_family").val(activeObj.get("fontFamily")); + + if (activeObj.get("underline") == true) { + $("#circle_text_underline").addClass("ui-radio-acitve") + } else { + $("#circle_text_underline").removeClass("ui-radio-acitve") + } + if (activeObj.get("fontWeight") == "bold") { + $("#circle_text_bold").addClass("ui-radio-acitve") + } else { + $("#circle_text_bold").removeClass("ui-radio-acitve") + } + if (activeObj.get("fontStyle") == "italic") { + $("#circle_text_italics").addClass("ui-radio-acitve") + } else { + $("#circle_text_italics").removeClass("ui-radio-acitve") + } + if (activeObj.get("linethrough") == true) { + $("#circle_text_linethrough").addClass("ui-radio-acitve") + } else { + $("#circle_text_linethrough").removeClass("ui-radio-acitve") + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#rotate").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectOutText(target, index) { + const align = canvas.getActiveObject().get('textAlign') + if (align === 'left') { + $('#out_left_alignment').parent().addClass('tool1') + } else if (align === 'right') { + $('#out_right_alignment').parent().addClass('tool1') + } else { + $('#out_center_alignment').parent().addClass('tool1') + } + $("#out_text_control").show(); + $("#out_text_color").val(canvas.getActiveObject().get("fill")); + $("#out_text_text").val(canvas.getActiveObject().get("text")); + let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi + if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 + $("#out_text_size").val(val); + $("#out_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#out_text_field_name").val(objs[idx - 1].name); + $("#out_text_font_family").val(canvas.getActiveObject().get("fontFamily")); + $("#exceed").prop("checked", objs[idx - 1].exceed ? 'checked' : ''); + $("#autoWrap").prop("checked", objs[idx - 1].autoWrap ? 'checked' : ''); + $("#fixSize").prop("checked", objs[idx - 1].fixSize ? 'checked' : ''); + if (canvas.getActiveObject().get("underline") == true) { + $("#out_text_underline").addClass("ui-radio-acitve") + } else { + $("#out_text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#out_text_bold").addClass("ui-radio-acitve") + } else { + $("#out_text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#out_text_italics").addClass("ui-radio-acitve") + } else { + $("#out_text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#out_text_linethrough").addClass("ui-radio-acitve") + } else { + $("#out_text_linethrough").removeClass("ui-radio-acitve") + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectRect(target, index) { + $("#rect_control").show(); + $("#rect_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); + $("#rect_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); + $("#rect_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); + $("#rect_radius").val(canvas.getActiveObject().get("rx")); + if (typeof rect_op !== 'undefined' && rect_op.setValue) { + rect_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + } else { + $("#rect_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + $("#rect_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectCircle(target, index) { + $("#circle_control").show(); + $("#circle_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); + $("#circle_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); + $("#circle_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); + if (typeof circle_op !== 'undefined' && circle_op.setValue) { + circle_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + } else { + $("#circle_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + $("#circle_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%"); + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectLine(target, index) { + $("#line_control").show(); + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#line_color").val(canvas.getActiveObject().get("stroke")); + $("#line_dist").val(canvas.getActiveObject().get("strokeDashArray")[0]); + $("#text_h").val(canvas.getActiveObject().get("strokeWidth")) + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function selectQrCode(target, index) { + $("#qrcode_Controll").show(); + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#qrcode_field_name").val(objs[idx - 1].name); + $("#qrcode_color").val(objs[idx - 1].color); + $("#qrcode_val").val(objs[idx - 1].val); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#barcode_controll").hide(); +} + +function selectBarCode(target, index) { + $("#barcode_controll").show(); + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#barcode_field_name").val(objs[idx - 1].name); + $("#barcode_font_family").val(objs[idx - 1].font); + let val = parseFloat(objs[idx - 1].fontSize) * 72 / dpi + if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 + $("#barcode_size").val(val); + $("#barcode_val").val(objs[idx - 1].val); + $("#barcode_show").prop("checked", objs[idx - 1].show ? 'checked' : ''); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); +} + +function selectCounter(target, index) { + $("#counter_control").show(); + let idx = getIndex(canvas.getActiveObject(), canvas); + $("#counter_start").val(objs[idx - 1].start); + $("#counter_add").val(objs[idx - 1].add); + $("#counter_color").val(canvas.getActiveObject().get("fill")); + $("#count_font_family").val(canvas.getActiveObject().get("fontFamily")); + let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi + if (val % 0.5 !== 0) val = Math.round(val * 2) / 2 + $("#count_size").val(val); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); +} + +function handleObjectSelected(e) { + // 确定当前活动的 canvas(从事件参数或全局变量) + let currentCanvas = (e && e.target && (e.target.canvas === canvas1 || e.target.canvas === canvas2)) + ? (e.target.canvas === canvas1 ? canvas1 : canvas2) + : canvas; + + // 如果事件来自 canvas,更新全局 canvas 变量 + if (currentCanvas === canvas1 || currentCanvas === canvas2) { + canvas = currentCanvas; + objs = (currentCanvas === canvas1) ? objs1 : objs2; + window.canvas = canvas; + window.objs = objs; + } + + let select_obj = canvas.getActiveObject(); + + // 1. 未选中任何对象 + if (!select_obj) { + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + $("#component_type").text(language_str("bg")); + selectObj(-1, true); // 传 true,只更新UI + return; + } + + // 2. 如果是辅助线,直接返回,不处理(在获取索引之前检查) + if (select_obj.isGuideLine) { + return; + } + + // 3. 获取索引 + let i = getIndex(select_obj, canvas); + + // 如果不是通过对象列表选中的,且对象被锁定,则取消选中 + if (!window._selectingFromList && isObjectLocked(select_obj)) { + canvas.discardActiveObject(); + canvas.renderAll(); + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + $("#component_type").text(language_str("bg")); + selectObj(-1, true); + return; + } + + if (i != 0) { + // --- 选中了普通对象 --- + --i; // 转换为 objs 数组下标 + + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (objs[i] && typeof objs[i].type !== 'undefined') { + switch (objs[i].type) { + case 1: // 图片 + $("#component_type").text(language_str("img")); + selectObject(select_obj, i); selectPic(select_obj, i); break; + case 2: // 合成图片 + $("#component_type").text(language_str("simg")); + selectObject(select_obj, i); selectOutPic(select_obj, i); break; + case 3: // 文本 + $("#component_type").text(language_str("text")); + selectObject(select_obj, i); selectText(select_obj, i); break; + case 4: // 合成文本 + $("#component_type").text(language_str("stext")); + selectObject(select_obj, i); selectOutText(select_obj, i); break; + case 5: // 矩形 + $("#component_type").text(language_str("rect")); + selectObject(select_obj, i); selectRect(select_obj, i); break; + case 6: // 圆形 + $("#component_type").text(language_str("circ")); + selectObject(select_obj, i); selectCircle(select_obj, i); break; + case 7: // 直线 + $("#component_type").text(language_str("line")); + selectObject(select_obj, i); selectLine(select_obj, i); break; + case 8: // 二维码 + $("#component_type").text(language_str("qrc")); + selectObject(select_obj, i); selectQrCode(select_obj, i); break; + case 9: // 条形码 + $("#component_type").text(language_str("barc")); + selectObject(select_obj, i); selectBarCode(select_obj, i); break; + case 10: // 计数器 + $("#component_type").text(language_str("counter")); + selectObject(select_obj, i); selectCounter(select_obj, i); break; + case 11: // 圆形文本 + $("#component_type").text(language_str("ctext")); + selectObject(select_obj, i); selectCircleText(select_obj, i); break; + } + } + + // 【关键】这里传入 true! + // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了 + selectObj(i, true); + + } else { + // --- 选中了背景 --- + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + selectObj(-1, true); + $("#component_type").text(language_str("bg")); + } +} + +function handleObjectMoving(e) { + let obj = e.target; + // 跳过辅助线 + if (obj.isGuideLine) { + updateControls(); + return; + } + let idx = getIndex(obj, canvas); + // 圆形文本特殊处理 + // 安全检查:确保 objs[idx - 1] 存在且有 type 属性 + if (idx > 0 && idx <= objs.length && objs[idx - 1] && typeof objs[idx - 1].type !== 'undefined' && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { + let pointer = { x: obj.left, y: obj.top }; + let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2); + let angleDeg = getDeg(pointer); + let flipped = pointer.y > canvas.height / 2 ? true : false; + + if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter.setValue) { + circle_text_diameter.setValue(distance * 2 - 50); + } + obj.set("diameter", distance * 2); + obj.set("angle", angleDeg); + obj.set("flipped", flipped); + } + updateControls(); +} + +// 辅助函数:检查对象是否被锁定 +// 只有当 lockMovementX 为 true 时才认为对象被锁定 +// selectable 为 false 可能是其他原因(如背景图),所以只检查 lockMovementX +function isObjectLocked(obj) { + if (!obj) return false; + // 主要检查 lockMovementX,这是锁定的主要标志 + return obj.get('lockMovementX') === true; +} +// 暴露到全局作用域,确保其他模块可以访问 +window.isObjectLocked = isObjectLocked; + +// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存) +// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped) +const TO_JSON_PROPERTIES = [ + "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", + "evented", "linethrough", "textBackgroundColor", "diameter", "flipped" +]; +// 暴露到全局作用域 +window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES; + +// 辅助函数:解锁所有对象(加载时使用) +function unlockAllObjects(canvas) { + if (!canvas) return; + const objects = canvas.getObjects(); + objects.forEach((obj, index) => { + // 背景对象(索引0)保持锁定状态 + if (index === 0) { + obj.set({ + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockSkewingX: true, + lockSkewingY: true, + selectable: false, + evented: false + }); + } else { + // 其他对象默认解锁 + obj.set({ + lockMovementX: false, + lockMovementY: false, + lockRotation: false, + lockScalingX: false, + lockScalingY: false, + lockSkewingX: false, + lockSkewingY: false, + selectable: true, + evented: true + }); + } + }); +} +// 暴露到全局作用域 +window.unlockAllObjects = unlockAllObjects; + +// 将 handleObjectSelected 附加到 window 对象,确保全局可访问 +window.handleObjectSelected = handleObjectSelected; + +// 防止重复初始化事件监听器 +var _canvasEventsInitialized = false; + +function initCanvasEvents() { + // 如果已经初始化过,直接返回 + if (_canvasEventsInitialized) { + return; + } + _canvasEventsInitialized = true; + + [canvas1, canvas2].forEach(currentCanvas => { + // 1. 鼠标移动 + currentCanvas.on("mouse:move", (e) => { + let offsetX = 0, offsetY = 0; + if (currentCanvas === canvas2 && typeof background_image !== 'undefined') { + offsetX = background_image.left; + offsetY = background_image.top; + } + if (e.pointer) { + $("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0)); + } + }); + + // 2. 鼠标移出 + currentCanvas.on("mouse:out", () => { + // 可以在这里隐藏标尺辅助线 + }); + + // 3. 鼠标松开 (添加对象) + currentCanvas.on("mouse:up", (e) => { + if (addState === 0) return; + const ptr = e.pointer; + switch (addState) { + case 1: addPic(ptr); break; + case 2: addOutPic(ptr); break; + case 3: addText(ptr); break; + case 4: addOutText(ptr); break; + case 5: addRect(ptr); break; + case 6: addCircle(ptr); break; + case 7: addLine(ptr); break; + case 8: addQrCode(ptr); break; + case 9: addBarCode(ptr); break; + case 10: addCounter(ptr); break; + case 11: addCircleText(ptr); break; + } + addState = 0; + if (typeof background_image !== 'undefined') background_image.hoverCursor = "default"; + currentCanvas.renderAll(); + $("#addPic, #addOutPic, #addText, #addOutText, #addRect, #addCircle, #addLine, #addQrCode, #addBarCode, #addCounter, #addCircleText").css("background-color", ""); + }); + + // 4. 选中处理 + currentCanvas.on('mouse:down', (e) => { + // 更新全局 canvas 变量 + canvas = currentCanvas; + objs = (currentCanvas === canvas1) ? objs1 : objs2; + handleObjectSelected(e); + }); + // 阻止被锁定的对象被框选选中,同时彻底排除辅助线 + currentCanvas.on('before:selection:created', (e) => { + if (!e.targets || e.targets.length === 0) return; + + // 调试日志:原始选区 + console.log('[Guide] before:selection:created raw targets', e.targets.map(t => ({ + isGuideLine: !!t.isGuideLine, + type: t.type, + left: t.left, + top: t.top + }))); + + // 选区里彻底排除辅助线:不允许辅助线进入 ActiveSelection + // 只保留未锁定的普通对象 + e.targets = e.targets.filter(target => { + if (target.isGuideLine) return false; // 完全移除辅助线 + return !isObjectLocked(target); // 过滤掉被锁定的普通对象 + }); + + // 调试日志:过滤后的选区 + console.log('[Guide] before:selection:created filtered targets', e.targets.map(t => ({ + isGuideLine: !!t.isGuideLine, + type: t.type, + left: t.left, + top: t.top + }))); + }); + + // 添加 selection 事件以确保对象选中时正确显示属性面板 + // 注意:真正排除辅助线的逻辑已经在 before:selection:created 里完成, + // 这里不再对 ActiveSelection 做任何重建,避免 Fabric 内部递归导致栈溢出。 + currentCanvas.on('selection:created', (e) => { + canvas = currentCanvas; + objs = (currentCanvas === canvas1) ? objs1 : objs2; + handleObjectSelected(e); + }); + currentCanvas.on('selection:updated', (e) => { + canvas = currentCanvas; + objs = (currentCanvas === canvas1) ? objs1 : objs2; + handleObjectSelected(e); + }); + currentCanvas.on('object:selected', (e) => { + canvas = currentCanvas; + objs = (currentCanvas === canvas1) ? objs1 : objs2; + + // 如果不是通过对象列表选中的,且对象被锁定,则取消选中 + // 注意:只有真正被锁定的对象才会被取消选中,未锁定的对象应该正常选中 + if (!window._selectingFromList && e.target && isObjectLocked(e.target)) { + currentCanvas.discardActiveObject(); + currentCanvas.renderAll(); + return; + } + + handleObjectSelected(e); + // 更新对象列表以同步锁状态 + if (typeof window.updateList === 'function') { + window.updateList(); + } + }); + + // 5. 对象移动 + currentCanvas.on("object:moving", (e) => { + // 阻止背景图(索引0)被移动(如果已锁定) + let movingObj = e.target; + let idx = getIndex(movingObj, currentCanvas); + if (idx === 0) { + let bgObj = currentCanvas.getObjects()[0]; + if (bgObj && bgObj.get('lockMovementX') === true) { + e.e.preventDefault(); + e.e.stopPropagation(); + return false; + } + } + handleObjectMoving(e); + }); + + // 6. 变换事件 + currentCanvas.on("object:rotating", updateControls); + currentCanvas.on("object:scaling", updateControls); + currentCanvas.on("object:rotated", () => { recordState(); }); + currentCanvas.on('text:editing:entered', () => { _textEdit = true; }); + currentCanvas.on('text:editing:exited', () => { _textEdit = false; }); + currentCanvas.on("text:changed", (e) => { $("#text_text").val(e.target.get("text")); }); + + // 7. 对象修改后 + currentCanvas.on('object:modified', function (event) { + if (currentCanvas.getActiveObjects().length > 1) return; + // 跳过辅助线 + if (event.target.isGuideLine) { + return; + } + let idx = getIndex(event.target, currentCanvas); + if (idx <= 0) return; + let objData = objs[idx - 1]; + // 安全检查:确保 objData 存在且有 type 属性 + if (!objData || typeof objData.type === 'undefined') { + return; + } + let target = event.target; + + if ([3, 4, 10].includes(objData.type)) { + target.fontSize = (target.fontSize * target.scaleX).toFixed(0); + target.scaleX = 1; target.scaleY = 1; + target._clearCache(); + if (objData.type == 3) $("#text_size").val(target.fontSize); + if (objData.type == 4) $("#out_text_size").val(target.fontSize); + if (objData.type == 10) $("#count_size").val(target.fontSize); + currentCanvas.renderAll(); + updateControls(); + } else if (objData.type == 5) { + target.width *= target.scaleX; target.height *= target.scaleY; + target.scaleX = 1; target.scaleY = 1; + updateControls(); + } + recordState(); + // 更新对象列表以同步锁状态 + if (typeof window.updateList === 'function') { + window.updateList(); + } + }); + }); +} + +// 将 updateList 附加到 window 对象,确保全局可访问 +window.updateList = function updateList() { + // 确保使用正确的 objs(优先使用 window.objs,因为它可能被撤回/重做更新) + const currentObjs = window.objs || objs; + const currentCanvas = window.canvas || canvas; + + let str = ""; + let i = 0; + + // 定义行样式:Flex布局,两端对齐,相对定位防止溢出 + const rowStyle = "display:flex; justify-content:space-between; align-items:center; height:32px; padding:0 5px; cursor:pointer; border-bottom:1px solid #444; color:#ccc; font-size:12px; position:relative;"; + // 定义左侧内容样式 + const leftStyle = "display:flex; align-items:center; overflow:hidden; white-space:nowrap; max-width:180px;"; + // 定义序号样式:靠右,有左边框 + const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;"; + + for (let item of currentObjs) { + let displayIndex = i + 1; // 序号底层为1 + + // 图标逻辑 + let iconName = "rect.svg"; + switch (item.type) { + case 1: iconName = "images.svg"; break; + case 2: iconName = "images_add.svg"; break; + case 3: iconName = "text.svg"; break; + case 4: iconName = "text2.svg"; break; + case 5: iconName = "rect.svg"; break; + case 6: iconName = "round.svg"; break; + case 7: iconName = "line.svg"; break; + case 8: iconName = "qrcode.svg"; break; + case 9: iconName = "barcode.svg"; break; + case 10: iconName = "counter.svg"; break; + case 11: iconName = "text3.svg"; break; + } + + // 名称逻辑 + let nameText = item.name ? item.name : + (item.type === 3 ? "TEXT_" + displayIndex : + (item.type === 1 ? "IMG_" + displayIndex : "OBJ_" + displayIndex)); + + // 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性) + // 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1 + let isLocked = false; + if (currentCanvas.getObjects()[i + 1]) { + let canvasObj = currentCanvas.getObjects()[i + 1]; + isLocked = canvasObj.get('lockMovementX') === true; + } + + // 锁定状态图标 + const lockIcon = isLocked ? + '' : + ''; + + // 组装 HTML + str = `
+
+ + ${nameText} + ${lockIcon} +
+ ${displayIndex} +
` + str; + i++; + } + $(".obj_list").html(str); + + // 重新绑定点击事件(防止 updateList 后点击失效) + $(".obj_list > div").on("click", function () { + let imgId = $(this).find("img").attr("id"); + if (imgId) { + let idx = parseInt(imgId.replace("obj", "")); + selectObj(idx); + } + }); +} + +// 标记是否通过对象列表选中(用于区分鼠标点击和列表点击) +window._selectingFromList = false; + +// 将 selectObj 附加到 window 对象,确保全局可访问 +window.selectObj = function selectObj(objIndices, isFromCanvas = false) { + let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []); + + // 清除所有高亮 + $('.obj_list > div').css("background-color", "transparent"); + + // 遍历列表查找并高亮 + $('.obj_list > div').each(function () { + let $img = $(this).find("img"); + if ($img.length === 0) return; + + let idIndex = parseInt($img.attr("id").replace("obj", "")); + + if (indicesArray.includes(idIndex)) { + $(this).css("background-color", "#6F7A84"); + + // 自动滚动到可视区域 + if (isFromCanvas && indicesArray[0] === idIndex) { + this.scrollIntoView({ behavior: "smooth", block: "nearest" }); + } + } + }); + + // 如果是点击列表,触发画布选中 + if (!isFromCanvas && indicesArray.length === 1) { + let target = canvas.getObjects()[indicesArray[0] + 1]; + if (target) { + // 检查对象是否被锁定 + let isLocked = isObjectLocked(target); + + // 设置标志位,表示这是通过对象列表选中的(无论是否锁定) + window._selectingFromList = true; + + // 如果被锁定,临时设置 selectable 和 evented 为 true,以便能够选中 + if (isLocked) { + target.set({ + selectable: true, + evented: true + }); + } + + canvas.setActiveObject(target); + canvas.requestRenderAll(); + + // 如果对象被锁定,选中后立即恢复锁定状态(但保持选中状态) + if (isLocked) { + setTimeout(() => { + target.set({ + selectable: false, + evented: false + }); + canvas.requestRenderAll(); + }, 0); + } + + // 清除标志位(延迟清除,确保事件处理完成) + setTimeout(() => { + window._selectingFromList = false; + }, 10); + + handleObjectSelected(); + } + } +} + +// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 +window.recordState = function recordState() { + // 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期 + if (window._isApplyingHistory) { + return; + } + // 确定当前是正面还是背面,直接使用对应的数组 + const isFront = $('#front_side').hasClass('ui-button-active'); + const currentStep = isFront ? (window.step1 || step1) : (window.step2 || step2); + const currentRecordObjs = isFront ? (window.recordObjs1 || recordObjs1) : (window.recordObjs2 || recordObjs2); + const currentRecordJson = isFront ? (window.recordJson1 || recordJson1) : (window.recordJson2 || recordJson2); + const currentObjs = window.objs || objs; + const currentCanvas = window.canvas || canvas; + + ++currentStep.val; + for (let i = currentStep.val; i < currentRecordObjs.length; i++) { + currentRecordObjs.pop(); + currentRecordJson.pop(); + } + currentRecordObjs.push(JSON.stringify(currentObjs)) + // 保存所有锁定相关的属性,确保锁定状态可以正确恢复 + let j = currentCanvas.toJSON(TO_JSON_PROPERTIES); + j.objects[0].hoverCursor = "default"; + currentRecordJson.push(j); + + // 同步更新 window 变量和局部变量 + if (isFront) { + window.step1 = currentStep; + window.recordObjs1 = currentRecordObjs; + window.recordJson1 = currentRecordJson; + window.step = currentStep; + window.recordObjs = currentRecordObjs; + window.recordJson = currentRecordJson; + step1 = currentStep; + recordObjs1 = currentRecordObjs; + recordJson1 = currentRecordJson; + step = currentStep; + recordObjs = currentRecordObjs; + recordJson = currentRecordJson; + } else { + window.step2 = currentStep; + window.recordObjs2 = currentRecordObjs; + window.recordJson2 = currentRecordJson; + window.step = currentStep; + window.recordObjs = currentRecordObjs; + window.recordJson = currentRecordJson; + step2 = currentStep; + recordObjs2 = currentRecordObjs; + recordJson2 = currentRecordJson; + step = currentStep; + recordObjs = currentRecordObjs; + recordJson = currentRecordJson; + } +}; +// 向后兼容 +function recordState() { + return window.recordState(); +} + +// 将 getIndex 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问 +window.getIndex = function getIndex(target, _canvas = null) { + let temp_objs; + // 使用 window.canvas 作为默认值,确保在 eval() 执行时也能访问 + const currentCanvas = _canvas || window.canvas || canvas; + if (!currentCanvas) { + return 0; + } + temp_objs = currentCanvas.getObjects(); + + let i = 0; + for (let res of temp_objs) { + if (target == res) { + return i; + } + i++; + } + return 0; +}; +// 向后兼容 +function getIndex(target, _canvas = null) { + return window.getIndex(target, _canvas); +} + +function getType(target) { + // 如果是辅助线,返回 null + if (target.isGuideLine) { + return null; + } + let temp_objs = canvas.getObjects(); + let i = 0; + for (let res of temp_objs) { + if (target == res) { + // 安全检查:确保 objs[i] 存在且有 type 属性 + if (!objs[i] || typeof objs[i].type === 'undefined') { + return null; + } + return objs[i].type; + } + i++; + } + return null; +} + +function checkName(name) { + //检查字段名是否重复了 + for (let item of objs1) { + if (item.name == name) { + return false; + } + } + for (let item of objs2) { + if (item.name == name) { + return false; + } + } + return true; +} + +function resName(pre_name) { + //返回的是不重复的字段名 + let num = (objs1.length + objs2.length) - 1; + do { + num++; + } + while (!checkName(pre_name + num));//重复了就再加1 + return pre_name + num; +} + +function getDisdance(x1, y1, x2, y2) { + var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值 + var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值 + var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离 + return distance; +} + +function getDeg(pointer) { + // 计算点击位置与画布中心的坐标差 + var centerX = canvas.width / 2; + var centerY = canvas.height / 2; + var mouseX = pointer.x; + var mouseY = pointer.y; + var dx = mouseX - centerX; + var dy = mouseY - centerY; + // 计算旋转角度(弧度) + var angleRad = Math.atan2(dy, dx); + // 将角度转换为度数 + var angleDeg = angleRad * (180 / Math.PI); + if (angleDeg < 0) { + angleDeg += 360; + } + angleDeg += 90 + return angleDeg +} + +function getCoordsMinX(acoords) { + x = acoords[0].x; + for (let item of acoords) { + if (item.x < x) { + x = item.x + } + } + return x; +} + +function getCoordsMaxX(acoords) { + x = acoords[0].x; + for (let item of acoords) { + if (item.x > x) { + x = item.x + } + } + return x; +} + +function getCoordsMinY(acoords) { + y = acoords[0].y; + for (let item of acoords) { + if (item.y < y) { + y = item.y + } + } + return y; +} + +function getCoordsMaxY(acoords) { + y = acoords[0].y; + for (let item of acoords) { + if (item.y > y) { + y = item.y + } + } + return y; +} + +function addBackground() { + // 类型改变 + fabric.Image.fromURL('./public/images/bg_front_2.png', function (image) { + image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2 + image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2 + + image.selectable = false + image.hoverable = false + image.hoverCursor = 'default' + image.setSrc(image.toDataURL(), function (image) { + background_image1 = image + background_image = background_image1 + window.background_image1 = background_image1; + window.background_image = background_image; + canvas1.add(image) + canvas1.renderAll() + + recordObjs1.push(JSON.stringify(objs1)) + const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped']; + recordJson1.push(canvas1.toJSON(props)) + }) + // 类型改变 + fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) { + image.left = ($('#canvas2').width() - image.width) / 2 + image.top = ($('#canvas2').height() - image.height) / 2 + + image.selectable = false + image.hoverable = false + image.hoverCursor = 'default' + + image.setSrc(image.toDataURL(), function (image) { + background_image2 = image + window.background_image2 = background_image2; + canvas2.add(image) + canvas2.renderAll() + + recordObjs2.push(JSON.stringify(objs2)) + recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES)) + + let file = GetFile().get('file'); + if (!file && typeof sessionStorage !== 'undefined') { + try { + file = sessionStorage.getItem('soondesign_open_file'); + if (file) { + sessionStorage.removeItem('soondesign_open_file'); + sessionStorage.removeItem('soondesign_open_type'); + } + } catch (e) {} + } + if (file && file != 'empty') { + var p = window.openFile(file); + if (p && p.then) p.then(function() {}, function() {}); + } + }) + }) + is_bgi_add = true + }) +} + +function addPic(pointer) { + pre_add_image.left = pointer.x + pre_add_image.top = pointer.y + + canvas.add(pre_add_image) + + objs.push({ type: 1, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +function addOutPic(pointer) { + fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) { + let width = canvas.getWidth() + let height = canvas.getHeight() + image.left = pointer.x + image.top = pointer.y + image.opacity = 1 + image.setSrc(image.toDataURL(), function (image) { + canvas.add(image) + objs.push({ type: 2, name: resName('OUT_PIC_'), black: false }) + canvas.renderAll() + updateList() + recordState() + }) + }) +} + +function addText(pointer) { + // 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt + let text = new fabric.IText('TEXT', { + left: pointer.x, + top: pointer.y, + fill: '#000000', + textBackgroundColor: '', + fontStyle: 'normal', + fontWeight: 'normal', + fontSize: 30, + underline: false, + linethrough: false, + fontFamily: 'Arial' + }) + text.setControlVisible('ml', false) + text.setControlVisible('mt', false) + text.setControlVisible('mr', false) + text.setControlVisible('mb', false) + canvas.add(text) + objs.push({ type: 3, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +function addCircleText(pointer) { + let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2) + let angleDeg = getDeg(pointer) + let text = new fabric.CurvedText('Circle TEXT', { + left: pointer.x, + top: pointer.y, + fill: "#000000", + textBackgroundColor: "", + fontStyle: "normal", + fontWeight: "normal", + diameter: distance * 2, + fontSize: 30, + underline: false, + linethrough: false, + lockScalingX: true, + lockScalingY: true, + fontFamily: 'Arial', + originX: 'center', + originY: 'center', + angle: angleDeg, + flipped: pointer.y > canvas.height / 2 ? true : false + }); + text.setControlVisible("ml", false); + text.setControlVisible("mt", false); + text.setControlVisible("mr", false); + text.setControlVisible("mb", false); + canvas.add(text); + objs.push({ type: 11, black: false, circleCenter: 0 }); + canvas.renderAll(); + updateList(); + recordState(); +} + +function addOutText(pointer) { + let text = new fabric.Text('MERGE TEXT', { + left: pointer.x, + top: pointer.y, + fill: '#000000', + fontStyle: 'normal', + fontWeight: 'normal', + textBackgroundColor: '', + underline: false, + linethrough: false, + fontFamily: 'Arial', + textAlign: 'center' + }) + text.setControlVisible('ml', false) + text.setControlVisible('mt', false) + text.setControlVisible('mr', false) + text.setControlVisible('mb', false) + + canvas.add(text) + objs.push({ type: 4, name: resName('OUT_TEXT_'), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' }) + canvas.renderAll() + updateList() + recordState() +} + +function addRect(pointer) { + let rect = new fabric.Rect({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: "#ff0000", + strokeWidth: 3, + width: 100, + height: 100 + }) + canvas.add(rect) + objs.push({ type: 5, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +function addCircle(pointer) { + let circle = new fabric.Circle({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: '#ff0000', + strokeWidth: 3, + radius: 100, + strokeUniform: true + }) + canvas.add(circle) + objs.push({ type: 6, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +function addLine(pointer) { + let line = new fabric.Line([50, 100, 200, 100], { + left: pointer.x, + top: pointer.y, + stroke: '#ff0000', + strokeWidth: 3, + strokeDashArray: [0, 0], + }) + + line.setControlVisible('tl', false) + line.setControlVisible('tr', false) + line.setControlVisible('br', false) + line.setControlVisible('bl', false) + line.setControlVisible('mt', false) + line.setControlVisible('mb', false) + canvas.add(line) + objs.push({ type: 7, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +function addQrCode(pointer) { + let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 }) + fabric.Image.fromURL(qr, function (image) { + image.left = pointer.x + image.top = pointer.y + canvas.add(image) + objs.push({ type: 8, name: resName('QR_CODE_'), color: '#000000', val: 'http://www.cardsoon.com', black: false }) + canvas.renderAll() + updateList() + recordState() + }) +} + +function addBarCode(pointer) { + JsBarcode('#barcode', '123456789', { margin: 0, lineColor: '#000000', fontSize: 18, font: 'Arial' }) + let barcode = document.getElementById('barcode') + let bar = barcode.toDataURL('image/png') + fabric.Image.fromURL(bar, function (image) { + image.left = pointer.x + image.top = pointer.y + + canvas.add(image) + objs.push({ type: 9, name: resName('BAR_CODE_'), val: '123456789', color: '#000000', fontSize: 18, font: 'Arial', show: false, black: false }) + canvas.renderAll() + updateList() + recordState() + }) +} + +function addCounter(pointer) { + let text = new fabric.Text('0', { + left: pointer.x, + top: pointer.y, + fill: '#000000' + }) + canvas.add(text) + objs.push({ type: 10, name: resName('COUNTER_'), start: 0, add: 1, black: false }) + canvas.renderAll() + updateList() + recordState() +} + +var front_list = [] +function applyFontList(data) { + var newFontList = [] + ;(data || []).map(function(item) { + if (item.indexOf('"') === 0) { + newFontList.push(item.replace(/^"|"$/g, '')) + } else { + newFontList.push(item) + } + }) + front_list = newFontList + for (var i = 0; i < front_list.length; i++) { + var item = front_list[i] + $('#text_font_family').append("') + $("#circle_text_font_family").append("") + $('#out_text_font_family').append("') + $('#barcode_font_family').append("') + $('#count_font_family').append("') + } +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('font-list', function(event, data) { applyFontList(data); }) +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) { + window.platformBridge.getSystemFonts().then(applyFontList) +} +function applySysLan(data) { + let lang = localStorage.getItem('lang') + if (lang) { + s_lan = lang + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge(lang) + $('#language_select').val(lang) + return + } + switch (data) { + case 'zh-CN': + s_lan = 'zh' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('zh') + $('#language_select').val('zh') + localStorage.setItem('lang', 'zh') + break + case 'zh': + s_lan = 'zh' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('zh') + $('#language_select').val('zh') + localStorage.setItem('lang', 'zh') + break + case 'zh-TW': + s_lan = 'ozh' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('ozh') + $('#language_select').val('ozh') + localStorage.setItem('lang', 'ozh') + break + case 'en': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-AU': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-CA': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-GB': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-NZ': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-US': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-ZA': + s_lan = 'en' + // 同步到全局作用域(用于 .jsc 文件加载) + if (typeof window !== 'undefined') { + window.s_lan = s_lan; + } + if (typeof global !== 'undefined') { + global.s_lan = s_lan; + } + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + } +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('sys-lan', function(event, data) { applySysLan(data); }) +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getLocale) { + applySysLan(window.platformBridge.getLocale()) +} +function onCloseConfirm() { + layer.confirm( + '' + language_str('whetherSave') + '', + { + btn: [language_str('save'), language_str('noSave'), language_str('cancel')], + btn3: function (index, layero) { } + }, + function (index, layero) { + if (typeof window.output === 'function') { + window.output(function() { + if (ipcRenderer) ipcRenderer.send('run-close') + else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose() + }) + } + }, + function (index) { + if (ipcRenderer) ipcRenderer.send('run-close') + else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose() + } + ) +} +if (typeof ipcRenderer !== 'undefined' && ipcRenderer) { + ipcRenderer.on('close', function(event, message) { onCloseConfirm(); }) +} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.onClose) { + window.platformBridge.onClose(onCloseConfirm) +} + +addBackground() //添加背景 白色卡片 + +initAligningGuidelines(canvas1) +initAligningGuidelines(canvas2) + +// 初始化 Canvas 事件监听 +initCanvasEvents(); + +// 初始化所有 Slider +var circle_text_size_updating = false; // 标志位,防止循环更新 +var circle_text_size = slider.render({ + elem: '#circle_text_size', + min: 10, + max: 120, + value: 50, + change: function (value) { + if (circle_text_size_updating) return; + if (canvas && canvas.getActiveObject && canvas.getActiveObject()) { + canvas.getActiveObject().set('fontSize', value); + canvas.renderAll(); + if (typeof updateControls === 'function') { + updateControls(); + } + if (typeof recordState === 'function') { + recordState(); + } + } + } +}); + +var circle_text_diameter = slider.render({ + elem: '#circle_text_diameter', + min: 50, + max: 1100, + value: 500, + change: function (value) { + if (canvas && canvas.getActiveObject && canvas.getActiveObject()) { + canvas.getActiveObject().set('diameter', value); + canvas.renderAll(); + if (typeof updateControls === 'function') { + updateControls(); + } + if (typeof recordState === 'function') { + recordState(); + } + } + } +}); + +var out_pic_op = slider.render({ + elem: '#out_pic_op', + min: 0, + max: 100, + value: 0, + change: function (value) { + if (canvas.getActiveObject()) { + canvas.getActiveObject().set('opacity', 1 - value / 100); + canvas.renderAll(); + updateControls(); + recordState(); + } + } +}); + +var pic_op = slider.render({ + elem: '#pic_op', + min: 0, + max: 100, + value: 0, + change: function (value) { + if (canvas.getActiveObject()) { + canvas.getActiveObject().set('opacity', 1 - value / 100); + canvas.renderAll(); + updateControls(); + recordState(); + } + } +}); + +var rect_op = slider.render({ + elem: '#rect_op', + min: 0, + max: 100, + value: 0, + change: function (value) { + if (canvas.getActiveObject()) { + canvas.getActiveObject().set('opacity', 1 - value / 100); + canvas.renderAll(); + updateControls(); + recordState(); + } + } +}); + +var circle_op = slider.render({ + elem: '#circle_op', + min: 0, + max: 100, + value: 0, + change: function (value) { + if (canvas.getActiveObject()) { + canvas.getActiveObject().set('opacity', 1 - value / 100); + canvas.renderAll(); + updateControls(); + recordState(); + } + } +}); + +//canvas.setZoom(0.5) +//recordObjs1.push(JSON.stringify(objs1)); +//recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); +//recordObjs2.push(JSON.stringify(objs2)); +//recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); +canvas.renderAll() //重新渲染画布 +var zoomSlider = slider.render({ + elem: '#zoomSlide', //绑定元素 + min: 25, + max: 500, + value: 100, + change(val) { + canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), val / 100) + zoom = val / 100 + //$(".canvas-container").css("transform","scale(" + val / 100 + ")") + } +}) +window.rotate = function (id) { + let angle = $(`#${id}`).attr('angle') - 0 + angle += 90 + $(`#${id}`).css('transform', `rotate(${angle}deg)`) + $(`#${id}`).attr('angle', angle) +} +// 注意:#display 事件绑定已在 ui.js 中定义(第2633行),此处已删除重复定义 +// 导出文件 +// 将 savePdf 附加到 window 对象,确保在 output.js 中也能访问 +window.savePdf = async function savePdf(bufferOrBlob) { + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + var defaultPath = (typeof path !== 'undefined' && path && exePath) ? path.join(exePath, 'output.pdf') : 'output.pdf'; + var result = await dialogApi.showSaveDialog({ + title: '保存文件', + defaultPath: defaultPath, + filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] + }); + var canceled = result.canceled; + var filePath = result.filePath || (result.fileHandle && result.fileHandle.name); + if (canceled || (!filePath && !result.fileHandle)) return; + var name = (typeof filePath === 'string' ? filePath.split(/[/\\]/).pop() : 'output.pdf') || 'output.pdf'; + if (!/\.pdf$/i.test(name)) name = name + '.pdf'; + if (window.platformBridge && window.platformBridge.writeFile) { + var blob = bufferOrBlob instanceof Blob ? bufferOrBlob : (bufferOrBlob && bufferOrBlob.buffer ? new Blob([bufferOrBlob]) : new Blob([bufferOrBlob])); + window.platformBridge.writeFile(result.fileHandle || filePath, blob).then(function() { + layer.msg(language_str("saveSucc") + name); + }); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs && filePath) { + var buf = bufferOrBlob && bufferOrBlob.buffer ? Buffer.from(bufferOrBlob) : (bufferOrBlob instanceof Uint8Array ? Buffer.from(bufferOrBlob) : bufferOrBlob); + fs.writeFile(filePath, buf, function(err) { + if (!err) layer.msg(language_str("saveSucc") + filePath); + }); + } +}; +// 向后兼容 +async function savePdf(buffer) { + return window.savePdf(buffer); +} + +// display_func 函数已移至 output.js,避免重复定义 +// function display_func(img1, img2, img3) { ... } + +// output 函数已移至 output.js,避免重复定义 +// function output(callback = null, _save = save) { ... } + +var _keyctrl = false, + _keyc = false, + _textEdit = false, + _inputEdit = false +$('input').focus(function () { + _inputEdit = true +}) +$('input').blur(function () { + _inputEdit = false +}) +$('textarea').focus(function () { + _inputEdit = true +}) +$('textarea').blur(function () { + _inputEdit = false +}) +$('body').keyup(function (event) { + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放 + _keyctrl = false + } + if (event.keyCode == 67) { + _keyc = false + } +}) +$('body').keydown(function (event) { + // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta) + const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端 + + // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的) + if (!_inputEdit && !_textEdit) { + // CTRL/CMD+N - 新建 + if (isModifierKey && event.keyCode == 78) { + event.preventDefault(); + $('#new').click(); + return; + } + // CTRL/CMD+O - 打开 + if (isModifierKey && event.keyCode == 79) { + event.preventDefault(); + $('#open').click(); + return; + } + // CTRL/CMD+S - 保存 + if (isModifierKey && !event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + $('#save').click(); + return; + } + // CTRL/CMD+SHIFT+S - 保存副本 + if (isModifierKey && event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + if (typeof window.output === 'function') { + window.output(null, saveAs); + } + return; + } + // CTRL/CMD+Z - 还原 + if (isModifierKey && !event.shiftKey && event.keyCode == 90) { + event.preventDefault(); + $('#previous').click(); + return; + } + // CTRL/CMD+Y - 重做 + if (isModifierKey && !event.shiftKey && event.keyCode == 89) { + event.preventDefault(); + $('#next').click(); + return; + } + // CTRL/CMD+C - 复制 + if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') { + event.preventDefault(); + window.copySelection(); + return; + } + // CTRL/CMD+V - 粘贴 + if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') { + event.preventDefault(); + window.pasteFromClipboard(); + return; + } + // CTRL/CMD+SHIFT+DEL - 清空 + if (isModifierKey && event.shiftKey && event.keyCode == 46) { + event.preventDefault(); + $('#clean').click(); + return; + } + // CTRL/CMD+DEL - 删除 + if (isModifierKey && !event.shiftKey && event.keyCode == 46) { + event.preventDefault(); + $('#delete').click(); + return; + } + } + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd) + _keyctrl = true + if (_keyc && _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 67) { + _keyc = true + if (_keyctrl & _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 37) { + //left + let v = -1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5 + } + if (canvas.getActiveObject()) { + let x = canvas.getActiveObject().get('left') + x += v + canvas.getActiveObject().set('left', x) + canvas.renderAll() + recordState() + updateControls() + } + } + if (event.keyCode == 38) { + //up + let v = -1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5 + } + if (canvas.getActiveObject()) { + let y = canvas.getActiveObject().get('top') + y += v + canvas.getActiveObject().set('top', y) + canvas.renderAll() + recordState() + updateControls() + } + } + if (event.keyCode == 39) { + //right + let v = 1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5 + } + if (canvas.getActiveObject()) { + let x = canvas.getActiveObject().get('left') + x += v + canvas.getActiveObject().set('left', x) + canvas.renderAll() + recordState() + updateControls() + } + } + if (event.keyCode == 40) { + //down + let v = +1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5 + } + if (canvas.getActiveObject()) { + let y = canvas.getActiveObject().get('top') + y += v + canvas.getActiveObject().set('top', y) + canvas.renderAll() + recordState() + updateControls() + } + } + // 普通删除键(DEL键,没有按CTRL/CMD) + if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) { + let actObjs = canvas.getActiveObjects() + let allObjs = canvas.getObjects() + let flag = 0 + for (let item of actObjs) { + if (item != allObjs[0]) { + let i = getIndex(item, canvas) //0是背景,所以从1开始,但objs不包含背景图片,故要-1 + objs.splice(i - 1, 1) + canvas.remove(item) + flag = 1 + } + } + updateList() + canvas.discardActiveObject() + if (flag == 1) { + recordState() + canvas.renderAll() + handleObjectSelected() + } + } +}) + +// 复制选中对象 +window.copySelection = function copySelection() { + const activeObjects = canvas.getActiveObjects(); + + if (!activeObjects || activeObjects.length === 0) { + layer.msg("未选中任何对象") + return; + } + + // 【核心修复】遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据 + clipboardData.data = activeObjects.map(obj => { + let idx = getIndex(obj, canvas); // 获取对象在画布中的索引 + + // 获取业务属性 (objs数组中对应的那一项) + // 注意:idx=0 是背景图,objs数组是从下标0开始存第1个对象的,所以是 idx-1 + let appData = (idx > 0 && objs[idx - 1]) ? objs[idx - 1] : { type: 0 }; + + return { + // 1. 保存画面样式 (坐标、颜色、字号等,包含图片的 src) + fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data', 'src']), + // 2. 保存业务属性 (深拷贝,防止引用冲突) + appData: JSON.parse(JSON.stringify(appData)) + }; + }); + + clipboardData.offset = 10; // 重置偏移量 + layer.msg("已复制"); +} + +// 粘贴对象 +window.pasteFromClipboard = function pasteFromClipboard() { + // 检查是否有数据 + if (!clipboardData.data || clipboardData.data.length === 0) { + layer.msg("剪贴板为空") + return; + } + + // 防止重复调用:如果正在粘贴,直接返回 + if (window._isPasting) { + return; + } + window._isPasting = true; + + // 1. 提取 Fabric JSON 准备恢复画面 + const jsonToEnliven = clipboardData.data.map(item => item.fabricJson); + + // 2. 恢复对象 (异步过程) + fabric.util.enlivenObjects(jsonToEnliven, function (objects) { + canvas.discardActiveObject(); + + objects.forEach((clone, index) => { + // --- A. 处理画面位置 --- + clone.set({ + left: clone.left + clipboardData.offset, + top: clone.top + clipboardData.offset, + evented: true + }); + + // 如果是组内对象,需要特殊处理坐标 + if (clone.group) { + clone.set({ + left: clone.group.left + clipboardData.offset, + top: clone.group.top + clipboardData.offset + }); + } + + // --- B. 处理业务数据 --- + // 取出复制时保存的 appData + let savedAppData = clipboardData.data[index].appData; + let newAppData = JSON.parse(JSON.stringify(savedAppData)); // 再次深拷贝 + + // 修改名称,防止 ID 冲突 (例如 TEXT_1 -> TEXT_1_copy) + if (newAppData.name) { + newAppData.name = newAppData.name + "_copy"; + } + + // --- C. 添加到画布和数据数组 --- + canvas.add(clone); + objs.push(newAppData); // 将新业务数据推入当前页面的 objs 数组 + }); + + // 3. 选中新粘贴出来的对象 + let sel = new fabric.ActiveSelection(objects, { canvas: canvas }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + + // 4. 刷新列表和历史记录 + if (typeof window.updateList === 'function') { + window.updateList(); + } + recordState(); + + // 5. 高亮左侧列表最后一项 (即新粘贴的项) + if (typeof window.selectObj === 'function') { + if (objs.length > 0) { + window.selectObj(objs.length - 1); + } + } + + // 6. 增加下次粘贴的偏移量 (让用户知道粘贴了新东西) + clipboardData.offset += 10; + + // 清除粘贴标志 + window._isPasting = false; + }); +} + +// 将 onStateRestored 附加到 window 对象,确保全局可访问 +// 此函数在撤回/重做后调用,用于恢复画布状态 +window.onStateRestored = function onStateRestored() { + // 1. 重新获取背景图引用 (因为旧对象被销毁了) + // 注意:canvas 是全局变量,指向当前激活的画板 + if (canvas && canvas.getObjects().length > 0) { + background_image = canvas.getObjects()[0]; + background_image.selectable = false; // 确保背景不可选 + window.background_image = background_image; + background_image.evented = false; + } + + // 2. 渲染画布 + canvas.requestRenderAll(); + + // 3. 取消选中状态 + if (typeof window.selectObj === 'function') { + window.selectObj(-1); + } + + // 4. 隐藏所有属性面板 + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + + // 5. 刷新左侧列表 + if (typeof window.updateList === 'function') { + window.updateList(); + } +} diff --git a/lib/design2/output.js b/lib/design2/output.js index c1557a4..76bcd09 100644 --- a/lib/design2/output.js +++ b/lib/design2/output.js @@ -273,14 +273,27 @@ window.display_func = function display_func(img1, img2, img3) { h1 = document.body.clientHeight * 0.8 - 150 + 'px' w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 } - let fs = require('fs'); - const Buffer = require('buffer').Buffer; - const buffer3 = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); - const printPath3 = path.join(exePath, 'print3.png'); - fs.writeFileSync(printPath3, buffer3); - const buffer4 = Buffer.from(url4.replace(/^data:image\/\w+;base64,/, ""), 'base64'); - const printPath4 = path.join(exePath, 'print4.png'); - fs.writeFileSync(printPath4, buffer4); + var printPath3, printPath4; + try { + if (typeof require !== 'undefined' && typeof path !== 'undefined' && path && exePath) { + var fs = require('fs'); + var Buffer = require('buffer').Buffer; + var buffer3 = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + printPath3 = path.join(exePath, 'print3.png'); + fs.writeFileSync(printPath3, buffer3); + var buffer4 = Buffer.from(url4.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + printPath4 = path.join(exePath, 'print4.png'); + fs.writeFileSync(printPath4, buffer4); + } else { + printPath3 = url3; + printPath4 = url4; + } + } catch (e) { + printPath3 = url3; + printPath4 = url4; + } + if (typeof printPath3 === 'undefined') printPath3 = url3; + if (typeof printPath4 === 'undefined') printPath4 = url4; // 创建 PDF 实例 const pdf = new jspdf.jsPDF({ orientation: 'landscape', @@ -343,24 +356,28 @@ window.display_func = function display_func(img1, img2, img3) { `, btn: btns, //'导出' btn1: function (index, layero) { - // 只处理导出(保存PDF) if (typeof window.savePdf === 'function') { - window.savePdf(Buffer.from(pdfBuffer)); + window.savePdf(pdfBlob); } }, btn2: function () { - // 根据 btns[1] 的值决定打印哪一面 + if (window.platformBridge && window.platformBridge.printPdf && (printPath3.indexOf('data:') === 0 || printPath4.indexOf('data:') === 0)) { + if (btns[1] === '打印正面') window.platformBridge.printPdf(pdfBlob); + else if (btns[1] === '打印背面') window.platformBridge.printPdf(pdfBlob); + return; + } if (btns[1] === '打印正面') { - // 打印正面 - printJS({ printable: printPath3, type: 'image', style: 'img { width: 100%; height: auto; }' }) + printJS({ printable: printPath3, type: 'image', style: 'img { width: 100%; height: auto; }' }); } else if (btns[1] === '打印背面') { - // 打印背面(只有背面时) - printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }) + printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }); } }, btn3: function () { - // 打印背面(两面都有时,btn3 对应 btns[2] = '打印背面') - printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }) + if (window.platformBridge && window.platformBridge.printPdf) { + window.platformBridge.printPdf(pdfBlob); + return; + } + printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }); }, end: function () { // 预览窗口关闭后,恢复所有对象的 selectable 和 evented 状态 @@ -1060,39 +1077,36 @@ function saveAs(op1, callback) { let o = { f: j, b: b, fo: objs1, bo: objs2 } let con_o = Object.assign(o, op1) - dialog - .showSaveDialog({ - title: language_str('saveFile'), //'保存文件' - filters: [{ name: 'Soon File Type', extensions: ['soon'] }], - defaultPath: openAs.name || undefined - }) - .then((result) => { - if (result.filePath == '') { - return + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined }) + .then(function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + var ext = (typeof path !== 'undefined' && path && path.extname) ? path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : ''); + if (ext !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon'; + con_o.soonType = 2; + var content = JSON.stringify(con_o); + if (window.platformBridge && window.platformBridge.writeFile) { + window.platformBridge.writeFile(result.fileHandle || fp, content).then(function() { + openAs.name = 'soondesign_session:' + fp; + webSessionStore(content, fp); + layer.msg(language_str('saveSucc') + fp); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + }); + return; } - if (result.filePath == null || result.filePath == undefined) { - return - } - // 检查文件扩展名,确保以.soon结尾 - const path = require('path') - let filePath = result.filePath - const ext = path.extname(filePath).toLowerCase() - if (ext !== '.soon') { - filePath = filePath + '.soon' - } - result.filePath = filePath - let fs = require('fs') - con_o.soonType = 2 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath - layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' - saveHistory() - if (callback && typeof callback === 'function') { - callback() + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(fp, content, 'utf8'); + openAs.name = fp; + layer.msg(language_str('saveSucc') + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); } }) - .catch((err) => { - }) + .catch(function(err) {}); } function save(op1, callback) { @@ -1128,73 +1142,119 @@ function save(op1, callback) { let o = { f: j, b: b, fo: objs1, bo: objs2 } let con_o = Object.assign(o, op1) + con_o.soonType = 2 + var content = JSON.stringify(con_o) if (openAs.name != '') { - //打开的文件 - let fs = require('fs') - con_o.soonType = 2 - fs.writeFileSync(openAs.name, JSON.stringify(con_o), 'utf8') - layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' - saveHistory() - if (callback && typeof callback === 'function') { - callback() + if (openAs.name.indexOf('soondesign_session:') === 0) { + webSessionStore(content, openAs.name); + // 服务器环境:同时保存到服务器 + var fileName = openAs.name.replace(/^soondesign_session:/, ''); + if (window.platformBridge && window.platformBridge.writeFile && fileName) { + window.platformBridge.writeFile(fileName, content).catch(function() {}); + } + layer.msg(language_str('saveSucc') + fileName); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + return; } - return + if (window.platformBridge && window.platformBridge.writeFile) { + webSessionStore(content, openAs.name); + window.platformBridge.writeFile(openAs.name, content).then(function() { + layer.msg(language_str('saveSucc') + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + }); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(openAs.name, content, 'utf8'); + layer.msg(language_str('saveSucc') + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + } + return; } - dialog - .showSaveDialog({ - title: language_str('saveFile'), //'保存文件' - filters: [{ name: 'Soon File Type', extensions: ['soon'] }], - defaultPath: openAs.name || undefined - }) - .then((result) => { - if (result.filePath == '') { - return + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }], defaultPath: openAs.name || undefined }) + .then(function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + var ext = (typeof path !== 'undefined' && path && path.extname) ? path.extname(fp) : (fp.indexOf('.') >= 0 ? fp.slice(fp.lastIndexOf('.')) : ''); + if (ext !== '.soon') fp = (fp || 'design').replace(/\.soon$/i, '') + '.soon'; + if (window.platformBridge && window.platformBridge.writeFile) { + window.platformBridge.writeFile(result.fileHandle || fp, content).then(function() { + openAs.name = 'soondesign_session:' + fp; + webSessionStore(content, fp); + layer.msg(language_str('saveSucc') + fp); + saveHistory(); + if (callback && typeof callback === 'function') callback(); + }); + return; } - if (result.filePath == null || result.filePath == undefined) { - return - } - // 检查文件扩展名,确保以.soon结尾 - const path = require('path') - let filePath = result.filePath - const ext = path.extname(filePath).toLowerCase() - if (ext !== '.soon') { - filePath = filePath + '.soon' - } - result.filePath = filePath - let fs = require('fs') - con_o.soonType = 2 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath - layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' - saveHistory() - if (callback && typeof callback === 'function') { - callback() + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(fp, content, 'utf8'); + openAs.name = fp; + layer.msg(language_str('saveSucc') + openAs.name); + saveHistory(); + if (callback && typeof callback === 'function') callback(); } }) - .catch((err) => { - }) + .catch(function(err) {}); +} + +// 网页端:写入 sessionStorage 与 localStorage,供首页历史读取(localStorage 跨标签/重开可用) +function webSessionStore(content, fileName) { + if (!window.platformBridge || typeof sessionStorage === 'undefined') return; + var name = fileName || openAs.name || 'design.soon'; + var key = (name && name.indexOf('soondesign_session:') === 0) ? name : ('soondesign_session:' + name); + try { + sessionStorage.setItem(key, content); + if (typeof localStorage !== 'undefined') localStorage.setItem(key, content); + } catch (e) {} } // 将 saveHistory 附加到 window 对象,确保在 ui.js 中也能访问 window.saveHistory = function saveHistory() { - let fs = require('fs') - let j = {} - try { - fsData = fs.readFileSync(fullPath) - j = JSON.parse(fsData.toString()) - for (let item of j.history) { - if (item.path == openAs.name) { - item.time = getDate() - fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8') - return - } + function doWrite(j) { + var currentPath = openAs.name; + var pathForHistory = currentPath; + if (window.platformBridge && (currentPath.indexOf('soondesign_session:') !== 0)) { + pathForHistory = 'soondesign_session:' + (currentPath || 'design.soon'); + } + var list = Array.isArray(j.history) ? j.history : []; + var newList = list.filter(function(item) { return item.path !== pathForHistory && item.path !== currentPath; }); + newList.unshift({ time: getDate(), path: pathForHistory, type: 2 }); + if (newList.length > 20) newList = newList.slice(0, 20); + j.history = newList; + if (window.platformBridge && window.platformBridge.writeHistory) { + window.platformBridge.writeHistory(j); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs && typeof fullPath !== 'undefined' && fullPath) { + try { fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8'); } catch (e) {} } - j.history.push({ time: getDate(), path: openAs.name }) - fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8') - } catch (e) { - let j = { history: { time: getDate(), path: openAs.name } } - fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8') } + if (window.platformBridge && window.platformBridge.readHistory) { + window.platformBridge.readHistory().then(function(j) { + if (!j || !Array.isArray(j.history)) j = { history: [] }; + doWrite(j); + }).catch(function() { doWrite({ history: [] }); }); + return; + } + var fs = typeof require !== 'undefined' ? require('fs') : null; + var j = { history: [] }; + if (fs && typeof fullPath !== 'undefined' && fullPath) { + try { + var fsData = fs.readFileSync(fullPath); + if (fsData && fsData.toString) j = JSON.parse(fsData.toString()); + } catch (e) { j = { history: [] }; } + if (!Array.isArray(j.history)) j.history = []; + } + doWrite(j); }; // 向后兼容 function saveHistory() { @@ -1202,12 +1262,12 @@ function saveHistory() { } // 使用 window.openFile 避免与浏览器原生的 window.open 冲突 -window.openFile = function open(file) { - let fs = require('fs') - var fsData = fs.readFileSync(file) - openAs.name = file - let j = JSON.parse(fsData.toString()) - let left = background_image.left +window.openFile = function open(file, jAlready) { + if (!file) return; + function doOpenWithJson(j) { + if (!j || !background_image) return; + openAs.name = file; + var left = background_image.left let top = background_image.top let new_left = j.f.objects[0].left let new_top = j.f.objects[0].top @@ -1390,7 +1450,27 @@ window.openFile = function open(file) { canvas2Loaded = true saveInitialState() }) -} + } + if (arguments.length >= 2 && jAlready) { + doOpenWithJson(jAlready); + return; + } + if (window.platformBridge && window.platformBridge.readJsonFile) { + return window.platformBridge.readJsonFile(file).then(function(j) { + if (j) { + doOpenWithJson(j); + openAs.name = file; + } + }); + } + try { + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (!fs || file.indexOf('soondesign_session:') === 0) return; + var fsData = fs.readFileSync(file); + var j = JSON.parse(fsData.toString()); + doOpenWithJson(j); + } catch (e) {} +}; function clearAll() { //let blank = "{\"f\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":1012,\"height\":648,\"fill\":\"#000000\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"b\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":1012,\"height\":648,\"fill\":\"rgb(0,0,0)\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAX2YPDVmXlAHjVBgAABAgQIECBAgAABAgQIlAh8bQ8Gt83MQ0oWtiYBAgQIECBAgAABAgQIECCQBW7fg8GtM/OoPGuCAAECBAgQIECAAAECBAgQKBG4dQ8GN8zMdSULW5MAAQIECBAgQIAAAQIECBDIAjfuweADM/OsPGuCAAECBAgQIECAAAECBAgQKBH44B4M3jIzLy9Z2JoECBAgQIAAAQIECBAgQIBAFnjrHgxeMDPvzrMmCBAgQIAAAQIECBAgQIAAgRKBn9uDwYmZ+UTJwtYkQIAAAQIECBAgQIAAAQIEssCJPRg8cGa+ODNX5XkTBAgQIECAAAECBAgQIECAwGUucMfMPGztS27b9qGZedplvrD1CBAgQIAAAQIECBAgQIAAgSzw4bXW008Gg/1LD/cvP/QgQIAAAQIECBAgQIAAAQIEugVevtb6zZPB4JqZuanbw/YECBAgQIAAAQIECBAgQIDAzFy71rr568Fgf2zbdv3MPBENAQIECBAgQIAAAQIECBAgUCtw/VrrSfv2pwaDF8/Mb9eSWJwAAQIECBAgQIAAAQIECBB48VrrHXcPBg+dmc/OzLfwIUCAAAECBAgQIECAAAECBOoE/ntmrl5r3fYNwWD/H9u2vW1mXlJHYmECBAgQIECAAAECBAgQIEDg7Wutl55kuOufJByCwWNm5p9n5kGcCBAgQIAAAQIECBAgQIAAgRqB/5uZ711r3XKPweAQDd41Mz9fQ2JRAgQIECBAgAABAgQIECBA4HfXWi88leEbPmFwCAaPPHzK4CpeBAgQIECAAAECBAgQIECAwGUvcMfMPHattX+v4V2P04LBIRq8embecNmTWJAAAQIECBAgQIAAAQIECBB47VrrtAZwpmCwf4fBDTPzeG4ECBAgQIAAAQIECBAgQIDAZSvwqZk5sdb6yt03vMdgsA9t23ZiZj42M1dctiwWI0CAAAECBAgQIECAAAECvQJ3zsxT1lr7BwZOe5wxGByiwS/OzK/32tmcAAECBAgQIECAAAECBAhctgKvWmv92pm2S8Fg//M/nplnX7Y8FiNAgAABAgQIECBAgAABAn0C75uZ56y1tnMKBvsPbdv2rTPz5zPz5D4/GxMgQIAAAQIECBAgQIAAgctO4OMz87S11u1n2+ysnzA4+YPbtj388H0G1152TBYiQIAAAQIECBAgQIAAAQI9AjcdvrfgC2nlIwWD/Um2bXvMzHx0Zh6VntSfEyBAgAABAgQIECBAgAABAvc7gc/MzFPXWjcf5Z0dORgcosE1M/ORmfmuozy5GQIECBAgQIAAAQIECBAgQOB+IfDpmfnxtdb+CYMjPY4VDA7RYP+EwZ/MzBOO9AqGCBAgQIAAAQIECBAgQIAAgftS4JMz88y11v4JgyM/jh0MDtHgwTPzhzPzk0d+JYMECBAgQIAAAQIECBAgQIDAxRZ4/8w8f631peO+8DkFg0M02H/2lTPzKzNzxXFf2DwBAgQIECBAgAABAgQIECBwwQTunJlfnpk3ne1XJ57t1c85GJx80m3bThw+bfC4C7amJyZAgAABAgQIECBAgAABAgSOKvCPM/O8tdaNR/2Be5q718Fgf9Jt2/ZPGOyfNnj1zFx1b96QnyVAgAABAgQIECBAgAABAgTOSeCOmXnj4VMF+ycM7tXjvASDk+9g27ZHzMzrZuZnZ+ab7tU788MECBAgQIAAAQIECBAgQIDAUQT+d2Z+b2Zev9b6/FF+4Cgz5zUYnBIOrp6ZV8zMi2bmyqO8ETMECBAgQIAAAQIECBAgQIDAsQS+PDPvnJk3r7U+d6yfPMLwBQkGp4SDh87Mc/d/OzEzP3iE92OEAAECBAgQIECAAAECBAgQOLvA9TPznpl571rrtguFdUGDwalvetu2x8zMM2bmR2bmqTPzsAu1lOclQIAAAQIECBAgQIAAAQKXkcAXZ+avZ+avZubP1lo3X4zdLlowuFs8eMDMPGFmrpuZ75uZa2bmO2fm2w9fmrh/MsGDAAECBAgQIECAAAECBAi0COyfFNi/tPDfZ+bWmbllZvbfdrD/poNPrrW+drEh/h+5oaPdsxLhuQAAAABJRU5ErkJggg==\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"fo\":[],\"bo\":[]}"; diff --git a/lib/design2/ui.js b/lib/design2/ui.js index f98a570..7385c93 100644 --- a/lib/design2/ui.js +++ b/lib/design2/ui.js @@ -1283,32 +1283,34 @@ function saveAs(op1, callback) { let o = { f: j, b: b, fo: objs1, bo: objs2 } let con_o = Object.assign(o, op1) - dialog - .showSaveDialog({ - title: language_str('saveFile'), //'保存文件' - filters: [{ name: 'Soon File Type', extensions: ['soon'] }] - }) - .then((result) => { - if (result.filePath == '') { - return + con_o.soonType = 2 + var content = JSON.stringify(con_o) + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showSaveDialog ? { showSaveDialog: function(opts) { return window.platformBridge.showSaveDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showSaveDialog({ title: language_str('saveFile'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }] }) + .then(function(result) { + var fp = result.filePath || (result.fileHandle && result.fileHandle.name); + if (!fp && !result.fileHandle) return; + if (typeof fp === 'string' && fp.substring(fp.length - 5).indexOf('.') === -1) fp += '.soon'; + if (window.platformBridge && window.platformBridge.writeFile) { + window.platformBridge.writeFile(result.fileHandle || fp, content).then(function() { + openAs.name = fp; + layer.msg(language_str('saveSucc') + openAs.name); + if (typeof window.saveHistory === 'function') window.saveHistory(); + if (callback && typeof callback === 'function') callback(); + }); + return; } - if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { - result.filePath += '.soon' - } - let fs = require('fs') - con_o.soonType = 2 - fs.writeFileSync(result.filePath, JSON.stringify(con_o), 'utf8') - openAs.name = result.filePath - layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' - if (typeof window.saveHistory === 'function') { - window.saveHistory(); - } - if (callback && typeof callback === 'function') { - callback() + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (fs) { + fs.writeFileSync(fp, content, 'utf8'); + openAs.name = fp; + layer.msg(language_str('saveSucc') + openAs.name); + if (typeof window.saveHistory === 'function') window.saveHistory(); + if (callback && typeof callback === 'function') callback(); } }) - .catch((err) => { - }) + .catch(function(err) {}); } // save 函数已在 output.js 中定义 // 复制 @@ -1354,49 +1356,60 @@ $('#open').click(function () { } ) function OpenDialog() { - dialog - .showOpenDialog({ - title: '请选择文件', - buttonLabel: language_str('comf'), - filters: [{ name: 'Soon File Type', extensions: ['soon'] }] - }) - .then((result) => { + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showOpenDialog({ title: '请选择文件', buttonLabel: language_str('comf'), filters: [{ name: 'Soon File Type', extensions: ['soon'] }] }) + .then(function(result) { if (result.canceled) return; - let filePath = result.filePaths[0]; - let fs = require('fs'); - let fsData = fs.readFileSync(filePath); - let j = JSON.parse(fsData.toString()); - let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; - - if (type == 1) { - ipcRenderer.send('open-design-page', filePath, type); + var filePath = result.filePaths && result.filePaths[0]; + var file = result.files && result.files[0]; + if (file && window.platformBridge && window.platformBridge.readJsonFile) { + window.platformBridge.readJsonFile(file).then(function(j) { + if (!j) return; + var type = j.soonType ? j.soonType : (j.backBlackPic ? 2 : 1); + if (type == 1 && window.platformBridge.openDesignPage) { + window.platformBridge.openDesignPage(file.name, 1); + return; + } + if (typeof window.openFile === 'function') window.openFile(file.name, j); + }); return; } - - // 调用 output.js 中的 openFile 函数 - if (typeof window.openFile === 'function') { - window.openFile(filePath); + if (!filePath) return; + var fs = typeof require !== 'undefined' ? require('fs') : null; + if (!fs) return; + var fsData = fs.readFileSync(filePath); + var j = JSON.parse(fsData.toString()); + var type = j.soonType ? j.soonType : (j.backBlackPic ? 2 : 1); + if (type == 1) { + if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type); + return; } + if (typeof window.openFile === 'function') window.openFile(filePath); }) - .catch((err) => { - }) + .catch(function(err) {}); } }) +function goFirstPage() { + if (window.platformBridge && window.platformBridge.openFirstPage) window.platformBridge.openFirstPage(); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-first-page'); +} $('#new').click(function () { if (step.val == 0) { - ipcRenderer.send('open-first-page'); + goFirstPage(); return; } layer.confirm( '' + language_str('whetherSave') + '', function (index) { if (typeof window.output === 'function') { - window.output(() => ipcRenderer.send('open-first-page')); + window.output(goFirstPage); } layer.close(index); }, function (index) { - ipcRenderer.send('open-first-page'); + goFirstPage(); } ) }) @@ -2718,8 +2731,9 @@ $("#about").click(async function () { '' }); }) -$("#help").click(() => { - ipcRenderer.send('open-help-file'); +$("#help").click(function() { + if (window.platformBridge && window.platformBridge.openHelp) window.platformBridge.openHelp(); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-help-file'); }); // =========================================================== diff --git a/lib/index.js b/lib/index.js index bce1041..1cf1627 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,269 +1,303 @@ -const { ipcRenderer } = require('electron'); -const { dialog } = require('@electron/remote'); - -// 辅助函数:获取文件名 (保留扩展名) -function get_filename(filePath) { - if (!filePath) return ""; - return filePath.split(/[/\\]/).pop(); -} - -let s_lan = "zh"; - -function language_str(str) { - let t = { - "noFile": { zh: "当前文件不存在!", ozh: "This file does not exist!", en: "File not found!" }, - "saveTime": { zh: "保存时间:", ozh: "保存時間:", en: "Saved on:" }, - "selectFile": { zh: "请选择文件", ozh: "請選擇文件", en: "Please select a file" }, - "comfirm": { zh: "确认", ozh: "確認", en: "Confirm" }, - "cancel": { zh: "取消", ozh: "取消", en: "Cancel" }, - "SDFile": { zh: "SoonDesign模板文件", ozh: "SoonDesign模闆文件", en: "SoonDesign Template File" }, - "vers": { zh: "版本:", ozh: "版本:", en: "Version:" }, - "copyright": { zh: "版权所有 © 2023", ozh: "版權所有 © 2023", en: "Copyright © 2023" }, - "about": { zh: "关于 SoonDesign", ozh: "關於 SoonDesign", en: "About SoonDesign" }, - "lost": { zh: "(文件已丢失)", ozh: "(文件已丟失)", en: "(File Lost)" }, - "clickToDelete": { zh: "文件已丢失,点击删除此记录", ozh: "文件已丟失,點擊刪除此記錄", en: "File lost, click to remove" }, - "deleted": { zh: "已删除", ozh: "已刪除", en: "Deleted" }, - "whetherSave": { zh: "是否保存当前文件?", ozh: "是否保存當前文件?", en: "Save current file?" }, // 补全可能用到的翻译 - "delTitle": { zh: "警告", ozh: "警告", en: "Warning" }, - "delContent": { zh: "文件已丢失,确认是否删除?", ozh: "文件已丟失,確認是否刪除?", en: "File lost, confirm delete?" } - } - return t[str][s_lan] || t[str]['zh']; -} - -layui.use(['layer', 'form', 'jquery'], function () { - var $ = layui.$; - var layer = layui.layer - , form = layui.form; - - if (window.sysAPI && window.sysAPI.getAppVersion) { - window.sysAPI.getAppVersion().then(ver => { - $("#app-version").text("v" + ver); - }).catch(e => console.log(e)); - } - - ipcRenderer.send('get-sys-language'); - - ipcRenderer.on('close', (event, message) => { - ipcRenderer.send('run-close'); - }) - - function langua_ge(lan = 'zh') { - $("[language='m']").each(function (i) { - $(this).html($(this).attr(lan)); - }) - $("[language='t']").each(function (i) { - $(this).attr("title", $(this).attr(lan)); - }) - } - - $('#language_select').on('change', function () { - langua_ge($(this).find('option:selected').val()); - s_lan = $(this).find('option:selected').val(); - localStorage.setItem("lang", s_lan); - loadHistory(); - }); - - ipcRenderer.on('sys-lan', (event, data) => { - let lang = localStorage.getItem("lang"); - if (lang) { - s_lan = lang; - } else { - if (data.startsWith('zh')) { - s_lan = data === 'zh-TW' ? 'ozh' : 'zh'; - } else { - s_lan = 'en'; - } - } - langua_ge(s_lan); - $("#language_select").val(s_lan); - localStorage.setItem("lang", s_lan); - loadHistory(); - }); - - // ========================================== - // 加载列表 - // ========================================== - async function loadHistory() { - try { - const j = await window.sysAPI.readHistory(); - let h = ""; - let needUpdate = false; - - for (let item of j.history) { - let src = ""; - let fileExists = true; - let imgStyle = ""; - let realType = item.type; - - const soonData = await window.sysAPI.readJsonFile(item.path); - - if (soonData) { - src = soonData.frontDisplayPic; - let fileType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); - if (item.type != fileType) { - item.type = fileType; - realType = fileType; - needUpdate = true; - } - } else { - fileExists = false; - if (realType && (realType == 2 || realType == "2")) { - src = "./public/images/bg_2.png"; - } else { - src = "./public/images/bg_1.png"; - } - imgStyle = "opacity: 0.6; filter: grayscale(100%);"; - } - - let displayName = get_filename(item.path); - let cardTitle = item.path; - let cardStyle = ""; - - if (!fileExists) { - displayName += ` ${language_str("lost")}`; - cardTitle = language_str("clickToDelete"); - cardStyle = "border: 1px dashed #ff5722;"; - } - - h += `
-
- -
${displayName}
-
-
` - } - - $(".card-list").html(h); - - if (needUpdate) { - await window.sysAPI.writeHistory({ history: j.history }); - } - - } catch (e) { - console.error("加载历史记录出错", e); - } - } - - // 按钮事件 - $("#openfile").click(function () { OpenDialog(); }); - $("#new1").click(function () { - // 这里如果你也想改成和保存一样的弹窗逻辑,可以在这里加 - ipcRenderer.send('open-design-page', "", 1); - }); - $("#new2").click(function () { ipcRenderer.send('open-design-page', "", 2); }); - - $("#about").click(async function () { - let version = "3.0.0"; - try { - if (window.sysAPI && window.sysAPI.getAppVersion) { - const v = await window.sysAPI.getAppVersion(); - version = "v" + v; - } - } catch (e) {} - - layer.open({ - type: 1 - , title: language_str("about") - , area: '450px;' - , id: 'LAY_layuipro' - , moveType: 1 - , content: '
' + - '
' + - '
Soon Design
' + - '
' + language_str("vers") + ' ' + version + '
' + - '
' + language_str("copyright") + '
' + - '
' - }); - }); - - // ========================================== - // 点击事件 (修改后:完全匹配“保存”弹窗样式) - // ========================================== - $(".card-list").on("click", '.card', async function () { - let filePath = $(this).attr("data"); - - // 再次检查文件是否存在 - const soonData = await window.sysAPI.readJsonFile(filePath); - - if (soonData) { - // 1. 文件存在 -> 正常打开 - let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); - - const currentData = await window.sysAPI.readHistory(); - let newHistory = currentData.history.filter(item => item.path !== filePath); - newHistory.unshift({ path: filePath, time: Date.now(), type: type }); - await window.sysAPI.writeHistory({ history: newHistory }); - - ipcRenderer.send('open-design-page', filePath, type); - } else { - // 2. 文件不存在 -> 警告确认 - - // 【关键修改】 - // - 纯文字,左对齐 (text-align: left) - // - 适当的 padding - // - 去除所有图标 - let contentHtml = ` -
- ${language_str("delContent")} -
- `; - - // 使用 layer.open - layer.open({ - type: 1, - title: language_str("delTitle"), // 标题:警告 - content: contentHtml, - btn: [language_str("comfirm"), language_str("cancel")], - btnAlign: 'r', // 【关键修改】按钮右对齐 - area: ['300px', 'auto'], // 【关键修改】宽度设为 300px,标准小窗口 - resize: false, - shadeClose: true, - yes: async function(index){ - const currentData = await window.sysAPI.readHistory(); - const newHistory = currentData.history.filter(item => item.path !== filePath); - - if (newHistory.length !== currentData.history.length) { - await window.sysAPI.writeHistory({ history: newHistory }); - layer.msg(language_str("deleted"), { icon: 1, time: 1000 }); - loadHistory(); - } - layer.close(index); - } - }); - } - }); - - function OpenDialog() { - dialog.showOpenDialog({ - title: language_str("selectFile"), - buttonLabel: language_str("comfirm"), - filters: [ - { name: language_str("SDFile"), extensions: ['soon'] }, - ] - }).then(async (result) => { - if (result.canceled || !result.filePaths[0]) return; - - let filePath = result.filePaths[0]; - - const soonData = await window.sysAPI.readJsonFile(filePath); - if (!soonData) { - layer.msg(language_str("noFile")); - return; - } - - let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); - - const currentData = await window.sysAPI.readHistory(); - let newHistory = currentData.history.filter(item => item.path !== filePath); - newHistory.unshift({ path: filePath, time: Date.now(), type: type }); - - await window.sysAPI.writeHistory({ history: newHistory }); - - ipcRenderer.send('open-design-page', filePath, type); - loadHistory(); - - }).catch(err => { - console.log(err) - }) - } +// 使用平台桥(桌面端由 lib/platform/electron.js 注入 window.ipcRenderer / window.dialog / window.sysAPI) +var ipcRenderer = typeof window !== 'undefined' ? window.ipcRenderer : null; +var dialog = typeof window !== 'undefined' ? window.dialog : null; + +// 辅助函数:获取文件名 (保留扩展名) +function get_filename(filePath) { + if (!filePath) return ""; + return filePath.split(/[/\\]/).pop(); +} + +// 辅助函数:HTML 属性转义,避免 path 中的 "&<> 破坏属性导致点击传参错误 +function escapeAttr(s) { + if (s == null) return ""; + return String(s).replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); +} + +let s_lan = "zh"; + +function language_str(str) { + let t = { + "noFile": { zh: "当前文件不存在!", ozh: "This file does not exist!", en: "File not found!" }, + "saveTime": { zh: "保存时间:", ozh: "保存時間:", en: "Saved on:" }, + "selectFile": { zh: "请选择文件", ozh: "請選擇文件", en: "Please select a file" }, + "comfirm": { zh: "确认", ozh: "確認", en: "Confirm" }, + "cancel": { zh: "取消", ozh: "取消", en: "Cancel" }, + "SDFile": { zh: "SoonDesign模板文件", ozh: "SoonDesign模闆文件", en: "SoonDesign Template File" }, + "vers": { zh: "版本:", ozh: "版本:", en: "Version:" }, + "copyright": { zh: "版权所有 © 2023", ozh: "版權所有 © 2023", en: "Copyright © 2023" }, + "about": { zh: "关于 SoonDesign", ozh: "關於 SoonDesign", en: "About SoonDesign" }, + "lost": { zh: "(文件已丢失)", ozh: "(文件已丟失)", en: "(File Lost)" }, + "clickToDelete": { zh: "文件已丢失,点击删除此记录", ozh: "文件已丟失,點擊刪除此記錄", en: "File lost, click to remove" }, + "deleted": { zh: "已删除", ozh: "已刪除", en: "Deleted" }, + "whetherSave": { zh: "是否保存当前文件?", ozh: "是否保存當前文件?", en: "Save current file?" }, // 补全可能用到的翻译 + "delTitle": { zh: "警告", ozh: "警告", en: "Warning" }, + "delContent": { zh: "文件已丢失,确认是否删除?", ozh: "文件已丟失,確認是否刪除?", en: "File lost, confirm delete?" } + } + return t[str][s_lan] || t[str]['zh']; +} + +layui.use(['layer', 'form', 'jquery'], function () { + var $ = layui.$; + var layer = layui.layer + , form = layui.form; + + if (window.sysAPI && window.sysAPI.getAppVersion) { + window.sysAPI.getAppVersion().then(ver => { + $("#app-version").text("v" + ver); + }).catch(e => console.log(e)); + } + + if (ipcRenderer) { + ipcRenderer.send('get-sys-language'); + ipcRenderer.on('close', (event, message) => { + ipcRenderer.send('run-close'); + }); + } + + function langua_ge(lan = 'zh') { + $("[language='m']").each(function (i) { + $(this).html($(this).attr(lan)); + }) + $("[language='t']").each(function (i) { + $(this).attr("title", $(this).attr(lan)); + }) + } + + $('#language_select').on('change', function () { + langua_ge($(this).find('option:selected').val()); + s_lan = $(this).find('option:selected').val(); + localStorage.setItem("lang", s_lan); + loadHistory(); + }); + + if (ipcRenderer && ipcRenderer.on) { + ipcRenderer.on('sys-lan', (event, data) => { + let lang = localStorage.getItem("lang"); + if (lang) { + s_lan = lang; + } else { + if (data && data.startsWith('zh')) { + s_lan = data === 'zh-TW' ? 'ozh' : 'zh'; + } else { + s_lan = 'en'; + } + } + langua_ge(s_lan); + $("#language_select").val(s_lan); + localStorage.setItem("lang", s_lan); + loadHistory(); + }); + } + + // ========================================== + // 加载列表 + // ========================================== + async function loadHistory() { + try { + if (!window.sysAPI || typeof window.sysAPI.readHistory !== 'function') { + $(".card-list").html(""); + return; + } + const j = await window.sysAPI.readHistory(); + if (!j || !Array.isArray(j.history)) { + $(".card-list").html(""); + return; + } + let h = ""; + let needUpdate = false; + + for (let item of j.history) { + let src = ""; + let fileExists = true; + let imgStyle = ""; + let realType = item.type; + const soonData = await window.sysAPI.readJsonFile(item.path); + if (soonData) { + src = soonData.frontDisplayPic; + let fileType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + if (item.type != fileType) { + item.type = fileType; + realType = fileType; + needUpdate = true; + } + } else { + fileExists = false; + if (realType && (realType == 2 || realType == "2")) { + src = "./public/images/bg_2.png"; + } else { + src = "./public/images/bg_1.png"; + } + imgStyle = "opacity: 0.6; filter: grayscale(100%);"; + } + + // 网页端历史 path 为 soondesign_session:文件名.soon,展示时去掉前缀 + const sessionPrefix = 'soondesign_session:'; + let displayName = (item.path && item.path.indexOf(sessionPrefix) === 0) + ? item.path.substring(sessionPrefix.length) + : get_filename(item.path); + let cardTitle = (item.path && item.path.indexOf(sessionPrefix) === 0) + ? displayName + : item.path; + let cardStyle = ""; + + if (!fileExists) { + displayName += ` ${language_str("lost")}`; + cardTitle = language_str("clickToDelete"); + cardStyle = "border: 1px dashed #ff5722;"; + } + + h += `
+
+ +
${displayName}
+
+
` + } + + $(".card-list").html(h); + + if (needUpdate) { + await window.sysAPI.writeHistory({ history: j.history }); + } + + } catch (e) { + console.error("加载历史记录出错", e); + } + } + + loadHistory(); + + // 按钮事件 + $("#openfile").click(function () { OpenDialog(); }); + $("#new1").click(function () { + if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage("", 1); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', "", 1); + }); + $("#new2").click(function () { + if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage("", 2); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', "", 2); + }); + + $("#about").click(async function () { + let version = "3.0.0"; + try { + if (window.sysAPI && window.sysAPI.getAppVersion) { + const v = await window.sysAPI.getAppVersion(); + version = "v" + v; + } + } catch (e) {} + + layer.open({ + type: 1 + , title: language_str("about") + , area: '450px;' + , id: 'LAY_layuipro' + , moveType: 1 + , content: '
' + + '
' + + '
Soon Design
' + + '
' + language_str("vers") + ' ' + version + '
' + + '
' + language_str("copyright") + '
' + + '
' + }); + }); + + // ========================================== + // 点击事件 (修改后:完全匹配“保存”弹窗样式) + // ========================================== + $(".card-list").on("click", '.card', async function () { + let filePath = $(this).attr("data-file") || $(this).attr("data"); + // 再次检查文件是否存在 + const soonData = await window.sysAPI.readJsonFile(filePath); + if (soonData) { + // 1. 文件存在 -> 正常打开 + let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + const currentData = await window.sysAPI.readHistory(); + let newHistory = currentData.history.filter(item => item.path !== filePath); + newHistory.unshift({ path: filePath, time: Date.now(), type: type }); + await window.sysAPI.writeHistory({ history: newHistory }); + + if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type); + } else { + // 2. 文件不存在 -> 警告确认 + + // 【关键修改】 + // - 纯文字,左对齐 (text-align: left) + // - 适当的 padding + // - 去除所有图标 + let contentHtml = ` +
+ ${language_str("delContent")} +
+ `; + + // 使用 layer.open + layer.open({ + type: 1, + title: language_str("delTitle"), // 标题:警告 + content: contentHtml, + btn: [language_str("comfirm"), language_str("cancel")], + btnAlign: 'r', // 【关键修改】按钮右对齐 + area: ['300px', 'auto'], // 【关键修改】宽度设为 300px,标准小窗口 + resize: false, + shadeClose: true, + yes: async function(index){ + const currentData = await window.sysAPI.readHistory(); + const newHistory = currentData.history.filter(item => item.path !== filePath); + + if (newHistory.length !== currentData.history.length) { + await window.sysAPI.writeHistory({ history: newHistory }); + layer.msg(language_str("deleted"), { icon: 1, time: 1000 }); + loadHistory(); + } + layer.close(index); + } + }); + } + }); + + function OpenDialog() { + var dialogApi = (typeof dialog !== 'undefined' && dialog) ? dialog : (window.platformBridge && window.platformBridge.showOpenDialog ? { showOpenDialog: function(opts) { return window.platformBridge.showOpenDialog(opts); } } : null); + if (!dialogApi) return; + dialogApi.showOpenDialog({ + title: language_str("selectFile"), + buttonLabel: language_str("comfirm"), + filters: [ + { name: language_str("SDFile"), extensions: ['soon'] }, + ] + }).then(async function(result) { + if (result.canceled) return; + var filePath = result.filePaths && result.filePaths[0]; + var file = (result.files && result.files[0]) || result.file; + var soonData; + if (file && file.text) { + filePath = file.name || 'design.soon'; + soonData = await window.sysAPI.readJsonFile(file); + // 网页端:将内容存到 sessionStorage,设计页通过 sessionKey 读取 + if (soonData && typeof sessionStorage !== 'undefined' && window.platformBridge) { + try { + sessionStorage.setItem('soondesign_session:' + filePath, JSON.stringify(soonData)); + filePath = 'soondesign_session:' + filePath; + } catch (e) {} + } + } else if (filePath) { + soonData = await window.sysAPI.readJsonFile(filePath); + } + if (!soonData) { + if (!filePath && !file) return; + layer.msg(language_str("noFile")); + return; + } + var type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + var currentData = await window.sysAPI.readHistory(); + var newHistory = currentData.history.filter(function(item) { return item.path !== filePath; }); + newHistory.unshift({ path: filePath, time: Date.now(), type: type }); + await window.sysAPI.writeHistory({ history: newHistory }); + if (window.platformBridge && window.platformBridge.openDesignPage) window.platformBridge.openDesignPage(filePath, type); + else if (ipcRenderer && ipcRenderer.send) ipcRenderer.send('open-design-page', filePath, type); + loadHistory(); + }).catch(function(err) { console.log(err); }); + } }); \ No newline at end of file diff --git a/lib/platform/bridge.js b/lib/platform/bridge.js new file mode 100644 index 0000000..31a7bb7 --- /dev/null +++ b/lib/platform/bridge.js @@ -0,0 +1,44 @@ +/** + * 平台抽象层 - 统一接口定义 + * 桌面端由 lib/platform/electron.js 实现,网页端由 lib/platform/web.js 实现。 + * 业务代码通过 window.platformBridge 或兼容的 window.sysAPI / window.dialog 调用。 + */ +(function () { + 'use strict'; + + // 若已存在(由 electron 或 web 注入),则不覆盖 + if (typeof window.platformBridge !== 'undefined') { + return; + } + + // 默认空实现,避免未注入时报错(仅作占位,实际运行前必须注入 electron 或 web 实现) + function notImplemented() { + console.warn('[platform] bridge not injected'); + return Promise.reject(new Error('Platform bridge not available')); + } + + window.platformBridge = { + readHistory: notImplemented, + writeHistory: notImplemented, + readJsonFile: notImplemented, + showOpenDialog: notImplemented, + showSaveDialog: notImplemented, + writeFile: notImplemented, + readFile: notImplemented, + getAppVersion: function () { return Promise.resolve('0.0.0'); }, + getLocale: function () { return 'zh'; }, + getUserDataPath: function () { return ''; }, + getSystemFonts: function () { return Promise.resolve([]); }, + openDesignPage: function () {}, + openFirstPage: function () {}, + onClose: function () {}, + runClose: function () {}, + openHelp: function () {}, + printPdf: notImplemented, + getScaleRate: function () { return typeof window !== 'undefined' ? (window.devicePixelRatio || 1) : 1; }, + clipboard: { + readText: function () { return Promise.resolve(''); }, + writeText: function () { return Promise.resolve(); } + } + }; +})(); diff --git a/lib/platform/electron.js b/lib/platform/electron.js new file mode 100644 index 0000000..b95aa84 --- /dev/null +++ b/lib/platform/electron.js @@ -0,0 +1,145 @@ +/** + * 平台抽象层 - Electron 实现 + * 仅在存在 require 且可加载 electron 时使用;封装 IPC、dialog、fs、remote。 + */ +(function () { + 'use strict'; + + if (typeof require === 'undefined') { + return; + } + + var ipcRenderer, dialog, path, fs, remote, clipboard; + try { + ipcRenderer = require('electron').ipcRenderer; + dialog = require('@electron/remote').dialog; + path = require('path'); + fs = require('fs'); + remote = require('@electron/remote'); + clipboard = require('electron').clipboard; + } catch (e) { + console.warn('[platform] Electron modules not available', e); + return; + } + + var exePath = remote.app.getPath('userData'); + var historyFilePath = path.join(exePath, 'data.json'); + + var bridge = { + readHistory: function () { + return ipcRenderer.invoke('history:read'); + }, + writeHistory: function (data) { + return ipcRenderer.invoke('history:write', data); + }, + readJsonFile: function (filePath) { + if (!filePath) return Promise.resolve(null); + return ipcRenderer.invoke('file:read-json', filePath); + }, + showOpenDialog: function (options) { + return dialog.showOpenDialog(options).then(function (result) { + return { canceled: result.canceled, filePaths: result.filePaths }; + }); + }, + showSaveDialog: function (options) { + return dialog.showSaveDialog(options); + }, + writeFile: function (filePath, content) { + return new Promise(function (resolve, reject) { + var buf = Buffer.isBuffer(content) ? content : (typeof content === 'string' ? Buffer.from(content, 'utf8') : Buffer.from(content)); + fs.writeFile(filePath, buf, 'utf8', function (err) { + if (err) reject(err); + else resolve(); + }); + }); + }, + writeFileSync: function (filePath, content) { + fs.writeFileSync(filePath, content, 'utf8'); + }, + readFile: function (filePath) { + return new Promise(function (resolve, reject) { + fs.readFile(filePath, function (err, data) { + if (err) reject(err); + else resolve(data); + }); + }); + }, + readFileSync: function (filePath) { + return fs.readFileSync(filePath); + }, + existsSync: function (p) { + return fs.existsSync(p); + }, + getAppVersion: function () { + return ipcRenderer.invoke('app:get-version'); + }, + getLocale: function () { + return remote.app.getLocale(); + }, + getUserDataPath: function () { + return exePath; + }, + getSystemFonts: function () { + return new Promise(function (resolve, reject) { + ipcRenderer.once('font-list', function (ev, fonts) { + resolve(fonts || []); + }); + ipcRenderer.send('get-sys-fonts'); + setTimeout(function () { + resolve([]); + }, 10000); + }); + }, + openDesignPage: function (file, type) { + ipcRenderer.send('open-design-page', file || 'empty', type || '1'); + }, + openFirstPage: function () { + ipcRenderer.send('open-first-page'); + }, + onClose: function (callback) { + ipcRenderer.on('close', callback); + }, + runClose: function () { + ipcRenderer.send('run-close'); + }, + openHelp: function () { + ipcRenderer.send('open-help-file'); + }, + printPdf: function (urlOrBlob) { + ipcRenderer.send('print-pdf', urlOrBlob); + }, + getScaleRate: function () { + return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1; + }, + clipboard: { + readText: function () { + return Promise.resolve(clipboard.readText()); + }, + writeText: function (text) { + clipboard.writeText(text); + return Promise.resolve(); + } + } + }; + + window.platformBridge = bridge; + + // 兼容现有命名 + window.sysAPI = { + readHistory: bridge.readHistory, + writeHistory: bridge.writeHistory, + readJsonFile: bridge.readJsonFile, + getAppVersion: bridge.getAppVersion + }; + window.dialog = { + showOpenDialog: bridge.showOpenDialog, + showSaveDialog: bridge.showSaveDialog + }; + window.path = path; + window.fs = fs; + window.remote = remote; + window.ipcRenderer = ipcRenderer; + window.clipboard = clipboard; + window.exePath = exePath; + window.fullPath = historyFilePath; +})(); diff --git a/lib/platform/web.js b/lib/platform/web.js new file mode 100644 index 0000000..7bdb5dc --- /dev/null +++ b/lib/platform/web.js @@ -0,0 +1,341 @@ +/** + * 平台抽象层 - 网页实现 + * 使用 LocalStorage、File Picker、Blob 下载、navigator 等 Web API。 + */ +(function () { + 'use strict'; + + var HISTORY_KEY = 'soondesign_history'; + var VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '3.2.101'; + + /** 站点目录前缀(支持部署在子目录 / 宝塔子路径):去掉入口 HTML 文件名 */ + function getAppBasePathname() { + var p = (typeof window !== 'undefined' && window.location && window.location.pathname) ? window.location.pathname : '/'; + // Nginx/宝塔可能把页面写成 /design2.web.html/ ,末尾斜杠会导致匹配不到 .html,误判站点根为「目录 URL」→ 回首页 404 + while (p.length > 1 && p.endsWith('/')) { + p = p.slice(0, -1); + } + if (/\.web\.html$/i.test(p) || /\.html$/i.test(p)) { + p = p.replace(/[^/]+$/, ''); + } + if (!p.endsWith('/')) p += '/'; + return p; + } + + // API 相对站点根目录(与 api/README 中「相对网站根目录的 /api/」一致) + var SERVER_API_BASE = (typeof window !== 'undefined' && window.location) + ? (window.location.origin + getAppBasePathname() + 'api/') + : '/api/'; + + function navigateToPage(pathWithQuery) { + try { + location.href = new URL(pathWithQuery, window.location.href).href; + } catch (e) { + location.href = pathWithQuery; + } + } + + function getLocale() { + var lang = typeof navigator !== 'undefined' ? (navigator.language || navigator.browserLanguage || '') : ''; + if (lang.indexOf('zh') === 0) return lang.indexOf('TW') >= 0 ? 'ozh' : 'zh'; + return 'en'; + } + + function getSystemFonts() { + var list = [ + 'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia', + 'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong', + 'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB' + ]; + if (typeof document !== 'undefined' && document.fonts && document.fonts.forEach) { + var set = {}; + document.fonts.forEach(function (f) { + var name = (f.family || '').replace(/^["']|["']$/g, ''); + if (name) set[name] = 1; + }); + list = Object.keys(set).length ? Object.keys(set).sort() : list; + } + return Promise.resolve(list); + } + + var bridge = { + readHistory: function () { + try { + var raw = localStorage.getItem(HISTORY_KEY); + return Promise.resolve(raw ? JSON.parse(raw) : { history: [] }); + } catch (e) { + return Promise.resolve({ history: [] }); + } + }, + writeHistory: function (data) { + try { + localStorage.setItem(HISTORY_KEY, JSON.stringify(data)); + return Promise.resolve({ success: true }); + } catch (e) { + return Promise.resolve({ success: false, error: e.message }); + } + }, + readJsonFile: function (pathOrHandle) { + if (!pathOrHandle) return Promise.resolve(null); + if (typeof pathOrHandle === 'object' && pathOrHandle.text) { + return pathOrHandle.text().then(function (t) { + try { return JSON.parse(t); } catch (e) { return null; } + }); + } + var key = typeof pathOrHandle === 'string' ? pathOrHandle : ''; + if (key.indexOf('soondesign_session:') === 0) { + try { + var j = sessionStorage.getItem(key); + if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key); + return Promise.resolve(j ? JSON.parse(j) : null); + } catch (e) { return Promise.resolve(null); } + } + // 服务器文件:从服务器读取 + if (key && key.indexOf('.soon') > 0 && typeof fetch !== 'undefined') { + var isServerEnv = typeof window !== 'undefined' && window.location && + (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1' && window.location.protocol !== 'file:'); + if (isServerEnv) { + return fetch(SERVER_API_BASE + 'read_file.php?fileName=' + encodeURIComponent(key)) + .then(function(response) { + if (!response.ok) return null; + return response.json(); + }) + .catch(function() { return null; }); + } + } + return Promise.resolve(null); + }, + showOpenDialog: function (options) { + return new Promise(function (resolve) { + var input = document.createElement('input'); + input.type = 'file'; + // 根据 options.filters 动态设置 accept + if (options && options.filters && Array.isArray(options.filters) && options.filters.length > 0) { + var acceptList = []; + options.filters.forEach(function(filter) { + if (filter.extensions && Array.isArray(filter.extensions)) { + filter.extensions.forEach(function(ext) { + // 移除点号(如果有),然后添加点号前缀 + var cleanExt = ext.replace(/^\./, ''); + // 图片类型使用 image/* MIME 类型,其他使用扩展名 + if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'].indexOf(cleanExt.toLowerCase()) >= 0) { + var mimeType = 'image/' + (cleanExt.toLowerCase() === 'jpg' ? 'jpeg' : cleanExt.toLowerCase()); + if (acceptList.indexOf(mimeType) < 0) acceptList.push(mimeType); + } else { + var extWithDot = '.' + cleanExt; + if (acceptList.indexOf(extWithDot) < 0) acceptList.push(extWithDot); + } + }); + } + }); + input.accept = acceptList.length > 0 ? acceptList.join(',') : ''; + } else { + // 默认:.soon 文件 + input.accept = '.soon,application/json'; + } + input.style.display = 'none'; + input.onchange = function () { + var f = input.files && input.files[0]; + document.body.removeChild(input); + if (!f) resolve({ canceled: true }); + else resolve({ canceled: false, filePaths: [], files: [f], file: f }); + }; + document.body.appendChild(input); + input.click(); + }); + }, + showSaveDialog: function (options) { + var raw = (options && options.defaultPath) ? options.defaultPath : ''; + var defaultName = raw + ? (raw.indexOf('soondesign_session:') === 0 ? raw.substring('soondesign_session:'.length) : raw.split(/[/\\]/).pop()) + : 'design.soon'; + return new Promise(function (resolve) { + if (typeof window.showSaveFilePicker !== 'function') { + var name = typeof prompt === 'function' ? prompt('保存为文件名(如 xxx.soon)', defaultName) : defaultName; + if (name === null) { + resolve({ canceled: true }); + return; + } + var fp = (name && String(name).trim()) ? String(name).trim() : defaultName; + if (fp.indexOf('.soon') === -1 || fp.slice(-5).toLowerCase() !== '.soon') { + fp = (fp.replace(/\.soon$/i, '') || 'design') + '.soon'; + } + resolve({ canceled: false, filePath: fp, useDownload: true }); + return; + } + window.showSaveFilePicker({ + suggestedName: defaultName, + types: [{ description: 'SoonDesign', accept: { 'application/json': ['.soon'] } }] + }).then(function (handle) { + var filePath = (handle && handle.name) ? handle.name : defaultName; + resolve({ canceled: false, filePath: filePath, fileHandle: handle }); + }).catch(function () { + resolve({ canceled: true }); + }); + }); + }, + writeFile: function (pathOrHandle, content) { + var isBlob = content instanceof Blob; + var str = typeof content === 'string' ? content : (isBlob ? null : (content && content.toString ? content.toString() : '')); + // File System Access API(浏览器原生文件保存) + if (pathOrHandle && pathOrHandle.createWritable) { + return pathOrHandle.createWritable().then(function (w) { + if (isBlob) w.write(content); + else w.write(str); + return w.close(); + }); + } + // 服务器环境:上传到服务器 + var name = typeof pathOrHandle === 'string' ? pathOrHandle : (isBlob ? 'output.pdf' : 'design.soon'); + var isServerEnv = typeof window !== 'undefined' && window.location && + (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1' && window.location.protocol !== 'file:'); + + if (isServerEnv && !isBlob && typeof fetch !== 'undefined') { + // 上传到服务器 + var formData = new FormData(); + formData.append('fileName', name); + formData.append('fileContent', str); + + return fetch(SERVER_API_BASE + 'save_file.php', { + method: 'POST', + body: formData + }).then(function(response) { + return response.json(); + }).then(function(result) { + if (result.success) { + return Promise.resolve(); + } else { + throw new Error(result.error || '保存失败'); + } + }).catch(function(err) { + // 如果服务器保存失败,降级为下载 + console.warn('服务器保存失败,降级为下载:', err); + var a = document.createElement('a'); + a.download = name; + a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(str); + a.click(); + return Promise.resolve(); + }); + } + // 本地环境或 Blob:触发下载 + var a = document.createElement('a'); + a.download = name; + if (isBlob) a.href = URL.createObjectURL(content); + else a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(str); + a.click(); + if (isBlob && a.href) URL.revokeObjectURL(a.href); + return Promise.resolve(); + }, + readFile: function (pathOrHandle) { + if (pathOrHandle && pathOrHandle.getFile) { + return pathOrHandle.getFile().then(function (f) { + return new Promise(function (res, rej) { + var r = new FileReader(); + r.onload = function () { res(r.result); }; + r.onerror = rej; + r.readAsArrayBuffer(f); + }); + }); + } + if (pathOrHandle && pathOrHandle.arrayBuffer) { + return pathOrHandle.arrayBuffer(); + } + return Promise.reject(new Error('No file')); + }, + getAppVersion: function () { return Promise.resolve(VERSION); }, + getLocale: getLocale, + getUserDataPath: function () { return ''; }, + getSystemFonts: getSystemFonts, + openDesignPage: function (file, type) { + var t = type || 1; + if (file && typeof sessionStorage !== 'undefined') { + try { + sessionStorage.setItem('soondesign_open_file', file); + sessionStorage.setItem('soondesign_open_type', String(t)); + } catch (e) {} + } + var fileParam = file ? encodeURIComponent(file) : ''; + navigateToPage('design' + t + '.web.html?file=' + fileParam + '&type=' + t); + }, + openFirstPage: function () { + var loc = typeof window !== 'undefined' ? window.location : null; + if (!loc) return; + // 显式拼站点根(与 getAppBasePathname 一致),避免「路径末尾斜杠 / URL API」与 navigateToPage('.') 在个别环境下的解析差异 + loc.href = loc.origin + getAppBasePathname(); + }, + onClose: function (callback) { + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', callback); + } + }, + runClose: function () { + if (typeof window !== 'undefined' && window.close) window.close(); + }, + openHelp: function () { + try { + window.open(new URL('help/User Manual.pdf', window.location.href).href, '_blank'); + } catch (e) { + window.open('help/User Manual.pdf', '_blank'); + } + }, + printPdf: function (urlOrBlob) { + var url = urlOrBlob; + if (urlOrBlob && typeof urlOrBlob === 'object' && !(urlOrBlob instanceof String)) { + url = URL.createObjectURL(urlOrBlob); + } + var w = window.open(url, '_blank'); + if (w) w.onload = function () { w.print(); }; + }, + getScaleRate: function () { + return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1; + }, + clipboard: { + readText: function () { + return navigator.clipboard && navigator.clipboard.readText ? navigator.clipboard.readText() : Promise.resolve(''); + }, + writeText: function (text) { + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } + return Promise.resolve(); + } + } + }; + + window.platformBridge = bridge; + + var pathStub = { + join: function () { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); } + }; + + window.sysAPI = { + readHistory: bridge.readHistory, + writeHistory: bridge.writeHistory, + readJsonFile: bridge.readJsonFile, + getAppVersion: bridge.getAppVersion + }; + window.dialog = { + showOpenDialog: bridge.showOpenDialog, + showSaveDialog: bridge.showSaveDialog + }; + window.path = pathStub; + window.fs = null; + window.remote = null; + var loc = getLocale(); + window.ipcRenderer = { + send: function (ch, a1, a2) { + if (ch === 'get-sys-language') { window._sysLanPending = true; setTimeout(function () { if (window._sysLanCb && window._sysLanPending) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); } + if (ch === 'open-first-page' && bridge.openFirstPage) { bridge.openFirstPage(); } + if (ch === 'open-design-page' && bridge.openDesignPage && (a1 !== undefined || a2 !== undefined)) { bridge.openDesignPage(a1 || '', a2 || 1); } + if (ch === 'open-help-file' && bridge.openHelp) { bridge.openHelp(); } + if (ch === 'run-close' && bridge.runClose) { bridge.runClose(); } + }, + on: function (ch, cb) { + if (ch === 'sys-lan') { window._sysLanCb = cb; if (window._sysLanPending) setTimeout(function () { if (window._sysLanCb) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); } + if (ch === 'close') window._closeCb = cb; + } + }; + window.clipboard = bridge.clipboard; + window.exePath = ''; + window.fullPath = ''; +})(); diff --git a/package.json b/package.json index ad617e8..62bd944 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,19 @@ { "name": "soondesign", "version": "3.2.101", - "homepage": "https://www.cardsoon.com/index.php/cn/products/cardsoonsoft.html", - "author": "", - "description": "tools of design soon files", + "description": "SoonDesign - 专业的卡片和光盘设计工具,支持单面/双面模板设计、二维码/条形码生成、图片编辑等功能,提供桌面端(Electron)和网页端两种使用方式", "main": "main.js", + "homepage": "https://www.cardsoon.com/index.php/cn/products/cardsoonsoft.html", + "author": "CardSoon Team", + "license": "MIT", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, "scripts": { "start": "electron .", + "web": "npx http-server . -p 8080 -o index.web.html", + "web:dev": "npx http-server . -p 8080 -o index.web.html -c-1", "package": "electron-packager ./ SoonDesign --out ./OutApp --platform=win32 --arch=x64 --electron-version 15.1.1 --icon public/images/favicon.icns --asar --overwrite --download.mirrorOptions.mirror=https://npmmirror.com/mirrors/electron/", "dist": "webpack", "build": "electron-builder --arm64 --x64", @@ -79,15 +86,25 @@ ] } }, - "repository": "", + "repository": { + "type": "git", + "url": "" + }, + "bugs": { + "url": "" + }, "keywords": [ - "Electron", - "quick", - "start", - "tutorial", - "demo" + "soondesign", + "card-design", + "disc-design", + "design-tool", + "electron", + "fabricjs", + "qr-code", + "barcode", + "template-editor", + "cardsoon" ], - "license": "MIT", "devDependencies": { "app-builder-bin": "^4.2.0", "electron": "^15.2.0", diff --git a/public/js/JsBarcode.all.min.js b/public/js/JsBarcode.all.min.js new file mode 100644 index 0000000..bc710e1 --- /dev/null +++ b/public/js/JsBarcode.all.min.js @@ -0,0 +1,2 @@ +/*! JsBarcode v3.12.1 | (c) Johan Lindell | MIT license */ +!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=16)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});e.default=function t(e,n){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.data=e,this.text=n.text||e,this.options=n}},function(t,e,n){"use strict";var r;function o(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}Object.defineProperty(e,"__esModule",{value:!0});var i=e.SET_A=0,a=e.SET_B=1,u=e.SET_C=2,f=(e.SHIFT=98,e.START_A=103),c=e.START_B=104,s=e.START_C=105;e.MODULO=103,e.STOP=106,e.FNC1=207,e.SET_BY_CODE=(o(r={},f,i),o(r,c,a),o(r,s,u),r),e.SWAP={101:i,100:a,99:u},e.A_START_CHAR=String.fromCharCode(208),e.B_START_CHAR=String.fromCharCode(209),e.C_START_CHAR=String.fromCharCode(210),e.A_CHARS="[\0-_È-Ï]",e.B_CHARS="[ -È-Ï]",e.C_CHARS="(Ï*[0-9]{2}Ï*)",e.BARS=[11011001100,11001101100,11001100110,10010011e3,10010001100,10001001100,10011001e3,10011000100,10001100100,11001001e3,11001000100,11000100100,10110011100,10011011100,10011001110,10111001100,10011101100,10011100110,11001110010,11001011100,11001001110,11011100100,11001110100,11101101110,11101001100,11100101100,11100100110,11101100100,11100110100,11100110010,11011011e3,11011000110,11000110110,10100011e3,10001011e3,10001000110,10110001e3,10001101e3,10001100010,11010001e3,11000101e3,11000100010,10110111e3,10110001110,10001101110,10111011e3,10111000110,10001110110,11101110110,11010001110,11000101110,11011101e3,11011100010,11011101110,11101011e3,11101000110,11100010110,11101101e3,11101100010,11100011010,11101111010,11001000010,11110001010,1010011e4,10100001100,1001011e4,10010000110,10000101100,10000100110,1011001e4,10110000100,1001101e4,10011000010,10000110100,10000110010,11000010010,1100101e4,11110111010,11000010100,10001111010,10100111100,10010111100,10010011110,10111100100,10011110100,10011110010,11110100100,11110010100,11110010010,11011011110,11011110110,11110110110,10101111e3,10100011110,10001011110,10111101e3,10111100010,11110101e3,11110100010,10111011110,10111101110,11101011110,11110101110,11010000100,1101001e4,11010011100,1100011101011]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});e.SIDE_BIN="101",e.MIDDLE_BIN="01010",e.BINARIES={L:["0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"],G:["0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"],R:["1110010","1100110","1101100","1000010","1011100","1001110","1010000","1000100","1001000","1110100"],O:["0001101","0011001","0010011","0111101","0100011","0110001","0101111","0111011","0110111","0001011"],E:["0100111","0110011","0011011","0100001","0011101","0111001","0000101","0010001","0001001","0010111"]},e.EAN2_STRUCTURE=["LL","LG","GL","GG"],e.EAN5_STRUCTURE=["GGLLL","GLGLL","GLLGL","GLLLG","LGGLL","LLGGL","LLLGG","LGLGL","LGLLG","LLGLG"],e.EAN13_STRUCTURE=["LLLLLL","LLGLGG","LLGGLG","LLGGGL","LGLLGG","LGGLLG","LGGGLL","LGLGLG","LGLGGL","LGGLGL"]},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(2);e.default=function(t,e,n){var o=t.split("").map((function(t,n){return r.BINARIES[e[n]]})).map((function(e,n){return e?e[t[n]]:""}));if(n){var i=t.length-1;o=o.map((function(t,e){return e=200){i=t.shift()-105;var a=u.SWAP[i];void 0!==a?o=e.next(t,n+1,a):(r!==u.SET_A&&r!==u.SET_B||i!==u.SHIFT||(t[0]=r===u.SET_A?t[0]>95?t[0]-96:t[0]:t[0]<32?t[0]+96:t[0]),o=e.next(t,n+1,r))}else i=e.correctIndex(t,r),o=e.next(t,n+1,r);var f=i*n;return{result:e.getBar(i)+o.result,checksum:f+o.checksum}}}]),e}(a.default);e.default=f},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.mod10=function(t){for(var e=0,n=0;n10*n.width?10*n.width:n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),r(e,[{key:"encode",value:function(){return this.options.flat?this.encodeFlat():this.encodeGuarded()}},{key:"leftText",value:function(t,e){return this.text.substr(t,e)}},{key:"leftEncode",value:function(t,e){return(0,i.default)(t,e)}},{key:"rightText",value:function(t,e){return this.text.substr(t,e)}},{key:"rightEncode",value:function(t,e){return(0,i.default)(t,e)}},{key:"encodeGuarded",value:function(){var t={fontSize:this.fontSize},e={height:this.guardHeight};return[{data:o.SIDE_BIN,options:e},{data:this.leftEncode(),text:this.leftText(),options:t},{data:o.MIDDLE_BIN,options:e},{data:this.rightEncode(),text:this.rightText(),options:t},{data:o.SIDE_BIN,options:e}]}},{key:"encodeFlat",value:function(){return{data:[o.SIDE_BIN,this.leftEncode(),o.MIDDLE_BIN,this.rightEncode(),o.SIDE_BIN].join(""),text:this.text}}}]),e}(a(n(0)).default);e.default=u},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n10*n.width?r.fontSize=10*n.width:r.fontSize=n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),r(e,[{key:"valid",value:function(){return-1!==this.data.search(/^[0-9]{12}$/)&&this.data[11]==u(this.data)}},{key:"encode",value:function(){return this.options.flat?this.flatEncoding():this.guardedEncoding()}},{key:"flatEncoding",value:function(){var t="";return t+="101",t+=(0,o.default)(this.data.substr(0,6),"LLLLLL"),t+="01010",t+=(0,o.default)(this.data.substr(6,6),"RRRRRR"),{data:t+="101",text:this.text}}},{key:"guardedEncoding",value:function(){var t=[];return this.displayValue&&t.push({data:"00000000",text:this.text.substr(0,1),options:{textAlign:"left",fontSize:this.fontSize}}),t.push({data:"101"+(0,o.default)(this.data[0],"L"),options:{height:this.guardHeight}}),t.push({data:(0,o.default)(this.data.substr(1,5),"LLLLL"),text:this.text.substr(1,5),options:{fontSize:this.fontSize}}),t.push({data:"01010",options:{height:this.guardHeight}}),t.push({data:(0,o.default)(this.data.substr(6,5),"RRRRR"),text:this.text.substr(6,5),options:{fontSize:this.fontSize}}),t.push({data:(0,o.default)(this.data[11],"R")+"101",options:{height:this.guardHeight}}),this.displayValue&&t.push({data:"00000000",text:this.text.substr(11,1),options:{textAlign:"right",fontSize:this.fontSize}}),t}}]),e}(i(n(0)).default);function u(t){var e,n=0;for(e=1;e<11;e+=2)n+=parseInt(t[e]);for(e=0;e<11;e+=2)n+=3*parseInt(t[e]);return(10-n%10)%10}e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0?e.fontSize+e.textMargin:0)+e.marginTop+e.marginBottom}function u(t,e,n){if(n.displayValue&&ee&&(e=t[n].height);return e},e.getEncodingHeight=a,e.getBarcodePadding=u,e.calculateEncodingAttributes=function(t,e,n){for(var r=0;r=i(t);return e+String.fromCharCode(r?206:205)+u(t,r)}e.default=function(t){var e=void 0;if(a(t).length>=2)e=r.C_START_CHAR+f(t);else{var n=o(t)>i(t);e=(n?r.A_START_CHAR:r.B_START_CHAR)+u(t,n)}return e.replace(/[\xCD\xCE]([^])[\xCD\xCE]/,(function(t,e){return String.fromCharCode(203)+e}))}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n10*n.width?r.fontSize=10*n.width:r.fontSize=n.fontSize,r.guardHeight=n.height+r.fontSize/2+n.textMargin,r}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(e,t),r(e,[{key:"valid",value:function(){return this.isValid}},{key:"encode",value:function(){return this.options.flat?this.flatEncoding():this.guardedEncoding()}},{key:"flatEncoding",value:function(){var t="";return t+="101",t+=this.encodeMiddleDigits(),{data:t+="010101",text:this.text}}},{key:"guardedEncoding",value:function(){var t=[];return this.displayValue&&t.push({data:"00000000",text:this.text[0],options:{textAlign:"left",fontSize:this.fontSize}}),t.push({data:"101",options:{height:this.guardHeight}}),t.push({data:this.encodeMiddleDigits(),text:this.text.substring(1,7),options:{fontSize:this.fontSize}}),t.push({data:"010101",options:{height:this.guardHeight}}),this.displayValue&&t.push({data:"00000000",text:this.text[7],options:{textAlign:"right",fontSize:this.fontSize}}),t}},{key:"encodeMiddleDigits",value:function(){var t=this.upcA[0],e=this.upcA[this.upcA.length-1],n=s[parseInt(e)][parseInt(t)];return(0,o.default)(this.middleDigits,n)}}]),e}(i.default);function p(t,e){for(var n=parseInt(t[t.length-1]),r=c[n],o="",i=0,u=0;u=3&&this.number<=131070}}]),e}(((r=i)&&r.__esModule?r:{default:r}).default);e.pharmacode=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.codabar=void 0;var r,o=function(){function t(t,e){for(var n=0;n":["(%)","I"],"?":["(%)","J"],"@":["(%)","V"],"[":["(%)","K"],"\\":["(%)","L"],"]":["(%)","M"],"^":["(%)","N"],_:["(%)","O"],"`":["(%)","W"],a:["(+)","A"],b:["(+)","B"],c:["(+)","C"],d:["(+)","D"],e:["(+)","E"],f:["(+)","F"],g:["(+)","G"],h:["(+)","H"],i:["(+)","I"],j:["(+)","J"],k:["(+)","K"],l:["(+)","L"],m:["(+)","M"],n:["(+)","N"],o:["(+)","O"],p:["(+)","P"],q:["(+)","Q"],r:["(+)","R"],s:["(+)","S"],t:["(+)","T"],u:["(+)","U"],v:["(+)","V"],w:["(+)","W"],x:["(+)","X"],y:["(+)","Y"],z:["(+)","Z"],"{":["(%)","P"],"|":["(%)","Q"],"}":["(%)","R"],"~":["(%)","S"],"":["(%)","T"]}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0?(n=0,o.textAlign="left"):"right"==t.textAlign?(n=e.width-1,o.textAlign="right"):(n=e.width/2,o.textAlign="center"),o.fillText(e.text,n,r))}},{key:"moveCanvasDrawing",value:function(t){this.canvas.getContext("2d").translate(t.width,0)}},{key:"restoreCanvas",value:function(){this.canvas.getContext("2d").restore()}}]),t}();e.default=f},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r,o=function(){function t(t,e){for(var n=0;n0&&(this.drawRect(a-e.width*i,r,e.width*i,e.height,t),i=0);i>0&&this.drawRect(a-e.width*(i-1),r,e.width*i,e.height,t)}},{key:"drawSVGText",value:function(t,e,n){var r,o,i=this.document.createElementNS(f,"text");e.displayValue&&(i.setAttribute("style","font:"+e.fontOptions+" "+e.fontSize+"px "+e.font),o="top"==e.textPosition?e.fontSize-e.textMargin:e.height+e.textMargin+e.fontSize,"left"==e.textAlign||n.barcodePadding>0?(r=0,i.setAttribute("text-anchor","start")):"right"==e.textAlign?(r=n.width-1,i.setAttribute("text-anchor","end")):(r=n.width/2,i.setAttribute("text-anchor","middle")),i.setAttribute("x",r),i.setAttribute("y",o),i.appendChild(this.document.createTextNode(n.text)),t.appendChild(i))}},{key:"setSvgAttributes",value:function(t,e){var n=this.svg;n.setAttribute("width",t+"px"),n.setAttribute("height",e+"px"),n.setAttribute("x","0px"),n.setAttribute("y","0px"),n.setAttribute("viewBox","0 0 "+t+" "+e),n.setAttribute("xmlns",f),n.setAttribute("version","1.1"),n.setAttribute("style","transform: translate(0,0)")}},{key:"createGroup",value:function(t,e,n){var r=this.document.createElementNS(f,"g");return r.setAttribute("transform","translate("+t+", "+e+")"),n.appendChild(r),r}},{key:"setGroupOptions",value:function(t,e){t.setAttribute("style","fill:"+e.lineColor+";")}},{key:"drawRect",value:function(t,e,n,r,o){var i=this.document.createElementNS(f,"rect");return i.setAttribute("x",t),i.setAttribute("y",e),i.setAttribute("width",n),i.setAttribute("height",r),o.appendChild(i),i}}]),t}();e.default=c},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n=1&&n<=127?e+=t.charAt(r):n>2047?(e+=String.fromCharCode(224|n>>12&15),e+=String.fromCharCode(128|n>>6&63),e+=String.fromCharCode(128|n>>0&63)):(e+=String.fromCharCode(192|n>>6&31),e+=String.fromCharCode(128|n>>0&63));return e},t.utf8to16=function(t){var e,r,o,n,i,a;for(e="",o=t.length,r=0;r>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:e+=t.charAt(r-1);break;case 12:case 13:i=t.charCodeAt(r++),e+=String.fromCharCode((31&n)<<6|63&i);break;case 14:i=t.charCodeAt(r++),a=t.charCodeAt(r++),e+=String.fromCharCode((15&n)<<12|(63&i)<<6|(63&a)<<0)}return e},t}()},function(t,e,r){"use strict";r(1),r(4);var o=r(2),n=function(){function t(t){var e=new QRCode(t.typeNumber,t.correctLevel);e.addData(t.text),e.make();var r=document.createElement("canvas");r.width=t.width,r.height=t.height;var o=r.getContext("2d"),n=(t.width-2*t.padding)/e.getModuleCount(),i=(t.height-2*t.padding)/e.getModuleCount();if(t.reverse){var a="rgba(0, 0, 0, 0)";o.fillStyle=a,t.foreground=a}else o.fillStyle=t.background;o.fillRect(0,0,r.width,r.height);for(var s=0;s=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,e)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4?this.modules[t+r][e+o]=!0:this.modules[t+r][e+o]=!1)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=f.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r),n=1;this.make();for(var i=0;i>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(var r=0;r<18;r++){var o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=f.getBCHTypeInfo(r),n=0;n<15;n++){var i=!t&&1==(o>>n&1);n<6?this.modules[n][8]=i:n<8?this.modules[n+1][8]=i:this.modules[this.moduleCount-15+n][8]=i}for(var n=0;n<15;n++){var i=!t&&1==(o>>n&1);n<8?this.modules[8][this.moduleCount-n-1]=i:n<9?this.modules[8][15-n-1+1]=i:this.modules[8][15-n-1]=i}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,n=7,i=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var u=!1;i>>n&1));var h=f.getMask(e,o,a-s);h&&(u=!u),this.modules[o][a-s]=u,n--,n==-1&&(i++,n=7)}if(o+=r,o<0||this.moduleCount<=o){o-=r,r=-r;break}}}},o.PAD0=236,o.PAD1=17,o.createData=function(t,e,r){for(var n=i.getRSBlocks(t,e),s=new a,u=0;u8*l)throw new Error("code length overflow. ("+s.getLengthInBits()+">"+8*l+")");for(s.getLengthInBits()+4<=8*l&&s.put(0,4);s.getLengthInBits()%8!=0;)s.putBit(!1);for(;;){if(s.getLengthInBits()>=8*l)break;if(s.put(o.PAD0,8),s.getLengthInBits()>=8*l)break;s.put(o.PAD1,8)}return o.createBytes(s,n)},o.createBytes=function(t,e){for(var r=0,o=0,i=0,a=new Array(e.length),s=new Array(e.length),u=0;u=0?v.get(m):0}}for(var p=0,g=0;g=0;)e^=f.G15<=0;)e^=f.G18<>>=1;return e},getPatternPosition:function(t){return f.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case h.PATTERN000:return(e+r)%2==0;case h.PATTERN001:return e%2==0;case h.PATTERN010:return r%3==0;case h.PATTERN011:return(e+r)%3==0;case h.PATTERN100:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case h.PATTERN101:return e*r%2+e*r%3==0;case h.PATTERN110:return(e*r%2+e*r%3)%2==0;case h.PATTERN111:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new n([1],0),r=0;r5&&(r+=3+i-5)}for(var o=0;o=256;)t-=255;return l.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},g=0;g<8;g++)l.EXP_TABLE[g]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}},window.QRCode=o,window.QRErrorCorrectLevel=u;try{t.exports={QRCode:o,QRErrorCorrectLevel:u}}catch(t){}}])}); \ No newline at end of file