优化bug
This commit is contained in:
@@ -10,10 +10,10 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 安装依赖(只需一次)
|
# 1. 安装依赖(只需一次)
|
||||||
npm install
|
yarn install
|
||||||
|
|
||||||
# 2. 启动程序
|
# 2. 启动程序
|
||||||
npm start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
> **前置要求**:需要安装 [Node.js](https://nodejs.org/)(推荐 16 或更高版本)
|
> **前置要求**:需要安装 [Node.js](https://nodejs.org/)(推荐 16 或更高版本)
|
||||||
@@ -26,13 +26,13 @@ npm start
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Windows 系统
|
# Windows 系统
|
||||||
npm run release:win
|
yarn release:win
|
||||||
|
|
||||||
# Mac 系统
|
# Mac 系统
|
||||||
npm run release:mac
|
yarn release:mac
|
||||||
|
|
||||||
# Linux 系统
|
# Linux 系统
|
||||||
npm run release:linux
|
yarn release:linux
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ **自动完成**:加密 → 测试 → 清理 → 打包
|
✅ **自动完成**:加密 → 测试 → 清理 → 打包
|
||||||
@@ -45,8 +45,8 @@ npm run release:linux
|
|||||||
#### 步骤 1:加密代码
|
#### 步骤 1:加密代码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run encrypt:win # Windows
|
yarn encrypt:win # Windows
|
||||||
npm run encrypt:linux # Mac/Linux
|
yarn encrypt:linux # Mac/Linux
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 生成 `.jsc` 加密文件(自动覆盖旧文件)
|
✅ 生成 `.jsc` 加密文件(自动覆盖旧文件)
|
||||||
@@ -54,7 +54,7 @@ npm run encrypt:linux # Mac/Linux
|
|||||||
#### 步骤 2:测试
|
#### 步骤 2:测试
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 确认程序正常运行,无报错
|
✅ 确认程序正常运行,无报错
|
||||||
@@ -62,7 +62,7 @@ npm start
|
|||||||
#### 步骤 3:清理源文件
|
#### 步骤 3:清理源文件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run cleanup
|
yarn cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 删除 `.js` 源文件,自动备份到 `.backup` 文件夹
|
✅ 删除 `.js` 源文件,自动备份到 `.backup` 文件夹
|
||||||
@@ -70,9 +70,9 @@ npm run cleanup
|
|||||||
#### 步骤 4:打包
|
#### 步骤 4:打包
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:win # Windows
|
yarn build:win # Windows
|
||||||
npm run build:mac # Mac
|
yarn build:mac # Mac
|
||||||
npm run build:linux # Linux
|
yarn build:linux # Linux
|
||||||
```
|
```
|
||||||
|
|
||||||
✅ 生成安装包到 `build` 文件夹
|
✅ 生成安装包到 `build` 文件夹
|
||||||
@@ -84,7 +84,7 @@ npm run build:linux # Linux
|
|||||||
### 恢复源文件
|
### 恢复源文件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run restore
|
yarn restore
|
||||||
```
|
```
|
||||||
|
|
||||||
> 从 `.backup` 文件夹恢复所有源文件(误删除时使用)
|
> 从 `.backup` 文件夹恢复所有源文件(误删除时使用)
|
||||||
@@ -92,7 +92,7 @@ npm run restore
|
|||||||
### 清理加密文件
|
### 清理加密文件
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run cleanup:jsc
|
yarn cleanup:jsc
|
||||||
```
|
```
|
||||||
|
|
||||||
> 删除所有 `.jsc` 文件(重新加密前使用)
|
> 删除所有 `.jsc` 文件(重新加密前使用)
|
||||||
@@ -113,7 +113,7 @@ npm run cleanup:jsc
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
然后重新运行 `npm run encrypt:win`
|
然后重新运行 `yarn encrypt:win`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+50
-25
@@ -41,6 +41,8 @@ fabric.Object.prototype.objectCaching = false;
|
|||||||
ctx1 = canvas1.getSelectionContext();
|
ctx1 = canvas1.getSelectionContext();
|
||||||
ctx2 = canvas2.getSelectionContext();
|
ctx2 = canvas2.getSelectionContext();
|
||||||
is_bgi_add = false;
|
is_bgi_add = false;
|
||||||
|
// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史
|
||||||
|
window._isApplyingHistory = false;
|
||||||
// 同步到 window 对象
|
// 同步到 window 对象
|
||||||
window.ctx1 = ctx1;
|
window.ctx1 = ctx1;
|
||||||
window.ctx2 = ctx2;
|
window.ctx2 = ctx2;
|
||||||
@@ -1224,6 +1226,10 @@ window.selectObj = function selectObj(objIndices, isFromCanvas = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.recordState = function recordState() {
|
window.recordState = function recordState() {
|
||||||
|
// 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期
|
||||||
|
if (window._isApplyingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
//pre_objs.push(JSON.stringify(objs));
|
//pre_objs.push(JSON.stringify(objs));
|
||||||
//pre_json.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
|
//pre_json.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
|
||||||
//next_json = [];
|
//next_json = [];
|
||||||
@@ -1492,8 +1498,11 @@ function isObjectLocked(obj) {
|
|||||||
window.isObjectLocked = isObjectLocked;
|
window.isObjectLocked = isObjectLocked;
|
||||||
|
|
||||||
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
||||||
|
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
||||||
|
// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped)
|
||||||
const TO_JSON_PROPERTIES = [
|
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;
|
window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES;
|
||||||
@@ -1608,13 +1617,14 @@ function initCanvasEvents() {
|
|||||||
handleObjectSelected(e);
|
handleObjectSelected(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 阻止被锁定的对象被框选选中(只过滤被锁定的对象,保留未锁定的对象)
|
// 阻止被锁定的对象和辅助线被框选选中:
|
||||||
|
// - 锁定对象:不能被框选(只能通过列表选中)
|
||||||
|
// - 辅助线:永远不进入选区,避免选区被拉得很大
|
||||||
currentCanvas.on('before:selection:created', (e) => {
|
currentCanvas.on('before:selection:created', (e) => {
|
||||||
if (!e.targets || e.targets.length === 0) return;
|
if (!e.targets || e.targets.length === 0) return;
|
||||||
// 过滤掉被锁定的对象,保留辅助线和未锁定的对象
|
|
||||||
e.targets = e.targets.filter(target => {
|
e.targets = e.targets.filter(target => {
|
||||||
if (target.isGuideLine) return true; // 保留辅助线
|
if (target.isGuideLine) return false; // 完全排除辅助线
|
||||||
return !isObjectLocked(target); // 移除被锁定的对象,保留未锁定的对象
|
return !isObjectLocked(target); // 排除被锁定的普通对象
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1850,6 +1860,11 @@ function initRulers() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 防止重复调用:如果正在拖拽,直接返回
|
||||||
|
if ($dragLine && $dragLine.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建临时显示的线
|
// 创建临时显示的线
|
||||||
$dragLine = $('<div class="guide-line-drag"></div>');
|
$dragLine = $('<div class="guide-line-drag"></div>');
|
||||||
$('.canvas-wrapper').append($dragLine);
|
$('.canvas-wrapper').append($dragLine);
|
||||||
@@ -1922,18 +1937,13 @@ function initRulers() {
|
|||||||
let canvasX = relX / zoom;
|
let canvasX = relX / zoom;
|
||||||
let canvasY = relY / zoom;
|
let canvasY = relY / zoom;
|
||||||
|
|
||||||
// 获取画布的实际尺寸和可见区域尺寸
|
// 获取画布的实际尺寸(用于决定辅助线的长度)
|
||||||
let canvasWidth = canvas.width || 10000;
|
let canvasWidth = canvas.width || 2000;
|
||||||
let canvasHeight = canvas.height || 10000;
|
let canvasHeight = canvas.height || 2000;
|
||||||
// 获取可见区域的尺寸(考虑缩放)
|
// 使用与画布尺寸相关的长度,避免“整块区域都像辅助线”,同时保证能横穿整个画布
|
||||||
let visibleWidth = canvasWrapper.width() || canvasWidth;
|
let lineExtent = Math.max(canvasWidth, canvasHeight) * 2;
|
||||||
let visibleHeight = canvasWrapper.height() || canvasHeight;
|
|
||||||
// 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域
|
|
||||||
// 使用 100000 确保无论画布多大、如何缩放,都能覆盖整个可见区域
|
|
||||||
let lineExtent = 100000;
|
|
||||||
|
|
||||||
// 构造线对象,使用足够大的范围确保覆盖整个画布
|
// 构造线对象
|
||||||
// 线的起点和终点要足够远,确保覆盖整个可见区域
|
|
||||||
let linePoints = [];
|
let linePoints = [];
|
||||||
if (type === 'horizontal') {
|
if (type === 'horizontal') {
|
||||||
// 水平线:从很远的左边到很远的右边
|
// 水平线:从很远的左边到很远的右边
|
||||||
@@ -1962,19 +1972,34 @@ function initRulers() {
|
|||||||
lockScalingX: true,
|
lockScalingX: true,
|
||||||
lockScalingY: true,
|
lockScalingY: true,
|
||||||
hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
|
hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
|
||||||
perPixelTargetFind: false, // 使用边界框检测而不是像素检测
|
// design1 中改回边框命中模型,保证更容易点中拖拽
|
||||||
targetFindTolerance: 10, // 增加点击容差,扩大可点击区域(像素)
|
perPixelTargetFind: false,
|
||||||
padding: 10, // 增加选择边界的内边距
|
targetFindTolerance: 10,
|
||||||
|
padding: 10,
|
||||||
isGuideLine: true,
|
isGuideLine: true,
|
||||||
ignoreSave: true
|
ignoreSave: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// 限制移动方向
|
// 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉)
|
||||||
guideLine.on('moving', function (e) {
|
guideLine.on('moving', function () {
|
||||||
if (type === 'horizontal') {
|
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 {
|
} 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 };
|
let appData = (idx > 0 && objs[idx - 1]) ? objs[idx - 1] : { type: 0 };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 1. 保存画面样式 (坐标、颜色、字号等)
|
// 1. 保存画面样式 (坐标、颜色、字号等,包含图片的 src)
|
||||||
fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data']),
|
fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data', 'src']),
|
||||||
// 2. 保存业务属性 (深拷贝,防止引用冲突)
|
// 2. 保存业务属性 (深拷贝,防止引用冲突)
|
||||||
appData: JSON.parse(JSON.stringify(appData))
|
appData: JSON.parse(JSON.stringify(appData))
|
||||||
};
|
};
|
||||||
|
|||||||
+47
-3
@@ -917,6 +917,11 @@ $("#set_bg").click(function () {//设置为背�?
|
|||||||
// onStateRestored 函数在 core.js 中定义并附加到 window 对象
|
// onStateRestored 函数在 core.js 中定义并附加到 window 对象
|
||||||
// ==========================================
|
// ==========================================
|
||||||
$("#previous").click(function () {
|
$("#previous").click(function () {
|
||||||
|
// 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
|
||||||
|
if (window._isApplyingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (step.val == 0) return;
|
if (step.val == 0) return;
|
||||||
|
|
||||||
step.val--;
|
step.val--;
|
||||||
@@ -928,29 +933,50 @@ $("#previous").click(function () {
|
|||||||
objs = objs1;
|
objs = objs1;
|
||||||
|
|
||||||
// 2. 恢复画布 (使用回调函数)
|
// 2. 恢复画布 (使用回调函数)
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson1[step.val], function () {
|
canvas.loadFromJSON(recordJson1[step.val], function () {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
|
window.regenerateQrCodesAndBarcodes(canvas, objs1);
|
||||||
|
}
|
||||||
if (typeof window.onStateRestored === 'function') {
|
if (typeof window.onStateRestored === 'function') {
|
||||||
window.onStateRestored(); // 图片加载完才执行这一行
|
window.onStateRestored(); // 图片加载完才执行这一行
|
||||||
}
|
}
|
||||||
|
window._isApplyingHistory = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 背面逻辑
|
// 背面逻辑
|
||||||
objs2 = JSON.parse(recordObjs2[step.val]);
|
objs2 = JSON.parse(recordObjs2[step.val]);
|
||||||
objs = objs2;
|
objs = objs2;
|
||||||
|
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson2[step.val], function () {
|
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') {
|
if (typeof window.onStateRestored === 'function') {
|
||||||
window.onStateRestored();
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
|
window._isApplyingHistory = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// ==========================================
|
// ==========================================
|
||||||
$("#next").click(function () {
|
$("#next").click(function () {
|
||||||
|
// 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
|
||||||
|
if (window._isApplyingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前历史记录的最大长度
|
// 获取当前历史记录的最大长度
|
||||||
let maxLen = $("#front_side").hasClass("ui-button-active") ? recordObjs1.length : recordObjs2.length;
|
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]);
|
objs1 = JSON.parse(recordObjs1[step.val]);
|
||||||
objs = objs1;
|
objs = objs1;
|
||||||
|
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson1[step.val], function () {
|
canvas.loadFromJSON(recordJson1[step.val], function () {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
|
window.regenerateQrCodesAndBarcodes(canvas, objs1);
|
||||||
|
}
|
||||||
if (typeof window.onStateRestored === 'function') {
|
if (typeof window.onStateRestored === 'function') {
|
||||||
window.onStateRestored(); // 图片加载完才执行这一行
|
window.onStateRestored(); // 图片加载完才执行这一行
|
||||||
}
|
}
|
||||||
|
window._isApplyingHistory = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
objs2 = JSON.parse(recordObjs2[step.val]);
|
objs2 = JSON.parse(recordObjs2[step.val]);
|
||||||
objs = objs2;
|
objs = objs2;
|
||||||
|
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson2[step.val], function () {
|
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') {
|
if (typeof window.onStateRestored === 'function') {
|
||||||
window.onStateRestored();
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
|
window._isApplyingHistory = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1280,8 +1322,9 @@ $("#circle_stroke_transparent").click(function () {
|
|||||||
|
|
||||||
// saveAs 函数保留,因为它被作为回调传递给 output 函数(第52行)
|
// saveAs 函数保留,因为它被作为回调传递给 output 函数(第52行)
|
||||||
function saveAs(op1, callback) {
|
function saveAs(op1, callback) {
|
||||||
let j = canvas1.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 b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]);
|
let j = canvas1.toJSON(props);
|
||||||
|
let b = canvas2.toJSON(props);
|
||||||
|
|
||||||
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
|
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
|
||||||
if (typeof window.cleanupSrcFields === 'function') {
|
if (typeof window.cleanupSrcFields === 'function') {
|
||||||
@@ -1634,7 +1677,8 @@ $("#circle_text_back_color").bind('input propertychange', function () {
|
|||||||
})
|
})
|
||||||
$("#circle_text_text").bind('input propertychange', function () {
|
$("#circle_text_text").bind('input propertychange', function () {
|
||||||
let val = $("#circle_text_text").val().trim()
|
let val = $("#circle_text_text").val().trim()
|
||||||
canvas.getActiveObject().set("text", ` ${val} `);
|
// 有内容时不加首行缩进(前面的空格),只保留后面的空格
|
||||||
|
canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
|
|||||||
+219
-88
@@ -32,6 +32,8 @@ canvas2 = new fabric.Canvas('canvas2')
|
|||||||
// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
// 同步到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
||||||
window.canvas1 = canvas1;
|
window.canvas1 = canvas1;
|
||||||
window.canvas2 = canvas2;
|
window.canvas2 = canvas2;
|
||||||
|
// 撤销/重做过程中使用的标志位,防止并发操作和误记录历史
|
||||||
|
window._isApplyingHistory = false;
|
||||||
fabric.Object.prototype.objectCaching = false
|
fabric.Object.prototype.objectCaching = false
|
||||||
ctx1 = canvas1.getSelectionContext()
|
ctx1 = canvas1.getSelectionContext()
|
||||||
ctx2 = canvas2.getSelectionContext()
|
ctx2 = canvas2.getSelectionContext()
|
||||||
@@ -304,52 +306,15 @@ function initRulers() {
|
|||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听画布缩放变化
|
// 监听画布缩放/渲染变化,仅用于重绘标尺
|
||||||
|
// 辅助线交给 Fabric 自己渲染,否则会出现“重影 / 变两三根”的现象
|
||||||
canvas1.on('after:render', function () {
|
canvas1.on('after:render', function () {
|
||||||
drawHorizontalRuler();
|
drawHorizontalRuler();
|
||||||
drawVerticalRuler();
|
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 () {
|
canvas2.on('after:render', function () {
|
||||||
drawHorizontalRuler();
|
drawHorizontalRuler();
|
||||||
drawVerticalRuler();
|
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) ---
|
// --- 辅助线功能 (参考 design1) ---
|
||||||
@@ -367,6 +332,11 @@ function initRulers() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 防止重复调用:如果正在拖拽,直接返回
|
||||||
|
if ($dragLine && $dragLine.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 创建临时显示的线
|
// 创建临时显示的线
|
||||||
$dragLine = $('<div class="guide-line-drag"></div>');
|
$dragLine = $('<div class="guide-line-drag"></div>');
|
||||||
canvasWrapper.append($dragLine);
|
canvasWrapper.append($dragLine);
|
||||||
@@ -440,8 +410,8 @@ function initRulers() {
|
|||||||
let canvasX = relX / zoom;
|
let canvasX = relX / zoom;
|
||||||
let canvasY = relY / zoom;
|
let canvasY = relY / zoom;
|
||||||
|
|
||||||
// 使用足够大的固定值,确保辅助线能够覆盖整个画布和可见区域
|
// 使用与画布尺寸相关的长度,避免“整块区域都像辅助线”
|
||||||
let lineExtent = 100000;
|
let lineExtent = Math.max(canvas1.width, canvas1.height) * 2;
|
||||||
|
|
||||||
// 构造线对象
|
// 构造线对象
|
||||||
let linePoints = [];
|
let linePoints = [];
|
||||||
@@ -470,19 +440,35 @@ function initRulers() {
|
|||||||
lockScalingX: true,
|
lockScalingX: true,
|
||||||
lockScalingY: true,
|
lockScalingY: true,
|
||||||
hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
|
hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize',
|
||||||
perPixelTargetFind: false,
|
// 只在靠近线条时触发命中,避免“大块区域都像辅助线”
|
||||||
targetFindTolerance: 10,
|
// 同时适当放宽 tolerance,保证易于点中拖拽
|
||||||
padding: 10,
|
perPixelTargetFind: true,
|
||||||
|
targetFindTolerance: 6,
|
||||||
|
padding: 2,
|
||||||
isGuideLine: true,
|
isGuideLine: true,
|
||||||
ignoreSave: true
|
ignoreSave: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// 限制移动方向
|
// 限制移动方向(只约束方向,不把整条线拖到极远,避免“整片区域是辅助线”的感觉)
|
||||||
guideLine.on('moving', function (e) {
|
guideLine.on('moving', function () {
|
||||||
if (type === 'horizontal') {
|
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 {
|
} else {
|
||||||
this.top = -lineExtent;
|
// 垂直线:根据当前中心点的 X,重算 y1/y2,使其竖穿画布
|
||||||
|
const cx = this.getCenterPoint().x;
|
||||||
|
this.set({
|
||||||
|
x1: cx,
|
||||||
|
y1: -lineExtent,
|
||||||
|
x2: cx,
|
||||||
|
y2: lineExtent
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -560,8 +546,10 @@ function selectPic(target, index) {
|
|||||||
$("#set_bg").show();
|
$("#set_bg").show();
|
||||||
let activeObj = canvas.getActiveObject();
|
let activeObj = canvas.getActiveObject();
|
||||||
|
|
||||||
// 检查对象是否被锁定
|
// 检查对象是否被锁定:
|
||||||
if (isObjectLocked(activeObj)) {
|
// - 从画布点击时(_selectingFromList 为 false):不允许选中,被锁定的对象保持不可选状态
|
||||||
|
// - 从对象列表点击时(_selectingFromList 为 true):允许选中,但对象在画布上仍然保持锁定
|
||||||
|
if (!window._selectingFromList && isObjectLocked(activeObj)) {
|
||||||
canvas.discardActiveObject();
|
canvas.discardActiveObject();
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
return;
|
return;
|
||||||
@@ -1035,8 +1023,10 @@ function isObjectLocked(obj) {
|
|||||||
window.isObjectLocked = isObjectLocked;
|
window.isObjectLocked = isObjectLocked;
|
||||||
|
|
||||||
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
// 统一的 toJSON 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
||||||
|
// 包含所有需要保存的属性,特别是自定义对象的属性(如 CurvedText 的 diameter, flipped)
|
||||||
const TO_JSON_PROPERTIES = [
|
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;
|
window.TO_JSON_PROPERTIES = TO_JSON_PROPERTIES;
|
||||||
@@ -1139,17 +1129,37 @@ function initCanvasEvents() {
|
|||||||
objs = (currentCanvas === canvas1) ? objs1 : objs2;
|
objs = (currentCanvas === canvas1) ? objs1 : objs2;
|
||||||
handleObjectSelected(e);
|
handleObjectSelected(e);
|
||||||
});
|
});
|
||||||
// 阻止被锁定的对象被框选选中(只过滤被锁定的对象,保留未锁定的对象)
|
// 阻止被锁定的对象被框选选中,同时彻底排除辅助线
|
||||||
currentCanvas.on('before:selection:created', (e) => {
|
currentCanvas.on('before:selection:created', (e) => {
|
||||||
if (!e.targets || e.targets.length === 0) return;
|
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 => {
|
e.targets = e.targets.filter(target => {
|
||||||
if (target.isGuideLine) return true; // 保留辅助线
|
if (target.isGuideLine) return false; // 完全移除辅助线
|
||||||
return !isObjectLocked(target); // 移除被锁定的对象,保留未锁定的对象
|
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 事件以确保对象选中时正确显示属性面板
|
// 添加 selection 事件以确保对象选中时正确显示属性面板
|
||||||
|
// 注意:真正排除辅助线的逻辑已经在 before:selection:created 里完成,
|
||||||
|
// 这里不再对 ActiveSelection 做任何重建,避免 Fabric 内部递归导致栈溢出。
|
||||||
currentCanvas.on('selection:created', (e) => {
|
currentCanvas.on('selection:created', (e) => {
|
||||||
canvas = currentCanvas;
|
canvas = currentCanvas;
|
||||||
objs = (currentCanvas === canvas1) ? objs1 : objs2;
|
objs = (currentCanvas === canvas1) ? objs1 : objs2;
|
||||||
@@ -1244,6 +1254,10 @@ function initCanvasEvents() {
|
|||||||
|
|
||||||
// 将 updateList 附加到 window 对象,确保全局可访问
|
// 将 updateList 附加到 window 对象,确保全局可访问
|
||||||
window.updateList = function updateList() {
|
window.updateList = function updateList() {
|
||||||
|
// 确保使用正确的 objs(优先使用 window.objs,因为它可能被撤回/重做更新)
|
||||||
|
const currentObjs = window.objs || objs;
|
||||||
|
const currentCanvas = window.canvas || canvas;
|
||||||
|
|
||||||
let str = "";
|
let str = "";
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
@@ -1254,7 +1268,7 @@ window.updateList = function updateList() {
|
|||||||
// 定义序号样式:靠右,有左边框
|
// 定义序号样式:靠右,有左边框
|
||||||
const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;";
|
const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;";
|
||||||
|
|
||||||
for (let item of objs) {
|
for (let item of currentObjs) {
|
||||||
let displayIndex = i + 1; // 序号底层为1
|
let displayIndex = i + 1; // 序号底层为1
|
||||||
|
|
||||||
// 图标逻辑
|
// 图标逻辑
|
||||||
@@ -1281,8 +1295,8 @@ window.updateList = function updateList() {
|
|||||||
// 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性)
|
// 检查锁定状态(通过lockMovementX判断,因为锁定时会设置所有lock属性)
|
||||||
// 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1
|
// 注意:索引0是背景图,objs数组是从下标0开始存第1个对象的,所以是 i+1
|
||||||
let isLocked = false;
|
let isLocked = false;
|
||||||
if (canvas.getObjects()[i + 1]) {
|
if (currentCanvas.getObjects()[i + 1]) {
|
||||||
let canvasObj = canvas.getObjects()[i + 1];
|
let canvasObj = currentCanvas.getObjects()[i + 1];
|
||||||
isLocked = canvasObj.get('lockMovementX') === true;
|
isLocked = canvasObj.get('lockMovementX') === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,12 +1399,18 @@ window.selectObj = function selectObj(objIndices, isFromCanvas = false) {
|
|||||||
|
|
||||||
// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
// 将 recordState 附加到 window 对象,确保在 eval() 加载 .jsc 文件时也能访问
|
||||||
window.recordState = function recordState() {
|
window.recordState = function recordState() {
|
||||||
// 使用 window 变量作为后备,确保在 eval() 执行时也能访问
|
// 撤销/重做过程中不记录历史,避免产生“中间状态”,导致需要点多次才能回到预期
|
||||||
const currentStep = window.step || step;
|
if (window._isApplyingHistory) {
|
||||||
const currentRecordObjs = window.recordObjs || recordObjs;
|
return;
|
||||||
const currentRecordJson = window.recordJson || recordJson;
|
}
|
||||||
|
// 确定当前是正面还是背面,直接使用对应的数组
|
||||||
|
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 currentObjs = window.objs || objs;
|
||||||
const currentCanvas = window.canvas || canvas;
|
const currentCanvas = window.canvas || canvas;
|
||||||
|
|
||||||
++currentStep.val;
|
++currentStep.val;
|
||||||
for (let i = currentStep.val; i < currentRecordObjs.length; i++) {
|
for (let i = currentStep.val; i < currentRecordObjs.length; i++) {
|
||||||
currentRecordObjs.pop();
|
currentRecordObjs.pop();
|
||||||
@@ -1401,6 +1421,35 @@ window.recordState = function recordState() {
|
|||||||
let j = currentCanvas.toJSON(TO_JSON_PROPERTIES);
|
let j = currentCanvas.toJSON(TO_JSON_PROPERTIES);
|
||||||
j.objects[0].hoverCursor = "default";
|
j.objects[0].hoverCursor = "default";
|
||||||
currentRecordJson.push(j);
|
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() {
|
function recordState() {
|
||||||
@@ -1560,7 +1609,8 @@ function addBackground() {
|
|||||||
canvas1.renderAll()
|
canvas1.renderAll()
|
||||||
|
|
||||||
recordObjs1.push(JSON.stringify(objs1))
|
recordObjs1.push(JSON.stringify(objs1))
|
||||||
recordJson1.push(canvas1.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'];
|
||||||
|
recordJson1.push(canvas1.toJSON(props))
|
||||||
})
|
})
|
||||||
// 类型改变
|
// 类型改变
|
||||||
fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) {
|
fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) {
|
||||||
@@ -1620,6 +1670,7 @@ function addOutPic(pointer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addText(pointer) {
|
function addText(pointer) {
|
||||||
|
// 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt
|
||||||
let text = new fabric.IText('TEXT', {
|
let text = new fabric.IText('TEXT', {
|
||||||
left: pointer.x,
|
left: pointer.x,
|
||||||
top: pointer.y,
|
top: pointer.y,
|
||||||
@@ -1627,6 +1678,7 @@ function addText(pointer) {
|
|||||||
textBackgroundColor: '',
|
textBackgroundColor: '',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
|
fontSize: 30,
|
||||||
underline: false,
|
underline: false,
|
||||||
linethrough: false,
|
linethrough: false,
|
||||||
fontFamily: 'Arial'
|
fontFamily: 'Arial'
|
||||||
@@ -2354,50 +2406,129 @@ window.copySelection = function copySelection() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储原始对象用于克隆
|
// 【核心修复】遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据
|
||||||
clipboardData.objects = activeObjects;
|
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() {
|
window.pasteFromClipboard = function pasteFromClipboard() {
|
||||||
if (!clipboardData.objects || clipboardData.objects.length === 0) {
|
// 检查是否有数据
|
||||||
|
if (!clipboardData.data || clipboardData.data.length === 0) {
|
||||||
layer.msg("剪贴板为空")
|
layer.msg("剪贴板为空")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 克隆所有对象
|
// 防止重复调用:如果正在粘贴,直接返回
|
||||||
fabric.util.enlivenObjects(
|
if (window._isPasting) {
|
||||||
clipboardData.objects.map(obj => obj.toObject()),
|
return;
|
||||||
clonedObjects => {
|
}
|
||||||
// 设置新位置(添加偏移)
|
window._isPasting = true;
|
||||||
clonedObjects.forEach((clone, index) => {
|
|
||||||
let idx = getIndex(clipboardData.objects[index], canvas)
|
// 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({
|
clone.set({
|
||||||
left: clone.left + clipboardData.offset,
|
left: clone.left + clipboardData.offset,
|
||||||
top: clone.top + clipboardData.offset,
|
top: clone.top + clipboardData.offset,
|
||||||
evented: true
|
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);
|
canvas.add(clone);
|
||||||
let tobj = { ...objs[idx - 1] }
|
objs.push(newAppData); // 将新业务数据推入当前页面的 objs 数组
|
||||||
if (tobj.name) tobj.name = tobj.name + objs.length
|
|
||||||
objs.push(tobj);
|
|
||||||
canvas.renderAll();
|
|
||||||
updateList();
|
|
||||||
recordState();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 选中粘贴的对象
|
// 3. 选中新粘贴出来的对象
|
||||||
canvas.discardActiveObject();
|
let sel = new fabric.ActiveSelection(objects, { canvas: canvas });
|
||||||
const sel = new fabric.ActiveSelection(clonedObjects, {
|
|
||||||
canvas: canvas
|
|
||||||
});
|
|
||||||
canvas.setActiveObject(sel);
|
canvas.setActiveObject(sel);
|
||||||
canvas.requestRenderAll();
|
canvas.requestRenderAll();
|
||||||
selectObj(objs.length - 1);
|
|
||||||
// 增加下次偏移
|
// 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;
|
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();
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-52
@@ -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,并在回调中重新生成二维码和条形码
|
// 加载 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 () {
|
canvas1.loadFromJSON(j.f, function () {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
@@ -1260,6 +1364,8 @@ window.openFile = function open(file) {
|
|||||||
window.updateList();
|
window.updateList();
|
||||||
}
|
}
|
||||||
canvas1.renderAll()
|
canvas1.renderAll()
|
||||||
|
canvas1Loaded = true
|
||||||
|
saveInitialState()
|
||||||
})
|
})
|
||||||
canvas2.loadFromJSON(j.b, function () {
|
canvas2.loadFromJSON(j.b, function () {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
@@ -1274,59 +1380,9 @@ window.openFile = function open(file) {
|
|||||||
window.updateList();
|
window.updateList();
|
||||||
}
|
}
|
||||||
canvas2.renderAll()
|
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() {
|
function clearAll() {
|
||||||
|
|||||||
+109
-146
@@ -28,69 +28,10 @@ $('body').keydown(function (event) {
|
|||||||
const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端
|
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) {
|
if (!_inputEdit && !_textEdit) {
|
||||||
// CTRL/CMD+N - 新建
|
// ui.js 特有的快捷键可以在这里添加
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
|
// 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta)
|
||||||
if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd)
|
if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd)
|
||||||
@@ -439,6 +380,10 @@ $('#front_side').click(function () {
|
|||||||
recordObjs = recordObjs1
|
recordObjs = recordObjs1
|
||||||
recordJson = recordJson1
|
recordJson = recordJson1
|
||||||
step = step1
|
step = step1
|
||||||
|
// 同步到 window 对象,确保 recordState 使用正确的数组
|
||||||
|
window.recordObjs = recordObjs1
|
||||||
|
window.recordJson = recordJson1
|
||||||
|
window.step = step1
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
}
|
}
|
||||||
@@ -476,6 +421,10 @@ $('#back_side').click(function () {
|
|||||||
recordObjs = recordObjs2
|
recordObjs = recordObjs2
|
||||||
recordJson = recordJson2
|
recordJson = recordJson2
|
||||||
step = step2
|
step = step2
|
||||||
|
// 同步到 window 对象,确保 recordState 使用正确的数组
|
||||||
|
window.recordObjs = recordObjs2
|
||||||
|
window.recordJson = recordJson2
|
||||||
|
window.step = step2
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
}
|
}
|
||||||
@@ -894,148 +843,156 @@ $('#set_bg').click(function () {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
$('#previous').click(function () {
|
$('#previous').click(function () {
|
||||||
|
// 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
|
||||||
|
if (window._isApplyingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
//撤销一次操作
|
//撤销一次操作
|
||||||
if (step.val == 0) {
|
if (step.val == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
step.val--
|
step.val--
|
||||||
|
|
||||||
if ($('#front_side').hasClass('ui-button-active')) {
|
if ($('#front_side').hasClass('ui-button-active')) {
|
||||||
// 使用 recordJson1 和 recordObjs1
|
// 使用 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]) {
|
if (recordJson1 && recordJson1[step.val]) {
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson1[step.val], function() {
|
canvas.loadFromJSON(recordJson1[step.val], function() {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
// 加载后更新列表和图标显示
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
window.updateList();
|
window.regenerateQrCodesAndBarcodes(canvas, objs1);
|
||||||
}
|
}
|
||||||
canvas.renderAll();
|
// 调用状态恢复函数
|
||||||
})
|
if (typeof window.onStateRestored === 'function') {
|
||||||
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
if (recordObjs1 && recordObjs1[step.val]) {
|
window._isApplyingHistory = false;
|
||||||
objs1 = JSON.parse(recordObjs1[step.val])
|
});
|
||||||
objs = objs1
|
|
||||||
}
|
}
|
||||||
} else if ($('#back_side').hasClass('ui-button-active')) {
|
} else if ($('#back_side').hasClass('ui-button-active')) {
|
||||||
// 使用 recordJson2 和 recordObjs2
|
// 使用 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]) {
|
if (recordJson2 && recordJson2[step.val]) {
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson2[step.val], function() {
|
canvas.loadFromJSON(recordJson2[step.val], function() {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
// 加载后更新列表和图标显示
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
window.updateList();
|
window.regenerateQrCodesAndBarcodes(canvas, objs2);
|
||||||
}
|
}
|
||||||
canvas.renderAll();
|
// 调用状态恢复函数
|
||||||
})
|
if (typeof window.onStateRestored === 'function') {
|
||||||
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
if (recordObjs2 && recordObjs2[step.val]) {
|
window._isApplyingHistory = false;
|
||||||
objs2 = JSON.parse(recordObjs2[step.val])
|
});
|
||||||
objs = objs2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 () {
|
$('#next').click(function () {
|
||||||
|
// 正在应用历史(撤销/重做)时,直接忽略新的点击,防止并发操作
|
||||||
|
if (window._isApplyingHistory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
//撤销一次撤销操作
|
//撤销一次撤销操作
|
||||||
// 根据当前是正面还是背面检查对应的 recordObjs 长度
|
// 根据当前是正面还是背面检查对应的 recordObjs 长度
|
||||||
|
let maxLen;
|
||||||
if ($('#front_side').hasClass('ui-button-active')) {
|
if ($('#front_side').hasClass('ui-button-active')) {
|
||||||
if (step.val >= recordObjs1.length - 1) {
|
maxLen = recordObjs1 ? recordObjs1.length : 0;
|
||||||
|
if (step.val >= maxLen - 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if ($('#back_side').hasClass('ui-button-active')) {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
++step.val
|
++step.val
|
||||||
|
|
||||||
if ($('#front_side').hasClass('ui-button-active')) {
|
if ($('#front_side').hasClass('ui-button-active')) {
|
||||||
// 使用 recordJson1 和 recordObjs1
|
// 使用 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]) {
|
if (recordJson1 && recordJson1[step.val]) {
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson1[step.val], function() {
|
canvas.loadFromJSON(recordJson1[step.val], function() {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
// 加载后更新列表和图标显示
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
window.updateList();
|
window.regenerateQrCodesAndBarcodes(canvas, objs1);
|
||||||
}
|
}
|
||||||
canvas.renderAll();
|
// 调用状态恢复函数
|
||||||
})
|
if (typeof window.onStateRestored === 'function') {
|
||||||
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
if (recordObjs1 && recordObjs1[step.val]) {
|
window._isApplyingHistory = false;
|
||||||
objs1 = JSON.parse(recordObjs1[step.val])
|
});
|
||||||
objs = objs1
|
|
||||||
}
|
}
|
||||||
} else if ($('#back_side').hasClass('ui-button-active')) {
|
} else if ($('#back_side').hasClass('ui-button-active')) {
|
||||||
// 使用 recordJson2 和 recordObjs2
|
// 使用 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]) {
|
if (recordJson2 && recordJson2[step.val]) {
|
||||||
|
window._isApplyingHistory = true;
|
||||||
canvas.loadFromJSON(recordJson2[step.val], function() {
|
canvas.loadFromJSON(recordJson2[step.val], function() {
|
||||||
// 加载后解锁所有对象(背景除外)
|
// 加载后解锁所有对象(背景除外)
|
||||||
if (typeof window.unlockAllObjects === 'function') {
|
if (typeof window.unlockAllObjects === 'function') {
|
||||||
window.unlockAllObjects(canvas);
|
window.unlockAllObjects(canvas);
|
||||||
}
|
}
|
||||||
// 加载后更新列表和图标显示
|
// 重新生成二维码和条码(因为它们需要根据 objs 数据动态生成)
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.regenerateQrCodesAndBarcodes === 'function') {
|
||||||
window.updateList();
|
window.regenerateQrCodesAndBarcodes(canvas, objs2);
|
||||||
}
|
}
|
||||||
canvas.renderAll();
|
// 调用状态恢复函数
|
||||||
})
|
if (typeof window.onStateRestored === 'function') {
|
||||||
|
window.onStateRestored();
|
||||||
}
|
}
|
||||||
if (recordObjs2 && recordObjs2[step.val]) {
|
window._isApplyingHistory = false;
|
||||||
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 () {
|
$('#clean').click(function () {
|
||||||
objs.splice(0, objs.length)
|
objs.splice(0, objs.length)
|
||||||
@@ -1316,8 +1273,9 @@ $('#circle_stroke_transparent').click(function () {
|
|||||||
// cleanupSrcFields 函数已在 output.js 中定义
|
// cleanupSrcFields 函数已在 output.js 中定义
|
||||||
|
|
||||||
function saveAs(op1, callback) {
|
function saveAs(op1, callback) {
|
||||||
let j = canvas1.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 b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])
|
let j = canvas1.toJSON(props)
|
||||||
|
let b = canvas2.toJSON(props)
|
||||||
|
|
||||||
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
|
// 清理不需要的 src 字段(使用 window.cleanupSrcFields,在 output.js 中定义)
|
||||||
if (typeof window.cleanupSrcFields === 'function') {
|
if (typeof window.cleanupSrcFields === 'function') {
|
||||||
@@ -1464,7 +1422,7 @@ function addBackground() {
|
|||||||
//pre_objs1.push(JSON.stringify(objs1));
|
//pre_objs1.push(JSON.stringify(objs1));
|
||||||
|
|
||||||
recordObjs1.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))
|
recordJson1.push(canvas1.toJSON(props))
|
||||||
})
|
})
|
||||||
// 类型改变
|
// 类型改变
|
||||||
@@ -1483,7 +1441,7 @@ function addBackground() {
|
|||||||
//pre_objs2.push(JSON.stringify(objs2));
|
//pre_objs2.push(JSON.stringify(objs2));
|
||||||
|
|
||||||
recordObjs2.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))
|
recordJson2.push(canvas2.toJSON(props))
|
||||||
|
|
||||||
let file = GetFile().get('file')
|
let file = GetFile().get('file')
|
||||||
@@ -1635,6 +1593,7 @@ function selectOutPic(target, index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addText(pointer) {
|
function addText(pointer) {
|
||||||
|
// 设置默认字号为30px(像素值),这样显示的字号约为 30 * 72 / 300 = 7.2pt
|
||||||
let text = new fabric.IText('TEXT', {
|
let text = new fabric.IText('TEXT', {
|
||||||
//源:文本
|
//源:文本
|
||||||
left: pointer.x,
|
left: pointer.x,
|
||||||
@@ -1643,6 +1602,7 @@ function addText(pointer) {
|
|||||||
textBackgroundColor: '',
|
textBackgroundColor: '',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
|
fontSize: 30,
|
||||||
underline: false,
|
underline: false,
|
||||||
linethrough: false,
|
linethrough: false,
|
||||||
fontFamily: 'Arial'
|
fontFamily: 'Arial'
|
||||||
@@ -2405,7 +2365,8 @@ $("#circle_text_back_color").bind('input propertychange', function () {
|
|||||||
})
|
})
|
||||||
$("#circle_text_text").bind('input propertychange', function () {
|
$("#circle_text_text").bind('input propertychange', function () {
|
||||||
let val = $("#circle_text_text").val().trim()
|
let val = $("#circle_text_text").val().trim()
|
||||||
canvas.getActiveObject().set("text", ` ${val} `);
|
// 有内容时不加首行缩进(前面的空格),只保留后面的空格
|
||||||
|
canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
@@ -2448,7 +2409,8 @@ $("#out_text_size").bind('input propertychange', function () {
|
|||||||
})
|
})
|
||||||
$('#out_text_text').bind('input propertychange', function () {
|
$('#out_text_text').bind('input propertychange', function () {
|
||||||
let val = $("#out_text_text").val().trim()
|
let val = $("#out_text_text").val().trim()
|
||||||
canvas.getActiveObject().set("text", ` ${val} `);
|
// 有内容时不加首行缩进(前面的空格),只保留后面的空格
|
||||||
|
canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
@@ -2565,7 +2527,8 @@ $('#line_dist').bind('input propertychange', function () {
|
|||||||
})
|
})
|
||||||
$('#text_text').bind('input propertychange', function () {
|
$('#text_text').bind('input propertychange', function () {
|
||||||
let val = $("#text_text").val().trim()
|
let val = $("#text_text").val().trim()
|
||||||
canvas.getActiveObject().set("text", ` ${val} `);
|
// 有内容时不加首行缩进(前面的空格),只保留后面的空格
|
||||||
|
canvas.getActiveObject().set("text", val ? `${val} ` : ` ${val} `);
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
if (typeof window.updateList === 'function') {
|
if (typeof window.updateList === 'function') {
|
||||||
window.updateList();
|
window.updateList();
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
"package:x64": "electron-forge package --arch=x64",
|
"package:x64": "electron-forge package --arch=x64",
|
||||||
"package:arm64": "electron-forge package --arch=arm64",
|
"package:arm64": "electron-forge package --arch=arm64",
|
||||||
"merge": "node ./merge.js",
|
"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": {
|
"electronPackagerConfig": {
|
||||||
"packageManager": "npm",
|
"packageManager": "npm",
|
||||||
|
|||||||
+5
-5
@@ -15,10 +15,10 @@
|
|||||||
"build:linux": "electron-builder --linux --x64",
|
"build:linux": "electron-builder --linux --x64",
|
||||||
"build:linux:arm64": "electron-builder --linux --arm64",
|
"build:linux:arm64": "electron-builder --linux --arm64",
|
||||||
"build:all": "electron-builder -mwl",
|
"build:all": "electron-builder -mwl",
|
||||||
"release:win": "npm run encrypt:win && npm run cleanup && npm run build:win",
|
"release:win": "yarn encrypt:win && yarn cleanup && yarn build:win",
|
||||||
"release:mac": "npm run encrypt:linux && npm run cleanup && npm run build:mac",
|
"release:mac": "yarn encrypt:linux && yarn cleanup && yarn build:mac",
|
||||||
"release:linux": "npm run encrypt:linux && npm run cleanup && npm run build:linux",
|
"release:linux": "yarn encrypt:linux && yarn cleanup && yarn build:linux",
|
||||||
"release:linux:arm64": "npm run build:linux:arm64"
|
"release:linux:arm64": "yarn build:linux:arm64"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"productName": "SoonDesign",
|
"productName": "SoonDesign",
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electronPackagerConfig": {
|
"electronPackagerConfig": {
|
||||||
"packageManager": "npm",
|
"packageManager": "yarn",
|
||||||
"icon": "./public/images/favicon.ico"
|
"icon": "./public/images/favicon.ico"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,8 @@
|
|||||||
|
|
||||||
2. **运行加密脚本**
|
2. **运行加密脚本**
|
||||||
```bash
|
```bash
|
||||||
npm run encrypt:win # Windows
|
yarncrypt:win # Windows
|
||||||
npm run encrypt:linux # Linux/Mac
|
yarncrypt:linux # Linux/Mac
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **验证结果**
|
3. **验证结果**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if (!process.versions.electron) {
|
|||||||
console.error('请使用以下方式运行:');
|
console.error('请使用以下方式运行:');
|
||||||
console.error(' Windows: encrypt-electron-win.bat');
|
console.error(' Windows: encrypt-electron-win.bat');
|
||||||
console.error(' Linux/Mac: ./encrypt-electron.sh');
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user