网页端:平台桥与部署修复(宝塔/Nginx)
- 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 <cursoragent@cursor.com>
This commit is contained in:
@@ -8,3 +8,6 @@ npm-debug.log.*
|
||||
thumbs.db
|
||||
!.gitkeep
|
||||
.backup/
|
||||
# 服务器文件存储目录(用户上传的文件)
|
||||
api/files/
|
||||
design/files/
|
||||
@@ -1,26 +1,123 @@
|
||||
# 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
|
||||
# 1. 安装依赖(只需一次)
|
||||
# 使用 yarn(推荐)
|
||||
yarn install
|
||||
|
||||
# 2. 启动程序
|
||||
yarn start
|
||||
# 或使用 npm
|
||||
npm install
|
||||
```
|
||||
|
||||
> **前置要求**:需要安装 [Node.js](https://nodejs.org/)(推荐 16 或更高版本)
|
||||
### 启动应用
|
||||
|
||||
```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)
|
||||
|
||||
---
|
||||
|
||||
## 📦 发布流程(傻瓜式操作)
|
||||
## 📦 构建和发布
|
||||
|
||||
### 方式一:一键发布 ⭐ 推荐
|
||||
|
||||
@@ -38,8 +135,6 @@ yarn release:linux
|
||||
✅ **自动完成**:加密 → 测试 → 清理 → 打包
|
||||
✅ **安装包位置**:`build` 文件夹
|
||||
|
||||
---
|
||||
|
||||
### 方式二:分步操作(手动控制)
|
||||
|
||||
#### 步骤 1:加密代码
|
||||
@@ -81,21 +176,30 @@ yarn build:linux # Linux
|
||||
|
||||
## 🔧 其他常用命令
|
||||
|
||||
### 恢复源文件
|
||||
### 开发相关
|
||||
|
||||
```bash
|
||||
yarn restore
|
||||
# 启动开发模式
|
||||
yarn start
|
||||
|
||||
# 构建 Web 资源
|
||||
yarn dist
|
||||
|
||||
# 使用 electron-packager 打包(旧方式)
|
||||
yarn package
|
||||
```
|
||||
|
||||
> 从 `.backup` 文件夹恢复所有源文件(误删除时使用)
|
||||
|
||||
### 清理加密文件
|
||||
### 文件管理
|
||||
|
||||
```bash
|
||||
# 恢复源文件
|
||||
yarn restore
|
||||
|
||||
# 清理加密文件
|
||||
yarn cleanup:jsc
|
||||
```
|
||||
|
||||
> 删除所有 `.jsc` 文件(重新加密前使用)
|
||||
> 从 `.backup` 文件夹恢复所有源文件(误删除时使用)
|
||||
|
||||
### 修改文件名映射
|
||||
|
||||
@@ -117,44 +221,141 @@ yarn cleanup:jsc
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- **开发时**:保留 `.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/ - 源文件备份(不会被打包)
|
||||
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` 打开开发者工具查看错误信息。
|
||||
### 桌面端
|
||||
|
||||
按 `F12` 或 `Ctrl+Shift+I`(Mac: `Cmd+Option+I`)打开开发者工具查看错误信息。
|
||||
|
||||
### 网页端
|
||||
|
||||
同样使用浏览器开发者工具(F12),查看 Console 面板的日志和错误信息。
|
||||
|
||||
---
|
||||
|
||||
## 📚 更多文档
|
||||
|
||||
- [加密详细说明](scripts/encrypt/README.md)
|
||||
- 当前版本:v2.3.017
|
||||
- [网页端功能清单](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
|
||||
|
||||
@@ -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 配置是否正确指向文件目录
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* SoonDesign 网页端文件读取接口
|
||||
* 从服务器读取 .soon 文件
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
// 处理 OPTIONS 预检请求
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 只接受 GET 请求
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => 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()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* SoonDesign 网页端文件保存接口
|
||||
* 接收 POST 请求,保存 .soon 文件到服务器
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
// 处理 OPTIONS 预检请求
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 只接受 POST 请求
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => 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()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
+1
-3
@@ -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);
|
||||
|
||||
@@ -0,0 +1,878 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Soon Design</title>
|
||||
<link rel="stylesheet" href="./layui/css/layui.css">
|
||||
<link rel="stylesheet" href="./public/css/design.css">
|
||||
<style>
|
||||
/* ==================================================
|
||||
1. 全局布局 (Flex 自适应:完美兼容 Windows/Mac)
|
||||
================================================== */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
.layui-fluid,
|
||||
.layui-row {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #1E1E1E;
|
||||
/* 画布底色保持深黑 */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* --- 左侧主区域 --- */
|
||||
.col-left {
|
||||
height: 100%;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid #1E1E1E;
|
||||
/* 分隔线 */
|
||||
}
|
||||
|
||||
/* --- 右侧属性栏 (修复:恢复灰色背景) --- */
|
||||
.col-right {
|
||||
background-color: #31373D;
|
||||
/* 【颜色修复】 */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- 顶部工具栏 (修复:恢复灰色背景) --- */
|
||||
.tools-bar {
|
||||
background-color: #31373D;
|
||||
/* 【颜色修复】 */
|
||||
flex-shrink: 0;
|
||||
/* 保持你原有的高度或内边距设定,如果有的话 */
|
||||
}
|
||||
|
||||
/* --- 修复切换按钮被挤压换行的问题 --- */
|
||||
#version_change {
|
||||
white-space: nowrap;
|
||||
/* 【按钮修复】禁止文字换行 */
|
||||
flex-shrink: 0;
|
||||
/* 【按钮修复】禁止被压缩 */
|
||||
min-width: 60px;
|
||||
/* 给一个最小宽度 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 确保所有顶部图标容器不被压缩 */
|
||||
.img-warp {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* --- 中间主要容器 --- */
|
||||
.container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 左侧工具箱 */
|
||||
.container .left {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
background-color: #31373D;
|
||||
/* 保持灰色 */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.container .left::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 右侧画布区域 */
|
||||
.container .right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: #1E252D;
|
||||
/* 画布背景深色 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 画布容器 */
|
||||
#canvas-div {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* 画布滚动包裹层 */
|
||||
.canvas-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 底部状态栏 (X: Y:) */
|
||||
.footer {
|
||||
height: 30px;
|
||||
background-color: #31373D;
|
||||
color: #ccc;
|
||||
padding-left: 10px;
|
||||
flex-shrink: 0;
|
||||
z-index: 100;
|
||||
border-top: 1px solid #1E1E1E;
|
||||
|
||||
/* 2. 使用 Flex 布局来控制文字位置 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* 让文字垂直居中 */
|
||||
|
||||
/* 3. 【核心修复】增加底部内边距,把文字往上顶 */
|
||||
/* 这样文字距离窗口底部就有 10px 的安全距离,不会被切掉 */
|
||||
padding-bottom: 5px;
|
||||
|
||||
/* 4. 兼容性设置 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ==================================================
|
||||
2. 对象列表样式 (Flex 修复:序号靠右,不遮挡)
|
||||
================================================== */
|
||||
.obj_list {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* 这里的背景色也跟随父容器,或者设为透明 */
|
||||
background-color: #31373D;
|
||||
}
|
||||
|
||||
.layer-row {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #444;
|
||||
/* 线条稍微深一点 */
|
||||
background-color: transparent;
|
||||
/* 默认透明,显示底色 */
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layer-row:hover {
|
||||
background-color: #4A525A;
|
||||
/* 悬停颜色稍微亮一点 */
|
||||
}
|
||||
|
||||
.layer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.layer-index {
|
||||
min-width: 24px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
border-left: 1px solid #444;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
/* ==================================================
|
||||
3. 标尺与其他基础样式 (保留)
|
||||
================================================== */
|
||||
|
||||
/* 1. 保留:背景图样式 (不动) */
|
||||
#source_front,
|
||||
#source_front2,
|
||||
#source_back {
|
||||
width: 2787px;
|
||||
height: 2787px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ruler-container {
|
||||
position: relative;
|
||||
background-color: #2C2C2C;
|
||||
border: 1px solid #1E1E1E;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 2. 替换:标尺样式 (改为更明显的颜色 + 增加鼠标手势) */
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
/* 改为更明显的颜色,不透明度提高到 0.9 */
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
/* 鼠标移上去显示上下调整箭头,提示可拖拽 */
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
/* 改为更明显的颜色,不透明度提高到 0.9 */
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
/* 鼠标移上去显示左右调整箭头 */
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.ruler-corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
/* 3. 新增:拖拽过程中的辅助线样式 */
|
||||
.guide-line-drag {
|
||||
position: absolute;
|
||||
background-color: #0066FF;
|
||||
/* 深蓝色高亮 */
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
/* 让鼠标穿透,不影响拖拽事件 */
|
||||
box-shadow: 0 0 6px #0066FF;
|
||||
/* 增强发光效果 */
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="layui-fluid main" style="display: none">
|
||||
<div class="layui-row" style="height: 100%;width: 100%">
|
||||
<div class="col-right">
|
||||
<div class="tools-bar right-bar">
|
||||
|
||||
<div class="mode2">
|
||||
<div class="tool">
|
||||
<img src="public/images/top24.png" id="top_alignment" language="t" zh="顶部对齐" ozh="頂部對齊"
|
||||
en="Top Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/bottom24.png" id="bottom_alignment" language="t" zh="底部对齐"
|
||||
ozh="底部對齊" en="Bottom Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_y24.png" id="center_y_alignment" language="t" zh="居中对齐"
|
||||
ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode2" style="margin-left: 2px;border-right: 0px">
|
||||
<div class="tool">
|
||||
<img src="public/images/left24.png" id="left_alignment" language="t" zh="左对齐" ozh="左對齊"
|
||||
en="Left Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/right24.png" id="right_alignment" language="t" zh="右对齐" ozh="右對齊"
|
||||
en="Right Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_x24.png" id="center_x_alignment" language="t" zh="居中对齐"
|
||||
ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="object_attribute">
|
||||
<div class="component-title" zh="对象属性" ozh="對象屬性" en="Object Properties" language="m"></div>
|
||||
<div>
|
||||
<div class="component-line">
|
||||
<span class="name" zh="类型:" ozh="類型:" en="Type:" language="m"></span>
|
||||
<span id="component_type" zh="背景" ozh="背景" en="Background" language="m"></span>
|
||||
</div>
|
||||
<div id="base_control">
|
||||
<div class="component-line">
|
||||
<div class="ui-button" id="up_obj" language="m" zh="移至上层" ozh="移至上層" en="upper layer">
|
||||
</div>
|
||||
<div class="ui-button" id="down_obj" language="m" zh="移至下层" ozh="移至下層" en="upper layer">
|
||||
</div>
|
||||
<div class="pic_bg" id="set_bg" language="m" zh="设为背景" ozh="設爲背景"
|
||||
en="Set as background">
|
||||
</div>
|
||||
<!-- <div class="pic_bg" id="vertical_obj" language="m" zh="垂直旋转" ozh="垂直旋轉" en="vertical rotation">
|
||||
</div>
|
||||
<div class="pic_bg" id="align_obj" language="m" zh="水平旋转" ozh="水平旋轉" en="vertical rotation">
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name">X:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_x" />
|
||||
|
||||
<span class="name">Y:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_y" />
|
||||
<div style="height: 20px;width: 20px;display: inline-block;" class="ui-radio" id="lock">
|
||||
<img src="public/images/unlock20.png" id="lock_img">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name">H:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_h" />
|
||||
<span class="name">W:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_w" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="角度:" ozh="角度:" en="Angle"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_r" />
|
||||
<img src="public/images/rotate20.png" language="t" zh="旋转90°" ozh="旋轉90°"
|
||||
en="Rotate 90°" id="rotate" />
|
||||
</div>
|
||||
<!-- <div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="text_black" id="text_black" class="radio">
|
||||
</div> -->
|
||||
</div>
|
||||
<div id="pic_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="pic_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="pic_black" id="pic_black" class="radio">
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div id="out_pic_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="out_pic_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="out_pic_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="out_pic_black" id="out_pic_black" class="radio">
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div id="text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" style="position: relative;top:-45px" language="m" zh="文本:" ozh="文本:"
|
||||
en="Text:">文本:</span>
|
||||
<textarea rows="3" style="height: 60px" id="text_text"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Fonts:"></span>
|
||||
<select class="ui-select" id="text_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="600" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="text_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="text_color" class="input_color">
|
||||
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_underline">
|
||||
<img src="public/images/underline24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_linethrough">
|
||||
<img src="public/images/linethrough24.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="text_back_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="circle_text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" style="position: relative;top:-45px" language="m" zh="圆形文本:"
|
||||
ozh="圆形文本:" en="Circle Text:">圆形文本:</span>
|
||||
<textarea rows="3" style="height: 60px" id="circle_text_text"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Fonts:"></span>
|
||||
<select class="ui-select" id="circle_text_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="中心辅助调整圆弧:" ozh="中心輔助調整圓弧:"
|
||||
en="Adjust the arc using the center:"></span>
|
||||
<input type="checkbox" name="circle_text_center" id="circle_text_center" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<div id="circle_text_size"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="直径:" ozh="直径:" en="Diameter:"></span>
|
||||
<div id="circle_text_diameter"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="circle_text_color" class="input_color">
|
||||
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="circle_text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="circle_text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="circle_text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="circle_text_back_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div id="out_text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="out_text_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="out_text_text" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="out_text_font_family">
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="600" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="out_text_size" />
|
||||
</div>
|
||||
<div class="component-line"
|
||||
style="margin-left: 2px;display: flex; align-items: center;gap: 10px;">
|
||||
<span class="name" language="m" zh="对齐方式:" ozh="對齊方式:" en="Alignment:"></span>
|
||||
<div class="mode2"
|
||||
style="margin-left: 2px;border: none;display: flex; align-items: center;gap: 10px;">
|
||||
<div class="tool">
|
||||
<img src="public/images/left24.png" id="out_left_alignment" language="t"
|
||||
zh="左对齐" ozh="左對齊" en="Left Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/right24.png" id="out_right_alignment" language="t"
|
||||
zh="右对齐" ozh="右對齊" en="Right Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_x24.png" id="out_center_alignment" language="t"
|
||||
zh="居中对齐" ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="out_text_color" class="input_color">
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_underline">
|
||||
<img src="public/images/underline24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_linethrough">
|
||||
<img src="public/images/linethrough24.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="out_text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="out_text_back_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="如果文本被截断,则作业失败" ozh="如果文本被截斷,則作業失敗"
|
||||
en="If the text is truncated, the operation fails"></span>
|
||||
<input type="checkbox" name="exceed" id="exceed" class="radio">
|
||||
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="自动换行:" ozh="自動換行:" en="Automatic line feed:"></span>
|
||||
<input type="checkbox" name="autoWrap" id="autoWrap" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="固定大小:" ozh="固定大小:" en="Fixed Size:"></span>
|
||||
<input type="checkbox" name="fixSize" id="fixSize" class="radio">
|
||||
</div>
|
||||
</div>
|
||||
<div id="rect_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="填充:" ozh="填充:" en="Fill:"></span>
|
||||
<input type="color" id="rect_color" class="input_color">
|
||||
<div class="transparent-button" id="rect_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="边框:" ozh="邊框:" en="Border:"></span>
|
||||
<input type="color" id="rect_stroke" class="input_color">
|
||||
<div class="transparent-button" id="rect_stroke_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="粗细:" ozh="粗細:" en="Thickness:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px"
|
||||
id="rect_stroke_width" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="圆角:" ozh="圓角:" en="Radius:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px" id="rect_radius" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="rect_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="line_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="line_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="虚线间距:" ozh="虛線間距:" en="Spacing:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="line_dist" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="circle_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="填充:" ozh="填充:" en="Fill:"></span>
|
||||
<input type="color" id="circle_color" class="input_color">
|
||||
<div class="transparent-button" id="circle_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="边框:" ozh="邊框:" en="Border:"></span>
|
||||
<input type="color" id="circle_stroke" class="input_color">
|
||||
<div class="transparent-button" id="circle_stroke_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="粗细:" ozh="粗細:" en="Thickness:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px"
|
||||
id="circle_stroke_width" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="circle_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="qrcode_Controll" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="qrcode_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="qrcode_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="qrcode_val" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="barcode_controll" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="barcode_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="barcode_val" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="取消文字:" ozh="取消文字:" en="Cancel text:"></span>
|
||||
<input type="checkbox" name="barcode_show" id="barcode_show" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="barcode_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="600" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="barcode_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="barcode_color" class="input_color">
|
||||
</div>
|
||||
</div>
|
||||
<div id="counter_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="count_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="600" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="count_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="counter_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="起始:" ozh="起始:" en="Start:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="counter_start" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="增量:" ozh="增量:" en="Incremental:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="counter_add" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="obj_list">
|
||||
<div class="component-title" language="m" zh="对象列表" ozh="對象列錶" en="Object List"></div>
|
||||
<div class="obj_list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-left" style="height: 100%">
|
||||
<div class="tools-bar left-bar">
|
||||
<div class="mode-left">
|
||||
<div class="mode-warp">
|
||||
<div class="img-warp" id="new">
|
||||
<img src="public/images/newfile24.png" language="t" zh="新建模板 CTRL+N" ozh="新建模闆"
|
||||
en="New template" />
|
||||
</div>
|
||||
<div class="img-warp" id="open">
|
||||
<img src="public/images/open24.png" language="t" zh="打开模板 CTRL+O" ozh="打開模闆 CTRL+O"
|
||||
en="Open Template CTRL+O" />
|
||||
</div>
|
||||
|
||||
<div class="img-warp" id="save">
|
||||
<img src="public/images/save24.png" language="t" zh="保存模板 CTRL+S" ozh="保存模闆 CTRL+S"
|
||||
en="Save Template CTRL+S" />
|
||||
</div>
|
||||
<div class="img-warp" id="output">
|
||||
<img src="public/images/output24.png" language="t" zh="另存为 CTRL+SHIFT+S"
|
||||
ozh="另存為 CTRL+SHIFT+S" en="Save as CTRL+SHIFT+S" />
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="img-warp">
|
||||
<img src="public/images/previous24.png" id="previous" language="t" zh="撤销 CTRL+Z"
|
||||
ozh="撤銷 CTRL+Z" en="Revocation CTRL+Z" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/next24.png" id="next" language="t" zh="恢复 CTRL+Y"
|
||||
ozh="恢復 CTRL+Y" en="Recovery CTRL+Y" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/clean24.png" id="clean" language="t" zh="清空 CTRL+SHIFT+DEL"
|
||||
ozh="清空 CTRL+SHIFT+DEL" en="Clear CTRL+SHIFT+DEL" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/delete24.png" id="delete" language="t" zh="删除 CTRL+DEL"
|
||||
ozh="刪除 CTRL+DEL" en="Delete CTRL+DEL" />
|
||||
</div>
|
||||
<div class="img-warp" id="copy">
|
||||
<img src="public/images/copy24.png" language="t" zh="复制 CTRL+C" ozh="複製 CTRL+C"
|
||||
en="Copy CTRL+C" />
|
||||
</div>
|
||||
<div class="img-warp" id="paste">
|
||||
<img src="public/images/paste24.png" language="t" zh="粘贴 CTRL+V" ozh="粘貼 CTRL+V"
|
||||
en="Paste CTRL+V" />
|
||||
</div>
|
||||
<div class="ui-button ui-button-active img-warp img-warp1" id="front_side">
|
||||
<span language="m" zh="正面" ozh="正面" en="Front"></span>
|
||||
</div>
|
||||
<div class="ui-button img-warp img-warp1" id="back_side">
|
||||
<span language="m" zh="背面" ozh="背面" en="Back"></span>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="ui-button img-warp" id="version_change">
|
||||
<span language="m" zh="切换" ozh="切换" en="Swap"></span>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="img-warp">
|
||||
<img src="public/images/about24.png" id="about" language="t" zh="关于我们" ozh="關於我們"
|
||||
en="About Us" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/help24.png" id="help" language="t" zh="帮助文档" ozh="幫助文檔"
|
||||
en="Help" />
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
<select class="ui-select" id="language_select" style="margin-left: 10px">
|
||||
<option value='zh'>中文-简体</option>
|
||||
<option value='ozh'>中文-繁體</option>
|
||||
<option value='en'>English</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode3">
|
||||
<button type="button" class="layui-btn layui-btn-primary white" id="display" language="m"
|
||||
zh="预览效果" ozh="預覽效果" en="Display"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<div class="tool" language="t" zh="图片" ozh="圖片" en="Picture">
|
||||
<img src="public/images/images.svg" id="addPic" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="合成图片" ozh="合成圖片" en="Synthetic Picture">
|
||||
<img src="public/images/images_add.svg" id="addOutPic" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="文本" ozh="文本" en="Text">
|
||||
<img src="public/images/text.svg" id="addText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="圆形文本" ozh="圆形文本" en="Circle Text">
|
||||
<img src="public/images/text3.svg" id="addCircleText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="合成文本" ozh="合成文本" en="Synthetic Text">
|
||||
<img src="public/images/text2.svg" id="addOutText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="方块" ozh="方塊" en="Rect">
|
||||
<img src="public/images/rect.svg" id="addRect" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="圆形" ozh="圓形" en="Circle">
|
||||
<img src="public/images/round.svg" id="addCircle" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="直线" ozh="直線" en="Line">
|
||||
<img src="public/images/line.svg" id="addLine" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="二维码" ozh="二維碼" en="QR Code">
|
||||
<img src="public/images/qrcode.svg" id="addQrCode" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="条形码" ozh="條形碼" en="Bar Code">
|
||||
<img src="public/images/barcode.svg" id="addBarCode" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="计数器" ozh="計數器" en="Counter">
|
||||
<img src="public/images/counter.svg" id="addCounter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" style="background-color: #1E252D">
|
||||
<div style="background-color: #1E252D;" id="canvas-div">
|
||||
<div class="canvas-wrapper">
|
||||
<div class="ruler-corner"></div>
|
||||
<canvas class="ruler-horizontal" id="ruler-h"></canvas>
|
||||
<canvas class="ruler-vertical" id="ruler-v"></canvas>
|
||||
<div class="canvas_box" style="margin-left: 20px; margin-top: 20px;">
|
||||
<canvas id="canvas1"></canvas>
|
||||
<canvas id="canvas2"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas_bg">
|
||||
<!-- 类型改变 -->
|
||||
<img id="canvas_bg_img" class="canvas_bg_img" src="./public/images/bg_1.png" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" style="background-color: #31373D">
|
||||
<div id="posText" style="display: inline-block">X : Y : </div>
|
||||
<div id="zoomSlide" style="width: 300px;display: none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas style="display: none" id="barcode"></canvas>
|
||||
<!-- 类型改变 -->
|
||||
<img id="source_front" src="./public/images/front_bg1_1.png" style="display: none" />
|
||||
<img id="source_front2" src="./public/images/front_bg2_1.png" style="display: none" />
|
||||
<img id="source_back" src="./public/images/back_bg_1.png" style="display: none" />
|
||||
</div>
|
||||
</div>
|
||||
<script src="./public/js/fabric1.min.js"></script>
|
||||
<script src="./public/js/guideLines1.js"></script>
|
||||
<script src="./public/js/print.min.js"></script>
|
||||
<script src="./public/js/crypto-js.min.js"></script>
|
||||
<script src="./public/js/jspdf.umd.min.js"></script>
|
||||
<script src="./layui/layui.js"></script>
|
||||
<script src="./lib/platform/web.js?v=basepath2"></script>
|
||||
<script src="./public/js/jr-qrcode.js"></script>
|
||||
<script src="./public/js/JsBarcode.all.min.js"></script>
|
||||
<script src="./lib/common/fabric-ext.js"></script>
|
||||
<script src="./lib/design1.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+1
-3
@@ -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);
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Soon Design</title>
|
||||
<link rel="stylesheet" href="./layui/css/layui.css">
|
||||
<link rel="stylesheet" href="./public/css/design.css">
|
||||
<style>
|
||||
/* 标尺样式 */
|
||||
.ruler-container {
|
||||
position: relative;
|
||||
background-color: #2C2C2C;
|
||||
border: 1px solid #1E1E1E;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-horizontal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
/* 改为更明显的颜色,不透明度提高到 0.9 */
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
/* 鼠标移上去显示上下调整箭头,提示可拖拽 */
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
/* 改为更明显的颜色,不透明度提高到 0.9 */
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1000;
|
||||
/* 鼠标移上去显示左右调整箭头 */
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.ruler-corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: rgba(44, 44, 44, 0.9) !important;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 拖拽过程中的辅助线样式 */
|
||||
.guide-line-drag {
|
||||
position: absolute;
|
||||
background-color: #0066FF;
|
||||
/* 深蓝色高亮 */
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
/* 让鼠标穿透,不影响拖拽事件 */
|
||||
box-shadow: 0 0 6px #0066FF;
|
||||
/* 增强发光效果 */
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="layui-fluid main" style="display: none">
|
||||
<div class="layui-row" style="height: 100%;width: 100%">
|
||||
<div class="col-right">
|
||||
<div class="tools-bar right-bar">
|
||||
|
||||
<div class="mode2">
|
||||
<div class="tool">
|
||||
<img src="public/images/top24.png" id="top_alignment" language="t" zh="顶部对齐" ozh="頂部對齊"
|
||||
en="Top Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/bottom24.png" id="bottom_alignment" language="t" zh="底部对齐"
|
||||
ozh="底部對齊" en="Bottom Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_y24.png" id="center_y_alignment" language="t" zh="居中对齐"
|
||||
ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode2" style="margin-left: 2px;border-right: 0px">
|
||||
<div class="tool">
|
||||
<img src="public/images/left24.png" id="left_alignment" language="t" zh="左对齐" ozh="左對齊"
|
||||
en="Left Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/right24.png" id="right_alignment" language="t" zh="右对齐" ozh="右對齊"
|
||||
en="Right Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_x24.png" id="center_x_alignment" language="t" zh="居中对齐"
|
||||
ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="object_attribute">
|
||||
<div class="component-title" zh="对象属性" ozh="對象屬性" en="Object Properties" language="m"></div>
|
||||
<div>
|
||||
<div class="component-line">
|
||||
<span class="name" zh="类型:" ozh="類型:" en="Type:" language="m"></span>
|
||||
<span id="component_type" zh="背景" ozh="背景" en="Background" language="m"></span>
|
||||
</div>
|
||||
<div id="base_control">
|
||||
<div class="component-line">
|
||||
<div class="ui-button" id="up_obj" language="m" zh="移至上层" ozh="移至上層" en="upper layer">
|
||||
</div>
|
||||
<div class="ui-button" id="down_obj" language="m" zh="移至下层" ozh="移至下層" en="upper layer">
|
||||
</div>
|
||||
<div class="pic_bg" id="set_bg" language="m" zh="设为背景" ozh="設爲背景"
|
||||
en="Set as background">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name">X:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_x" />
|
||||
|
||||
<span class="name">Y:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_y" />
|
||||
<div style="height: 20px;width: 20px;display: inline-block;" class="ui-radio" id="lock">
|
||||
<img src="public/images/unlock20.png" id="lock_img">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name">H:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_h" />
|
||||
<span class="name">W:</span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_w" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="角度:" ozh="角度:" en="Angle"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 80px" id="text_r" />
|
||||
<img src="public/images/rotate20.png" language="t" zh="旋转90°" ozh="旋轉90°"
|
||||
en="Rotate 90°" id="rotate" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="text_black" id="text_black" class="radio">
|
||||
</div>
|
||||
</div>
|
||||
<div id="pic_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="pic_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="pic_black" id="pic_black" class="radio">
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div id="out_pic_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="out_pic_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="out_pic_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="使用黑色带打印:" ozh="使用黑色帶打印:" en="Print use K:"></span>
|
||||
<input type="checkbox" name="out_pic_black" id="out_pic_black" class="radio">
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
<div id="text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" style="position: relative;top:-45px" language="m" zh="文本:" ozh="文本:"
|
||||
en="Text:">文本:</span>
|
||||
<textarea rows="3" style="height: 60px" id="text_text"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Fonts:"></span>
|
||||
<select class="ui-select" id="text_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="300" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="text_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="text_color" class="input_color">
|
||||
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_underline">
|
||||
<img src="public/images/underline24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="text_linethrough">
|
||||
<img src="public/images/linethrough24.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="text_back_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="circle_text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" style="position: relative;top:-45px" language="m" zh="圆形文本:"
|
||||
ozh="圆形文本:" en="Circle Text:">圆形文本:</span>
|
||||
<textarea rows="3" style="height: 60px" id="circle_text_text"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Fonts:"></span>
|
||||
<select class="ui-select" id="circle_text_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<div id="circle_text_size"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="直径:" ozh="直径:" en="Diameter:"></span>
|
||||
<div id="circle_text_diameter"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="中心辅助调整圆弧:" ozh="中心輔助調整圓弧:"
|
||||
en="Adjust the arc using the center:"></span>
|
||||
<input type="checkbox" name="circle_text_center" id="circle_text_center" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="circle_text_color" class="input_color">
|
||||
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="circle_text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="circle_text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="circle_text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="circle_text_back_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div id="out_text_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="out_text_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="out_text_text" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="out_text_font_family">
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="300" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="out_text_size" />
|
||||
</div>
|
||||
<div class="component-line"
|
||||
style="margin-left: 2px;display: flex; align-items: center;gap: 10px;">
|
||||
<span class="name" language="m" zh="对齐方式:" ozh="對齊方式:" en="Alignment:"></span>
|
||||
<div class="mode2"
|
||||
style="margin-left: 2px;border: none;display: flex; align-items: center;gap: 10px;">
|
||||
<div class="tool">
|
||||
<img src="public/images/left24.png" id="out_left_alignment" language="t"
|
||||
zh="左对齐" ozh="左對齊" en="Left Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/right24.png" id="out_right_alignment" language="t"
|
||||
zh="右对齐" ozh="右對齊" en="Right Alignment" />
|
||||
</div>
|
||||
<div class="tool">
|
||||
<img src="public/images/center_x24.png" id="out_center_alignment" language="t"
|
||||
zh="居中对齐" ozh="居中對齊" en="Center Alignment" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="out_text_color" class="input_color">
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_italics">
|
||||
<img src="public/images/italics24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_underline">
|
||||
<img src="public/images/underline24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_bold">
|
||||
<img src="public/images/bold24.png">
|
||||
</div>
|
||||
<div style="height: 24px;width: 24px;display: inline-block;position: relative;bottom: 4px;"
|
||||
class="ui-radio" id="out_text_linethrough">
|
||||
<img src="public/images/linethrough24.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="背景颜色:" ozh="背景顏色:" en="Background color:"></span>
|
||||
<input type="color" id="out_text_back_color" class="input_color">
|
||||
<div class="transparent-button" id="out_text_back_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="如果文本被截断,则作业失败" ozh="如果文本被截斷,則作業失敗"
|
||||
en="If the text is truncated, the operation fails"></span>
|
||||
<input type="checkbox" name="exceed" id="exceed" class="radio">
|
||||
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="自动换行:" ozh="自動換行:" en="Automatic line feed:"></span>
|
||||
<input type="checkbox" name="autoWrap" id="autoWrap" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="固定大小:" ozh="固定大小:" en="Fixed Size:"></span>
|
||||
<input type="checkbox" name="fixSize" id="fixSize" class="radio">
|
||||
</div>
|
||||
</div>
|
||||
<div id="rect_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="填充:" ozh="填充:" en="Fill:"></span>
|
||||
<input type="color" id="rect_color" class="input_color">
|
||||
<div class="transparent-button" id="rect_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="边框:" ozh="邊框:" en="Border:"></span>
|
||||
<input type="color" id="rect_stroke" class="input_color">
|
||||
<div class="transparent-button" id="rect_stroke_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px"
|
||||
id="rect_stroke_width" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="圆角:" ozh="圓角:" en="Radius:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px" id="rect_radius" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="rect_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="line_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="line_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="虚线间距:" ozh="虛線間距:" en="Spacing:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="line_dist" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="circle_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="填充:" ozh="填充:" en="Fill:"></span>
|
||||
<input type="color" id="circle_color" class="input_color">
|
||||
<div class="transparent-button" id="circle_transparent" language="m" zh="透明" ozh="透明"
|
||||
en="Transparent"></div>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="边框:" ozh="邊框:" en="Border:"></span>
|
||||
<input type="color" id="circle_stroke" class="input_color">
|
||||
<div class="transparent-button" id="circle_stroke_transparent" language="m" zh="透明"
|
||||
ozh="透明" en="Transparent"></div>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50px;height:25px;position:relative;bottom:2px"
|
||||
id="circle_stroke_width" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="透明度:" ozh="透明度:" en="Opacity:"></span>
|
||||
<div id="circle_op"
|
||||
style="display: inline-block;width: 150px;position: relative;top:-3px;left:5px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="qrcode_Controll" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="qrcode_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="qrcode_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="qrcode_val" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="barcode_controll" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字段名:" ozh="字段名:" en="Field Name:"></span>
|
||||
<input type="text" id="barcode_field_name" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="示例数据" ozh="示例數據" en="Sample Text:"></span>
|
||||
<input type="text" id="barcode_val" class="ui-input" style="width: 50%" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="取消文字:" ozh="取消文字:" en="Cancel text:"></span>
|
||||
<input type="checkbox" name="barcode_show" id="barcode_show" class="radio">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="barcode_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="300" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="barcode_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="barcode_color" class="input_color">
|
||||
</div>
|
||||
</div>
|
||||
<div id="counter_control" class="control">
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字体:" ozh="字體:" en="Font:"></span>
|
||||
<select class="ui-select" id="count_font_family">
|
||||
</select>
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="字号:" ozh="字號:" en="Size:"></span>
|
||||
<input min="1" max="300" step="0.5" lay-affix="number" type="number" class="ui-input"
|
||||
style="width: 50%" id="count_size" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="颜色:" ozh="顏色:" en="Color:"></span>
|
||||
<input type="color" id="counter_color" class="input_color">
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="起始:" ozh="起始:" en="Start:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="counter_start" />
|
||||
</div>
|
||||
<div class="component-line">
|
||||
<span class="name" language="m" zh="增量:" ozh="增量:" en="Incremental:"></span>
|
||||
<input type="text" oninput="value=value.replace(/[^\d]/g,'')" class="ui-input"
|
||||
style="width: 50%" id="counter_add" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="obj_list">
|
||||
<div class="component-title" language="m" zh="对象列表" ozh="對象列錶" en="Object List"></div>
|
||||
<div class="obj_list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-left" style="height: 100%">
|
||||
<div class="tools-bar left-bar">
|
||||
<div class="mode-left">
|
||||
<div class="mode-warp">
|
||||
<div class="img-warp" id="new">
|
||||
<img src="public/images/newfile24.png" language="t" zh="新建模板 CTRL+N" ozh="新建模闆"
|
||||
en="New template" />
|
||||
</div>
|
||||
<div class="img-warp" id="open">
|
||||
<img src="public/images/open24.png" language="t" zh="打开模板 CTRL+O" ozh="打開模闆 CTRL+O"
|
||||
en="Open Template CTRL+O" />
|
||||
</div>
|
||||
|
||||
<div class="img-warp" id="save">
|
||||
<img src="public/images/save24.png" language="t" zh="保存模板 CTRL+S" ozh="保存模闆 CTRL+S"
|
||||
en="Save Template CTRL+S" />
|
||||
</div>
|
||||
<div class="img-warp" id="output">
|
||||
<img src="public/images/output24.png" language="t" zh="另存为 CTRL+SHIFT+S"
|
||||
ozh="另存為 CTRL+SHIFT+S" en="Save as CTRL+SHIFT+S" />
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="img-warp">
|
||||
<img src="public/images/previous24.png" id="previous" language="t" zh="撤销 CTRL+Z"
|
||||
ozh="撤銷 CTRL+Z" en="Revocation CTRL+Z" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/next24.png" id="next" language="t" zh="恢复 CTRL+Y"
|
||||
ozh="恢復 CTRL+Y" en="Recovery CTRL+Y" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/clean24.png" id="clean" language="t" zh="清空 CTRL+SHIFT+DEL"
|
||||
ozh="清空 CTRL+SHIFT+DEL" en="Clear CTRL+SHIFT+DEL" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/delete24.png" id="delete" language="t" zh="删除 CTRL+DEL"
|
||||
ozh="刪除 CTRL+DEL" en="Delete CTRL+DEL" />
|
||||
</div>
|
||||
<div class="img-warp" id="copy">
|
||||
<img src="public/images/copy24.png" language="t" zh="复制 CTRL+C" ozh="複製 CTRL+C"
|
||||
en="Copy CTRL+C" />
|
||||
</div>
|
||||
<div class="img-warp" id="paste">
|
||||
<img src="public/images/paste24.png" language="t" zh="粘贴 CTRL+V" ozh="粘貼 CTRL+V"
|
||||
en="Paste CTRL+V" />
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="ui-button ui-button-active img-warp" id="front_side">
|
||||
<span language="m" zh="正面" ozh="正面" en="Front"></span>
|
||||
</div>
|
||||
<div class="ui-button img-warp" id="back_side">
|
||||
<span language="m" zh="背面" ozh="背面" en="Back"></span>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="ui-button img-warp" id="version_change">
|
||||
<span language="m" zh="切换" ozh="切换" en="Swap"></span>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
|
||||
<div class="img-warp">
|
||||
<img src="public/images/about24.png" id="about" language="t" zh="关于我们" ozh="關於我們"
|
||||
en="About Us" />
|
||||
</div>
|
||||
<div class="img-warp">
|
||||
<img src="public/images/help24.png" id="help" language="t" zh="帮助文档" ozh="幫助文檔"
|
||||
en="Help" />
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
<select class="ui-select" id="language_select" style="margin-left: 10px">
|
||||
<option value='zh'>中文-简体</option>
|
||||
<option value='ozh'>中文-繁體</option>
|
||||
<option value='en'>English</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode3">
|
||||
<button type="button" class="layui-btn layui-btn-primary white" id="display" language="m"
|
||||
zh="预览效果" ozh="預覽效果" en="Display"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<div class="tool" language="t" zh="图片" ozh="圖片" en="Picture">
|
||||
<img src="public/images/images.svg" id="addPic" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="合成图片" ozh="合成圖片" en="Synthetic Picture">
|
||||
<img src="public/images/images_add.svg" id="addOutPic" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="文本" ozh="文本" en="Text">
|
||||
<img src="public/images/text.svg" id="addText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="圆形文本" ozh="圆形文本" en="Circle Text">
|
||||
<img src="public/images/text3.svg" id="addCircleText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="合成文本" ozh="合成文本" en="Synthetic Text">
|
||||
<img src="public/images/text2.svg" id="addOutText" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="方块" ozh="方塊" en="Rect">
|
||||
<img src="public/images/rect.svg" id="addRect" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="圆形" ozh="圓形" en="Circle">
|
||||
<img src="public/images/round.svg" id="addCircle" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="直线" ozh="直線" en="Line">
|
||||
<img src="public/images/line.svg" id="addLine" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="二维码" ozh="二維碼" en="QR Code">
|
||||
<img src="public/images/qrcode.svg" id="addQrCode" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="条形码" ozh="條形碼" en="Bar Code">
|
||||
<img src="public/images/barcode.svg" id="addBarCode" />
|
||||
</div>
|
||||
<div class="tool" language="t" zh="计数器" ozh="計數器" en="Counter">
|
||||
<img src="public/images/counter.svg" id="addCounter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" style="background-color: #1E252D">
|
||||
<div style="background-color: #1E252D;" id="canvas-div">
|
||||
<div class="canvas-wrapper">
|
||||
<div class="ruler-corner"></div>
|
||||
<canvas class="ruler-horizontal" id="ruler-h"></canvas>
|
||||
<canvas class="ruler-vertical" id="ruler-v"></canvas>
|
||||
<div style="margin-left: 20px; margin-top: 20px;">
|
||||
<canvas id="canvas1"></canvas>
|
||||
<canvas id="canvas2"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" style="background-color: #31373D">
|
||||
<div id="posText" style="display: inline-block">X : Y : </div>
|
||||
<div id="zoomSlide" style="width: 300px;display: none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 类型改变 -->
|
||||
<canvas style="display: none" id="barcode"></canvas>
|
||||
<img id="source_front" src="./public/images/front_bg1_2.png" style="display: none" />
|
||||
<img id="source_front2" src="./public/images/front_bg2_2.png" style="display: none" />
|
||||
<img id="source_back" src="./public/images/back_bg_2.png" style="display: none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./public/js/fabric2.min.js"></script>
|
||||
<script src="./public/js/guideLines2.js"></script>
|
||||
<script src="./public/js/print.min.js"></script>
|
||||
<script src="./public/js/crypto-js.min.js"></script>
|
||||
<script src="./public/js/jspdf.umd.min.js"></script>
|
||||
<script src="./layui/layui.js"></script>
|
||||
<script src="./lib/platform/web.js?v=basepath2"></script>
|
||||
<script src="./public/js/jr-qrcode.js"></script>
|
||||
<script src="./public/js/JsBarcode.all.min.js"></script>
|
||||
<script src="./lib/common/fabric-ext.js"></script>
|
||||
<script src="./lib/design2.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,83 @@
|
||||
# SoonDesign 网页端功能清单
|
||||
|
||||
本文档列出 SoonDesign 各功能在**桌面端(Electron)**与**网页端**的实现情况,以及网页端不可用时的替代方案。
|
||||
|
||||
---
|
||||
|
||||
## 一、首页(index)
|
||||
|
||||
| 功能 | 桌面端 | 网页端 | 替代/说明 |
|
||||
|------|--------|--------|----------|
|
||||
| 打开文件 | ✅ 系统对话框选文件,打开设计页 | ✅ 使用 `<input type="file">` 选文件,项目 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()` 或 `<a download>` + `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 建议后续接桥或改为下载。
|
||||
- **无法在网页实现的**:无;仅“关闭窗口”受浏览器限制,其余均有实现或替代方案。
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>SoonDesign</title>
|
||||
<link rel="stylesheet" href="./layui/css/layui.css">
|
||||
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%; height: 100%; overflow: hidden; margin: 0; padding: 0;
|
||||
background-color: #1E1E1E;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.layui-fluid.main { height: 100%; padding: 0; display: flex; flex-direction: column; }
|
||||
.layui-row { height: 100%; display: flex; }
|
||||
.layui-col-md2 {
|
||||
width: 260px !important; flex-shrink: 0;
|
||||
background-color: #31373D; border-right: 1px solid #1E1E1E;
|
||||
height: 100%; overflow-y: auto;
|
||||
}
|
||||
.left { padding: 20px; }
|
||||
.left h1 {
|
||||
font-size: 18px; color: #fff; margin-bottom: 25px;
|
||||
border-left: 4px solid #009688; padding-left: 10px;
|
||||
line-height: 1; font-weight: 500;
|
||||
}
|
||||
.left .card { margin-bottom: 25px; cursor: pointer; }
|
||||
.left .rect {
|
||||
width: 100%; height: 150px;
|
||||
background-color: #252A2E; border: 1px solid #444; border-radius: 4px;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
transition: all 0.3s; position: relative;
|
||||
}
|
||||
.left .rect:hover { border-color: #009688; background-color: #3E464F; transform: translateY(-2px); }
|
||||
.left .rect img {
|
||||
width: 80px; height: 80px; object-fit: contain;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.left .rect1 img { transform: none !important; }
|
||||
.left .tip { color: #ccc; font-size: 13px; text-align: center; }
|
||||
.layui-col-md10 {
|
||||
flex: 1; background-color: #1E252D; height: 100%;
|
||||
overflow: hidden; position: relative;
|
||||
}
|
||||
.right { padding: 30px; height: 100%; display: flex; flex-direction: column; }
|
||||
#openfile {
|
||||
background-color: #009688; border: none; height: 34px; line-height: 34px;
|
||||
padding: 0 20px; font-size: 13px; border-radius: 3px; margin-right: 20px;
|
||||
color: #fff; cursor: pointer;
|
||||
}
|
||||
.recent { font-size: 18px; color: #ddd; vertical-align: middle; font-weight: 500; }
|
||||
.card-list {
|
||||
margin-top: 20px; flex: 1; overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 30px; align-content: start;
|
||||
padding: 10px; padding-bottom: 40px;
|
||||
}
|
||||
.card-list .rect {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background-color: #252A2E;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-list .rect:hover { border-color: #009688; background-color: #3E464F; }
|
||||
.card-list .rect img {
|
||||
width: 100%;
|
||||
height: 135px;
|
||||
object-fit: contain;
|
||||
margin-bottom: 0;
|
||||
background-color: #1E1E1E;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.card-list .rect1 img { transform: scale(0.8); }
|
||||
.card-list .rect1:hover img { transform: scale(0.85); }
|
||||
.card-list .tip {
|
||||
color: #aaa; font-size: 12px; width: 100%; text-align: center;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
height: 25px; line-height: 25px;
|
||||
}
|
||||
.card-list .rect:hover .tip { color: #fff; }
|
||||
.rect:not(.rect1) { position: relative; }
|
||||
.rect:not(.rect1)::after {
|
||||
content: ''; position: absolute; left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, calc(-50% - 12.5px));
|
||||
width: 24px; height: 24px;
|
||||
border-radius: 50%; background-color: #252A2E;
|
||||
box-shadow: inset 0 0 2px rgba(0,0,0,0.8);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
z-index: 10; pointer-events: none;
|
||||
}
|
||||
.left .rect:not(.rect1)::after {
|
||||
width: 16px; height: 16px;
|
||||
transform: translate(-50%, calc(-50% - 15px));
|
||||
}
|
||||
#language_select {
|
||||
position: absolute; top: 30px; right: 30px;
|
||||
background-color: #31373D; color: #ccc;
|
||||
border: 1px solid #444; padding: 5px;
|
||||
height: 30px; width: 100px;
|
||||
z-index: 999; outline: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-fluid main">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-md2">
|
||||
<div class="left">
|
||||
<h1 language="m" zh="新建" ozh="新建" en="New"></h1>
|
||||
<div class="card" id="new1">
|
||||
<div class="rect">
|
||||
<img src="./public/images/bg_1.png">
|
||||
<div class="tip" language="m" zh="空白光盘模板" ozh="空白光碟模闆" en="Disc Template"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="new2">
|
||||
<div class="rect rect1">
|
||||
<img src="./public/images/bg_2.png">
|
||||
<div class="tip" language="m" zh="空白卡片模板" ozh="空白卡片模闆" en="Card Template"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-col-md10">
|
||||
<div class="right">
|
||||
<div>
|
||||
<button class="layui-btn layui-btn-normal" id="openfile" language="m" zh="打开其他文件" ozh="打開其他文件" en="Open"></button>
|
||||
<span class="recent" language="m" zh="最近文件" ozh="最近文件" en="Recent Files"></span>
|
||||
</div>
|
||||
<div class="card-list"></div>
|
||||
</div>
|
||||
<select class="ui-select" id="language_select">
|
||||
<option value='zh'>中文-简体</option>
|
||||
<option value='ozh'>中文-繁體</option>
|
||||
<option value='en'>English</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="./layui/layui.js"></script>
|
||||
<script src="./lib/platform/web.js?v=basepath2"></script>
|
||||
<script src="./lib/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+43
-24
@@ -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消息
|
||||
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');
|
||||
});
|
||||
|
||||
+46
-20
@@ -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("<option value='" + item + "'>" + item + "</option>");
|
||||
$("#circle_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||||
$("#out_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||||
$("#barcode_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||||
$("#count_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||||
}
|
||||
});
|
||||
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('<font style="color:black">' + language_str("whetherSave") + '</font>', {//是否保存当前文件?
|
||||
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('<font style="color:black">' + language_str("whetherSave") + '</font>', {
|
||||
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() {})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+247
-234
@@ -14,28 +14,42 @@ 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文件到用户选择的路径
|
||||
var fs = typeof require !== 'undefined' ? require('fs') : null;
|
||||
if (fs) {
|
||||
fs.writeFile(filePath, buffer, (err) => {
|
||||
if (err) {
|
||||
} else {
|
||||
layer.msg(language_str("saveSucc") + filePath);//'保存成功至'
|
||||
}
|
||||
if (!err) layer.msg(language_str("saveSucc") + filePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 将 display_func 附加到 window 对象,确保全局可访问
|
||||
window.display_func = function display_func(img1, img2, img3) {
|
||||
@@ -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) {
|
||||
</div>`,
|
||||
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,109 +1076,50 @@ 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;
|
||||
}
|
||||
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';
|
||||
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[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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
@@ -1173,31 +1143,44 @@ window.openFile = function open(file) {
|
||||
}
|
||||
canvas1.renderAll();
|
||||
canvas2.renderAll();
|
||||
// 调用 updateList,现在它已附加到 window 对象
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (typeof window.updateList === 'function') {
|
||||
window.updateList();
|
||||
}
|
||||
}, 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";
|
||||
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))
|
||||
let j2 = canvas2.toJSON(props);
|
||||
recordObjs2.push(JSON.stringify(objs2));
|
||||
var j2 = canvas2.toJSON(props);
|
||||
recordJson2.push(j2);
|
||||
}, 0);
|
||||
} catch (e) {
|
||||
// 静默处理错误
|
||||
}
|
||||
});
|
||||
}
|
||||
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';
|
||||
}
|
||||
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);//'保存成功至'
|
||||
}).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();
|
||||
if (callback && typeof callback === 'function') callback();
|
||||
return;
|
||||
}
|
||||
}).catch(err => {
|
||||
})
|
||||
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(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);//'保存成功至'
|
||||
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();
|
||||
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';
|
||||
}
|
||||
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);//'保存成功至'
|
||||
}).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();
|
||||
if (callback && typeof callback === 'function') callback();
|
||||
return;
|
||||
}
|
||||
}).catch(err => {
|
||||
})
|
||||
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(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');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 出错时使用默认空数组,不中断流程
|
||||
j = { history: [] };
|
||||
}
|
||||
|
||||
// 确保 j.history 是数组 (防御性编程)
|
||||
if (!Array.isArray(j.history)) {
|
||||
j.history = [];
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 5. 统一写入
|
||||
try {
|
||||
fs.writeFileSync(fullPath, JSON.stringify(j), 'utf8');
|
||||
} catch (e) {
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
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 && 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() {
|
||||
|
||||
+70
-52
File diff suppressed because one or more lines are too long
+41
-23
@@ -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消息
|
||||
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');
|
||||
});
|
||||
|
||||
+68
-31
@@ -1630,9 +1630,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() {});
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1851,9 +1861,9 @@ function addCounter(pointer) {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -1861,15 +1871,21 @@ ipcRenderer.on('font-list', (event, data) => {
|
||||
}
|
||||
})
|
||||
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("<option value='" + item + "'>" + item + '</option>')
|
||||
$("#circle_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||||
$("#circle_text_font_family").append("<option value='" + item + "'>" + item + "</option>")
|
||||
$('#out_text_font_family').append("<option value='" + item + "'>" + item + '</option>')
|
||||
$('#barcode_font_family').append("<option value='" + item + "'>" + item + '</option>')
|
||||
$('#count_font_family').append("<option value='" + item + "'>" + item + '</option>')
|
||||
}
|
||||
})
|
||||
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
|
||||
@@ -2016,25 +2032,38 @@ ipcRenderer.on('sys-lan', (event, data) => {
|
||||
localStorage.setItem('lang', 'en')
|
||||
break
|
||||
}
|
||||
})
|
||||
ipcRenderer.on('close', (event, message) => {
|
||||
}
|
||||
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(
|
||||
'<font style="color:black">' + language_str('whetherSave') + '</font>',
|
||||
{
|
||||
//是否保存当前文件?
|
||||
btn: [language_str('save'), language_str('noSave'), language_str('cancel')], //['保存', '不保存', '取消']
|
||||
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() //添加背景 白色卡片
|
||||
|
||||
@@ -2171,26 +2200,34 @@ window.rotate = function (id) {
|
||||
// 注意:#display 事件绑定已在 ui.js 中定义(第2633行),此处已删除重复定义
|
||||
// 导出文件
|
||||
// 将 savePdf 附加到 window 对象,确保在 output.js 中也能访问
|
||||
window.savePdf = async function savePdf(buffer) {
|
||||
let fs = require('fs');
|
||||
// 弹出保存对话框
|
||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||
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: path.join(exePath, 'output.pdf'), // 默认路径
|
||||
filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] // 文件过滤器
|
||||
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);
|
||||
});
|
||||
|
||||
if (canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 将Buffer写入PDF文件到用户选择的路径
|
||||
fs.writeFile(filePath, buffer, (err) => {
|
||||
if (err) {
|
||||
} else {
|
||||
layer.msg(language_str("saveSucc") + filePath);//'保存成功至'
|
||||
}
|
||||
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) {
|
||||
|
||||
+184
-104
File diff suppressed because one or more lines are too long
+63
-49
@@ -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
|
||||
}
|
||||
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();
|
||||
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 (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;
|
||||
}
|
||||
|
||||
// 调用 output.js 中的 openFile 函数
|
||||
if (typeof window.openFile === 'function') {
|
||||
window.openFile(filePath);
|
||||
if (typeof window.openFile === 'function') window.openFile(file.name, j);
|
||||
});
|
||||
return;
|
||||
}
|
||||
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(function(err) {});
|
||||
}
|
||||
})
|
||||
.catch((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(
|
||||
'<font style="color:black">' + language_str('whetherSave') + '</font>',
|
||||
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 () {
|
||||
'</div>'
|
||||
});
|
||||
})
|
||||
$("#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');
|
||||
});
|
||||
|
||||
// ===========================================================
|
||||
|
||||
+71
-37
@@ -1,5 +1,6 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { dialog } = require('@electron/remote');
|
||||
// 使用平台桥(桌面端由 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) {
|
||||
@@ -7,6 +8,12 @@ function get_filename(filePath) {
|
||||
return filePath.split(/[/\\]/).pop();
|
||||
}
|
||||
|
||||
// 辅助函数:HTML 属性转义,避免 path 中的 "&<> 破坏属性导致点击传参错误
|
||||
function escapeAttr(s) {
|
||||
if (s == null) return "";
|
||||
return String(s).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
let s_lan = "zh";
|
||||
|
||||
function language_str(str) {
|
||||
@@ -41,11 +48,12 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
}).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) {
|
||||
@@ -63,12 +71,13 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
loadHistory();
|
||||
});
|
||||
|
||||
if (ipcRenderer && ipcRenderer.on) {
|
||||
ipcRenderer.on('sys-lan', (event, data) => {
|
||||
let lang = localStorage.getItem("lang");
|
||||
if (lang) {
|
||||
s_lan = lang;
|
||||
} else {
|
||||
if (data.startsWith('zh')) {
|
||||
if (data && data.startsWith('zh')) {
|
||||
s_lan = data === 'zh-TW' ? 'ozh' : 'zh';
|
||||
} else {
|
||||
s_lan = 'en';
|
||||
@@ -79,13 +88,22 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
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;
|
||||
|
||||
@@ -94,9 +112,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
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);
|
||||
@@ -115,8 +131,14 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
imgStyle = "opacity: 0.6; filter: grayscale(100%);";
|
||||
}
|
||||
|
||||
let displayName = get_filename(item.path);
|
||||
let cardTitle = item.path;
|
||||
// 网页端历史 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) {
|
||||
@@ -125,7 +147,7 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
cardStyle = "border: 1px dashed #ff5722;";
|
||||
}
|
||||
|
||||
h += `<div class="card" data="${item.path}" title="${cardTitle}" style="${cardStyle}">
|
||||
h += `<div class="card" data-file="${escapeAttr(item.path)}" title="${escapeAttr(cardTitle)}" style="${cardStyle}">
|
||||
<div class="rect ${realType == 2 ? 'rect1' : ''}">
|
||||
<img src="${src}" style="width:190px; ${imgStyle}">
|
||||
<div class="tip">${displayName}</div>
|
||||
@@ -144,13 +166,18 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
}
|
||||
}
|
||||
|
||||
loadHistory();
|
||||
|
||||
// 按钮事件
|
||||
$("#openfile").click(function () { OpenDialog(); });
|
||||
$("#new1").click(function () {
|
||||
// 这里如果你也想改成和保存一样的弹窗逻辑,可以在这里加
|
||||
ipcRenderer.send('open-design-page', "", 1);
|
||||
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);
|
||||
});
|
||||
$("#new2").click(function () { ipcRenderer.send('open-design-page', "", 2); });
|
||||
|
||||
$("#about").click(async function () {
|
||||
let version = "3.0.0";
|
||||
@@ -180,21 +207,19 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
// 点击事件 (修改后:完全匹配“保存”弹窗样式)
|
||||
// ==========================================
|
||||
$(".card-list").on("click", '.card', async function () {
|
||||
let filePath = $(this).attr("data");
|
||||
|
||||
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 });
|
||||
|
||||
ipcRenderer.send('open-design-page', filePath, type);
|
||||
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. 文件不存在 -> 警告确认
|
||||
|
||||
@@ -234,36 +259,45 @@ layui.use(['layer', 'form', 'jquery'], function () {
|
||||
});
|
||||
|
||||
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: 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);
|
||||
}).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;
|
||||
}
|
||||
|
||||
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);
|
||||
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 });
|
||||
|
||||
ipcRenderer.send('open-design-page', filePath, type);
|
||||
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(err => {
|
||||
console.log(err)
|
||||
})
|
||||
}).catch(function(err) { console.log(err); });
|
||||
}
|
||||
});
|
||||
@@ -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(); }
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -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;
|
||||
})();
|
||||
@@ -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 = '';
|
||||
})();
|
||||
+27
-10
@@ -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",
|
||||
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user