From 9056f144dad76081d595dbdc8f4d16bb28859114 Mon Sep 17 00:00:00 2001
From: 24kycj <1637269896@qq.com>
Date: Fri, 19 Dec 2025 19:37:41 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 30 +-
lib/design1/core.js | 75 +-
lib/design1/ui.js | 50 +-
lib/design2/core.js | 4937 ++++++++++++++-------------
lib/design2/output.js | 160 +-
lib/design2/ui.js | 263 +-
package arm64.json | 2 +-
package.json | 10 +-
scripts/encrypt/README.md | 4 +-
scripts/encrypt/encrypt-electron.js | 2 +-
10 files changed, 2876 insertions(+), 2657 deletions(-)
diff --git a/README.md b/README.md
index 50e299e..c212fc8 100644
--- a/README.md
+++ b/README.md
@@ -10,10 +10,10 @@
```bash
# 1. 安装依赖(只需一次)
-npm install
+yarn install
# 2. 启动程序
-npm start
+yarn start
```
> **前置要求**:需要安装 [Node.js](https://nodejs.org/)(推荐 16 或更高版本)
@@ -26,13 +26,13 @@ npm start
```bash
# Windows 系统
-npm run release:win
+yarn release:win
# Mac 系统
-npm run release:mac
+yarn release:mac
# Linux 系统
-npm run release:linux
+yarn release:linux
```
✅ **自动完成**:加密 → 测试 → 清理 → 打包
@@ -45,8 +45,8 @@ npm run release:linux
#### 步骤 1:加密代码
```bash
-npm run encrypt:win # Windows
-npm run encrypt:linux # Mac/Linux
+yarn encrypt:win # Windows
+yarn encrypt:linux # Mac/Linux
```
✅ 生成 `.jsc` 加密文件(自动覆盖旧文件)
@@ -54,7 +54,7 @@ npm run encrypt:linux # Mac/Linux
#### 步骤 2:测试
```bash
-npm start
+yarn start
```
✅ 确认程序正常运行,无报错
@@ -62,7 +62,7 @@ npm start
#### 步骤 3:清理源文件
```bash
-npm run cleanup
+yarn cleanup
```
✅ 删除 `.js` 源文件,自动备份到 `.backup` 文件夹
@@ -70,9 +70,9 @@ npm run cleanup
#### 步骤 4:打包
```bash
-npm run build:win # Windows
-npm run build:mac # Mac
-npm run build:linux # Linux
+yarn build:win # Windows
+yarn build:mac # Mac
+yarn build:linux # Linux
```
✅ 生成安装包到 `build` 文件夹
@@ -84,7 +84,7 @@ npm run build:linux # Linux
### 恢复源文件
```bash
-npm run restore
+yarn restore
```
> 从 `.backup` 文件夹恢复所有源文件(误删除时使用)
@@ -92,7 +92,7 @@ npm run restore
### 清理加密文件
```bash
-npm run cleanup:jsc
+yarn cleanup:jsc
```
> 删除所有 `.jsc` 文件(重新加密前使用)
@@ -113,7 +113,7 @@ npm run cleanup:jsc
}
```
-然后重新运行 `npm run encrypt:win`
+然后重新运行 `yarn encrypt:win`
---
diff --git a/lib/design1/core.js b/lib/design1/core.js
index b2d8803..9853d21 100644
--- a/lib/design1/core.js
+++ b/lib/design1/core.js
@@ -41,6 +41,8 @@ fabric.Object.prototype.objectCaching = false;
ctx1 = canvas1.getSelectionContext();
ctx2 = canvas2.getSelectionContext();
is_bgi_add = false;
+// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史
+window._isApplyingHistory = false;
// 同步到 window 对象
window.ctx1 = ctx1;
window.ctx2 = ctx2;
@@ -1224,6 +1226,10 @@ window.selectObj = function selectObj(objIndices, isFromCanvas = false) {
}
window.recordState = function recordState() {
+ // 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期
+ if (window._isApplyingHistory) {
+ return;
+ }
//pre_objs.push(JSON.stringify(objs));
//pre_json.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
//next_json = [];
@@ -1492,8 +1498,11 @@ function isObjectLocked(obj) {
window.isObjectLocked = isObjectLocked;
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
+// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
+// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped)
const TO_JSON_PROPERTIES = [
- "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"
+ "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline",
+ "evented", "linethrough", "textBackgroundColor", "diameter", "flipped"
];
// 暴露到全局作用域
window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES;
@@ -1608,13 +1617,14 @@ function initCanvasEvents() {
handleObjectSelected(e);
});
- // 阻止被锁定的对象被框选选中(只过滤被锁定的对象,保留未锁定的对象)
+ // 阻止被锁定的对象和辅助线被框选选中:
+ // - 锁定对象:不能被框选(只能通过列表选中)
+ // - 辅助线:永远不进入选区,避免选区被拉得很大
currentCanvas.on('before:selection:created', (e) => {
if (!e.targets || e.targets.length === 0) return;
- // 过滤掉被锁定的对象,保留辅助线和未锁定的对象
e.targets = e.targets.filter(target => {
- if (target.isGuideLine) return true; // 保留辅助线
- return !isObjectLocked(target); // 移除被锁定的对象,保留未锁定的对象
+ if (target.isGuideLine) return false; // 完全排除辅助线
+ return !isObjectLocked(target); // 排除被锁定的普通对象
});
});
@@ -1850,6 +1860,11 @@ function initRulers() {
e.preventDefault();
e.stopPropagation();
+ // 防止重复调用:如果正在拖拽,直接返回
+ if ($dragLine && $dragLine.length > 0) {
+ return;
+ }
+
// 创建临时显示的线
$dragLine = $('
');
$('.canvas-wrapper').append($dragLine);
@@ -1922,18 +1937,13 @@ function initRulers() {
let canvasX = relX / zoom;
let canvasY = relY / zoom;
- // 获取画布的实际尺寸和可见区域尺寸
- let canvasWidth = canvas.width || 10000;
- let canvasHeight = canvas.height || 10000;
- // 获取可见区域的尺寸(考虑缩放)
- let visibleWidth = canvasWrapper.width() || canvasWidth;
- let visibleHeight = canvasWrapper.height() || canvasHeight;
- // 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域
- // 使用 100000 确保无论画布多大、如何缩放,都能覆盖整个可见区域
- let lineExtent = 100000;
+ // 获取画布的实际尺寸(用于决定辅助线的长度)
+ let canvasWidth = canvas.width || 2000;
+ let canvasHeight = canvas.height || 2000;
+ // 使用与画布尺寸相关的长度,避免“整块区域都像辅助线”,同时保证能横穿整个画布
+ let lineExtent = Math.max(canvasWidth, canvasHeight) * 2;
- // 构造线对象,使用足够大的范围确保覆盖整个画布
- // 线的起点和终点要足够远,确保覆盖整个可见区域
+ // 构造线对象
let linePoints = [];
if (type === 'horizontal') {
// 水平线:从很远的左边到很远的右边
@@ -1962,19 +1972,34 @@ function initRulers() {
lockScalingX: true,
lockScalingY: true,
hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
- perPixelTargetFind: false, // 使用边界框检测而不是像素检测
- targetFindTolerance: 10, // 增加点击容差,扩大可点击区域(像素)
- padding: 10, // 增加选择边界的内边距
+ // design1 中改回边框命中模型,保证更容易点中拖拽
+ perPixelTargetFind: false,
+ targetFindTolerance: 10,
+ padding: 10,
isGuideLine: true,
ignoreSave: true
});
- // 限制移动方向
- guideLine.on('moving', function (e) {
+ // 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉)
+ guideLine.on('moving', function () {
if (type === 'horizontal') {
- this.left = -lineExtent;
+ // 水平线:根据当前中心点的 Y,重算 x1/x2,使其横穿画布
+ const cy = this.getCenterPoint().y;
+ this.set({
+ x1: -lineExtent,
+ y1: cy,
+ x2: lineExtent,
+ y2: cy
+ });
} else {
- this.top = -lineExtent;
+ // 垂直线:根据当前中心点的 X,重算 y1/y2,使其竖穿画布
+ const cx = this.getCenterPoint().x;
+ this.set({
+ x1: cx,
+ y1: -lineExtent,
+ x2: cx,
+ y2: lineExtent
+ });
}
});
@@ -2200,8 +2225,8 @@ window.copySelection = function copySelection() {
let appData = (idx > 0 && objs[idx - 1]) ? objs[idx - 1] : { type: 0 };
return {
- // 1. 保存画面样式 (坐标、颜色、字号等)
- fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data']),
+ // 1. 保存画面样式 (坐标、颜色、字号等,包含图片的 src)
+ fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data', 'src']),
// 2. 保存业务属性 (深拷贝,防止引用冲突)
appData: JSON.parse(JSON.stringify(appData))
};
diff --git a/lib/design1/ui.js b/lib/design1/ui.js
index c3d6027..3cb658e 100644
--- a/lib/design1/ui.js
+++ b/lib/design1/ui.js
@@ -917,6 +917,11 @@ $("#set_bg").click(function () {//设置为背�?
// onStateRestored 函数在 core.js 中定义并附加到 window 对象
// ==========================================
$("#previous").click(function () {
+ // 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
+ if (window._isApplyingHistory) {
+ return;
+ }
+
if (step.val == 0) return;
step.val--;
@@ -928,29 +933,50 @@ $("#previous").click(function () {
objs = objs1;
// 2. 恢复画布 (使用回调函数)
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson1[step.val], function () {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs1);
+ }
if (typeof window.onStateRestored === 'function') {
window.onStateRestored(); // 图片加载完才执行这一行
}
+ window._isApplyingHistory = false;
});
} else {
// 背面逻辑
objs2 = JSON.parse(recordObjs2[step.val]);
objs = objs2;
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson2[step.val], function () {
+ // 加载后解锁所有对象(背景除外)
+ if (typeof window.unlockAllObjects === 'function') {
+ window.unlockAllObjects(canvas);
+ }
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs2);
+ }
if (typeof window.onStateRestored === 'function') {
window.onStateRestored();
}
+ window._isApplyingHistory = false;
});
}
});
// ==========================================
$("#next").click(function () {
+ // 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
+ if (window._isApplyingHistory) {
+ return;
+ }
+
// 获取当前历史记录的最大长度
let maxLen = $("#front_side").hasClass("ui-button-active") ? recordObjs1.length : recordObjs2.length;
@@ -962,23 +988,39 @@ $("#next").click(function () {
objs1 = JSON.parse(recordObjs1[step.val]);
objs = objs1;
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson1[step.val], function () {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs1);
+ }
if (typeof window.onStateRestored === 'function') {
window.onStateRestored(); // 图片加载完才执行这一行
}
+ window._isApplyingHistory = false;
});
} else {
objs2 = JSON.parse(recordObjs2[step.val]);
objs = objs2;
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson2[step.val], function () {
+ // 加载后解锁所有对象(背景除外)
+ if (typeof window.unlockAllObjects === 'function') {
+ window.unlockAllObjects(canvas);
+ }
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs2);
+ }
if (typeof window.onStateRestored === 'function') {
window.onStateRestored();
}
+ window._isApplyingHistory = false;
});
}
});
@@ -1280,8 +1322,9 @@ $("#circle_stroke_transparent").click(function () {
// saveAs 函数保留,因为它被作为回调传递给 output 函数(第52行)
function saveAs(op1, callback) {
- let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]);
- let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]);
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
+ let j = canvas1.toJSON(props);
+ let b = canvas2.toJSON(props);
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
if (typeof window.cleanupSrcFields === 'function') {
@@ -1634,7 +1677,8 @@ $("#circle_text_back_color").bind('input propertychange', function () {
})
$("#circle_text_text").bind('input propertychange', function () {
let val = $("#circle_text_text").val().trim()
- canvas.getActiveObject().set("text", ` ${val} `);
+ // 有内容时不加首行缩进(前面的空格),只保留后面的空格
+ canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
canvas.renderAll();
if (typeof window.updateList === 'function') {
window.updateList();
diff --git a/lib/design2/core.js b/lib/design2/core.js
index d39803b..8e711a1 100644
--- a/lib/design2/core.js
+++ b/lib/design2/core.js
@@ -1,2403 +1,2534 @@
-// design2核心功能模块
-// 注意:此代码在layui.use回调函数内部执行
-// 全局变量已在主入口文件中定义,这里只需要初始化
-
-$('#canvas1').attr('height', document.body.clientHeight - 100)
-$('#canvas2').attr('height', document.body.clientHeight - 100)
-$('#object_attribute').css('height', '470px')
-$('#obj_list').css('height', document.body.clientHeight - 520)
-$('.obj_list').css('height', $('#obj_list').height() - 32)
-
-$('.main').show()
-$('#canvas1').attr('width', $('#canvas-div').width())
-$('#canvas2').attr('width', $('#canvas-div').width())
-
-// 初始化Canvas对象
-// 优化性能:在创建 Fabric Canvas 之前,先设置 canvas context 的 willReadFrequently
-try {
- const canvas1El = document.getElementById('canvas1');
- const canvas2El = document.getElementById('canvas2');
- if (canvas1El) {
- canvas1El.getContext('2d', { willReadFrequently: true });
- }
- if (canvas2El) {
- canvas2El.getContext('2d', { willReadFrequently: true });
- }
-} catch (e) {
- // 如果浏览器不支持 willReadFrequently,忽略错误
-}
-
-canvas1 = new fabric.Canvas('canvas1')
-canvas2 = new fabric.Canvas('canvas2')
-// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
-window.canvas1 = canvas1;
-window.canvas2 = canvas2;
-fabric.Object.prototype.objectCaching = false
-ctx1 = canvas1.getSelectionContext()
-ctx2 = canvas2.getSelectionContext()
-is_bgi_add = false
-// 同步到 window 对象
-window.ctx1 = ctx1;
-window.ctx2 = ctx2;
-window.is_bgi_add = is_bgi_add;
-canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200)
-//console.log(canvas1.width / 2, canvas1.height / 2);
-//console.log($("#canvas-div").width() / 1200 > 1 ? 1 : $("#canvas-div").width() / 1200);
-canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200)
-zoom = $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200
-
-// 初始化标尺
-function initRulers() {
- // dpi已在主入口文件中定义为全局变量
- const mmToPx = dpi / 25.4; // 1mm = 11.811px (300 DPI)
- const rulerHeight = 20;
- const rulerWidth = 20;
-
- // 获取实际画布的像素尺寸(px)
- // design2.html 的实际画布尺寸是 1012px x 648px
- const actualCanvasWidth = canvas1.width || 1012; // 实际画布宽度(px)
- const actualCanvasHeight = canvas1.height || 648; // 实际画布高度(px)
-
- // 获取显示区域的尺寸(包括 margin)
- const canvasDiv = $('#canvas-div');
- const canvasWrapper = $('.canvas-wrapper');
- // 使用 canvas-wrapper 的宽度,如果没有则使用 canvas_div 的宽度
- let canvasWidth = canvasWrapper.width();
- let canvasHeight = canvasWrapper.height();
- if (!canvasWidth || canvasWidth === 0) {
- const canvas1Width = $('#canvas1').width() || canvasDiv.width() - 20;
- canvasWidth = canvas1Width + 20; // 加上 margin-left: 20px
- }
- if (!canvasHeight || canvasHeight === 0) {
- const canvas1Height = $('#canvas1').height() || canvasDiv.height() - 20;
- canvasHeight = canvas1Height + 20; // 加上 margin-top: 20px
- }
-
- // 设置标尺尺寸(使用显示区域的尺寸)
- $('#ruler-h').attr('width', canvasWidth).attr('height', rulerHeight);
- $('#ruler-v').attr('width', rulerWidth).attr('height', canvasHeight);
- $('.ruler-horizontal').css('width', canvasWidth + 'px');
- $('.ruler-vertical').css('height', canvasHeight + 'px');
-
- // 绘制水平标尺
- function drawHorizontalRuler() {
- const ctx = document.getElementById('ruler-h').getContext('2d', { willReadFrequently: true });
- ctx.clearRect(0, 0, canvasWidth, rulerHeight);
-
- // 背景
- ctx.fillStyle = '#2C2C2C';
- ctx.fillRect(0, 0, canvasWidth, rulerHeight);
-
- // 边框
- ctx.strokeStyle = '#1E1E1E';
- ctx.lineWidth = 1;
- ctx.strokeRect(0, 0, canvasWidth, rulerHeight);
- ctx.beginPath();
- ctx.moveTo(0, rulerHeight - 1);
- ctx.lineTo(canvasWidth, rulerHeight - 1);
- ctx.stroke();
-
- // 刻度线
- ctx.strokeStyle = '#888888';
- ctx.fillStyle = '#CCCCCC';
- ctx.font = '10px Arial';
- ctx.textAlign = 'left';
- ctx.textBaseline = 'top';
-
- // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算
- // 实际画布物理宽度 = actualCanvasWidth / dpi * 25.4 (mm)
- // 显示区域宽度 = actualCanvasWidth * zoom (px)
- // 所以:1mm 在显示区域中 = (actualCanvasWidth * zoom) / (actualCanvasWidth / dpi * 25.4) = zoom * dpi / 25.4
- const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数
- const marginOffset = 20; // canvas 的 margin-left: 20px
- // 标尺0点应该在background_image的左上角位置
- // background_image在画布坐标系中的left值,需要转换到标尺坐标系
- // 画布从标尺的marginOffset位置开始,所以:标尺0点 = marginOffset + background_image.left (如果存在)
- let originX = marginOffset;
- if (typeof background_image !== 'undefined' && background_image) {
- originX = marginOffset + background_image.left;
- }
- const startX = 0;
- const endX = canvasWidth;
-
- // 绘制主要刻度(每10mm)
- // 从0点向左绘制负值刻度
- for (let mm = -10; originX + mm * pxPerMm >= startX; mm -= 10) {
- const x = originX + mm * pxPerMm;
- if (x < startX) break;
-
- ctx.beginPath();
- ctx.moveTo(x, 0);
- ctx.lineTo(x, rulerHeight);
- ctx.stroke();
-
- // 数字(负值)
- ctx.fillText(mm + 'mm', x + 2, 2);
- }
- // 从0点向右绘制正值刻度
- for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 10) {
- const x = originX + mm * pxPerMm;
- if (x > endX) break;
-
- ctx.beginPath();
- ctx.moveTo(x, 0);
- ctx.lineTo(x, rulerHeight);
- ctx.stroke();
-
- // 数字
- ctx.fillText(mm + 'mm', x + 2, 2);
- }
-
- // 绘制次要刻度(每1mm)
- // 从0点向左绘制负值刻度
- for (let mm = -1; originX + mm * pxPerMm >= startX; mm -= 1) {
- const x = originX + mm * pxPerMm;
- if (x < startX) break;
- if (mm % 10 === 0) continue; // 跳过主要刻度
-
- ctx.beginPath();
- ctx.moveTo(x, rulerHeight - 8);
- ctx.lineTo(x, rulerHeight);
- ctx.stroke();
- }
- // 从0点向右绘制正值刻度
- for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 1) {
- const x = originX + mm * pxPerMm;
- if (x > endX) break;
- if (mm % 10 === 0) continue; // 跳过主要刻度
-
- ctx.beginPath();
- ctx.moveTo(x, rulerHeight - 8);
- ctx.lineTo(x, rulerHeight);
- ctx.stroke();
- }
- }
-
- // 绘制垂直标尺
- function drawVerticalRuler() {
- const ctx = document.getElementById('ruler-v').getContext('2d', { willReadFrequently: true });
- ctx.clearRect(0, 0, rulerWidth, canvasHeight);
-
- // 背景
- ctx.fillStyle = '#2C2C2C';
- ctx.fillRect(0, 0, rulerWidth, canvasHeight);
-
- // 边框
- ctx.strokeStyle = '#1E1E1E';
- ctx.lineWidth = 1;
- ctx.strokeRect(0, 0, rulerWidth, canvasHeight);
- ctx.beginPath();
- ctx.moveTo(rulerWidth - 1, 0);
- ctx.lineTo(rulerWidth - 1, canvasHeight);
- ctx.stroke();
-
- // 刻度线
- ctx.strokeStyle = '#888888';
- ctx.fillStyle = '#CCCCCC';
- ctx.font = '10px Arial';
- ctx.textAlign = 'left';
- ctx.textBaseline = 'top';
-
- // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算
- const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数
- const marginOffset = 20; // canvas 的 margin-top: 20px (但还要加上ruler的高度)
- const rulerHeightOffset = 20; // ruler 的高度
- // 标尺0点应该在background_image的左上角位置
- // background_image在画布坐标系中的top值,需要转换到标尺坐标系
- // 画布从标尺的marginOffset + rulerHeightOffset位置开始,所以:标尺0点 = marginOffset + rulerHeightOffset + background_image.top (如果存在)
- let originY = marginOffset + rulerHeightOffset;
- if (typeof background_image !== 'undefined' && background_image) {
- originY = marginOffset + rulerHeightOffset + background_image.top;
- }
- const startY = 0;
- const endY = canvasHeight;
-
- // 绘制主要刻度(每10mm)
- // 从0点向上绘制负值刻度
- for (let mm = -10; originY + mm * pxPerMm >= startY; mm -= 10) {
- const y = originY + mm * pxPerMm;
- if (y < startY) break;
-
- ctx.beginPath();
- ctx.moveTo(0, y);
- ctx.lineTo(rulerWidth, y);
- ctx.stroke();
-
- // 数字(旋转90度,负值)
- ctx.save();
- ctx.translate(2, y + 8);
- ctx.rotate(-Math.PI / 2);
- ctx.fillText(mm + 'mm', 0, 0);
- ctx.restore();
- }
- // 从0点向下绘制正值刻度
- for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 10) {
- const y = originY + mm * pxPerMm;
- if (y > endY) break;
-
- ctx.beginPath();
- ctx.moveTo(0, y);
- ctx.lineTo(rulerWidth, y);
- ctx.stroke();
-
- // 数字(旋转90度)
- ctx.save();
- ctx.translate(2, y + 8);
- ctx.rotate(-Math.PI / 2);
- ctx.fillText(mm + 'mm', 0, 0);
- ctx.restore();
- }
-
- // 绘制次要刻度(每1mm)
- // 从0点向上绘制负值刻度
- for (let mm = -1; originY + mm * pxPerMm >= startY; mm -= 1) {
- const y = originY + mm * pxPerMm;
- if (y < startY) break;
- if (mm % 10 === 0) continue; // 跳过主要刻度
-
- ctx.beginPath();
- ctx.moveTo(rulerWidth - 8, y);
- ctx.lineTo(rulerWidth, y);
- ctx.stroke();
- }
- // 从0点向下绘制正值刻度
- for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 1) {
- const y = originY + mm * pxPerMm;
- if (y > endY) break;
- if (mm % 10 === 0) continue; // 跳过主要刻度
-
- ctx.beginPath();
- ctx.moveTo(rulerWidth - 8, y);
- ctx.lineTo(rulerWidth, y);
- ctx.stroke();
- }
- }
-
- // 初始绘制
- drawHorizontalRuler();
- drawVerticalRuler();
-
- // 监听窗口大小变化
- $(window).on('resize', function () {
- setTimeout(function () {
- const canvasWrapper = $('.canvas-wrapper');
- let newWidth = canvasWrapper.width();
- let newHeight = canvasWrapper.height();
- if (!newWidth || newWidth === 0) {
- const canvas1Width = $('#canvas1').width() || $('#canvas-div').width() - 20;
- newWidth = canvas1Width + 20; // 加上 margin-left: 20px
- }
- if (!newHeight || newHeight === 0) {
- const canvas1Height = $('#canvas1').height() || $('#canvas-div').height() - 20;
- newHeight = canvas1Height + 20; // 加上 margin-top: 20px
- }
- if (newWidth !== canvasWidth || newHeight !== canvasHeight) {
- canvasWidth = newWidth;
- canvasHeight = newHeight;
- $('#ruler-h').attr('width', newWidth);
- $('#ruler-v').attr('height', newHeight);
- $('.ruler-horizontal').css('width', newWidth + 'px');
- $('.ruler-vertical').css('height', newHeight + 'px');
- drawHorizontalRuler();
- drawVerticalRuler();
- }
- }, 100);
- });
-
- // 监听画布缩放变化
- canvas1.on('after:render', function () {
- drawHorizontalRuler();
- drawVerticalRuler();
- // 重新绘制辅助线(确保在遮罩之上)
- const ctx = canvas1.getContext();
- const objects = canvas1.getObjects();
- objects.forEach(obj => {
- if (obj.isGuideLine) {
- ctx.save();
- const m = obj.calcTransformMatrix();
- ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
- ctx.globalAlpha = obj.opacity || 1;
- if (obj.shadow) {
- ctx.shadowColor = obj.shadow.color;
- ctx.shadowBlur = obj.shadow.blur;
- ctx.shadowOffsetX = obj.shadow.offsetX;
- ctx.shadowOffsetY = obj.shadow.offsetY;
- }
- obj._render(ctx);
- ctx.restore();
- }
- });
- });
- canvas2.on('after:render', function () {
- drawHorizontalRuler();
- drawVerticalRuler();
- // 重新绘制辅助线(确保在遮罩之上)
- const ctx = canvas2.getContext();
- const objects = canvas2.getObjects();
- objects.forEach(obj => {
- if (obj.isGuideLine) {
- ctx.save();
- const m = obj.calcTransformMatrix();
- ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
- ctx.globalAlpha = obj.opacity || 1;
- if (obj.shadow) {
- ctx.shadowColor = obj.shadow.color;
- ctx.shadowBlur = obj.shadow.blur;
- ctx.shadowOffsetX = obj.shadow.offsetX;
- ctx.shadowOffsetY = obj.shadow.offsetY;
- }
- obj._render(ctx);
- ctx.restore();
- }
- });
- });
-
- // --- 辅助线功能 (参考 design1) ---
- // canvasWrapper 已在上面声明,直接使用
- const canvasBox = $('#canvas-div'); // design2 使用 canvas-div 作为容器
-
- // 确保容器定位
- if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative');
-
- // 定义临时拖拽线 DOM
- let $dragLine = null;
-
- // 通用拖拽开始函数
- const startDragGuide = (type, e) => {
- e.preventDefault();
- e.stopPropagation();
-
- // 创建临时显示的线
- $dragLine = $('');
- canvasWrapper.append($dragLine);
-
- if (type === 'horizontal') {
- // 从上往下拖 (水平线)
- $dragLine.css({
- left: 0, width: '100%', height: '1px', top: e.offsetY
- });
- } else {
- // 从左往右拖 (垂直线)
- $dragLine.css({
- top: 0, height: '100%', width: '1px', left: e.offsetX
- });
- }
-
- // 绑定 document 移动事件
- $(document).on('mousemove.ruler', function (moveEvent) {
- let wrapperOffset = canvasWrapper.offset();
- if (type === 'horizontal') {
- let relY = moveEvent.pageY - wrapperOffset.top;
- $dragLine.css('top', relY + 'px');
- } else {
- let relX = moveEvent.pageX - wrapperOffset.left;
- $dragLine.css('left', relX + 'px');
- }
- });
-
- // 绑定松开事件 (创建真实的 Fabric 线)
- $(document).on('mouseup.ruler', function (upEvent) {
- $(document).off('mousemove.ruler mouseup.ruler');
-
- if (!$dragLine) return;
-
- // 获取最终位置
- let wrapperOffset = canvasWrapper.offset();
- let finalX = upEvent.pageX - wrapperOffset.left;
- let finalY = upEvent.pageY - wrapperOffset.top;
-
- // 移除临时线
- $dragLine.remove();
- $dragLine = null;
-
- // 只有拖入画布区域才创建
- if (finalX > rulerWidth && finalY > rulerHeight) {
- createFabricGuide(type, finalX, finalY);
- }
- });
- };
-
- // 绑定到标尺 DOM
- $('#ruler-h').off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e));
- $('#ruler-v').off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e));
-
- // --- 在画布中创建辅助线 ---
- function createFabricGuide(type, domX, domY) {
- // domX 和 domY 是相对于 canvas-wrapper 的坐标
- // 需要转换为相对于 canvas 的坐标,然后再转换为 Fabric 画布坐标
-
- // 获取 canvas 相对于 canvas-wrapper 的位置
- let canvas1El = $('#canvas1');
- let canvasPosition = canvas1El.position();
- let canvasMarginLeft = parseFloat(canvas1El.css('margin-left')) || 0;
- let canvasMarginTop = parseFloat(canvas1El.css('margin-top')) || 0;
-
- // 计算相对于 canvas 的坐标(减去 canvas 在 wrapper 中的位置和 margin)
- let relX = domX - canvasPosition.left - canvasMarginLeft;
- let relY = domY - canvasPosition.top - canvasMarginTop;
-
- // 转为 Fabric 坐标 (除以 zoom)
- let canvasX = relX / zoom;
- let canvasY = relY / zoom;
-
- // 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域
- let lineExtent = 100000;
-
- // 构造线对象
- let linePoints = [];
- if (type === 'horizontal') {
- linePoints = [-lineExtent, canvasY, lineExtent, canvasY];
- } else {
- linePoints = [canvasX, -lineExtent, canvasX, lineExtent];
- }
-
- let guideLine = new fabric.Line(linePoints, {
- stroke: '#0066FF',
- strokeWidth: 1 / zoom,
- opacity: 0.9,
- shadow: {
- color: '#0066FF',
- blur: 6,
- offsetX: 0,
- offsetY: 0,
- affectStroke: true
- },
- selectable: true,
- evented: true,
- hasControls: false,
- hasBorders: false,
- lockRotation: true,
- lockScalingX: true,
- lockScalingY: true,
- hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
- perPixelTargetFind: false,
- targetFindTolerance: 10,
- padding: 10,
- isGuideLine: true,
- ignoreSave: true
- });
-
- // 限制移动方向
- guideLine.on('moving', function (e) {
- if (type === 'horizontal') {
- this.left = -lineExtent;
- } else {
- this.top = -lineExtent;
- }
- });
-
- // 确定应该添加到哪个 canvas(使用当前活动的 canvas)
- let targetCanvas = canvas;
- if (!targetCanvas || (targetCanvas !== canvas1 && targetCanvas !== canvas2)) {
- // 如果没有活动的 canvas,默认使用 canvas1
- targetCanvas = canvas1;
- }
-
- targetCanvas.add(guideLine);
- targetCanvas.setActiveObject(guideLine);
- targetCanvas.renderAll();
- }
-
-}
-initRulers();
-$('#source_front').width(zoom * $('#source_front').width())
-$('#source_back').width(zoom * $('#source_back').width())
-$('.canvas-container:eq(1)').hide()
-canvas = canvas1
-window.canvas = canvas
-fabric.Object.prototype.set({
- borderColor: '#9013FE',
- cornerColor: '#9013FE', //激活状态角落图标的填充颜色
- cornerStrokeColor: '#9013FE', //激活状态角落图标的边框颜色
- borderOpacityWhenMoving: 1,
- borderScaleFactor: 1,
- cornerSize: 6,
- cornerStyle: 'circle', //rect,circle
- centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点
- centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转
- transparentCorners: false, //激活状态角落的图标是否透明
- rotatingPointOffset: 20, //旋转距旋转体的距离
- padding: 0
-})
-canvas1.preserveObjectStacking = true
-canvas2.preserveObjectStacking = true
-
-// ==========================================
-// 对象选择和属性更新函数
-// ==========================================
-// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
-window.updateControls = function updateControls() {
- // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问
- const currentCanvas = window.canvas || canvas;
- const currentBackgroundImage = window.background_image || background_image;
- if (!currentCanvas || !currentCanvas.getActiveObject()) return;
- $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0));
- $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0));
- $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0));
- $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0));
- $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0));
- // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性)
- if (currentCanvas.getActiveObject().get("lockMovementX")) {
- $("#lock_img").attr("src", "public/images/lock20.png")//已锁定
- } else {
- $("#lock_img").attr("src", "public/images/unlock20.png")//未锁定
- }
-};
-// 向后兼容
-function updateControls() {
- return window.updateControls();
-}
-function selectObject(target, index) {
- updateControls();
- $("#base_control").show();
-}
-
-function selectPic(target, index) {
- // 检查是否有选中对象,如果没有则直接返回
- if (!canvas || !canvas.getActiveObject()) return;
-
- $("#pic_control").show();
- $("#set_bg").show();
- let activeObj = canvas.getActiveObject();
-
- // 检查对象是否被锁定
- if (isObjectLocked(activeObj)) {
- canvas.discardActiveObject();
- canvas.renderAll();
- return;
- }
-
- if (typeof pic_op !== 'undefined' && pic_op && typeof pic_op.setValue === 'function') {
- pic_op.setValue(100 - (activeObj.get("opacity") * 100));
- } else {
- $("#pic_op").next().children(".layui-slider-bar").css("width", (1 - activeObj.get("opacity")) * 100 + "%");
- $("#pic_op").next().children(".layui-slider-handle").css("left", (1 - activeObj.get("opacity")) * 100 + "%");
- }
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectOutPic(target, index) {
- $("#out_pic_control").show();
- if (typeof out_pic_op !== 'undefined' && out_pic_op.setValue) {
- out_pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
- } else {
- $("#out_pic_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- $("#out_pic_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- }
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#out_pic_field_name").val(objs[idx - 1].name);
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectText(target, index) {
- $("#text_control").show();
- $("#text_color").val(canvas.getActiveObject().get("fill"));
- $("#text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor"));
- let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
- if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
- $("#text_size").val(val);
- $("#text_text").val(canvas.getActiveObject().get("text"));
- $("#text_font_family").val(canvas.getActiveObject().get("fontFamily"));
-
- if (canvas.getActiveObject().get("underline") == true) {
- $("#text_underline").addClass("ui-radio-acitve")
- } else {
- $("#text_underline").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("fontWeight") == "bold") {
- $("#text_bold").addClass("ui-radio-acitve")
- } else {
- $("#text_bold").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("fontStyle") == "italic") {
- $("#text_italics").addClass("ui-radio-acitve")
- } else {
- $("#text_italics").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("linethrough") == true) {
- $("#text_linethrough").addClass("ui-radio-acitve")
- } else {
- $("#text_linethrough").removeClass("ui-radio-acitve")
- }
- $("#circle_text_control").hide();
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectCircleText(target, index) {
- const activeObj = target || canvas.getActiveObject();
- if (!activeObj) return;
-
- let idx = getIndex(activeObj);
- $("#circle_text_control").show();
- $("#circle_text_color").val(activeObj.get("fill"));
- $("#circle_text_back_color").val(activeObj.get("textBackgroundColor") == "" ? '#ffffff' : activeObj.get("textBackgroundColor"));
-
- if (typeof circle_text_size !== 'undefined' && circle_text_size && typeof circle_text_size.setValue === 'function') {
- if (typeof circle_text_size_updating !== 'undefined') {
- circle_text_size_updating = true;
- }
- circle_text_size.setValue(activeObj.get("fontSize") - 10);
- if (typeof circle_text_size_updating !== 'undefined') {
- circle_text_size_updating = false;
- }
- }
-
- if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter && typeof circle_text_diameter.setValue === 'function') {
- circle_text_diameter.setValue(activeObj.get("diameter") - 50);
- }
-
- $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : '');
- $("#circle_text_text").val(activeObj.get("text"));
- $("#circle_text_font_family").val(activeObj.get("fontFamily"));
-
- if (activeObj.get("underline") == true) {
- $("#circle_text_underline").addClass("ui-radio-acitve")
- } else {
- $("#circle_text_underline").removeClass("ui-radio-acitve")
- }
- if (activeObj.get("fontWeight") == "bold") {
- $("#circle_text_bold").addClass("ui-radio-acitve")
- } else {
- $("#circle_text_bold").removeClass("ui-radio-acitve")
- }
- if (activeObj.get("fontStyle") == "italic") {
- $("#circle_text_italics").addClass("ui-radio-acitve")
- } else {
- $("#circle_text_italics").removeClass("ui-radio-acitve")
- }
- if (activeObj.get("linethrough") == true) {
- $("#circle_text_linethrough").addClass("ui-radio-acitve")
- } else {
- $("#circle_text_linethrough").removeClass("ui-radio-acitve")
- }
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#rotate").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectOutText(target, index) {
- const align = canvas.getActiveObject().get('textAlign')
- if (align === 'left') {
- $('#out_left_alignment').parent().addClass('tool1')
- } else if (align === 'right') {
- $('#out_right_alignment').parent().addClass('tool1')
- } else {
- $('#out_center_alignment').parent().addClass('tool1')
- }
- $("#out_text_control").show();
- $("#out_text_color").val(canvas.getActiveObject().get("fill"));
- $("#out_text_text").val(canvas.getActiveObject().get("text"));
- let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
- if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
- $("#out_text_size").val(val);
- $("#out_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor"));
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#out_text_field_name").val(objs[idx - 1].name);
- $("#out_text_font_family").val(canvas.getActiveObject().get("fontFamily"));
- $("#exceed").prop("checked", objs[idx - 1].exceed ? 'checked' : '');
- $("#autoWrap").prop("checked", objs[idx - 1].autoWrap ? 'checked' : '');
- $("#fixSize").prop("checked", objs[idx - 1].fixSize ? 'checked' : '');
- if (canvas.getActiveObject().get("underline") == true) {
- $("#out_text_underline").addClass("ui-radio-acitve")
- } else {
- $("#out_text_underline").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("fontWeight") == "bold") {
- $("#out_text_bold").addClass("ui-radio-acitve")
- } else {
- $("#out_text_bold").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("fontStyle") == "italic") {
- $("#out_text_italics").addClass("ui-radio-acitve")
- } else {
- $("#out_text_italics").removeClass("ui-radio-acitve")
- }
- if (canvas.getActiveObject().get("linethrough") == true) {
- $("#out_text_linethrough").addClass("ui-radio-acitve")
- } else {
- $("#out_text_linethrough").removeClass("ui-radio-acitve")
- }
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectRect(target, index) {
- $("#rect_control").show();
- $("#rect_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill"));
- $("#rect_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke"));
- $("#rect_stroke_width").val(canvas.getActiveObject().get("strokeWidth"));
- $("#rect_radius").val(canvas.getActiveObject().get("rx"));
- if (typeof rect_op !== 'undefined' && rect_op.setValue) {
- rect_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
- } else {
- $("#rect_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- $("#rect_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- }
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#circle_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectCircle(target, index) {
- $("#circle_control").show();
- $("#circle_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill"));
- $("#circle_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke"));
- $("#circle_stroke_width").val(canvas.getActiveObject().get("strokeWidth"));
- if (typeof circle_op !== 'undefined' && circle_op.setValue) {
- circle_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
- } else {
- $("#circle_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- $("#circle_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
- }
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#line_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectLine(target, index) {
- $("#line_control").show();
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#line_color").val(canvas.getActiveObject().get("stroke"));
- $("#line_dist").val(canvas.getActiveObject().get("strokeDashArray")[0]);
- $("#text_h").val(canvas.getActiveObject().get("strokeWidth"))
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function selectQrCode(target, index) {
- $("#qrcode_Controll").show();
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#qrcode_field_name").val(objs[idx - 1].name);
- $("#qrcode_color").val(objs[idx - 1].color);
- $("#qrcode_val").val(objs[idx - 1].val);
- $("#line_control").hide();
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#counter_control").hide();
- $("#barcode_controll").hide();
-}
-
-function selectBarCode(target, index) {
- $("#barcode_controll").show();
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#barcode_field_name").val(objs[idx - 1].name);
- $("#barcode_font_family").val(objs[idx - 1].font);
- let val = parseFloat(objs[idx - 1].fontSize) * 72 / dpi
- if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
- $("#barcode_size").val(val);
- $("#barcode_val").val(objs[idx - 1].val);
- $("#barcode_show").prop("checked", objs[idx - 1].show ? 'checked' : '');
- $("#line_control").hide();
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#counter_control").hide();
- $("#qrcode_Controll").hide();
-}
-
-function selectCounter(target, index) {
- $("#counter_control").show();
- let idx = getIndex(canvas.getActiveObject(), canvas);
- $("#counter_start").val(objs[idx - 1].start);
- $("#counter_add").val(objs[idx - 1].add);
- $("#counter_color").val(canvas.getActiveObject().get("fill"));
- $("#count_font_family").val(canvas.getActiveObject().get("fontFamily"));
- let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
- if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
- $("#count_size").val(val);
- $("#line_control").hide();
- $("#pic_control").hide();
- $("#set_bg").hide();
- $("#out_pic_control").hide();
- $("#text_control").hide();
- $("#circle_text_control").hide();
- $("#out_text_control").hide();
- $("#rect_control").hide();
- $("#circle_control").hide();
- $("#qrcode_Controll").hide();
- $("#barcode_controll").hide();
-}
-
-function handleObjectSelected(e) {
- // 确定当前活动的 canvas(从事件参数或全局变量)
- let currentCanvas = (e && e.target && (e.target.canvas === canvas1 || e.target.canvas === canvas2))
- ? (e.target.canvas === canvas1 ? canvas1 : canvas2)
- : canvas;
-
- // 如果事件来自 canvas,更新全局 canvas 变量
- if (currentCanvas === canvas1 || currentCanvas === canvas2) {
- canvas = currentCanvas;
- objs = (currentCanvas === canvas1) ? objs1 : objs2;
- window.canvas = canvas;
- window.objs = objs;
- }
-
- let select_obj = canvas.getActiveObject();
-
- // 1. 未选中任何对象
- if (!select_obj) {
- $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
- $("#component_type").text(language_str("bg"));
- selectObj(-1, true); // 传 true,只更新UI
- return;
- }
-
- // 2. 如果是辅助线,直接返回,不处理(在获取索引之前检查)
- if (select_obj.isGuideLine) {
- return;
- }
-
- // 3. 获取索引
- let i = getIndex(select_obj, canvas);
-
- // 如果不是通过对象列表选中的,且对象被锁定,则取消选中
- if (!window._selectingFromList && isObjectLocked(select_obj)) {
- canvas.discardActiveObject();
- canvas.renderAll();
- $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
- $("#component_type").text(language_str("bg"));
- selectObj(-1, true);
- return;
- }
-
- if (i != 0) {
- // --- 选中了普通对象 ---
- --i; // 转换为 objs 数组下标
-
- // 安全检查:确保 objs[i] 存在且有 type 属性
- if (objs[i] && typeof objs[i].type !== 'undefined') {
- switch (objs[i].type) {
- case 1: // 图片
- $("#component_type").text(language_str("img"));
- selectObject(select_obj, i); selectPic(select_obj, i); break;
- case 2: // 合成图片
- $("#component_type").text(language_str("simg"));
- selectObject(select_obj, i); selectOutPic(select_obj, i); break;
- case 3: // 文本
- $("#component_type").text(language_str("text"));
- selectObject(select_obj, i); selectText(select_obj, i); break;
- case 4: // 合成文本
- $("#component_type").text(language_str("stext"));
- selectObject(select_obj, i); selectOutText(select_obj, i); break;
- case 5: // 矩形
- $("#component_type").text(language_str("rect"));
- selectObject(select_obj, i); selectRect(select_obj, i); break;
- case 6: // 圆形
- $("#component_type").text(language_str("circ"));
- selectObject(select_obj, i); selectCircle(select_obj, i); break;
- case 7: // 直线
- $("#component_type").text(language_str("line"));
- selectObject(select_obj, i); selectLine(select_obj, i); break;
- case 8: // 二维码
- $("#component_type").text(language_str("qrc"));
- selectObject(select_obj, i); selectQrCode(select_obj, i); break;
- case 9: // 条形码
- $("#component_type").text(language_str("barc"));
- selectObject(select_obj, i); selectBarCode(select_obj, i); break;
- case 10: // 计数器
- $("#component_type").text(language_str("counter"));
- selectObject(select_obj, i); selectCounter(select_obj, i); break;
- case 11: // 圆形文本
- $("#component_type").text(language_str("ctext"));
- selectObject(select_obj, i); selectCircleText(select_obj, i); break;
- }
- }
-
- // 【关键】这里传入 true!
- // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了
- selectObj(i, true);
-
- } else {
- // --- 选中了背景 ---
- $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
- selectObj(-1, true);
- $("#component_type").text(language_str("bg"));
- }
-}
-
-function handleObjectMoving(e) {
- let obj = e.target;
- // 跳过辅助线
- if (obj.isGuideLine) {
- updateControls();
- return;
- }
- let idx = getIndex(obj, canvas);
- // 圆形文本特殊处理
- // 安全检查:确保 objs[idx - 1] 存在且有 type 属性
- if (idx > 0 && idx <= objs.length && objs[idx - 1] && typeof objs[idx - 1].type !== 'undefined' && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) {
- let pointer = { x: obj.left, y: obj.top };
- let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2);
- let angleDeg = getDeg(pointer);
- let flipped = pointer.y > canvas.height / 2 ? true : false;
-
- if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter.setValue) {
- circle_text_diameter.setValue(distance * 2 - 50);
- }
- obj.set("diameter", distance * 2);
- obj.set("angle", angleDeg);
- obj.set("flipped", flipped);
- }
- updateControls();
-}
-
-// 辅助函数:检查对象是否被锁定
-// 只有当 lockMovementX 为 true 时才认为对象被锁定
-// selectable 为 false 可能是其他原因(如背景图),所以只检查 lockMovementX
-function isObjectLocked(obj) {
- if (!obj) return false;
- // 主要检查 lockMovementX,这是锁定的主要标志
- return obj.get('lockMovementX') === true;
-}
-// 暴露到全局作用域,确保其他模块可以访问
-window.isObjectLocked = isObjectLocked;
-
-// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
-const TO_JSON_PROPERTIES = [
- "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline", "evented"
-];
-// 暴露到全局作用域
-window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES;
-
-// 辅助函数:解锁所有对象(加载时使用)
-function unlockAllObjects(canvas) {
- if (!canvas) return;
- const objects = canvas.getObjects();
- objects.forEach((obj, index) => {
- // 背景对象(索引0)保持锁定状态
- if (index === 0) {
- obj.set({
- lockMovementX: true,
- lockMovementY: true,
- lockRotation: true,
- lockScalingX: true,
- lockScalingY: true,
- lockSkewingX: true,
- lockSkewingY: true,
- selectable: false,
- evented: false
- });
- } else {
- // 其他对象默认解锁
- obj.set({
- lockMovementX: false,
- lockMovementY: false,
- lockRotation: false,
- lockScalingX: false,
- lockScalingY: false,
- lockSkewingX: false,
- lockSkewingY: false,
- selectable: true,
- evented: true
- });
- }
- });
-}
-// 暴露到全局作用域
-window.unlockAllObjects = unlockAllObjects;
-
-// 将 handleObjectSelected 附加到 window 对象,确保全局可访问
-window.handleObjectSelected = handleObjectSelected;
-
-// 防止重复初始化事件监听器
-var _canvasEventsInitialized = false;
-
-function initCanvasEvents() {
- // 如果已经初始化过,直接返回
- if (_canvasEventsInitialized) {
- return;
- }
- _canvasEventsInitialized = true;
-
- [canvas1, canvas2].forEach(currentCanvas => {
- // 1. 鼠标移动
- currentCanvas.on("mouse:move", (e) => {
- let offsetX = 0, offsetY = 0;
- if (currentCanvas === canvas2 && typeof background_image !== 'undefined') {
- offsetX = background_image.left;
- offsetY = background_image.top;
- }
- if (e.pointer) {
- $("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0));
- }
- });
-
- // 2. 鼠标移出
- currentCanvas.on("mouse:out", () => {
- // 可以在这里隐藏标尺辅助线
- });
-
- // 3. 鼠标松开 (添加对象)
- currentCanvas.on("mouse:up", (e) => {
- if (addState === 0) return;
- const ptr = e.pointer;
- switch (addState) {
- case 1: addPic(ptr); break;
- case 2: addOutPic(ptr); break;
- case 3: addText(ptr); break;
- case 4: addOutText(ptr); break;
- case 5: addRect(ptr); break;
- case 6: addCircle(ptr); break;
- case 7: addLine(ptr); break;
- case 8: addQrCode(ptr); break;
- case 9: addBarCode(ptr); break;
- case 10: addCounter(ptr); break;
- case 11: addCircleText(ptr); break;
- }
- addState = 0;
- if (typeof background_image !== 'undefined') background_image.hoverCursor = "default";
- currentCanvas.renderAll();
- $("#addPic, #addOutPic, #addText, #addOutText, #addRect, #addCircle, #addLine, #addQrCode, #addBarCode, #addCounter, #addCircleText").css("background-color", "");
- });
-
- // 4. 选中处理
- currentCanvas.on('mouse:down', (e) => {
- // 更新全局 canvas 变量
- canvas = currentCanvas;
- objs = (currentCanvas === canvas1) ? objs1 : objs2;
- handleObjectSelected(e);
- });
- // 阻止被锁定的对象被框选选中(只过滤被锁定的对象,保留未锁定的对象)
- currentCanvas.on('before:selection:created', (e) => {
- if (!e.targets || e.targets.length === 0) return;
- // 过滤掉被锁定的对象,保留辅助线和未锁定的对象
- e.targets = e.targets.filter(target => {
- if (target.isGuideLine) return true; // 保留辅助线
- return !isObjectLocked(target); // 移除被锁定的对象,保留未锁定的对象
- });
- });
-
- // 添加 selection 事件以确保对象选中时正确显示属性面板
- currentCanvas.on('selection:created', (e) => {
- canvas = currentCanvas;
- objs = (currentCanvas === canvas1) ? objs1 : objs2;
- handleObjectSelected(e);
- });
- currentCanvas.on('selection:updated', (e) => {
- canvas = currentCanvas;
- objs = (currentCanvas === canvas1) ? objs1 : objs2;
- handleObjectSelected(e);
- });
- currentCanvas.on('object:selected', (e) => {
- canvas = currentCanvas;
- objs = (currentCanvas === canvas1) ? objs1 : objs2;
-
- // 如果不是通过对象列表选中的,且对象被锁定,则取消选中
- // 注意:只有真正被锁定的对象才会被取消选中,未锁定的对象应该正常选中
- if (!window._selectingFromList && e.target && isObjectLocked(e.target)) {
- currentCanvas.discardActiveObject();
- currentCanvas.renderAll();
- return;
- }
-
- handleObjectSelected(e);
- // 更新对象列表以同步锁状态
- if (typeof window.updateList === 'function') {
- window.updateList();
- }
- });
-
- // 5. 对象移动
- currentCanvas.on("object:moving", (e) => {
- // 阻止背景图(索引0)被移动(如果已锁定)
- let movingObj = e.target;
- let idx = getIndex(movingObj, currentCanvas);
- if (idx === 0) {
- let bgObj = currentCanvas.getObjects()[0];
- if (bgObj && bgObj.get('lockMovementX') === true) {
- e.e.preventDefault();
- e.e.stopPropagation();
- return false;
- }
- }
- handleObjectMoving(e);
- });
-
- // 6. 变换事件
- currentCanvas.on("object:rotating", updateControls);
- currentCanvas.on("object:scaling", updateControls);
- currentCanvas.on("object:rotated", () => { recordState(); });
- currentCanvas.on('text:editing:entered', () => { _textEdit = true; });
- currentCanvas.on('text:editing:exited', () => { _textEdit = false; });
- currentCanvas.on("text:changed", (e) => { $("#text_text").val(e.target.get("text")); });
-
- // 7. 对象修改后
- currentCanvas.on('object:modified', function (event) {
- if (currentCanvas.getActiveObjects().length > 1) return;
- // 跳过辅助线
- if (event.target.isGuideLine) {
- return;
- }
- let idx = getIndex(event.target, currentCanvas);
- if (idx <= 0) return;
- let objData = objs[idx - 1];
- // 安全检查:确保 objData 存在且有 type 属性
- if (!objData || typeof objData.type === 'undefined') {
- return;
- }
- let target = event.target;
-
- if ([3, 4, 10].includes(objData.type)) {
- target.fontSize = (target.fontSize * target.scaleX).toFixed(0);
- target.scaleX = 1; target.scaleY = 1;
- target._clearCache();
- if (objData.type == 3) $("#text_size").val(target.fontSize);
- if (objData.type == 4) $("#out_text_size").val(target.fontSize);
- if (objData.type == 10) $("#count_size").val(target.fontSize);
- currentCanvas.renderAll();
- updateControls();
- } else if (objData.type == 5) {
- target.width *= target.scaleX; target.height *= target.scaleY;
- target.scaleX = 1; target.scaleY = 1;
- updateControls();
- }
- recordState();
- // 更新对象列表以同步锁状态
- if (typeof window.updateList === 'function') {
- window.updateList();
- }
- });
- });
-}
-
-// 将 updateList 附加到 window 对象,确保全局可访问
-window.updateList = function updateList() {
- let str = "";
- let i = 0;
-
- // 定义行样式:Flex布局,两端对齐,相对定位防止溢出
- const rowStyle = "display:flex; justify-content:space-between; align-items:center; height:32px; padding:0 5px; cursor:pointer; border-bottom:1px solid #444; color:#ccc; font-size:12px; position:relative;";
- // 定义左侧内容样式
- const leftStyle = "display:flex; align-items:center; overflow:hidden; white-space:nowrap; max-width:180px;";
- // 定义序号样式:靠右,有左边框
- const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;";
-
- for (let item of objs) {
- let displayIndex = i + 1; // 序号底层为1
-
- // 图标逻辑
- let iconName = "rect.svg";
- switch (item.type) {
- case 1: iconName = "images.svg"; break;
- case 2: iconName = "images_add.svg"; break;
- case 3: iconName = "text.svg"; break;
- case 4: iconName = "text2.svg"; break;
- case 5: iconName = "rect.svg"; break;
- case 6: iconName = "round.svg"; break;
- case 7: iconName = "line.svg"; break;
- case 8: iconName = "qrcode.svg"; break;
- case 9: iconName = "barcode.svg"; break;
- case 10: iconName = "counter.svg"; break;
- case 11: iconName = "text3.svg"; break;
- }
-
- // 名称逻辑
- let nameText = item.name ? item.name :
- (item.type === 3 ? "TEXT_" + displayIndex :
- (item.type === 1 ? "IMG_" + displayIndex : "OBJ_" + displayIndex));
-
- // 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性)
- // 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1
- let isLocked = false;
- if (canvas.getObjects()[i + 1]) {
- let canvasObj = canvas.getObjects()[i + 1];
- isLocked = canvasObj.get('lockMovementX') === true;
- }
-
- // 锁定状态图标
- const lockIcon = isLocked ?
- '
' :
- '
';
-
- // 组装 HTML
- str = `
-
-

-
${nameText}
- ${lockIcon}
-
-
${displayIndex}
-
` + str;
- i++;
- }
- $(".obj_list").html(str);
-
- // 重新绑定点击事件(防止 updateList 后点击失效)
- $(".obj_list > div").on("click", function () {
- let imgId = $(this).find("img").attr("id");
- if (imgId) {
- let idx = parseInt(imgId.replace("obj", ""));
- selectObj(idx);
- }
- });
-}
-
-// 标记是否通过对象列表选中(用于区分鼠标点击和列表点击)
-window._selectingFromList = false;
-
-// 将 selectObj 附加到 window 对象,确保全局可访问
-window.selectObj = function selectObj(objIndices, isFromCanvas = false) {
- let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []);
-
- // 清除所有高亮
- $('.obj_list > div').css("background-color", "transparent");
-
- // 遍历列表查找并高亮
- $('.obj_list > div').each(function () {
- let $img = $(this).find("img");
- if ($img.length === 0) return;
-
- let idIndex = parseInt($img.attr("id").replace("obj", ""));
-
- if (indicesArray.includes(idIndex)) {
- $(this).css("background-color", "#6F7A84");
-
- // 自动滚动到可视区域
- if (isFromCanvas && indicesArray[0] === idIndex) {
- this.scrollIntoView({ behavior: "smooth", block: "nearest" });
- }
- }
- });
-
- // 如果是点击列表,触发画布选中
- if (!isFromCanvas && indicesArray.length === 1) {
- let target = canvas.getObjects()[indicesArray[0] + 1];
- if (target) {
- // 检查对象是否被锁定
- let isLocked = isObjectLocked(target);
-
- // 设置标志位,表示这是通过对象列表选中的(无论是否锁定)
- window._selectingFromList = true;
-
- // 如果被锁定,临时设置 selectable 和 evented 为 true,以便能够选中
- if (isLocked) {
- target.set({
- selectable: true,
- evented: true
- });
- }
-
- canvas.setActiveObject(target);
- canvas.requestRenderAll();
-
- // 如果对象被锁定,选中后立即恢复锁定状态(但保持选中状态)
- if (isLocked) {
- setTimeout(() => {
- target.set({
- selectable: false,
- evented: false
- });
- canvas.requestRenderAll();
- }, 0);
- }
-
- // 清除标志位(延迟清除,确保事件处理完成)
- setTimeout(() => {
- window._selectingFromList = false;
- }, 10);
-
- handleObjectSelected();
- }
- }
-}
-
-// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
-window.recordState = function recordState() {
- // 使用 window 变量作为后备,确保在 eval() 执行时也能访问
- const currentStep = window.step || step;
- const currentRecordObjs = window.recordObjs || recordObjs;
- const currentRecordJson = window.recordJson || recordJson;
- const currentObjs = window.objs || objs;
- const currentCanvas = window.canvas || canvas;
- ++currentStep.val;
- for (let i = currentStep.val; i < currentRecordObjs.length; i++) {
- currentRecordObjs.pop();
- currentRecordJson.pop();
- }
- currentRecordObjs.push(JSON.stringify(currentObjs))
- // 保存所有锁定相关的属性,确保锁定状态可以正确恢复
- let j = currentCanvas.toJSON(TO_JSON_PROPERTIES);
- j.objects[0].hoverCursor = "default";
- currentRecordJson.push(j);
-};
-// 向后兼容
-function recordState() {
- return window.recordState();
-}
-
-// 将 getIndex 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
-window.getIndex = function getIndex(target, _canvas = null) {
- let temp_objs;
- // 使用 window.canvas 作为默认值,确保在 eval() 执行时也能访问
- const currentCanvas = _canvas || window.canvas || canvas;
- if (!currentCanvas) {
- return 0;
- }
- temp_objs = currentCanvas.getObjects();
-
- let i = 0;
- for (let res of temp_objs) {
- if (target == res) {
- return i;
- }
- i++;
- }
- return 0;
-};
-// 向后兼容
-function getIndex(target, _canvas = null) {
- return window.getIndex(target, _canvas);
-}
-
-function getType(target) {
- // 如果是辅助线,返回 null
- if (target.isGuideLine) {
- return null;
- }
- let temp_objs = canvas.getObjects();
- let i = 0;
- for (let res of temp_objs) {
- if (target == res) {
- // 安全检查:确保 objs[i] 存在且有 type 属性
- if (!objs[i] || typeof objs[i].type === 'undefined') {
- return null;
- }
- return objs[i].type;
- }
- i++;
- }
- return null;
-}
-
-function checkName(name) {
- //检查字段名是否重复了
- for (let item of objs1) {
- if (item.name == name) {
- return false;
- }
- }
- for (let item of objs2) {
- if (item.name == name) {
- return false;
- }
- }
- return true;
-}
-
-function resName(pre_name) {
- //返回的是不重复的字段名
- let num = (objs1.length + objs2.length) - 1;
- do {
- num++;
- }
- while (!checkName(pre_name + num));//重复了就再加1
- return pre_name + num;
-}
-
-function getDisdance(x1, y1, x2, y2) {
- var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值
- var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值
- var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离
- return distance;
-}
-
-function getDeg(pointer) {
- // 计算点击位置与画布中心的坐标差
- var centerX = canvas.width / 2;
- var centerY = canvas.height / 2;
- var mouseX = pointer.x;
- var mouseY = pointer.y;
- var dx = mouseX - centerX;
- var dy = mouseY - centerY;
- // 计算旋转角度(弧度)
- var angleRad = Math.atan2(dy, dx);
- // 将角度转换为度数
- var angleDeg = angleRad * (180 / Math.PI);
- if (angleDeg < 0) {
- angleDeg += 360;
- }
- angleDeg += 90
- return angleDeg
-}
-
-function getCoordsMinX(acoords) {
- x = acoords[0].x;
- for (let item of acoords) {
- if (item.x < x) {
- x = item.x
- }
- }
- return x;
-}
-
-function getCoordsMaxX(acoords) {
- x = acoords[0].x;
- for (let item of acoords) {
- if (item.x > x) {
- x = item.x
- }
- }
- return x;
-}
-
-function getCoordsMinY(acoords) {
- y = acoords[0].y;
- for (let item of acoords) {
- if (item.y < y) {
- y = item.y
- }
- }
- return y;
-}
-
-function getCoordsMaxY(acoords) {
- y = acoords[0].y;
- for (let item of acoords) {
- if (item.y > y) {
- y = item.y
- }
- }
- return y;
-}
-
-function addBackground() {
- // 类型改变
- fabric.Image.fromURL('./public/images/bg_front_2.png', function (image) {
- image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2
- image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2
-
- image.selectable = false
- image.hoverable = false
- image.hoverCursor = 'default'
- image.setSrc(image.toDataURL(), function (image) {
- background_image1 = image
- background_image = background_image1
- window.background_image1 = background_image1;
- window.background_image = background_image;
- canvas1.add(image)
- canvas1.renderAll()
-
- recordObjs1.push(JSON.stringify(objs1))
- recordJson1.push(canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']))
- })
- // 类型改变
- fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) {
- image.left = ($('#canvas2').width() - image.width) / 2
- image.top = ($('#canvas2').height() - image.height) / 2
-
- image.selectable = false
- image.hoverable = false
- image.hoverCursor = 'default'
-
- image.setSrc(image.toDataURL(), function (image) {
- background_image2 = image
- window.background_image2 = background_image2;
- canvas2.add(image)
- canvas2.renderAll()
-
- recordObjs2.push(JSON.stringify(objs2))
- recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES))
-
- let file = GetFile().get('file')
- if (file && file != 'empty') {
- window.openFile(file);
- }
- })
- })
- is_bgi_add = true
- })
-}
-
-function addPic(pointer) {
- pre_add_image.left = pointer.x
- pre_add_image.top = pointer.y
-
- canvas.add(pre_add_image)
-
- objs.push({ type: 1, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addOutPic(pointer) {
- fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) {
- let width = canvas.getWidth()
- let height = canvas.getHeight()
- image.left = pointer.x
- image.top = pointer.y
- image.opacity = 1
- image.setSrc(image.toDataURL(), function (image) {
- canvas.add(image)
- objs.push({ type: 2, name: resName('OUT_PIC_'), black: false })
- canvas.renderAll()
- updateList()
- recordState()
- })
- })
-}
-
-function addText(pointer) {
- let text = new fabric.IText('TEXT', {
- left: pointer.x,
- top: pointer.y,
- fill: '#000000',
- textBackgroundColor: '',
- fontStyle: 'normal',
- fontWeight: 'normal',
- underline: false,
- linethrough: false,
- fontFamily: 'Arial'
- })
- text.setControlVisible('ml', false)
- text.setControlVisible('mt', false)
- text.setControlVisible('mr', false)
- text.setControlVisible('mb', false)
- canvas.add(text)
- objs.push({ type: 3, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addCircleText(pointer) {
- let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2)
- let angleDeg = getDeg(pointer)
- let text = new fabric.CurvedText('Circle TEXT', {
- left: pointer.x,
- top: pointer.y,
- fill: "#000000",
- textBackgroundColor: "",
- fontStyle: "normal",
- fontWeight: "normal",
- diameter: distance * 2,
- fontSize: 30,
- underline: false,
- linethrough: false,
- lockScalingX: true,
- lockScalingY: true,
- fontFamily: 'Arial',
- originX: 'center',
- originY: 'center',
- angle: angleDeg,
- flipped: pointer.y > canvas.height / 2 ? true : false
- });
- text.setControlVisible("ml", false);
- text.setControlVisible("mt", false);
- text.setControlVisible("mr", false);
- text.setControlVisible("mb", false);
- canvas.add(text);
- objs.push({ type: 11, black: false, circleCenter: 0 });
- canvas.renderAll();
- updateList();
- recordState();
-}
-
-function addOutText(pointer) {
- let text = new fabric.Text('MERGE TEXT', {
- left: pointer.x,
- top: pointer.y,
- fill: '#000000',
- fontStyle: 'normal',
- fontWeight: 'normal',
- textBackgroundColor: '',
- underline: false,
- linethrough: false,
- fontFamily: 'Arial',
- textAlign: 'center'
- })
- text.setControlVisible('ml', false)
- text.setControlVisible('mt', false)
- text.setControlVisible('mr', false)
- text.setControlVisible('mb', false)
-
- canvas.add(text)
- objs.push({ type: 4, name: resName('OUT_TEXT_'), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addRect(pointer) {
- let rect = new fabric.Rect({
- left: pointer.x,
- top: pointer.y,
- fill: '',
- stroke: "#ff0000",
- strokeWidth: 3,
- width: 100,
- height: 100
- })
- canvas.add(rect)
- objs.push({ type: 5, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addCircle(pointer) {
- let circle = new fabric.Circle({
- left: pointer.x,
- top: pointer.y,
- fill: '',
- stroke: '#ff0000',
- strokeWidth: 3,
- radius: 100,
- strokeUniform: true
- })
- canvas.add(circle)
- objs.push({ type: 6, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addLine(pointer) {
- let line = new fabric.Line([50, 100, 200, 100], {
- left: pointer.x,
- top: pointer.y,
- stroke: '#ff0000',
- strokeWidth: 3,
- strokeDashArray: [0, 0],
- })
-
- line.setControlVisible('tl', false)
- line.setControlVisible('tr', false)
- line.setControlVisible('br', false)
- line.setControlVisible('bl', false)
- line.setControlVisible('mt', false)
- line.setControlVisible('mb', false)
- canvas.add(line)
- objs.push({ type: 7, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-function addQrCode(pointer) {
- let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 })
- fabric.Image.fromURL(qr, function (image) {
- image.left = pointer.x
- image.top = pointer.y
- canvas.add(image)
- objs.push({ type: 8, name: resName('QR_CODE_'), color: '#000000', val: 'http://www.cardsoon.com', black: false })
- canvas.renderAll()
- updateList()
- recordState()
- })
-}
-
-function addBarCode(pointer) {
- JsBarcode('#barcode', '123456789', { margin: 0, lineColor: '#000000', fontSize: 18, font: 'Arial' })
- let barcode = document.getElementById('barcode')
- let bar = barcode.toDataURL('image/png')
- fabric.Image.fromURL(bar, function (image) {
- image.left = pointer.x
- image.top = pointer.y
-
- canvas.add(image)
- objs.push({ type: 9, name: resName('BAR_CODE_'), val: '123456789', color: '#000000', fontSize: 18, font: 'Arial', show: false, black: false })
- canvas.renderAll()
- updateList()
- recordState()
- })
-}
-
-function addCounter(pointer) {
- let text = new fabric.Text('0', {
- left: pointer.x,
- top: pointer.y,
- fill: '#000000'
- })
- canvas.add(text)
- objs.push({ type: 10, name: resName('COUNTER_'), start: 0, add: 1, black: false })
- canvas.renderAll()
- updateList()
- recordState()
-}
-
-var front_list = []
-ipcRenderer.on('font-list', (event, data) => {
- const newFontList = []
- data.map((item) => {
- if (item.indexOf('"') === 0) {
- newFontList.push(item.replace(/^"|"$/g, ''))
- } else {
- newFontList.push(item)
- }
- })
- front_list = newFontList
- for (let item of front_list) {
- $('#text_font_family').append("')
- $("#circle_text_font_family").append("");
- $('#out_text_font_family').append("')
- $('#barcode_font_family').append("')
- $('#count_font_family').append("')
- }
-})
-ipcRenderer.on('sys-lan', (event, data) => {
- let lang = localStorage.getItem('lang')
- if (lang) {
- s_lan = lang
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge(lang)
- $('#language_select').val(lang)
- return
- }
- switch (data) {
- case 'zh-CN':
- s_lan = 'zh'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('zh')
- $('#language_select').val('zh')
- localStorage.setItem('lang', 'zh')
- break
- case 'zh':
- s_lan = 'zh'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('zh')
- $('#language_select').val('zh')
- localStorage.setItem('lang', 'zh')
- break
- case 'zh-TW':
- s_lan = 'ozh'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('ozh')
- $('#language_select').val('ozh')
- localStorage.setItem('lang', 'ozh')
- break
- case 'en':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-AU':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-CA':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-GB':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-NZ':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-US':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- case 'en-ZA':
- s_lan = 'en'
- // 同步到全局作用域(用于 .jsc 文件加载)
- if (typeof window !== 'undefined') {
- window.s_lan = s_lan;
- }
- if (typeof global !== 'undefined') {
- global.s_lan = s_lan;
- }
- langua_ge('en')
- $('#language_select').val('en')
- localStorage.setItem('lang', 'en')
- break
- }
-})
-ipcRenderer.on('close', (event, message) => {
- layer.confirm(
- '' + language_str('whetherSave') + '',
- {
- //是否保存当前文件?
- btn: [language_str('save'), language_str('noSave'), language_str('cancel')], //['保存', '不保存', '取消']
- btn3: function (index, layero) { }
- },
- function (index, layero) {
- if (typeof window.output === 'function') {
- window.output(() => ipcRenderer.send('run-close'));
- }
- },
- function (index) {
- ipcRenderer.send('run-close')
- }
- )
-})
-
-addBackground() //添加背景 白色卡片
-
-initAligningGuidelines(canvas1)
-initAligningGuidelines(canvas2)
-
-// 初始化 Canvas 事件监听
-initCanvasEvents();
-
-// 初始化所有 Slider
-var circle_text_size_updating = false; // 标志位,防止循环更新
-var circle_text_size = slider.render({
- elem: '#circle_text_size',
- min: 10,
- max: 120,
- value: 50,
- change: function (value) {
- if (circle_text_size_updating) return;
- if (canvas && canvas.getActiveObject && canvas.getActiveObject()) {
- canvas.getActiveObject().set('fontSize', value);
- canvas.renderAll();
- if (typeof updateControls === 'function') {
- updateControls();
- }
- if (typeof recordState === 'function') {
- recordState();
- }
- }
- }
-});
-
-var circle_text_diameter = slider.render({
- elem: '#circle_text_diameter',
- min: 50,
- max: 1100,
- value: 500,
- change: function (value) {
- if (canvas && canvas.getActiveObject && canvas.getActiveObject()) {
- canvas.getActiveObject().set('diameter', value);
- canvas.renderAll();
- if (typeof updateControls === 'function') {
- updateControls();
- }
- if (typeof recordState === 'function') {
- recordState();
- }
- }
- }
-});
-
-var out_pic_op = slider.render({
- elem: '#out_pic_op',
- min: 0,
- max: 100,
- value: 0,
- change: function (value) {
- if (canvas.getActiveObject()) {
- canvas.getActiveObject().set('opacity', 1 - value / 100);
- canvas.renderAll();
- updateControls();
- recordState();
- }
- }
-});
-
-var pic_op = slider.render({
- elem: '#pic_op',
- min: 0,
- max: 100,
- value: 0,
- change: function (value) {
- if (canvas.getActiveObject()) {
- canvas.getActiveObject().set('opacity', 1 - value / 100);
- canvas.renderAll();
- updateControls();
- recordState();
- }
- }
-});
-
-var rect_op = slider.render({
- elem: '#rect_op',
- min: 0,
- max: 100,
- value: 0,
- change: function (value) {
- if (canvas.getActiveObject()) {
- canvas.getActiveObject().set('opacity', 1 - value / 100);
- canvas.renderAll();
- updateControls();
- recordState();
- }
- }
-});
-
-var circle_op = slider.render({
- elem: '#circle_op',
- min: 0,
- max: 100,
- value: 0,
- change: function (value) {
- if (canvas.getActiveObject()) {
- canvas.getActiveObject().set('opacity', 1 - value / 100);
- canvas.renderAll();
- updateControls();
- recordState();
- }
- }
-});
-
-//canvas.setZoom(0.5)
-//recordObjs1.push(JSON.stringify(objs1));
-//recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
-//recordObjs2.push(JSON.stringify(objs2));
-//recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
-canvas.renderAll() //重新渲染画布
-var zoomSlider = slider.render({
- elem: '#zoomSlide', //绑定元素
- min: 25,
- max: 500,
- value: 100,
- change(val) {
- canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), val / 100)
- zoom = val / 100
- //$(".canvas-container").css("transform","scale(" + val / 100 + ")")
- }
-})
-window.rotate = function (id) {
- let angle = $(`#${id}`).attr('angle') - 0
- angle += 90
- $(`#${id}`).css('transform', `rotate(${angle}deg)`)
- $(`#${id}`).attr('angle', angle)
-}
-// 注意:#display 事件绑定已在 ui.js 中定义(第2633行),此处已删除重复定义
-// 导出文件
-// 将 savePdf 附加到 window 对象,确保在 output.js 中也能访问
-window.savePdf = async function savePdf(buffer) {
- let fs = require('fs');
- // 弹出保存对话框
- const { canceled, filePath } = await dialog.showSaveDialog({
- title: '保存文件',
- defaultPath: path.join(exePath, 'output.pdf'), // 默认路径
- filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] // 文件过滤器
- });
-
- if (canceled) {
- return;
- }
-
- // 将Buffer写入PDF文件到用户选择的路径
- fs.writeFile(filePath, buffer, (err) => {
- if (err) {
- } else {
- layer.msg(language_str("saveSucc") + filePath);//'保存成功至'
- }
- });
-};
-// 向后兼容
-async function savePdf(buffer) {
- return window.savePdf(buffer);
-}
-
-// display_func 函数已移至 output.js,避免重复定义
-// function display_func(img1, img2, img3) { ... }
-
-// output 函数已移至 output.js,避免重复定义
-// function output(callback = null, _save = save) { ... }
-
-var _keyctrl = false,
- _keyc = false,
- _textEdit = false,
- _inputEdit = false
-$('input').focus(function () {
- _inputEdit = true
-})
-$('input').blur(function () {
- _inputEdit = false
-})
-$('textarea').focus(function () {
- _inputEdit = true
-})
-$('textarea').blur(function () {
- _inputEdit = false
-})
-$('body').keyup(function (event) {
- // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
- if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放
- _keyctrl = false
- }
- if (event.keyCode == 67) {
- _keyc = false
- }
-})
-$('body').keydown(function (event) {
- // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta)
- const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端
-
- // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的)
- if (!_inputEdit && !_textEdit) {
- // CTRL/CMD+N - 新建
- if (isModifierKey && event.keyCode == 78) {
- event.preventDefault();
- $('#new').click();
- return;
- }
- // CTRL/CMD+O - 打开
- if (isModifierKey && event.keyCode == 79) {
- event.preventDefault();
- $('#open').click();
- return;
- }
- // CTRL/CMD+S - 保存
- if (isModifierKey && !event.shiftKey && event.keyCode == 83) {
- event.preventDefault();
- $('#save').click();
- return;
- }
- // CTRL/CMD+SHIFT+S - 保存副本
- if (isModifierKey && event.shiftKey && event.keyCode == 83) {
- event.preventDefault();
- if (typeof window.output === 'function') {
- window.output(null, saveAs);
- }
- return;
- }
- // CTRL/CMD+Z - 还原
- if (isModifierKey && !event.shiftKey && event.keyCode == 90) {
- event.preventDefault();
- $('#previous').click();
- return;
- }
- // CTRL/CMD+Y - 重做
- if (isModifierKey && !event.shiftKey && event.keyCode == 89) {
- event.preventDefault();
- $('#next').click();
- return;
- }
- // CTRL/CMD+C - 复制
- if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') {
- event.preventDefault();
- window.copySelection();
- return;
- }
- // CTRL/CMD+V - 粘贴
- if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') {
- event.preventDefault();
- window.pasteFromClipboard();
- return;
- }
- // CTRL/CMD+SHIFT+DEL - 清空
- if (isModifierKey && event.shiftKey && event.keyCode == 46) {
- event.preventDefault();
- $('#clean').click();
- return;
- }
- // CTRL/CMD+DEL - 删除
- if (isModifierKey && !event.shiftKey && event.keyCode == 46) {
- event.preventDefault();
- $('#delete').click();
- return;
- }
- }
- // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
- if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd)
- _keyctrl = true
- if (_keyc && _textEdit) {
- //同时按下且在编辑状态
- setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1)
- //clipboard.writeText((clipboard.readText()+' '))
- }
- }
- if (event.keyCode == 67) {
- _keyc = true
- if (_keyctrl & _textEdit) {
- //同时按下且在编辑状态
- setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1)
- //clipboard.writeText((clipboard.readText()+' '))
- }
- }
- if (event.keyCode == 37) {
- //left
- let v = -1
- if (_keyctrl || event.ctrlKey || event.metaKey) {
- v = -5
- }
- if (canvas.getActiveObject()) {
- let x = canvas.getActiveObject().get('left')
- x += v
- canvas.getActiveObject().set('left', x)
- canvas.renderAll()
- recordState()
- updateControls()
- }
- }
- if (event.keyCode == 38) {
- //up
- let v = -1
- if (_keyctrl || event.ctrlKey || event.metaKey) {
- v = -5
- }
- if (canvas.getActiveObject()) {
- let y = canvas.getActiveObject().get('top')
- y += v
- canvas.getActiveObject().set('top', y)
- canvas.renderAll()
- recordState()
- updateControls()
- }
- }
- if (event.keyCode == 39) {
- //right
- let v = 1
- if (_keyctrl || event.ctrlKey || event.metaKey) {
- v = 5
- }
- if (canvas.getActiveObject()) {
- let x = canvas.getActiveObject().get('left')
- x += v
- canvas.getActiveObject().set('left', x)
- canvas.renderAll()
- recordState()
- updateControls()
- }
- }
- if (event.keyCode == 40) {
- //down
- let v = +1
- if (_keyctrl || event.ctrlKey || event.metaKey) {
- v = 5
- }
- if (canvas.getActiveObject()) {
- let y = canvas.getActiveObject().get('top')
- y += v
- canvas.getActiveObject().set('top', y)
- canvas.renderAll()
- recordState()
- updateControls()
- }
- }
- // 普通删除键(DEL键,没有按CTRL/CMD)
- if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) {
- let actObjs = canvas.getActiveObjects()
- let allObjs = canvas.getObjects()
- let flag = 0
- for (let item of actObjs) {
- if (item != allObjs[0]) {
- let i = getIndex(item, canvas) //0是背景,所以从1开始,但objs不包含背景图片,故要-1
- objs.splice(i - 1, 1)
- canvas.remove(item)
- flag = 1
- }
- }
- updateList()
- canvas.discardActiveObject()
- if (flag == 1) {
- recordState()
- canvas.renderAll()
- handleObjectSelected()
- }
- }
-})
-
-// 复制选中对象
-window.copySelection = function copySelection() {
- const activeObjects = canvas.getActiveObjects();
-
- if (!activeObjects || activeObjects.length === 0) {
- layer.msg("未选中任何对象")
- return;
- }
-
- // 存储原始对象用于克隆
- clipboardData.objects = activeObjects;
-}
-
-// 粘贴对象
-window.pasteFromClipboard = function pasteFromClipboard() {
- if (!clipboardData.objects || clipboardData.objects.length === 0) {
- layer.msg("剪贴板为空")
- return;
- }
-
- // 克隆所有对象
- fabric.util.enlivenObjects(
- clipboardData.objects.map(obj => obj.toObject()),
- clonedObjects => {
- // 设置新位置(添加偏移)
- clonedObjects.forEach((clone, index) => {
- let idx = getIndex(clipboardData.objects[index], canvas)
- clone.set({
- left: clone.left + clipboardData.offset,
- top: clone.top + clipboardData.offset,
- evented: true
- });
-
- // 添加到画布
- canvas.add(clone);
- let tobj = { ...objs[idx - 1] }
- if (tobj.name) tobj.name = tobj.name + objs.length
- objs.push(tobj);
- canvas.renderAll();
- updateList();
- recordState();
- });
-
- // 选中粘贴的对象
- canvas.discardActiveObject();
- const sel = new fabric.ActiveSelection(clonedObjects, {
- canvas: canvas
- });
- canvas.setActiveObject(sel);
- canvas.requestRenderAll();
- selectObj(objs.length - 1);
- // 增加下次偏移
- clipboardData.offset += 10;
- }
- );
-}
+// design2核心功能模块
+// 注意:此代码在layui.use回调函数内部执行
+// 全局变量已在主入口文件中定义,这里只需要初始化
+
+$('#canvas1').attr('height', document.body.clientHeight - 100)
+$('#canvas2').attr('height', document.body.clientHeight - 100)
+$('#object_attribute').css('height', '470px')
+$('#obj_list').css('height', document.body.clientHeight - 520)
+$('.obj_list').css('height', $('#obj_list').height() - 32)
+
+$('.main').show()
+$('#canvas1').attr('width', $('#canvas-div').width())
+$('#canvas2').attr('width', $('#canvas-div').width())
+
+// 初始化Canvas对象
+// 优化性能:在创建 Fabric Canvas 之前,先设置 canvas context 的 willReadFrequently
+try {
+ const canvas1El = document.getElementById('canvas1');
+ const canvas2El = document.getElementById('canvas2');
+ if (canvas1El) {
+ canvas1El.getContext('2d', { willReadFrequently: true });
+ }
+ if (canvas2El) {
+ canvas2El.getContext('2d', { willReadFrequently: true });
+ }
+} catch (e) {
+ // 如果浏览器不支持 willReadFrequently,忽略错误
+}
+
+canvas1 = new fabric.Canvas('canvas1')
+canvas2 = new fabric.Canvas('canvas2')
+// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
+window.canvas1 = canvas1;
+window.canvas2 = canvas2;
+// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史
+window._isApplyingHistory = false;
+fabric.Object.prototype.objectCaching = false
+ctx1 = canvas1.getSelectionContext()
+ctx2 = canvas2.getSelectionContext()
+is_bgi_add = false
+// 同步到 window 对象
+window.ctx1 = ctx1;
+window.ctx2 = ctx2;
+window.is_bgi_add = is_bgi_add;
+canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200)
+//console.log(canvas1.width / 2, canvas1.height / 2);
+//console.log($("#canvas-div").width() / 1200 > 1 ? 1 : $("#canvas-div").width() / 1200);
+canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200)
+zoom = $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200
+
+// 初始化标尺
+function initRulers() {
+ // dpi已在主入口文件中定义为全局变量
+ const mmToPx = dpi / 25.4; // 1mm = 11.811px (300 DPI)
+ const rulerHeight = 20;
+ const rulerWidth = 20;
+
+ // 获取实际画布的像素尺寸(px)
+ // design2.html 的实际画布尺寸是 1012px x 648px
+ const actualCanvasWidth = canvas1.width || 1012; // 实际画布宽度(px)
+ const actualCanvasHeight = canvas1.height || 648; // 实际画布高度(px)
+
+ // 获取显示区域的尺寸(包括 margin)
+ const canvasDiv = $('#canvas-div');
+ const canvasWrapper = $('.canvas-wrapper');
+ // 使用 canvas-wrapper 的宽度,如果没有则使用 canvas_div 的宽度
+ let canvasWidth = canvasWrapper.width();
+ let canvasHeight = canvasWrapper.height();
+ if (!canvasWidth || canvasWidth === 0) {
+ const canvas1Width = $('#canvas1').width() || canvasDiv.width() - 20;
+ canvasWidth = canvas1Width + 20; // 加上 margin-left: 20px
+ }
+ if (!canvasHeight || canvasHeight === 0) {
+ const canvas1Height = $('#canvas1').height() || canvasDiv.height() - 20;
+ canvasHeight = canvas1Height + 20; // 加上 margin-top: 20px
+ }
+
+ // 设置标尺尺寸(使用显示区域的尺寸)
+ $('#ruler-h').attr('width', canvasWidth).attr('height', rulerHeight);
+ $('#ruler-v').attr('width', rulerWidth).attr('height', canvasHeight);
+ $('.ruler-horizontal').css('width', canvasWidth + 'px');
+ $('.ruler-vertical').css('height', canvasHeight + 'px');
+
+ // 绘制水平标尺
+ function drawHorizontalRuler() {
+ const ctx = document.getElementById('ruler-h').getContext('2d', { willReadFrequently: true });
+ ctx.clearRect(0, 0, canvasWidth, rulerHeight);
+
+ // 背景
+ ctx.fillStyle = '#2C2C2C';
+ ctx.fillRect(0, 0, canvasWidth, rulerHeight);
+
+ // 边框
+ ctx.strokeStyle = '#1E1E1E';
+ ctx.lineWidth = 1;
+ ctx.strokeRect(0, 0, canvasWidth, rulerHeight);
+ ctx.beginPath();
+ ctx.moveTo(0, rulerHeight - 1);
+ ctx.lineTo(canvasWidth, rulerHeight - 1);
+ ctx.stroke();
+
+ // 刻度线
+ ctx.strokeStyle = '#888888';
+ ctx.fillStyle = '#CCCCCC';
+ ctx.font = '10px Arial';
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'top';
+
+ // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算
+ // 实际画布物理宽度 = actualCanvasWidth / dpi * 25.4 (mm)
+ // 显示区域宽度 = actualCanvasWidth * zoom (px)
+ // 所以:1mm 在显示区域中 = (actualCanvasWidth * zoom) / (actualCanvasWidth / dpi * 25.4) = zoom * dpi / 25.4
+ const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数
+ const marginOffset = 20; // canvas 的 margin-left: 20px
+ // 标尺0点应该在background_image的左上角位置
+ // background_image在画布坐标系中的left值,需要转换到标尺坐标系
+ // 画布从标尺的marginOffset位置开始,所以:标尺0点 = marginOffset + background_image.left (如果存在)
+ let originX = marginOffset;
+ if (typeof background_image !== 'undefined' && background_image) {
+ originX = marginOffset + background_image.left;
+ }
+ const startX = 0;
+ const endX = canvasWidth;
+
+ // 绘制主要刻度(每10mm)
+ // 从0点向左绘制负值刻度
+ for (let mm = -10; originX + mm * pxPerMm >= startX; mm -= 10) {
+ const x = originX + mm * pxPerMm;
+ if (x < startX) break;
+
+ ctx.beginPath();
+ ctx.moveTo(x, 0);
+ ctx.lineTo(x, rulerHeight);
+ ctx.stroke();
+
+ // 数字(负值)
+ ctx.fillText(mm + 'mm', x + 2, 2);
+ }
+ // 从0点向右绘制正值刻度
+ for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 10) {
+ const x = originX + mm * pxPerMm;
+ if (x > endX) break;
+
+ ctx.beginPath();
+ ctx.moveTo(x, 0);
+ ctx.lineTo(x, rulerHeight);
+ ctx.stroke();
+
+ // 数字
+ ctx.fillText(mm + 'mm', x + 2, 2);
+ }
+
+ // 绘制次要刻度(每1mm)
+ // 从0点向左绘制负值刻度
+ for (let mm = -1; originX + mm * pxPerMm >= startX; mm -= 1) {
+ const x = originX + mm * pxPerMm;
+ if (x < startX) break;
+ if (mm % 10 === 0) continue; // 跳过主要刻度
+
+ ctx.beginPath();
+ ctx.moveTo(x, rulerHeight - 8);
+ ctx.lineTo(x, rulerHeight);
+ ctx.stroke();
+ }
+ // 从0点向右绘制正值刻度
+ for (let mm = 0; originX + mm * pxPerMm <= endX; mm += 1) {
+ const x = originX + mm * pxPerMm;
+ if (x > endX) break;
+ if (mm % 10 === 0) continue; // 跳过主要刻度
+
+ ctx.beginPath();
+ ctx.moveTo(x, rulerHeight - 8);
+ ctx.lineTo(x, rulerHeight);
+ ctx.stroke();
+ }
+ }
+
+ // 绘制垂直标尺
+ function drawVerticalRuler() {
+ const ctx = document.getElementById('ruler-v').getContext('2d', { willReadFrequently: true });
+ ctx.clearRect(0, 0, rulerWidth, canvasHeight);
+
+ // 背景
+ ctx.fillStyle = '#2C2C2C';
+ ctx.fillRect(0, 0, rulerWidth, canvasHeight);
+
+ // 边框
+ ctx.strokeStyle = '#1E1E1E';
+ ctx.lineWidth = 1;
+ ctx.strokeRect(0, 0, rulerWidth, canvasHeight);
+ ctx.beginPath();
+ ctx.moveTo(rulerWidth - 1, 0);
+ ctx.lineTo(rulerWidth - 1, canvasHeight);
+ ctx.stroke();
+
+ // 刻度线
+ ctx.strokeStyle = '#888888';
+ ctx.fillStyle = '#CCCCCC';
+ ctx.font = '10px Arial';
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'top';
+
+ // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算
+ const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数
+ const marginOffset = 20; // canvas 的 margin-top: 20px (但还要加上ruler的高度)
+ const rulerHeightOffset = 20; // ruler 的高度
+ // 标尺0点应该在background_image的左上角位置
+ // background_image在画布坐标系中的top值,需要转换到标尺坐标系
+ // 画布从标尺的marginOffset + rulerHeightOffset位置开始,所以:标尺0点 = marginOffset + rulerHeightOffset + background_image.top (如果存在)
+ let originY = marginOffset + rulerHeightOffset;
+ if (typeof background_image !== 'undefined' && background_image) {
+ originY = marginOffset + rulerHeightOffset + background_image.top;
+ }
+ const startY = 0;
+ const endY = canvasHeight;
+
+ // 绘制主要刻度(每10mm)
+ // 从0点向上绘制负值刻度
+ for (let mm = -10; originY + mm * pxPerMm >= startY; mm -= 10) {
+ const y = originY + mm * pxPerMm;
+ if (y < startY) break;
+
+ ctx.beginPath();
+ ctx.moveTo(0, y);
+ ctx.lineTo(rulerWidth, y);
+ ctx.stroke();
+
+ // 数字(旋转90度,负值)
+ ctx.save();
+ ctx.translate(2, y + 8);
+ ctx.rotate(-Math.PI / 2);
+ ctx.fillText(mm + 'mm', 0, 0);
+ ctx.restore();
+ }
+ // 从0点向下绘制正值刻度
+ for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 10) {
+ const y = originY + mm * pxPerMm;
+ if (y > endY) break;
+
+ ctx.beginPath();
+ ctx.moveTo(0, y);
+ ctx.lineTo(rulerWidth, y);
+ ctx.stroke();
+
+ // 数字(旋转90度)
+ ctx.save();
+ ctx.translate(2, y + 8);
+ ctx.rotate(-Math.PI / 2);
+ ctx.fillText(mm + 'mm', 0, 0);
+ ctx.restore();
+ }
+
+ // 绘制次要刻度(每1mm)
+ // 从0点向上绘制负值刻度
+ for (let mm = -1; originY + mm * pxPerMm >= startY; mm -= 1) {
+ const y = originY + mm * pxPerMm;
+ if (y < startY) break;
+ if (mm % 10 === 0) continue; // 跳过主要刻度
+
+ ctx.beginPath();
+ ctx.moveTo(rulerWidth - 8, y);
+ ctx.lineTo(rulerWidth, y);
+ ctx.stroke();
+ }
+ // 从0点向下绘制正值刻度
+ for (let mm = 0; originY + mm * pxPerMm <= endY; mm += 1) {
+ const y = originY + mm * pxPerMm;
+ if (y > endY) break;
+ if (mm % 10 === 0) continue; // 跳过主要刻度
+
+ ctx.beginPath();
+ ctx.moveTo(rulerWidth - 8, y);
+ ctx.lineTo(rulerWidth, y);
+ ctx.stroke();
+ }
+ }
+
+ // 初始绘制
+ drawHorizontalRuler();
+ drawVerticalRuler();
+
+ // 监听窗口大小变化
+ $(window).on('resize', function () {
+ setTimeout(function () {
+ const canvasWrapper = $('.canvas-wrapper');
+ let newWidth = canvasWrapper.width();
+ let newHeight = canvasWrapper.height();
+ if (!newWidth || newWidth === 0) {
+ const canvas1Width = $('#canvas1').width() || $('#canvas-div').width() - 20;
+ newWidth = canvas1Width + 20; // 加上 margin-left: 20px
+ }
+ if (!newHeight || newHeight === 0) {
+ const canvas1Height = $('#canvas1').height() || $('#canvas-div').height() - 20;
+ newHeight = canvas1Height + 20; // 加上 margin-top: 20px
+ }
+ if (newWidth !== canvasWidth || newHeight !== canvasHeight) {
+ canvasWidth = newWidth;
+ canvasHeight = newHeight;
+ $('#ruler-h').attr('width', newWidth);
+ $('#ruler-v').attr('height', newHeight);
+ $('.ruler-horizontal').css('width', newWidth + 'px');
+ $('.ruler-vertical').css('height', newHeight + 'px');
+ drawHorizontalRuler();
+ drawVerticalRuler();
+ }
+ }, 100);
+ });
+
+ // 监听画布缩放/渲染变化,仅用于重绘标尺
+ // 辅助线交给 Fabric 自己渲染,否则会出现“重影 / 变两三根”的现象
+ canvas1.on('after:render', function () {
+ drawHorizontalRuler();
+ drawVerticalRuler();
+ });
+ canvas2.on('after:render', function () {
+ drawHorizontalRuler();
+ drawVerticalRuler();
+ });
+
+ // --- 辅助线功能 (参考 design1) ---
+ // canvasWrapper 已在上面声明,直接使用
+ const canvasBox = $('#canvas-div'); // design2 使用 canvas-div 作为容器
+
+ // 确保容器定位
+ if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative');
+
+ // 定义临时拖拽线 DOM
+ let $dragLine = null;
+
+ // 通用拖拽开始函数
+ const startDragGuide = (type, e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // 防止重复调用:如果正在拖拽,直接返回
+ if ($dragLine && $dragLine.length > 0) {
+ return;
+ }
+
+ // 创建临时显示的线
+ $dragLine = $('');
+ canvasWrapper.append($dragLine);
+
+ if (type === 'horizontal') {
+ // 从上往下拖 (水平线)
+ $dragLine.css({
+ left: 0, width: '100%', height: '1px', top: e.offsetY
+ });
+ } else {
+ // 从左往右拖 (垂直线)
+ $dragLine.css({
+ top: 0, height: '100%', width: '1px', left: e.offsetX
+ });
+ }
+
+ // 绑定 document 移动事件
+ $(document).on('mousemove.ruler', function (moveEvent) {
+ let wrapperOffset = canvasWrapper.offset();
+ if (type === 'horizontal') {
+ let relY = moveEvent.pageY - wrapperOffset.top;
+ $dragLine.css('top', relY + 'px');
+ } else {
+ let relX = moveEvent.pageX - wrapperOffset.left;
+ $dragLine.css('left', relX + 'px');
+ }
+ });
+
+ // 绑定松开事件 (创建真实的 Fabric 线)
+ $(document).on('mouseup.ruler', function (upEvent) {
+ $(document).off('mousemove.ruler mouseup.ruler');
+
+ if (!$dragLine) return;
+
+ // 获取最终位置
+ let wrapperOffset = canvasWrapper.offset();
+ let finalX = upEvent.pageX - wrapperOffset.left;
+ let finalY = upEvent.pageY - wrapperOffset.top;
+
+ // 移除临时线
+ $dragLine.remove();
+ $dragLine = null;
+
+ // 只有拖入画布区域才创建
+ if (finalX > rulerWidth && finalY > rulerHeight) {
+ createFabricGuide(type, finalX, finalY);
+ }
+ });
+ };
+
+ // 绑定到标尺 DOM
+ $('#ruler-h').off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e));
+ $('#ruler-v').off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e));
+
+ // --- 在画布中创建辅助线 ---
+ function createFabricGuide(type, domX, domY) {
+ // domX 和 domY 是相对于 canvas-wrapper 的坐标
+ // 需要转换为相对于 canvas 的坐标,然后再转换为 Fabric 画布坐标
+
+ // 获取 canvas 相对于 canvas-wrapper 的位置
+ let canvas1El = $('#canvas1');
+ let canvasPosition = canvas1El.position();
+ let canvasMarginLeft = parseFloat(canvas1El.css('margin-left')) || 0;
+ let canvasMarginTop = parseFloat(canvas1El.css('margin-top')) || 0;
+
+ // 计算相对于 canvas 的坐标(减去 canvas 在 wrapper 中的位置和 margin)
+ let relX = domX - canvasPosition.left - canvasMarginLeft;
+ let relY = domY - canvasPosition.top - canvasMarginTop;
+
+ // 转为 Fabric 坐标 (除以 zoom)
+ let canvasX = relX / zoom;
+ let canvasY = relY / zoom;
+
+ // 使用与画布尺寸相关的长度,避免“整块区域都像辅助线”
+ let lineExtent = Math.max(canvas1.width, canvas1.height) * 2;
+
+ // 构造线对象
+ let linePoints = [];
+ if (type === 'horizontal') {
+ linePoints = [-lineExtent, canvasY, lineExtent, canvasY];
+ } else {
+ linePoints = [canvasX, -lineExtent, canvasX, lineExtent];
+ }
+
+ let guideLine = new fabric.Line(linePoints, {
+ stroke: '#0066FF',
+ strokeWidth: 1 / zoom,
+ opacity: 0.9,
+ shadow: {
+ color: '#0066FF',
+ blur: 6,
+ offsetX: 0,
+ offsetY: 0,
+ affectStroke: true
+ },
+ selectable: true,
+ evented: true,
+ hasControls: false,
+ hasBorders: false,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
+ // 只在靠近线条时触发命中,避免“大块区域都像辅助线”
+ // 同时适当放宽 tolerance,保证易于点中拖拽
+ perPixelTargetFind: true,
+ targetFindTolerance: 6,
+ padding: 2,
+ isGuideLine: true,
+ ignoreSave: true
+ });
+
+ // 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉)
+ guideLine.on('moving', function () {
+ if (type === 'horizontal') {
+ // 水平线:根据当前中心点的 Y,重算 x1/x2,使其横穿画布
+ const cy = this.getCenterPoint().y;
+ this.set({
+ x1: -lineExtent,
+ y1: cy,
+ x2: lineExtent,
+ y2: cy
+ });
+ } else {
+ // 垂直线:根据当前中心点的 X,重算 y1/y2,使其竖穿画布
+ const cx = this.getCenterPoint().x;
+ this.set({
+ x1: cx,
+ y1: -lineExtent,
+ x2: cx,
+ y2: lineExtent
+ });
+ }
+ });
+
+ // 确定应该添加到哪个 canvas(使用当前活动的 canvas)
+ let targetCanvas = canvas;
+ if (!targetCanvas || (targetCanvas !== canvas1 && targetCanvas !== canvas2)) {
+ // 如果没有活动的 canvas,默认使用 canvas1
+ targetCanvas = canvas1;
+ }
+
+ targetCanvas.add(guideLine);
+ targetCanvas.setActiveObject(guideLine);
+ targetCanvas.renderAll();
+ }
+
+}
+initRulers();
+$('#source_front').width(zoom * $('#source_front').width())
+$('#source_back').width(zoom * $('#source_back').width())
+$('.canvas-container:eq(1)').hide()
+canvas = canvas1
+window.canvas = canvas
+fabric.Object.prototype.set({
+ borderColor: '#9013FE',
+ cornerColor: '#9013FE', //激活状态角落图标的填充颜色
+ cornerStrokeColor: '#9013FE', //激活状态角落图标的边框颜色
+ borderOpacityWhenMoving: 1,
+ borderScaleFactor: 1,
+ cornerSize: 6,
+ cornerStyle: 'circle', //rect,circle
+ centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点
+ centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转
+ transparentCorners: false, //激活状态角落的图标是否透明
+ rotatingPointOffset: 20, //旋转距旋转体的距离
+ padding: 0
+})
+canvas1.preserveObjectStacking = true
+canvas2.preserveObjectStacking = true
+
+// ==========================================
+// 对象选择和属性更新函数
+// ==========================================
+// 将 updateControls 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
+window.updateControls = function updateControls() {
+ // 使用 window.canvas 和 window.background_image 作为后备,确保在 eval() 执行时也能访问
+ const currentCanvas = window.canvas || canvas;
+ const currentBackgroundImage = window.background_image || background_image;
+ if (!currentCanvas || !currentCanvas.getActiveObject()) return;
+ $("#text_r").val(currentCanvas.getActiveObject().get("angle").toFixed(0));
+ $("#text_x").val((currentCanvas.getActiveObject().get("left") - currentBackgroundImage.left).toFixed(0));
+ $("#text_y").val((currentCanvas.getActiveObject().get("top") - currentBackgroundImage.top).toFixed(0));
+ $("#text_h").val((currentCanvas.getActiveObject().get("scaleY") * currentCanvas.getActiveObject().get("height")).toFixed(0));
+ $("#text_w").val((currentCanvas.getActiveObject().get("scaleX") * currentCanvas.getActiveObject().get("width")).toFixed(0));
+ // 根据锁定状态显示图标(使用lockMovementX判断,因为锁定时会设置所有lock属性)
+ if (currentCanvas.getActiveObject().get("lockMovementX")) {
+ $("#lock_img").attr("src", "public/images/lock20.png")//已锁定
+ } else {
+ $("#lock_img").attr("src", "public/images/unlock20.png")//未锁定
+ }
+};
+// 向后兼容
+function updateControls() {
+ return window.updateControls();
+}
+function selectObject(target, index) {
+ updateControls();
+ $("#base_control").show();
+}
+
+function selectPic(target, index) {
+ // 检查是否有选中对象,如果没有则直接返回
+ if (!canvas || !canvas.getActiveObject()) return;
+
+ $("#pic_control").show();
+ $("#set_bg").show();
+ let activeObj = canvas.getActiveObject();
+
+ // 检查对象是否被锁定:
+ // - 从画布点击时(_selectingFromList 为 false):不允许选中,被锁定的对象保持不可选状态
+ // - 从对象列表点击时(_selectingFromList 为 true):允许选中,但对象在画布上仍然保持锁定
+ if (!window._selectingFromList && isObjectLocked(activeObj)) {
+ canvas.discardActiveObject();
+ canvas.renderAll();
+ return;
+ }
+
+ if (typeof pic_op !== 'undefined' && pic_op && typeof pic_op.setValue === 'function') {
+ pic_op.setValue(100 - (activeObj.get("opacity") * 100));
+ } else {
+ $("#pic_op").next().children(".layui-slider-bar").css("width", (1 - activeObj.get("opacity")) * 100 + "%");
+ $("#pic_op").next().children(".layui-slider-handle").css("left", (1 - activeObj.get("opacity")) * 100 + "%");
+ }
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectOutPic(target, index) {
+ $("#out_pic_control").show();
+ if (typeof out_pic_op !== 'undefined' && out_pic_op.setValue) {
+ out_pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
+ } else {
+ $("#out_pic_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ $("#out_pic_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ }
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#out_pic_field_name").val(objs[idx - 1].name);
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectText(target, index) {
+ $("#text_control").show();
+ $("#text_color").val(canvas.getActiveObject().get("fill"));
+ $("#text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor"));
+ let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
+ if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
+ $("#text_size").val(val);
+ $("#text_text").val(canvas.getActiveObject().get("text"));
+ $("#text_font_family").val(canvas.getActiveObject().get("fontFamily"));
+
+ if (canvas.getActiveObject().get("underline") == true) {
+ $("#text_underline").addClass("ui-radio-acitve")
+ } else {
+ $("#text_underline").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("fontWeight") == "bold") {
+ $("#text_bold").addClass("ui-radio-acitve")
+ } else {
+ $("#text_bold").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("fontStyle") == "italic") {
+ $("#text_italics").addClass("ui-radio-acitve")
+ } else {
+ $("#text_italics").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("linethrough") == true) {
+ $("#text_linethrough").addClass("ui-radio-acitve")
+ } else {
+ $("#text_linethrough").removeClass("ui-radio-acitve")
+ }
+ $("#circle_text_control").hide();
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectCircleText(target, index) {
+ const activeObj = target || canvas.getActiveObject();
+ if (!activeObj) return;
+
+ let idx = getIndex(activeObj);
+ $("#circle_text_control").show();
+ $("#circle_text_color").val(activeObj.get("fill"));
+ $("#circle_text_back_color").val(activeObj.get("textBackgroundColor") == "" ? '#ffffff' : activeObj.get("textBackgroundColor"));
+
+ if (typeof circle_text_size !== 'undefined' && circle_text_size && typeof circle_text_size.setValue === 'function') {
+ if (typeof circle_text_size_updating !== 'undefined') {
+ circle_text_size_updating = true;
+ }
+ circle_text_size.setValue(activeObj.get("fontSize") - 10);
+ if (typeof circle_text_size_updating !== 'undefined') {
+ circle_text_size_updating = false;
+ }
+ }
+
+ if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter && typeof circle_text_diameter.setValue === 'function') {
+ circle_text_diameter.setValue(activeObj.get("diameter") - 50);
+ }
+
+ $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : '');
+ $("#circle_text_text").val(activeObj.get("text"));
+ $("#circle_text_font_family").val(activeObj.get("fontFamily"));
+
+ if (activeObj.get("underline") == true) {
+ $("#circle_text_underline").addClass("ui-radio-acitve")
+ } else {
+ $("#circle_text_underline").removeClass("ui-radio-acitve")
+ }
+ if (activeObj.get("fontWeight") == "bold") {
+ $("#circle_text_bold").addClass("ui-radio-acitve")
+ } else {
+ $("#circle_text_bold").removeClass("ui-radio-acitve")
+ }
+ if (activeObj.get("fontStyle") == "italic") {
+ $("#circle_text_italics").addClass("ui-radio-acitve")
+ } else {
+ $("#circle_text_italics").removeClass("ui-radio-acitve")
+ }
+ if (activeObj.get("linethrough") == true) {
+ $("#circle_text_linethrough").addClass("ui-radio-acitve")
+ } else {
+ $("#circle_text_linethrough").removeClass("ui-radio-acitve")
+ }
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#rotate").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectOutText(target, index) {
+ const align = canvas.getActiveObject().get('textAlign')
+ if (align === 'left') {
+ $('#out_left_alignment').parent().addClass('tool1')
+ } else if (align === 'right') {
+ $('#out_right_alignment').parent().addClass('tool1')
+ } else {
+ $('#out_center_alignment').parent().addClass('tool1')
+ }
+ $("#out_text_control").show();
+ $("#out_text_color").val(canvas.getActiveObject().get("fill"));
+ $("#out_text_text").val(canvas.getActiveObject().get("text"));
+ let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
+ if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
+ $("#out_text_size").val(val);
+ $("#out_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor"));
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#out_text_field_name").val(objs[idx - 1].name);
+ $("#out_text_font_family").val(canvas.getActiveObject().get("fontFamily"));
+ $("#exceed").prop("checked", objs[idx - 1].exceed ? 'checked' : '');
+ $("#autoWrap").prop("checked", objs[idx - 1].autoWrap ? 'checked' : '');
+ $("#fixSize").prop("checked", objs[idx - 1].fixSize ? 'checked' : '');
+ if (canvas.getActiveObject().get("underline") == true) {
+ $("#out_text_underline").addClass("ui-radio-acitve")
+ } else {
+ $("#out_text_underline").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("fontWeight") == "bold") {
+ $("#out_text_bold").addClass("ui-radio-acitve")
+ } else {
+ $("#out_text_bold").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("fontStyle") == "italic") {
+ $("#out_text_italics").addClass("ui-radio-acitve")
+ } else {
+ $("#out_text_italics").removeClass("ui-radio-acitve")
+ }
+ if (canvas.getActiveObject().get("linethrough") == true) {
+ $("#out_text_linethrough").addClass("ui-radio-acitve")
+ } else {
+ $("#out_text_linethrough").removeClass("ui-radio-acitve")
+ }
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectRect(target, index) {
+ $("#rect_control").show();
+ $("#rect_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill"));
+ $("#rect_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke"));
+ $("#rect_stroke_width").val(canvas.getActiveObject().get("strokeWidth"));
+ $("#rect_radius").val(canvas.getActiveObject().get("rx"));
+ if (typeof rect_op !== 'undefined' && rect_op.setValue) {
+ rect_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
+ } else {
+ $("#rect_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ $("#rect_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ }
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#circle_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectCircle(target, index) {
+ $("#circle_control").show();
+ $("#circle_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill"));
+ $("#circle_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke"));
+ $("#circle_stroke_width").val(canvas.getActiveObject().get("strokeWidth"));
+ if (typeof circle_op !== 'undefined' && circle_op.setValue) {
+ circle_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100));
+ } else {
+ $("#circle_op").next().children(".layui-slider-bar").css("width", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ $("#circle_op").next().children(".layui-slider-handle").css("left", (1 - canvas.getActiveObject().get("opacity")) * 100 + "%");
+ }
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#line_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectLine(target, index) {
+ $("#line_control").show();
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#line_color").val(canvas.getActiveObject().get("stroke"));
+ $("#line_dist").val(canvas.getActiveObject().get("strokeDashArray")[0]);
+ $("#text_h").val(canvas.getActiveObject().get("strokeWidth"))
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectQrCode(target, index) {
+ $("#qrcode_Controll").show();
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#qrcode_field_name").val(objs[idx - 1].name);
+ $("#qrcode_color").val(objs[idx - 1].color);
+ $("#qrcode_val").val(objs[idx - 1].val);
+ $("#line_control").hide();
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#counter_control").hide();
+ $("#barcode_controll").hide();
+}
+
+function selectBarCode(target, index) {
+ $("#barcode_controll").show();
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#barcode_field_name").val(objs[idx - 1].name);
+ $("#barcode_font_family").val(objs[idx - 1].font);
+ let val = parseFloat(objs[idx - 1].fontSize) * 72 / dpi
+ if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
+ $("#barcode_size").val(val);
+ $("#barcode_val").val(objs[idx - 1].val);
+ $("#barcode_show").prop("checked", objs[idx - 1].show ? 'checked' : '');
+ $("#line_control").hide();
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#counter_control").hide();
+ $("#qrcode_Controll").hide();
+}
+
+function selectCounter(target, index) {
+ $("#counter_control").show();
+ let idx = getIndex(canvas.getActiveObject(), canvas);
+ $("#counter_start").val(objs[idx - 1].start);
+ $("#counter_add").val(objs[idx - 1].add);
+ $("#counter_color").val(canvas.getActiveObject().get("fill"));
+ $("#count_font_family").val(canvas.getActiveObject().get("fontFamily"));
+ let val = parseFloat(canvas.getActiveObject().get("fontSize")) * 72 / dpi
+ if (val % 0.5 !== 0) val = Math.round(val * 2) / 2
+ $("#count_size").val(val);
+ $("#line_control").hide();
+ $("#pic_control").hide();
+ $("#set_bg").hide();
+ $("#out_pic_control").hide();
+ $("#text_control").hide();
+ $("#circle_text_control").hide();
+ $("#out_text_control").hide();
+ $("#rect_control").hide();
+ $("#circle_control").hide();
+ $("#qrcode_Controll").hide();
+ $("#barcode_controll").hide();
+}
+
+function handleObjectSelected(e) {
+ // 确定当前活动的 canvas(从事件参数或全局变量)
+ let currentCanvas = (e && e.target && (e.target.canvas === canvas1 || e.target.canvas === canvas2))
+ ? (e.target.canvas === canvas1 ? canvas1 : canvas2)
+ : canvas;
+
+ // 如果事件来自 canvas,更新全局 canvas 变量
+ if (currentCanvas === canvas1 || currentCanvas === canvas2) {
+ canvas = currentCanvas;
+ objs = (currentCanvas === canvas1) ? objs1 : objs2;
+ window.canvas = canvas;
+ window.objs = objs;
+ }
+
+ let select_obj = canvas.getActiveObject();
+
+ // 1. 未选中任何对象
+ if (!select_obj) {
+ $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
+ $("#component_type").text(language_str("bg"));
+ selectObj(-1, true); // 传 true,只更新UI
+ return;
+ }
+
+ // 2. 如果是辅助线,直接返回,不处理(在获取索引之前检查)
+ if (select_obj.isGuideLine) {
+ return;
+ }
+
+ // 3. 获取索引
+ let i = getIndex(select_obj, canvas);
+
+ // 如果不是通过对象列表选中的,且对象被锁定,则取消选中
+ if (!window._selectingFromList && isObjectLocked(select_obj)) {
+ canvas.discardActiveObject();
+ canvas.renderAll();
+ $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
+ $("#component_type").text(language_str("bg"));
+ selectObj(-1, true);
+ return;
+ }
+
+ if (i != 0) {
+ // --- 选中了普通对象 ---
+ --i; // 转换为 objs 数组下标
+
+ // 安全检查:确保 objs[i] 存在且有 type 属性
+ if (objs[i] && typeof objs[i].type !== 'undefined') {
+ switch (objs[i].type) {
+ case 1: // 图片
+ $("#component_type").text(language_str("img"));
+ selectObject(select_obj, i); selectPic(select_obj, i); break;
+ case 2: // 合成图片
+ $("#component_type").text(language_str("simg"));
+ selectObject(select_obj, i); selectOutPic(select_obj, i); break;
+ case 3: // 文本
+ $("#component_type").text(language_str("text"));
+ selectObject(select_obj, i); selectText(select_obj, i); break;
+ case 4: // 合成文本
+ $("#component_type").text(language_str("stext"));
+ selectObject(select_obj, i); selectOutText(select_obj, i); break;
+ case 5: // 矩形
+ $("#component_type").text(language_str("rect"));
+ selectObject(select_obj, i); selectRect(select_obj, i); break;
+ case 6: // 圆形
+ $("#component_type").text(language_str("circ"));
+ selectObject(select_obj, i); selectCircle(select_obj, i); break;
+ case 7: // 直线
+ $("#component_type").text(language_str("line"));
+ selectObject(select_obj, i); selectLine(select_obj, i); break;
+ case 8: // 二维码
+ $("#component_type").text(language_str("qrc"));
+ selectObject(select_obj, i); selectQrCode(select_obj, i); break;
+ case 9: // 条形码
+ $("#component_type").text(language_str("barc"));
+ selectObject(select_obj, i); selectBarCode(select_obj, i); break;
+ case 10: // 计数器
+ $("#component_type").text(language_str("counter"));
+ selectObject(select_obj, i); selectCounter(select_obj, i); break;
+ case 11: // 圆形文本
+ $("#component_type").text(language_str("ctext"));
+ selectObject(select_obj, i); selectCircleText(select_obj, i); break;
+ }
+ }
+
+ // 【关键】这里传入 true!
+ // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了
+ selectObj(i, true);
+
+ } else {
+ // --- 选中了背景 ---
+ $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
+ selectObj(-1, true);
+ $("#component_type").text(language_str("bg"));
+ }
+}
+
+function handleObjectMoving(e) {
+ let obj = e.target;
+ // 跳过辅助线
+ if (obj.isGuideLine) {
+ updateControls();
+ return;
+ }
+ let idx = getIndex(obj, canvas);
+ // 圆形文本特殊处理
+ // 安全检查:确保 objs[idx - 1] 存在且有 type 属性
+ if (idx > 0 && idx <= objs.length && objs[idx - 1] && typeof objs[idx - 1].type !== 'undefined' && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) {
+ let pointer = { x: obj.left, y: obj.top };
+ let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2);
+ let angleDeg = getDeg(pointer);
+ let flipped = pointer.y > canvas.height / 2 ? true : false;
+
+ if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter.setValue) {
+ circle_text_diameter.setValue(distance * 2 - 50);
+ }
+ obj.set("diameter", distance * 2);
+ obj.set("angle", angleDeg);
+ obj.set("flipped", flipped);
+ }
+ updateControls();
+}
+
+// 辅助函数:检查对象是否被锁定
+// 只有当 lockMovementX 为 true 时才认为对象被锁定
+// selectable 为 false 可能是其他原因(如背景图),所以只检查 lockMovementX
+function isObjectLocked(obj) {
+ if (!obj) return false;
+ // 主要检查 lockMovementX,这是锁定的主要标志
+ return obj.get('lockMovementX') === true;
+}
+// 暴露到全局作用域,确保其他模块可以访问
+window.isObjectLocked = isObjectLocked;
+
+// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
+// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped)
+const TO_JSON_PROPERTIES = [
+ "selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline",
+ "evented", "linethrough", "textBackgroundColor", "diameter", "flipped"
+];
+// 暴露到全局作用域
+window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES;
+
+// 辅助函数:解锁所有对象(加载时使用)
+function unlockAllObjects(canvas) {
+ if (!canvas) return;
+ const objects = canvas.getObjects();
+ objects.forEach((obj, index) => {
+ // 背景对象(索引0)保持锁定状态
+ if (index === 0) {
+ obj.set({
+ lockMovementX: true,
+ lockMovementY: true,
+ lockRotation: true,
+ lockScalingX: true,
+ lockScalingY: true,
+ lockSkewingX: true,
+ lockSkewingY: true,
+ selectable: false,
+ evented: false
+ });
+ } else {
+ // 其他对象默认解锁
+ obj.set({
+ lockMovementX: false,
+ lockMovementY: false,
+ lockRotation: false,
+ lockScalingX: false,
+ lockScalingY: false,
+ lockSkewingX: false,
+ lockSkewingY: false,
+ selectable: true,
+ evented: true
+ });
+ }
+ });
+}
+// 暴露到全局作用域
+window.unlockAllObjects = unlockAllObjects;
+
+// 将 handleObjectSelected 附加到 window 对象,确保全局可访问
+window.handleObjectSelected = handleObjectSelected;
+
+// 防止重复初始化事件监听器
+var _canvasEventsInitialized = false;
+
+function initCanvasEvents() {
+ // 如果已经初始化过,直接返回
+ if (_canvasEventsInitialized) {
+ return;
+ }
+ _canvasEventsInitialized = true;
+
+ [canvas1, canvas2].forEach(currentCanvas => {
+ // 1. 鼠标移动
+ currentCanvas.on("mouse:move", (e) => {
+ let offsetX = 0, offsetY = 0;
+ if (currentCanvas === canvas2 && typeof background_image !== 'undefined') {
+ offsetX = background_image.left;
+ offsetY = background_image.top;
+ }
+ if (e.pointer) {
+ $("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0));
+ }
+ });
+
+ // 2. 鼠标移出
+ currentCanvas.on("mouse:out", () => {
+ // 可以在这里隐藏标尺辅助线
+ });
+
+ // 3. 鼠标松开 (添加对象)
+ currentCanvas.on("mouse:up", (e) => {
+ if (addState === 0) return;
+ const ptr = e.pointer;
+ switch (addState) {
+ case 1: addPic(ptr); break;
+ case 2: addOutPic(ptr); break;
+ case 3: addText(ptr); break;
+ case 4: addOutText(ptr); break;
+ case 5: addRect(ptr); break;
+ case 6: addCircle(ptr); break;
+ case 7: addLine(ptr); break;
+ case 8: addQrCode(ptr); break;
+ case 9: addBarCode(ptr); break;
+ case 10: addCounter(ptr); break;
+ case 11: addCircleText(ptr); break;
+ }
+ addState = 0;
+ if (typeof background_image !== 'undefined') background_image.hoverCursor = "default";
+ currentCanvas.renderAll();
+ $("#addPic, #addOutPic, #addText, #addOutText, #addRect, #addCircle, #addLine, #addQrCode, #addBarCode, #addCounter, #addCircleText").css("background-color", "");
+ });
+
+ // 4. 选中处理
+ currentCanvas.on('mouse:down', (e) => {
+ // 更新全局 canvas 变量
+ canvas = currentCanvas;
+ objs = (currentCanvas === canvas1) ? objs1 : objs2;
+ handleObjectSelected(e);
+ });
+ // 阻止被锁定的对象被框选选中,同时彻底排除辅助线
+ currentCanvas.on('before:selection:created', (e) => {
+ if (!e.targets || e.targets.length === 0) return;
+
+ // 调试日志:原始选区
+ console.log('[Guide] before:selection:created raw targets', e.targets.map(t => ({
+ isGuideLine: !!t.isGuideLine,
+ type: t.type,
+ left: t.left,
+ top: t.top
+ })));
+
+ // 选区里彻底排除辅助线:不允许辅助线进入 ActiveSelection
+ // 只保留未锁定的普通对象
+ e.targets = e.targets.filter(target => {
+ if (target.isGuideLine) return false; // 完全移除辅助线
+ return !isObjectLocked(target); // 过滤掉被锁定的普通对象
+ });
+
+ // 调试日志:过滤后的选区
+ console.log('[Guide] before:selection:created filtered targets', e.targets.map(t => ({
+ isGuideLine: !!t.isGuideLine,
+ type: t.type,
+ left: t.left,
+ top: t.top
+ })));
+ });
+
+ // 添加 selection 事件以确保对象选中时正确显示属性面板
+ // 注意:真正排除辅助线的逻辑已经在 before:selection:created 里完成,
+ // 这里不再对 ActiveSelection 做任何重建,避免 Fabric 内部递归导致栈溢出。
+ currentCanvas.on('selection:created', (e) => {
+ canvas = currentCanvas;
+ objs = (currentCanvas === canvas1) ? objs1 : objs2;
+ handleObjectSelected(e);
+ });
+ currentCanvas.on('selection:updated', (e) => {
+ canvas = currentCanvas;
+ objs = (currentCanvas === canvas1) ? objs1 : objs2;
+ handleObjectSelected(e);
+ });
+ currentCanvas.on('object:selected', (e) => {
+ canvas = currentCanvas;
+ objs = (currentCanvas === canvas1) ? objs1 : objs2;
+
+ // 如果不是通过对象列表选中的,且对象被锁定,则取消选中
+ // 注意:只有真正被锁定的对象才会被取消选中,未锁定的对象应该正常选中
+ if (!window._selectingFromList && e.target && isObjectLocked(e.target)) {
+ currentCanvas.discardActiveObject();
+ currentCanvas.renderAll();
+ return;
+ }
+
+ handleObjectSelected(e);
+ // 更新对象列表以同步锁状态
+ if (typeof window.updateList === 'function') {
+ window.updateList();
+ }
+ });
+
+ // 5. 对象移动
+ currentCanvas.on("object:moving", (e) => {
+ // 阻止背景图(索引0)被移动(如果已锁定)
+ let movingObj = e.target;
+ let idx = getIndex(movingObj, currentCanvas);
+ if (idx === 0) {
+ let bgObj = currentCanvas.getObjects()[0];
+ if (bgObj && bgObj.get('lockMovementX') === true) {
+ e.e.preventDefault();
+ e.e.stopPropagation();
+ return false;
+ }
+ }
+ handleObjectMoving(e);
+ });
+
+ // 6. 变换事件
+ currentCanvas.on("object:rotating", updateControls);
+ currentCanvas.on("object:scaling", updateControls);
+ currentCanvas.on("object:rotated", () => { recordState(); });
+ currentCanvas.on('text:editing:entered', () => { _textEdit = true; });
+ currentCanvas.on('text:editing:exited', () => { _textEdit = false; });
+ currentCanvas.on("text:changed", (e) => { $("#text_text").val(e.target.get("text")); });
+
+ // 7. 对象修改后
+ currentCanvas.on('object:modified', function (event) {
+ if (currentCanvas.getActiveObjects().length > 1) return;
+ // 跳过辅助线
+ if (event.target.isGuideLine) {
+ return;
+ }
+ let idx = getIndex(event.target, currentCanvas);
+ if (idx <= 0) return;
+ let objData = objs[idx - 1];
+ // 安全检查:确保 objData 存在且有 type 属性
+ if (!objData || typeof objData.type === 'undefined') {
+ return;
+ }
+ let target = event.target;
+
+ if ([3, 4, 10].includes(objData.type)) {
+ target.fontSize = (target.fontSize * target.scaleX).toFixed(0);
+ target.scaleX = 1; target.scaleY = 1;
+ target._clearCache();
+ if (objData.type == 3) $("#text_size").val(target.fontSize);
+ if (objData.type == 4) $("#out_text_size").val(target.fontSize);
+ if (objData.type == 10) $("#count_size").val(target.fontSize);
+ currentCanvas.renderAll();
+ updateControls();
+ } else if (objData.type == 5) {
+ target.width *= target.scaleX; target.height *= target.scaleY;
+ target.scaleX = 1; target.scaleY = 1;
+ updateControls();
+ }
+ recordState();
+ // 更新对象列表以同步锁状态
+ if (typeof window.updateList === 'function') {
+ window.updateList();
+ }
+ });
+ });
+}
+
+// 将 updateList 附加到 window 对象,确保全局可访问
+window.updateList = function updateList() {
+ // 确保使用正确的 objs(优先使用 window.objs,因为它可能被撤回/重做更新)
+ const currentObjs = window.objs || objs;
+ const currentCanvas = window.canvas || canvas;
+
+ let str = "";
+ let i = 0;
+
+ // 定义行样式:Flex布局,两端对齐,相对定位防止溢出
+ const rowStyle = "display:flex; justify-content:space-between; align-items:center; height:32px; padding:0 5px; cursor:pointer; border-bottom:1px solid #444; color:#ccc; font-size:12px; position:relative;";
+ // 定义左侧内容样式
+ const leftStyle = "display:flex; align-items:center; overflow:hidden; white-space:nowrap; max-width:180px;";
+ // 定义序号样式:靠右,有左边框
+ const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;";
+
+ for (let item of currentObjs) {
+ let displayIndex = i + 1; // 序号底层为1
+
+ // 图标逻辑
+ let iconName = "rect.svg";
+ switch (item.type) {
+ case 1: iconName = "images.svg"; break;
+ case 2: iconName = "images_add.svg"; break;
+ case 3: iconName = "text.svg"; break;
+ case 4: iconName = "text2.svg"; break;
+ case 5: iconName = "rect.svg"; break;
+ case 6: iconName = "round.svg"; break;
+ case 7: iconName = "line.svg"; break;
+ case 8: iconName = "qrcode.svg"; break;
+ case 9: iconName = "barcode.svg"; break;
+ case 10: iconName = "counter.svg"; break;
+ case 11: iconName = "text3.svg"; break;
+ }
+
+ // 名称逻辑
+ let nameText = item.name ? item.name :
+ (item.type === 3 ? "TEXT_" + displayIndex :
+ (item.type === 1 ? "IMG_" + displayIndex : "OBJ_" + displayIndex));
+
+ // 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性)
+ // 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1
+ let isLocked = false;
+ if (currentCanvas.getObjects()[i + 1]) {
+ let canvasObj = currentCanvas.getObjects()[i + 1];
+ isLocked = canvasObj.get('lockMovementX') === true;
+ }
+
+ // 锁定状态图标
+ const lockIcon = isLocked ?
+ '
' :
+ '
';
+
+ // 组装 HTML
+ str = `
+
+

+
${nameText}
+ ${lockIcon}
+
+
${displayIndex}
+
` + str;
+ i++;
+ }
+ $(".obj_list").html(str);
+
+ // 重新绑定点击事件(防止 updateList 后点击失效)
+ $(".obj_list > div").on("click", function () {
+ let imgId = $(this).find("img").attr("id");
+ if (imgId) {
+ let idx = parseInt(imgId.replace("obj", ""));
+ selectObj(idx);
+ }
+ });
+}
+
+// 标记是否通过对象列表选中(用于区分鼠标点击和列表点击)
+window._selectingFromList = false;
+
+// 将 selectObj 附加到 window 对象,确保全局可访问
+window.selectObj = function selectObj(objIndices, isFromCanvas = false) {
+ let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []);
+
+ // 清除所有高亮
+ $('.obj_list > div').css("background-color", "transparent");
+
+ // 遍历列表查找并高亮
+ $('.obj_list > div').each(function () {
+ let $img = $(this).find("img");
+ if ($img.length === 0) return;
+
+ let idIndex = parseInt($img.attr("id").replace("obj", ""));
+
+ if (indicesArray.includes(idIndex)) {
+ $(this).css("background-color", "#6F7A84");
+
+ // 自动滚动到可视区域
+ if (isFromCanvas && indicesArray[0] === idIndex) {
+ this.scrollIntoView({ behavior: "smooth", block: "nearest" });
+ }
+ }
+ });
+
+ // 如果是点击列表,触发画布选中
+ if (!isFromCanvas && indicesArray.length === 1) {
+ let target = canvas.getObjects()[indicesArray[0] + 1];
+ if (target) {
+ // 检查对象是否被锁定
+ let isLocked = isObjectLocked(target);
+
+ // 设置标志位,表示这是通过对象列表选中的(无论是否锁定)
+ window._selectingFromList = true;
+
+ // 如果被锁定,临时设置 selectable 和 evented 为 true,以便能够选中
+ if (isLocked) {
+ target.set({
+ selectable: true,
+ evented: true
+ });
+ }
+
+ canvas.setActiveObject(target);
+ canvas.requestRenderAll();
+
+ // 如果对象被锁定,选中后立即恢复锁定状态(但保持选中状态)
+ if (isLocked) {
+ setTimeout(() => {
+ target.set({
+ selectable: false,
+ evented: false
+ });
+ canvas.requestRenderAll();
+ }, 0);
+ }
+
+ // 清除标志位(延迟清除,确保事件处理完成)
+ setTimeout(() => {
+ window._selectingFromList = false;
+ }, 10);
+
+ handleObjectSelected();
+ }
+ }
+}
+
+// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
+window.recordState = function recordState() {
+ // 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期
+ if (window._isApplyingHistory) {
+ return;
+ }
+ // 确定当前是正面还是背面,直接使用对应的数组
+ const isFront = $('#front_side').hasClass('ui-button-active');
+ const currentStep = isFront ? (window.step1 || step1) : (window.step2 || step2);
+ const currentRecordObjs = isFront ? (window.recordObjs1 || recordObjs1) : (window.recordObjs2 || recordObjs2);
+ const currentRecordJson = isFront ? (window.recordJson1 || recordJson1) : (window.recordJson2 || recordJson2);
+ const currentObjs = window.objs || objs;
+ const currentCanvas = window.canvas || canvas;
+
+ ++currentStep.val;
+ for (let i = currentStep.val; i < currentRecordObjs.length; i++) {
+ currentRecordObjs.pop();
+ currentRecordJson.pop();
+ }
+ currentRecordObjs.push(JSON.stringify(currentObjs))
+ // 保存所有锁定相关的属性,确保锁定状态可以正确恢复
+ let j = currentCanvas.toJSON(TO_JSON_PROPERTIES);
+ j.objects[0].hoverCursor = "default";
+ currentRecordJson.push(j);
+
+ // 同步更新 window 变量和局部变量
+ if (isFront) {
+ window.step1 = currentStep;
+ window.recordObjs1 = currentRecordObjs;
+ window.recordJson1 = currentRecordJson;
+ window.step = currentStep;
+ window.recordObjs = currentRecordObjs;
+ window.recordJson = currentRecordJson;
+ step1 = currentStep;
+ recordObjs1 = currentRecordObjs;
+ recordJson1 = currentRecordJson;
+ step = currentStep;
+ recordObjs = currentRecordObjs;
+ recordJson = currentRecordJson;
+ } else {
+ window.step2 = currentStep;
+ window.recordObjs2 = currentRecordObjs;
+ window.recordJson2 = currentRecordJson;
+ window.step = currentStep;
+ window.recordObjs = currentRecordObjs;
+ window.recordJson = currentRecordJson;
+ step2 = currentStep;
+ recordObjs2 = currentRecordObjs;
+ recordJson2 = currentRecordJson;
+ step = currentStep;
+ recordObjs = currentRecordObjs;
+ recordJson = currentRecordJson;
+ }
+};
+// 向后兼容
+function recordState() {
+ return window.recordState();
+}
+
+// 将 getIndex 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
+window.getIndex = function getIndex(target, _canvas = null) {
+ let temp_objs;
+ // 使用 window.canvas 作为默认值,确保在 eval() 执行时也能访问
+ const currentCanvas = _canvas || window.canvas || canvas;
+ if (!currentCanvas) {
+ return 0;
+ }
+ temp_objs = currentCanvas.getObjects();
+
+ let i = 0;
+ for (let res of temp_objs) {
+ if (target == res) {
+ return i;
+ }
+ i++;
+ }
+ return 0;
+};
+// 向后兼容
+function getIndex(target, _canvas = null) {
+ return window.getIndex(target, _canvas);
+}
+
+function getType(target) {
+ // 如果是辅助线,返回 null
+ if (target.isGuideLine) {
+ return null;
+ }
+ let temp_objs = canvas.getObjects();
+ let i = 0;
+ for (let res of temp_objs) {
+ if (target == res) {
+ // 安全检查:确保 objs[i] 存在且有 type 属性
+ if (!objs[i] || typeof objs[i].type === 'undefined') {
+ return null;
+ }
+ return objs[i].type;
+ }
+ i++;
+ }
+ return null;
+}
+
+function checkName(name) {
+ //检查字段名是否重复了
+ for (let item of objs1) {
+ if (item.name == name) {
+ return false;
+ }
+ }
+ for (let item of objs2) {
+ if (item.name == name) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function resName(pre_name) {
+ //返回的是不重复的字段名
+ let num = (objs1.length + objs2.length) - 1;
+ do {
+ num++;
+ }
+ while (!checkName(pre_name + num));//重复了就再加1
+ return pre_name + num;
+}
+
+function getDisdance(x1, y1, x2, y2) {
+ var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值
+ var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值
+ var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离
+ return distance;
+}
+
+function getDeg(pointer) {
+ // 计算点击位置与画布中心的坐标差
+ var centerX = canvas.width / 2;
+ var centerY = canvas.height / 2;
+ var mouseX = pointer.x;
+ var mouseY = pointer.y;
+ var dx = mouseX - centerX;
+ var dy = mouseY - centerY;
+ // 计算旋转角度(弧度)
+ var angleRad = Math.atan2(dy, dx);
+ // 将角度转换为度数
+ var angleDeg = angleRad * (180 / Math.PI);
+ if (angleDeg < 0) {
+ angleDeg += 360;
+ }
+ angleDeg += 90
+ return angleDeg
+}
+
+function getCoordsMinX(acoords) {
+ x = acoords[0].x;
+ for (let item of acoords) {
+ if (item.x < x) {
+ x = item.x
+ }
+ }
+ return x;
+}
+
+function getCoordsMaxX(acoords) {
+ x = acoords[0].x;
+ for (let item of acoords) {
+ if (item.x > x) {
+ x = item.x
+ }
+ }
+ return x;
+}
+
+function getCoordsMinY(acoords) {
+ y = acoords[0].y;
+ for (let item of acoords) {
+ if (item.y < y) {
+ y = item.y
+ }
+ }
+ return y;
+}
+
+function getCoordsMaxY(acoords) {
+ y = acoords[0].y;
+ for (let item of acoords) {
+ if (item.y > y) {
+ y = item.y
+ }
+ }
+ return y;
+}
+
+function addBackground() {
+ // 类型改变
+ fabric.Image.fromURL('./public/images/bg_front_2.png', function (image) {
+ image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2
+ image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2
+
+ image.selectable = false
+ image.hoverable = false
+ image.hoverCursor = 'default'
+ image.setSrc(image.toDataURL(), function (image) {
+ background_image1 = image
+ background_image = background_image1
+ window.background_image1 = background_image1;
+ window.background_image = background_image;
+ canvas1.add(image)
+ canvas1.renderAll()
+
+ recordObjs1.push(JSON.stringify(objs1))
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
+ recordJson1.push(canvas1.toJSON(props))
+ })
+ // 类型改变
+ fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) {
+ image.left = ($('#canvas2').width() - image.width) / 2
+ image.top = ($('#canvas2').height() - image.height) / 2
+
+ image.selectable = false
+ image.hoverable = false
+ image.hoverCursor = 'default'
+
+ image.setSrc(image.toDataURL(), function (image) {
+ background_image2 = image
+ window.background_image2 = background_image2;
+ canvas2.add(image)
+ canvas2.renderAll()
+
+ recordObjs2.push(JSON.stringify(objs2))
+ recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES))
+
+ let file = GetFile().get('file')
+ if (file && file != 'empty') {
+ window.openFile(file);
+ }
+ })
+ })
+ is_bgi_add = true
+ })
+}
+
+function addPic(pointer) {
+ pre_add_image.left = pointer.x
+ pre_add_image.top = pointer.y
+
+ canvas.add(pre_add_image)
+
+ objs.push({ type: 1, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addOutPic(pointer) {
+ fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) {
+ let width = canvas.getWidth()
+ let height = canvas.getHeight()
+ image.left = pointer.x
+ image.top = pointer.y
+ image.opacity = 1
+ image.setSrc(image.toDataURL(), function (image) {
+ canvas.add(image)
+ objs.push({ type: 2, name: resName('OUT_PIC_'), black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+ })
+ })
+}
+
+function addText(pointer) {
+ // 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt
+ let text = new fabric.IText('TEXT', {
+ left: pointer.x,
+ top: pointer.y,
+ fill: '#000000',
+ textBackgroundColor: '',
+ fontStyle: 'normal',
+ fontWeight: 'normal',
+ fontSize: 30,
+ underline: false,
+ linethrough: false,
+ fontFamily: 'Arial'
+ })
+ text.setControlVisible('ml', false)
+ text.setControlVisible('mt', false)
+ text.setControlVisible('mr', false)
+ text.setControlVisible('mb', false)
+ canvas.add(text)
+ objs.push({ type: 3, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addCircleText(pointer) {
+ let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2)
+ let angleDeg = getDeg(pointer)
+ let text = new fabric.CurvedText('Circle TEXT', {
+ left: pointer.x,
+ top: pointer.y,
+ fill: "#000000",
+ textBackgroundColor: "",
+ fontStyle: "normal",
+ fontWeight: "normal",
+ diameter: distance * 2,
+ fontSize: 30,
+ underline: false,
+ linethrough: false,
+ lockScalingX: true,
+ lockScalingY: true,
+ fontFamily: 'Arial',
+ originX: 'center',
+ originY: 'center',
+ angle: angleDeg,
+ flipped: pointer.y > canvas.height / 2 ? true : false
+ });
+ text.setControlVisible("ml", false);
+ text.setControlVisible("mt", false);
+ text.setControlVisible("mr", false);
+ text.setControlVisible("mb", false);
+ canvas.add(text);
+ objs.push({ type: 11, black: false, circleCenter: 0 });
+ canvas.renderAll();
+ updateList();
+ recordState();
+}
+
+function addOutText(pointer) {
+ let text = new fabric.Text('MERGE TEXT', {
+ left: pointer.x,
+ top: pointer.y,
+ fill: '#000000',
+ fontStyle: 'normal',
+ fontWeight: 'normal',
+ textBackgroundColor: '',
+ underline: false,
+ linethrough: false,
+ fontFamily: 'Arial',
+ textAlign: 'center'
+ })
+ text.setControlVisible('ml', false)
+ text.setControlVisible('mt', false)
+ text.setControlVisible('mr', false)
+ text.setControlVisible('mb', false)
+
+ canvas.add(text)
+ objs.push({ type: 4, name: resName('OUT_TEXT_'), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addRect(pointer) {
+ let rect = new fabric.Rect({
+ left: pointer.x,
+ top: pointer.y,
+ fill: '',
+ stroke: "#ff0000",
+ strokeWidth: 3,
+ width: 100,
+ height: 100
+ })
+ canvas.add(rect)
+ objs.push({ type: 5, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addCircle(pointer) {
+ let circle = new fabric.Circle({
+ left: pointer.x,
+ top: pointer.y,
+ fill: '',
+ stroke: '#ff0000',
+ strokeWidth: 3,
+ radius: 100,
+ strokeUniform: true
+ })
+ canvas.add(circle)
+ objs.push({ type: 6, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addLine(pointer) {
+ let line = new fabric.Line([50, 100, 200, 100], {
+ left: pointer.x,
+ top: pointer.y,
+ stroke: '#ff0000',
+ strokeWidth: 3,
+ strokeDashArray: [0, 0],
+ })
+
+ line.setControlVisible('tl', false)
+ line.setControlVisible('tr', false)
+ line.setControlVisible('br', false)
+ line.setControlVisible('bl', false)
+ line.setControlVisible('mt', false)
+ line.setControlVisible('mb', false)
+ canvas.add(line)
+ objs.push({ type: 7, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+function addQrCode(pointer) {
+ let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 })
+ fabric.Image.fromURL(qr, function (image) {
+ image.left = pointer.x
+ image.top = pointer.y
+ canvas.add(image)
+ objs.push({ type: 8, name: resName('QR_CODE_'), color: '#000000', val: 'http://www.cardsoon.com', black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+ })
+}
+
+function addBarCode(pointer) {
+ JsBarcode('#barcode', '123456789', { margin: 0, lineColor: '#000000', fontSize: 18, font: 'Arial' })
+ let barcode = document.getElementById('barcode')
+ let bar = barcode.toDataURL('image/png')
+ fabric.Image.fromURL(bar, function (image) {
+ image.left = pointer.x
+ image.top = pointer.y
+
+ canvas.add(image)
+ objs.push({ type: 9, name: resName('BAR_CODE_'), val: '123456789', color: '#000000', fontSize: 18, font: 'Arial', show: false, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+ })
+}
+
+function addCounter(pointer) {
+ let text = new fabric.Text('0', {
+ left: pointer.x,
+ top: pointer.y,
+ fill: '#000000'
+ })
+ canvas.add(text)
+ objs.push({ type: 10, name: resName('COUNTER_'), start: 0, add: 1, black: false })
+ canvas.renderAll()
+ updateList()
+ recordState()
+}
+
+var front_list = []
+ipcRenderer.on('font-list', (event, data) => {
+ const newFontList = []
+ data.map((item) => {
+ if (item.indexOf('"') === 0) {
+ newFontList.push(item.replace(/^"|"$/g, ''))
+ } else {
+ newFontList.push(item)
+ }
+ })
+ front_list = newFontList
+ for (let item of front_list) {
+ $('#text_font_family').append("')
+ $("#circle_text_font_family").append("");
+ $('#out_text_font_family').append("')
+ $('#barcode_font_family').append("')
+ $('#count_font_family').append("')
+ }
+})
+ipcRenderer.on('sys-lan', (event, data) => {
+ let lang = localStorage.getItem('lang')
+ if (lang) {
+ s_lan = lang
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge(lang)
+ $('#language_select').val(lang)
+ return
+ }
+ switch (data) {
+ case 'zh-CN':
+ s_lan = 'zh'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('zh')
+ $('#language_select').val('zh')
+ localStorage.setItem('lang', 'zh')
+ break
+ case 'zh':
+ s_lan = 'zh'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('zh')
+ $('#language_select').val('zh')
+ localStorage.setItem('lang', 'zh')
+ break
+ case 'zh-TW':
+ s_lan = 'ozh'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('ozh')
+ $('#language_select').val('ozh')
+ localStorage.setItem('lang', 'ozh')
+ break
+ case 'en':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-AU':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-CA':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-GB':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-NZ':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-US':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ case 'en-ZA':
+ s_lan = 'en'
+ // 同步到全局作用域(用于 .jsc 文件加载)
+ if (typeof window !== 'undefined') {
+ window.s_lan = s_lan;
+ }
+ if (typeof global !== 'undefined') {
+ global.s_lan = s_lan;
+ }
+ langua_ge('en')
+ $('#language_select').val('en')
+ localStorage.setItem('lang', 'en')
+ break
+ }
+})
+ipcRenderer.on('close', (event, message) => {
+ layer.confirm(
+ '' + language_str('whetherSave') + '',
+ {
+ //是否保存当前文件?
+ btn: [language_str('save'), language_str('noSave'), language_str('cancel')], //['保存', '不保存', '取消']
+ btn3: function (index, layero) { }
+ },
+ function (index, layero) {
+ if (typeof window.output === 'function') {
+ window.output(() => ipcRenderer.send('run-close'));
+ }
+ },
+ function (index) {
+ ipcRenderer.send('run-close')
+ }
+ )
+})
+
+addBackground() //添加背景 白色卡片
+
+initAligningGuidelines(canvas1)
+initAligningGuidelines(canvas2)
+
+// 初始化 Canvas 事件监听
+initCanvasEvents();
+
+// 初始化所有 Slider
+var circle_text_size_updating = false; // 标志位,防止循环更新
+var circle_text_size = slider.render({
+ elem: '#circle_text_size',
+ min: 10,
+ max: 120,
+ value: 50,
+ change: function (value) {
+ if (circle_text_size_updating) return;
+ if (canvas && canvas.getActiveObject && canvas.getActiveObject()) {
+ canvas.getActiveObject().set('fontSize', value);
+ canvas.renderAll();
+ if (typeof updateControls === 'function') {
+ updateControls();
+ }
+ if (typeof recordState === 'function') {
+ recordState();
+ }
+ }
+ }
+});
+
+var circle_text_diameter = slider.render({
+ elem: '#circle_text_diameter',
+ min: 50,
+ max: 1100,
+ value: 500,
+ change: function (value) {
+ if (canvas && canvas.getActiveObject && canvas.getActiveObject()) {
+ canvas.getActiveObject().set('diameter', value);
+ canvas.renderAll();
+ if (typeof updateControls === 'function') {
+ updateControls();
+ }
+ if (typeof recordState === 'function') {
+ recordState();
+ }
+ }
+ }
+});
+
+var out_pic_op = slider.render({
+ elem: '#out_pic_op',
+ min: 0,
+ max: 100,
+ value: 0,
+ change: function (value) {
+ if (canvas.getActiveObject()) {
+ canvas.getActiveObject().set('opacity', 1 - value / 100);
+ canvas.renderAll();
+ updateControls();
+ recordState();
+ }
+ }
+});
+
+var pic_op = slider.render({
+ elem: '#pic_op',
+ min: 0,
+ max: 100,
+ value: 0,
+ change: function (value) {
+ if (canvas.getActiveObject()) {
+ canvas.getActiveObject().set('opacity', 1 - value / 100);
+ canvas.renderAll();
+ updateControls();
+ recordState();
+ }
+ }
+});
+
+var rect_op = slider.render({
+ elem: '#rect_op',
+ min: 0,
+ max: 100,
+ value: 0,
+ change: function (value) {
+ if (canvas.getActiveObject()) {
+ canvas.getActiveObject().set('opacity', 1 - value / 100);
+ canvas.renderAll();
+ updateControls();
+ recordState();
+ }
+ }
+});
+
+var circle_op = slider.render({
+ elem: '#circle_op',
+ min: 0,
+ max: 100,
+ value: 0,
+ change: function (value) {
+ if (canvas.getActiveObject()) {
+ canvas.getActiveObject().set('opacity', 1 - value / 100);
+ canvas.renderAll();
+ updateControls();
+ recordState();
+ }
+ }
+});
+
+//canvas.setZoom(0.5)
+//recordObjs1.push(JSON.stringify(objs1));
+//recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
+//recordObjs2.push(JSON.stringify(objs2));
+//recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
+canvas.renderAll() //重新渲染画布
+var zoomSlider = slider.render({
+ elem: '#zoomSlide', //绑定元素
+ min: 25,
+ max: 500,
+ value: 100,
+ change(val) {
+ canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), val / 100)
+ zoom = val / 100
+ //$(".canvas-container").css("transform","scale(" + val / 100 + ")")
+ }
+})
+window.rotate = function (id) {
+ let angle = $(`#${id}`).attr('angle') - 0
+ angle += 90
+ $(`#${id}`).css('transform', `rotate(${angle}deg)`)
+ $(`#${id}`).attr('angle', angle)
+}
+// 注意:#display 事件绑定已在 ui.js 中定义(第2633行),此处已删除重复定义
+// 导出文件
+// 将 savePdf 附加到 window 对象,确保在 output.js 中也能访问
+window.savePdf = async function savePdf(buffer) {
+ let fs = require('fs');
+ // 弹出保存对话框
+ const { canceled, filePath } = await dialog.showSaveDialog({
+ title: '保存文件',
+ defaultPath: path.join(exePath, 'output.pdf'), // 默认路径
+ filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] // 文件过滤器
+ });
+
+ if (canceled) {
+ return;
+ }
+
+ // 将Buffer写入PDF文件到用户选择的路径
+ fs.writeFile(filePath, buffer, (err) => {
+ if (err) {
+ } else {
+ layer.msg(language_str("saveSucc") + filePath);//'保存成功至'
+ }
+ });
+};
+// 向后兼容
+async function savePdf(buffer) {
+ return window.savePdf(buffer);
+}
+
+// display_func 函数已移至 output.js,避免重复定义
+// function display_func(img1, img2, img3) { ... }
+
+// output 函数已移至 output.js,避免重复定义
+// function output(callback = null, _save = save) { ... }
+
+var _keyctrl = false,
+ _keyc = false,
+ _textEdit = false,
+ _inputEdit = false
+$('input').focus(function () {
+ _inputEdit = true
+})
+$('input').blur(function () {
+ _inputEdit = false
+})
+$('textarea').focus(function () {
+ _inputEdit = true
+})
+$('textarea').blur(function () {
+ _inputEdit = false
+})
+$('body').keyup(function (event) {
+ // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
+ if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放
+ _keyctrl = false
+ }
+ if (event.keyCode == 67) {
+ _keyc = false
+ }
+})
+$('body').keydown(function (event) {
+ // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta)
+ const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端
+
+ // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的)
+ if (!_inputEdit && !_textEdit) {
+ // CTRL/CMD+N - 新建
+ if (isModifierKey && event.keyCode == 78) {
+ event.preventDefault();
+ $('#new').click();
+ return;
+ }
+ // CTRL/CMD+O - 打开
+ if (isModifierKey && event.keyCode == 79) {
+ event.preventDefault();
+ $('#open').click();
+ return;
+ }
+ // CTRL/CMD+S - 保存
+ if (isModifierKey && !event.shiftKey && event.keyCode == 83) {
+ event.preventDefault();
+ $('#save').click();
+ return;
+ }
+ // CTRL/CMD+SHIFT+S - 保存副本
+ if (isModifierKey && event.shiftKey && event.keyCode == 83) {
+ event.preventDefault();
+ if (typeof window.output === 'function') {
+ window.output(null, saveAs);
+ }
+ return;
+ }
+ // CTRL/CMD+Z - 还原
+ if (isModifierKey && !event.shiftKey && event.keyCode == 90) {
+ event.preventDefault();
+ $('#previous').click();
+ return;
+ }
+ // CTRL/CMD+Y - 重做
+ if (isModifierKey && !event.shiftKey && event.keyCode == 89) {
+ event.preventDefault();
+ $('#next').click();
+ return;
+ }
+ // CTRL/CMD+C - 复制
+ if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') {
+ event.preventDefault();
+ window.copySelection();
+ return;
+ }
+ // CTRL/CMD+V - 粘贴
+ if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') {
+ event.preventDefault();
+ window.pasteFromClipboard();
+ return;
+ }
+ // CTRL/CMD+SHIFT+DEL - 清空
+ if (isModifierKey && event.shiftKey && event.keyCode == 46) {
+ event.preventDefault();
+ $('#clean').click();
+ return;
+ }
+ // CTRL/CMD+DEL - 删除
+ if (isModifierKey && !event.shiftKey && event.keyCode == 46) {
+ event.preventDefault();
+ $('#delete').click();
+ return;
+ }
+ }
+ // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
+ if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd)
+ _keyctrl = true
+ if (_keyc && _textEdit) {
+ //同时按下且在编辑状态
+ setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1)
+ //clipboard.writeText((clipboard.readText()+' '))
+ }
+ }
+ if (event.keyCode == 67) {
+ _keyc = true
+ if (_keyctrl & _textEdit) {
+ //同时按下且在编辑状态
+ setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1)
+ //clipboard.writeText((clipboard.readText()+' '))
+ }
+ }
+ if (event.keyCode == 37) {
+ //left
+ let v = -1
+ if (_keyctrl || event.ctrlKey || event.metaKey) {
+ v = -5
+ }
+ if (canvas.getActiveObject()) {
+ let x = canvas.getActiveObject().get('left')
+ x += v
+ canvas.getActiveObject().set('left', x)
+ canvas.renderAll()
+ recordState()
+ updateControls()
+ }
+ }
+ if (event.keyCode == 38) {
+ //up
+ let v = -1
+ if (_keyctrl || event.ctrlKey || event.metaKey) {
+ v = -5
+ }
+ if (canvas.getActiveObject()) {
+ let y = canvas.getActiveObject().get('top')
+ y += v
+ canvas.getActiveObject().set('top', y)
+ canvas.renderAll()
+ recordState()
+ updateControls()
+ }
+ }
+ if (event.keyCode == 39) {
+ //right
+ let v = 1
+ if (_keyctrl || event.ctrlKey || event.metaKey) {
+ v = 5
+ }
+ if (canvas.getActiveObject()) {
+ let x = canvas.getActiveObject().get('left')
+ x += v
+ canvas.getActiveObject().set('left', x)
+ canvas.renderAll()
+ recordState()
+ updateControls()
+ }
+ }
+ if (event.keyCode == 40) {
+ //down
+ let v = +1
+ if (_keyctrl || event.ctrlKey || event.metaKey) {
+ v = 5
+ }
+ if (canvas.getActiveObject()) {
+ let y = canvas.getActiveObject().get('top')
+ y += v
+ canvas.getActiveObject().set('top', y)
+ canvas.renderAll()
+ recordState()
+ updateControls()
+ }
+ }
+ // 普通删除键(DEL键,没有按CTRL/CMD)
+ if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) {
+ let actObjs = canvas.getActiveObjects()
+ let allObjs = canvas.getObjects()
+ let flag = 0
+ for (let item of actObjs) {
+ if (item != allObjs[0]) {
+ let i = getIndex(item, canvas) //0是背景,所以从1开始,但objs不包含背景图片,故要-1
+ objs.splice(i - 1, 1)
+ canvas.remove(item)
+ flag = 1
+ }
+ }
+ updateList()
+ canvas.discardActiveObject()
+ if (flag == 1) {
+ recordState()
+ canvas.renderAll()
+ handleObjectSelected()
+ }
+ }
+})
+
+// 复制选中对象
+window.copySelection = function copySelection() {
+ const activeObjects = canvas.getActiveObjects();
+
+ if (!activeObjects || activeObjects.length === 0) {
+ layer.msg("未选中任何对象")
+ return;
+ }
+
+ // 【核心修复】遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据
+ clipboardData.data = activeObjects.map(obj => {
+ let idx = getIndex(obj, canvas); // 获取对象在画布中的索引
+
+ // 获取业务属性 (objs数组中对应的那一项)
+ // 注意:idx=0 是背景图,objs数组是从下标0开始存第1个对象的,所以是 idx-1
+ let appData = (idx > 0 && objs[idx - 1]) ? objs[idx - 1] : { type: 0 };
+
+ return {
+ // 1. 保存画面样式 (坐标、颜色、字号等,包含图片的 src)
+ fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data', 'src']),
+ // 2. 保存业务属性 (深拷贝,防止引用冲突)
+ appData: JSON.parse(JSON.stringify(appData))
+ };
+ });
+
+ clipboardData.offset = 10; // 重置偏移量
+ layer.msg("已复制");
+}
+
+// 粘贴对象
+window.pasteFromClipboard = function pasteFromClipboard() {
+ // 检查是否有数据
+ if (!clipboardData.data || clipboardData.data.length === 0) {
+ layer.msg("剪贴板为空")
+ return;
+ }
+
+ // 防止重复调用:如果正在粘贴,直接返回
+ if (window._isPasting) {
+ return;
+ }
+ window._isPasting = true;
+
+ // 1. 提取 Fabric JSON 准备恢复画面
+ const jsonToEnliven = clipboardData.data.map(item => item.fabricJson);
+
+ // 2. 恢复对象 (异步过程)
+ fabric.util.enlivenObjects(jsonToEnliven, function (objects) {
+ canvas.discardActiveObject();
+
+ objects.forEach((clone, index) => {
+ // --- A. 处理画面位置 ---
+ clone.set({
+ left: clone.left + clipboardData.offset,
+ top: clone.top + clipboardData.offset,
+ evented: true
+ });
+
+ // 如果是组内对象,需要特殊处理坐标
+ if (clone.group) {
+ clone.set({
+ left: clone.group.left + clipboardData.offset,
+ top: clone.group.top + clipboardData.offset
+ });
+ }
+
+ // --- B. 处理业务数据 ---
+ // 取出复制时保存的 appData
+ let savedAppData = clipboardData.data[index].appData;
+ let newAppData = JSON.parse(JSON.stringify(savedAppData)); // 再次深拷贝
+
+ // 修改名称,防止 ID 冲突 (例如 TEXT_1 -> TEXT_1_copy)
+ if (newAppData.name) {
+ newAppData.name = newAppData.name + "_copy";
+ }
+
+ // --- C. 添加到画布和数据数组 ---
+ canvas.add(clone);
+ objs.push(newAppData); // 将新业务数据推入当前页面的 objs 数组
+ });
+
+ // 3. 选中新粘贴出来的对象
+ let sel = new fabric.ActiveSelection(objects, { canvas: canvas });
+ canvas.setActiveObject(sel);
+ canvas.requestRenderAll();
+
+ // 4. 刷新列表和历史记录
+ if (typeof window.updateList === 'function') {
+ window.updateList();
+ }
+ recordState();
+
+ // 5. 高亮左侧列表最后一项 (即新粘贴的项)
+ if (typeof window.selectObj === 'function') {
+ if (objs.length > 0) {
+ window.selectObj(objs.length - 1);
+ }
+ }
+
+ // 6. 增加下次粘贴的偏移量 (让用户知道粘贴了新东西)
+ clipboardData.offset += 10;
+
+ // 清除粘贴标志
+ window._isPasting = false;
+ });
+}
+
+// 将 onStateRestored 附加到 window 对象,确保全局可访问
+// 此函数在撤回/重做后调用,用于恢复画布状态
+window.onStateRestored = function onStateRestored() {
+ // 1. 重新获取背景图引用 (因为旧对象被销毁了)
+ // 注意:canvas 是全局变量,指向当前激活的画板
+ if (canvas && canvas.getObjects().length > 0) {
+ background_image = canvas.getObjects()[0];
+ background_image.selectable = false; // 确保背景不可选
+ window.background_image = background_image;
+ background_image.evented = false;
+ }
+
+ // 2. 渲染画布
+ canvas.requestRenderAll();
+
+ // 3. 取消选中状态
+ if (typeof window.selectObj === 'function') {
+ window.selectObj(-1);
+ }
+
+ // 4. 隐藏所有属性面板
+ $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide();
+
+ // 5. 刷新左侧列表
+ if (typeof window.updateList === 'function') {
+ window.updateList();
+ }
+}
diff --git a/lib/design2/output.js b/lib/design2/output.js
index e957e3f..c9462aa 100644
--- a/lib/design2/output.js
+++ b/lib/design2/output.js
@@ -1246,7 +1246,111 @@ window.openFile = function open(file) {
}
})
+ // 先清空历史记录和重置 step
+ objs2 = j.bo || []
+ objs1 = j.fo || []
+ step1.val = 0
+ step2.val = 0
+ recordJson1 = []
+ recordJson2 = []
+ recordObjs1 = []
+ recordObjs2 = []
+
+ // 同步更新 window 变量
+ window.objs1 = objs1
+ window.objs2 = objs2
+ window.step1 = step1
+ window.step2 = step2
+ window.recordJson1 = recordJson1
+ window.recordJson2 = recordJson2
+ window.recordObjs1 = recordObjs1
+ window.recordObjs2 = recordObjs2
+
+ if ($('#front_side').hasClass('ui-button-active')) {
+ objs = objs1
+ recordObjs = recordObjs1
+ recordJson = recordJson1
+ step = step1
+ window.objs = objs1
+ window.recordObjs = recordObjs1
+ window.recordJson = recordJson1
+ window.step = step1
+ } else if ($('#back_side').hasClass('ui-button-active')) {
+ objs = objs2
+ recordObjs = recordObjs2
+ recordJson = recordJson2
+ background_image = background_image2
+ step = step2
+ window.objs = objs2
+ window.recordObjs = recordObjs2
+ window.recordJson = recordJson2
+ window.step = step2
+ }
+
// 加载 JSON,并在回调中重新生成二维码和条形码
+ // 使用 Promise 确保两个画布都加载完成后再保存初始状态
+ let canvas1Loaded = false
+ let canvas2Loaded = false
+
+ function saveInitialState() {
+ // 两个画布都加载完成后才保存初始状态
+ if (!canvas1Loaded || !canvas2Loaded) return
+
+ // 确保使用正确的 objs1 和 objs2(从文件加载的 fo 和 bo)
+ // 注意:必须使用 fo 和 bo,因为它们是直接从文件 JSON 中读取的
+ // 同时确保 objs1 和 objs2 也被正确设置
+ objs1 = fo || []
+ objs2 = bo || []
+ window.objs1 = objs1
+ window.objs2 = objs2
+
+ // 更新背景图引用
+ background_image1 = canvas1.getObjects()[0]
+ background_image2 = canvas2.getObjects()[0]
+ window.background_image1 = background_image1
+ window.background_image2 = background_image2
+
+ if ($('#front_side').hasClass('ui-button-active')) {
+ background_image = background_image1
+ window.background_image = background_image1
+ objs = objs1
+ window.objs = objs1
+ } else if ($('#back_side').hasClass('ui-button-active')) {
+ background_image = background_image2 || background_image1
+ window.background_image = background_image
+ objs = objs2
+ window.objs = objs2
+ }
+
+ // 保存初始状态(使用从文件加载的 objs 数组)
+ recordObjs1.push(JSON.stringify(objs1))
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
+ let j1 = canvas1.toJSON(props)
+ j1.objects[0].hoverCursor = 'default'
+ recordJson1.push(j1)
+
+ recordObjs2.push(JSON.stringify(objs2))
+ let j2 = canvas2.toJSON(props)
+ j2.objects[0].hoverCursor = 'default'
+ recordJson2.push(j2)
+
+ // 同步更新 window 变量和局部变量
+ window.recordObjs1 = recordObjs1
+ window.recordJson1 = recordJson1
+ window.recordObjs2 = recordObjs2
+ window.recordJson2 = recordJson2
+ // 确保局部变量也同步
+ if ($('#front_side').hasClass('ui-button-active')) {
+ recordObjs = recordObjs1
+ recordJson = recordJson1
+ step = step1
+ } else if ($('#back_side').hasClass('ui-button-active')) {
+ recordObjs = recordObjs2
+ recordJson = recordJson2
+ step = step2
+ }
+ }
+
canvas1.loadFromJSON(j.f, function () {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
@@ -1260,6 +1364,8 @@ window.openFile = function open(file) {
window.updateList();
}
canvas1.renderAll()
+ canvas1Loaded = true
+ saveInitialState()
})
canvas2.loadFromJSON(j.b, function () {
// 加载后解锁所有对象(背景除外)
@@ -1274,59 +1380,9 @@ window.openFile = function open(file) {
window.updateList();
}
canvas2.renderAll()
+ canvas2Loaded = true
+ saveInitialState()
})
- background_image1 = canvas1.getObjects()[0]
- background_image2 = canvas2.getObjects()[0]
-
- objs2 = j.bo
- objs1 = j.fo
- step1.val = 0
- step2.val = 0
- recordJson1 = []
- recordJson2 = []
- recordObjs1 = []
- recordObjs2 = []
- if ($('#front_side').hasClass('ui-button-active')) {
- objs = objs1
- recordObjs = recordObjs1
- recordJson = recordJson1
- step = step1
- background_image = background_image1
- } else if ($('#back_side').hasClass('ui-button-active')) {
- objs = objs2
- recordObjs = recordObjs2
- recordJson = recordJson2
- background_image = background_image2
- step = step2
- }
- canvas1.renderAll()
- canvas2.renderAll()
- // 调用 updateList,现在它已附加到 window 对象
- if (typeof window.updateList === 'function') {
- window.updateList(); /*
- 修复打开新文件后无法撤销的bug
- 2022-07-16
- */
- } else {
- // 延迟检查,确保 core.js 已加载
- 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'
- recordJson1.push(j1)
-
- recordObjs2.push(JSON.stringify(objs2))
- let j2 = canvas2.toJSON(props)
- j2.objects[0].hoverCursor = 'default'
- recordJson2.push(j2)
- }, 0)
}
function clearAll() {
diff --git a/lib/design2/ui.js b/lib/design2/ui.js
index f7b97ce..2cdaf80 100644
--- a/lib/design2/ui.js
+++ b/lib/design2/ui.js
@@ -28,69 +28,10 @@ $('body').keydown(function (event) {
const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端
// 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的)
+ // 注意:大部分快捷键已在 core.js 中统一处理,此处只保留 ui.js 特有的快捷键
+ // 已移除的重复快捷键:CTRL/CMD+N/O/S/Z/Y/C/V/SHIFT+DEL/DEL(已在 core.js 中处理)
if (!_inputEdit && !_textEdit) {
- // CTRL/CMD+N - 新建
- if (isModifierKey && event.keyCode == 78) {
- event.preventDefault();
- $('#new').click();
- return;
- }
- // CTRL/CMD+O - 打开
- if (isModifierKey && event.keyCode == 79) {
- event.preventDefault();
- $('#open').click();
- return;
- }
- // CTRL/CMD+S - 保存
- if (isModifierKey && !event.shiftKey && event.keyCode == 83) {
- event.preventDefault();
- $('#save').click();
- return;
- }
- // CTRL/CMD+SHIFT+S - 保存副本
- if (isModifierKey && event.shiftKey && event.keyCode == 83) {
- event.preventDefault();
- if (typeof window.output === 'function') {
- window.output(null, saveAs);
- }
- return;
- }
- // CTRL/CMD+Z - 还原
- if (isModifierKey && !event.shiftKey && event.keyCode == 90) {
- event.preventDefault();
- $('#previous').click();
- return;
- }
- // CTRL/CMD+Y - 重做
- if (isModifierKey && !event.shiftKey && event.keyCode == 89) {
- event.preventDefault();
- $('#next').click();
- return;
- }
- // CTRL/CMD+C - 复制
- if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') {
- event.preventDefault();
- window.copySelection();
- return;
- }
- // CTRL/CMD+V - 粘贴
- if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') {
- event.preventDefault();
- window.pasteFromClipboard();
- return;
- }
- // CTRL/CMD+SHIFT+DEL (Windows) 或 CMD+SHIFT+Backspace (Mac) - 清空
- if (isModifierKey && event.shiftKey && (event.keyCode == 46 || event.keyCode == 8)) {
- event.preventDefault();
- $('#clean').click();
- return;
- }
- // CTRL/CMD+DEL (Windows) 或 CMD+Backspace (Mac) - 删除
- if (isModifierKey && !event.shiftKey && (event.keyCode == 46 || event.keyCode == 8)) {
- event.preventDefault();
- $('#delete').click();
- return;
- }
+ // ui.js 特有的快捷键可以在这里添加
}
// 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd)
@@ -439,6 +380,10 @@ $('#front_side').click(function () {
recordObjs = recordObjs1
recordJson = recordJson1
step = step1
+ // 同步到 window 对象,确保 recordState 使用正确的数组
+ window.recordObjs = recordObjs1
+ window.recordJson = recordJson1
+ window.step = step1
if (typeof window.updateList === 'function') {
window.updateList();
}
@@ -476,6 +421,10 @@ $('#back_side').click(function () {
recordObjs = recordObjs2
recordJson = recordJson2
step = step2
+ // 同步到 window 对象,确保 recordState 使用正确的数组
+ window.recordObjs = recordObjs2
+ window.recordJson = recordJson2
+ window.step = step2
if (typeof window.updateList === 'function') {
window.updateList();
}
@@ -894,147 +843,155 @@ $('#set_bg').click(function () {
}
})
$('#previous').click(function () {
+ // 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
+ if (window._isApplyingHistory) {
+ return;
+ }
//撤销一次操作
if (step.val == 0) {
return
}
step.val--
+
if ($('#front_side').hasClass('ui-button-active')) {
// 使用 recordJson1 和 recordObjs1
+ if (recordObjs1 && recordObjs1[step.val]) {
+ objs1 = JSON.parse(recordObjs1[step.val]);
+ objs = objs1;
+ // 同步更新 window 变量
+ window.objs1 = objs1;
+ window.objs = objs;
+ }
+
if (recordJson1 && recordJson1[step.val]) {
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson1[step.val], function() {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
- // 加载后更新列表和图标显示
- if (typeof window.updateList === 'function') {
- window.updateList();
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs1);
}
- canvas.renderAll();
- })
- }
- if (recordObjs1 && recordObjs1[step.val]) {
- objs1 = JSON.parse(recordObjs1[step.val])
- objs = objs1
+ // 调用状态恢复函数
+ if (typeof window.onStateRestored === 'function') {
+ window.onStateRestored();
+ }
+ window._isApplyingHistory = false;
+ });
}
} else if ($('#back_side').hasClass('ui-button-active')) {
// 使用 recordJson2 和 recordObjs2
+ if (recordObjs2 && recordObjs2[step.val]) {
+ objs2 = JSON.parse(recordObjs2[step.val]);
+ objs = objs2;
+ // 同步更新 window 变量
+ window.objs2 = objs2;
+ window.objs = objs;
+ }
+
if (recordJson2 && recordJson2[step.val]) {
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson2[step.val], function() {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
- // 加载后更新列表和图标显示
- if (typeof window.updateList === 'function') {
- window.updateList();
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs2);
}
- canvas.renderAll();
- })
- }
- if (recordObjs2 && recordObjs2[step.val]) {
- objs2 = JSON.parse(recordObjs2[step.val])
- objs = objs2
+ // 调用状态恢复函数
+ if (typeof window.onStateRestored === 'function') {
+ window.onStateRestored();
+ }
+ window._isApplyingHistory = false;
+ });
}
}
-
- background_image = canvas.getObjects()[0]
- canvas.renderAll()
- //recordState();
- if (typeof window.updateList === 'function') {
- window.updateList();
- }
-
- selectObj(-1)
-
- $('#base_control').hide()
- $('#line_control').hide()
- $('#pic_control').hide()
- $("#set_bg").hide();
- $('#out_pic_control').hide()
- $('#text_control').hide()
- $("#circle_text_control").hide();
- $('#out_text_control').hide()
- $('#rect_control').hide()
- $('#circle_control').hide()
- $('#counter_control').hide()
- $('#qrcode_Controll').hide()
- $('#barcode_controll').hide()
})
$('#next').click(function () {
+ // 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
+ if (window._isApplyingHistory) {
+ return;
+ }
//撤销一次撤销操作
// 根据当前是正面还是背面检查对应的 recordObjs 长度
+ let maxLen;
if ($('#front_side').hasClass('ui-button-active')) {
- if (step.val >= recordObjs1.length - 1) {
+ maxLen = recordObjs1 ? recordObjs1.length : 0;
+ if (step.val >= maxLen - 1) {
return
}
} else if ($('#back_side').hasClass('ui-button-active')) {
- if (step.val >= recordObjs2.length - 1) {
+ maxLen = recordObjs2 ? recordObjs2.length : 0;
+ if (step.val >= maxLen - 1) {
return
}
+ } else {
+ return;
}
++step.val
+
if ($('#front_side').hasClass('ui-button-active')) {
// 使用 recordJson1 和 recordObjs1
+ if (recordObjs1 && recordObjs1[step.val]) {
+ objs1 = JSON.parse(recordObjs1[step.val]);
+ objs = objs1;
+ // 同步更新 window 变量
+ window.objs1 = objs1;
+ window.objs = objs;
+ }
+
if (recordJson1 && recordJson1[step.val]) {
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson1[step.val], function() {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
- // 加载后更新列表和图标显示
- if (typeof window.updateList === 'function') {
- window.updateList();
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs1);
}
- canvas.renderAll();
- })
- }
- if (recordObjs1 && recordObjs1[step.val]) {
- objs1 = JSON.parse(recordObjs1[step.val])
- objs = objs1
+ // 调用状态恢复函数
+ if (typeof window.onStateRestored === 'function') {
+ window.onStateRestored();
+ }
+ window._isApplyingHistory = false;
+ });
}
} else if ($('#back_side').hasClass('ui-button-active')) {
// 使用 recordJson2 和 recordObjs2
+ if (recordObjs2 && recordObjs2[step.val]) {
+ objs2 = JSON.parse(recordObjs2[step.val]);
+ objs = objs2;
+ // 同步更新 window 变量
+ window.objs2 = objs2;
+ window.objs = objs;
+ }
+
if (recordJson2 && recordJson2[step.val]) {
+ window._isApplyingHistory = true;
canvas.loadFromJSON(recordJson2[step.val], function() {
// 加载后解锁所有对象(背景除外)
if (typeof window.unlockAllObjects === 'function') {
window.unlockAllObjects(canvas);
}
- // 加载后更新列表和图标显示
- if (typeof window.updateList === 'function') {
- window.updateList();
+ // 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
+ if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
+ window.regenerateQrCodesAndBarcodes(canvas, objs2);
}
- canvas.renderAll();
- })
+ // 调用状态恢复函数
+ if (typeof window.onStateRestored === 'function') {
+ window.onStateRestored();
+ }
+ window._isApplyingHistory = false;
+ });
}
- if (recordObjs2 && recordObjs2[step.val]) {
- objs2 = JSON.parse(recordObjs2[step.val])
- objs = objs2
- }
- }
- background_image = canvas.getObjects()[0]
- canvas.renderAll()
- selectObj(-1)
-
- $('#base_control').hide()
- $('#line_control').hide()
- $('#pic_control').hide()
- $("#set_bg").hide();
- $('#out_pic_control').hide()
- $('#text_control').hide()
- $("#circle_text_control").hide();
- $('#out_text_control').hide()
- $('#rect_control').hide()
- $('#circle_control').hide()
- $('#counter_control').hide()
- $('#qrcode_Controll').hide()
- $('#barcode_controll').hide()
- if (typeof window.updateList === 'function') {
- window.updateList();
}
})
$('#clean').click(function () {
@@ -1316,8 +1273,9 @@ $('#circle_stroke_transparent').click(function () {
// cleanupSrcFields 函数已在 output.js 中定义
function saveAs(op1, callback) {
- let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
- let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
+ let j = canvas1.toJSON(props)
+ let b = canvas2.toJSON(props)
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
if (typeof window.cleanupSrcFields === 'function') {
@@ -1464,7 +1422,7 @@ function addBackground() {
//pre_objs1.push(JSON.stringify(objs1));
recordObjs1.push(JSON.stringify(objs1))
- const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented'];
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
recordJson1.push(canvas1.toJSON(props))
})
// 类型改变
@@ -1483,7 +1441,7 @@ function addBackground() {
//pre_objs2.push(JSON.stringify(objs2));
recordObjs2.push(JSON.stringify(objs2))
- const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented'];
+ const props = window.TO_JSON_PROPERTIES || ['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline', 'evented', 'linethrough', 'textBackgroundColor', 'diameter', 'flipped'];
recordJson2.push(canvas2.toJSON(props))
let file = GetFile().get('file')
@@ -1635,6 +1593,7 @@ function selectOutPic(target, index) {
}
function addText(pointer) {
+ // 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt
let text = new fabric.IText('TEXT', {
//源:文本
left: pointer.x,
@@ -1643,6 +1602,7 @@ function addText(pointer) {
textBackgroundColor: '',
fontStyle: 'normal',
fontWeight: 'normal',
+ fontSize: 30,
underline: false,
linethrough: false,
fontFamily: 'Arial'
@@ -2405,7 +2365,8 @@ $("#circle_text_back_color").bind('input propertychange', function () {
})
$("#circle_text_text").bind('input propertychange', function () {
let val = $("#circle_text_text").val().trim()
- canvas.getActiveObject().set("text", ` ${val} `);
+ // 有内容时不加首行缩进(前面的空格),只保留后面的空格
+ canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
canvas.renderAll();
if (typeof window.updateList === 'function') {
window.updateList();
@@ -2448,7 +2409,8 @@ $("#out_text_size").bind('input propertychange', function () {
})
$('#out_text_text').bind('input propertychange', function () {
let val = $("#out_text_text").val().trim()
- canvas.getActiveObject().set("text", ` ${val} `);
+ // 有内容时不加首行缩进(前面的空格),只保留后面的空格
+ canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
canvas.renderAll()
if (typeof window.updateList === 'function') {
window.updateList();
@@ -2565,7 +2527,8 @@ $('#line_dist').bind('input propertychange', function () {
})
$('#text_text').bind('input propertychange', function () {
let val = $("#text_text").val().trim()
- canvas.getActiveObject().set("text", ` ${val} `);
+ // 有内容时不加首行缩进(前面的空格),只保留后面的空格
+ canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
canvas.renderAll()
if (typeof window.updateList === 'function') {
window.updateList();
diff --git a/package arm64.json b/package arm64.json
index 987591e..6b1838a 100644
--- a/package arm64.json
+++ b/package arm64.json
@@ -8,7 +8,7 @@
"package:x64": "electron-forge package --arch=x64",
"package:arm64": "electron-forge package --arch=arm64",
"merge": "node ./merge.js",
- "package:universal": "npm run package:x64 && npm run package:arm64 && npm run merge"
+ "package:universal": "yarn package:x64 && yarn package:arm64 && yarn merge"
},
"electronPackagerConfig": {
"packageManager": "npm",
diff --git a/package.json b/package.json
index 78c8a19..d7677d5 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,10 @@
"build:linux": "electron-builder --linux --x64",
"build:linux:arm64": "electron-builder --linux --arm64",
"build:all": "electron-builder -mwl",
- "release:win": "npm run encrypt:win && npm run cleanup && npm run build:win",
- "release:mac": "npm run encrypt:linux && npm run cleanup && npm run build:mac",
- "release:linux": "npm run encrypt:linux && npm run cleanup && npm run build:linux",
- "release:linux:arm64": "npm run build:linux:arm64"
+ "release:win": "yarn encrypt:win && yarn cleanup && yarn build:win",
+ "release:mac": "yarn encrypt:linux && yarn cleanup && yarn build:mac",
+ "release:linux": "yarn encrypt:linux && yarn cleanup && yarn build:linux",
+ "release:linux:arm64": "yarn build:linux:arm64"
},
"build": {
"productName": "SoonDesign",
@@ -103,7 +103,7 @@
}
},
"electronPackagerConfig": {
- "packageManager": "npm",
+ "packageManager": "yarn",
"icon": "./public/images/favicon.ico"
},
diff --git a/scripts/encrypt/README.md b/scripts/encrypt/README.md
index 7e44116..b678338 100644
--- a/scripts/encrypt/README.md
+++ b/scripts/encrypt/README.md
@@ -83,8 +83,8 @@
2. **运行加密脚本**
```bash
- npm run encrypt:win # Windows
- npm run encrypt:linux # Linux/Mac
+ yarncrypt:win # Windows
+ yarncrypt:linux # Linux/Mac
```
3. **验证结果**
diff --git a/scripts/encrypt/encrypt-electron.js b/scripts/encrypt/encrypt-electron.js
index 8076e67..fe0b235 100644
--- a/scripts/encrypt/encrypt-electron.js
+++ b/scripts/encrypt/encrypt-electron.js
@@ -14,7 +14,7 @@ if (!process.versions.electron) {
console.error('请使用以下方式运行:');
console.error(' Windows: encrypt-electron-win.bat');
console.error(' Linux/Mac: ./encrypt-electron.sh');
- console.error(' 或: npm run encrypt:win / npm run encrypt:linux');
+ console.error(' 或: yarncrypt:win / yaryarnpt:linux');
process.exit(1);
}