877fd278c2
统一门户 JSON 契约:新增 GET /templates/{id},模板/云文件/保存/打开走虚拟 key;
layer 保存弹层替代 prompt,本地 .soon 已登录后台 POST 登记;修复保存静默失败与 design2 全宽布局。
Co-authored-by: Cursor <cursoragent@cursor.com>
2350 lines
87 KiB
JavaScript
2350 lines
87 KiB
JavaScript
// design1核心功能模块
|
||
// 注意:此代码在layui.use回调函数内部执行,不需要$(function())包装
|
||
// 全局变量已在主入口文件中定义,这里只需要初始化
|
||
|
||
$("#object_attribute").css("height", "470px");
|
||
$("#obj_list").css("height", document.body.clientHeight - 520);
|
||
$(".obj_list").css("height", $("#obj_list").height() - 32);
|
||
|
||
var mainEl = document.querySelector('.layui-fluid.main');
|
||
if (mainEl) mainEl.style.display = 'flex';
|
||
else $(".main").show();
|
||
$(".canvas_box").css("height", document.body.clientHeight - 100);
|
||
zoom = $(".canvas_bg_img").width() / 2787
|
||
$("#canvas1").attr("width", $(".canvas_box").width() / zoom);
|
||
$("#canvas1").attr("height", $(".canvas_box").height() / zoom);
|
||
$("#canvas2").attr("width", $(".canvas_box").width() / zoom);
|
||
$("#canvas2").attr("height", $(".canvas_box").height() / zoom);
|
||
|
||
// 优化性能:在创建 Fabric Canvas 之前,先设置 canvas context 的 willReadFrequently
|
||
// 注意:这需要在 Fabric.js 创建 canvas 之前执行
|
||
try {
|
||
const canvas1El = document.getElementById('canvas1');
|
||
const canvas2El = document.getElementById('canvas2');
|
||
if (canvas1El) {
|
||
// 先获取 context 并设置 willReadFrequently,这样 Fabric.js 创建时会复用这个 context
|
||
canvas1El.getContext('2d', { willReadFrequently: true });
|
||
}
|
||
if (canvas2El) {
|
||
canvas2El.getContext('2d', { willReadFrequently: true });
|
||
}
|
||
} catch (e) {
|
||
// 如果浏览器不支持 willReadFrequently,忽略错误
|
||
}
|
||
|
||
// 初始化Canvas对象
|
||
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._isApplyingHistory = false;
|
||
// 同步到 window 对象
|
||
window.ctx1 = ctx1;
|
||
window.ctx2 = ctx2;
|
||
window.is_bgi_add = is_bgi_add;
|
||
canvas1.zoomToPoint(new fabric.Point(0, 0), 1);
|
||
canvas2.zoomToPoint(new fabric.Point(0, 0), 1);
|
||
|
||
// 初始化标尺
|
||
initRulers();
|
||
|
||
$("#source_front").width(zoom * $("#source_front").width());
|
||
$("#source_back").width(zoom * $("#source_back").width());
|
||
$(".canvas-container:eq(0)").css('transform', 'scale(' + zoom + ')')
|
||
$(".canvas-container:eq(1)").css('transform', 'scale(' + zoom + ')')
|
||
$(".canvas-container:eq(0)").css('width', 0)
|
||
|
||
$(".canvas-container:eq(1)").hide();
|
||
canvas = canvas1;
|
||
window.canvas = canvas;
|
||
fabric.Object.prototype.set({
|
||
borderColor: "#9013FE",
|
||
cornerColor: "#9013FE", //激活状态角落图标的填充颜色
|
||
cornerStrokeColor: "#9013FE", //激活状态角落图标的边框颜色
|
||
borderOpacityWhenMoving: 1,
|
||
borderScaleFactor: 6,
|
||
cornerSize: 24,
|
||
cornerStyle: "circle", //rect,circle
|
||
centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点
|
||
centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转
|
||
transparentCorners: false, //激活状态角落的图标是否透明
|
||
rotatingPointOffset: 20, //旋转距旋转体的距离
|
||
padding: 0,
|
||
});
|
||
canvas1.preserveObjectStacking = true;
|
||
canvas2.preserveObjectStacking = true;
|
||
|
||
|
||
var front_list = [];
|
||
function applyFontList(data) {
|
||
var newFontList = [];
|
||
(data || []).map(function(item) {
|
||
if (item.indexOf('"') === 0) {
|
||
newFontList.push(item.replace(/^"|"$/g, ''));
|
||
} else {
|
||
newFontList.push(item);
|
||
}
|
||
});
|
||
front_list = newFontList;
|
||
for (var i = 0; i < front_list.length; i++) {
|
||
var item = front_list[i];
|
||
$("#text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||
$("#circle_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||
$("#out_text_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||
$("#barcode_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||
$("#count_font_family").append("<option value='" + item + "'>" + item + "</option>");
|
||
}
|
||
}
|
||
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
|
||
ipcRenderer.on('font-list', function(event, data) { applyFontList(data); });
|
||
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getSystemFonts) {
|
||
window.platformBridge.getSystemFonts().then(applyFontList);
|
||
}
|
||
function applySysLan(data) {
|
||
let lang = localStorage.getItem("lang");
|
||
if (lang) {
|
||
s_lan = lang;
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge(lang);
|
||
$("#language_select").val(lang);
|
||
return;
|
||
}
|
||
switch (data) {
|
||
case 'zh-CN':
|
||
s_lan = "zh";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("zh");
|
||
$("#language_select").val("zh");
|
||
localStorage.setItem("lang", "zh");
|
||
break;
|
||
case 'zh':
|
||
s_lan = "zh";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("zh");
|
||
$("#language_select").val("zh");
|
||
localStorage.setItem("lang", "zh");
|
||
break;
|
||
case 'zh-TW':
|
||
s_lan = "ozh";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("ozh");
|
||
$("#language_select").val("ozh");
|
||
localStorage.setItem("lang", "ozh");
|
||
break;
|
||
case 'en':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-AU':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-CA':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-GB':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-NZ':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-US':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
case 'en-ZA':
|
||
s_lan = "en";
|
||
// 同步到全局作用域(用于 .jsc 文件加载)
|
||
if (typeof window !== 'undefined') {
|
||
window.s_lan = s_lan;
|
||
}
|
||
if (typeof global !== 'undefined') {
|
||
global.s_lan = s_lan;
|
||
}
|
||
langua_ge("en");
|
||
$("#language_select").val("en");
|
||
localStorage.setItem("lang", "en");
|
||
break;
|
||
}
|
||
}
|
||
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
|
||
ipcRenderer.on('sys-lan', function(event, data) { applySysLan(data); });
|
||
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.getLocale) {
|
||
applySysLan(window.platformBridge.getLocale());
|
||
}
|
||
function onCloseConfirm() {
|
||
layer.confirm(language_str("whetherSave"), {
|
||
btn: [language_str("save"), language_str("noSave"), language_str("cancel")]
|
||
, btn3: function (index, layero) {}
|
||
}, function (index, layero) {
|
||
if (typeof window.output === 'function') {
|
||
window.output(function() {
|
||
if (ipcRenderer) ipcRenderer.send('run-close');
|
||
else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose();
|
||
});
|
||
}
|
||
}, function (index) {
|
||
if (ipcRenderer) ipcRenderer.send('run-close');
|
||
else if (window.platformBridge && window.platformBridge.runClose) window.platformBridge.runClose();
|
||
});
|
||
}
|
||
if (typeof ipcRenderer !== 'undefined' && ipcRenderer) {
|
||
ipcRenderer.on('close', function(event, message) { onCloseConfirm(); });
|
||
} else if (typeof window !== 'undefined' && window.platformBridge && window.platformBridge.onClose) {
|
||
window.platformBridge.onClose(onCloseConfirm);
|
||
}
|
||
addBackground();//添加背景 白色卡片
|
||
initAligningGuidelines(canvas1);
|
||
initAligningGuidelines(canvas2);
|
||
//canvas.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();//重新渲染画布
|
||
|
||
// 立即启动事件初始化(必须在 slider 初始化之前)
|
||
initCanvasEvents();
|
||
|
||
var zoomSlider = slider.render({
|
||
elem: '#zoomSlide', //绑定元素
|
||
min: 25,
|
||
max: 500,
|
||
value: 100,
|
||
change(val) {
|
||
canvas.zoomToPoint(
|
||
new fabric.Point(0, 0),
|
||
val / 100
|
||
);
|
||
// zoom = val / 100;
|
||
//$(".canvas-container").css("transform","scale(" + val / 100 + ")")
|
||
}
|
||
|
||
});
|
||
// ===========================================================
|
||
// Slider 滑块初始化
|
||
// ===========================================================
|
||
var circle_text_size = slider.render({
|
||
elem: '#circle_text_size',
|
||
min: 10, max: 120,
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set("fontSize", value);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
var circle_text_diameter = slider.render({
|
||
elem: '#circle_text_diameter',
|
||
min: 500, max: 4000,
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set("diameter", value);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
var out_pic_op = slider.render({
|
||
elem: '#out_pic_op',
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set('opacity', (100 - value) / 100);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
var pic_op = slider.render({
|
||
elem: '#pic_op',
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set("opacity", (100 - value) / 100);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
var rect_op = slider.render({
|
||
elem: '#rect_op',
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set("opacity", (100 - value) / 100);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
var circle_op = slider.render({
|
||
elem: '#circle_op',
|
||
change: function (value) {
|
||
if (canvas.getActiveObject()) {
|
||
canvas.getActiveObject().set("opacity", (100 - value) / 100);
|
||
canvas.renderAll();
|
||
}
|
||
}
|
||
});
|
||
window.rotate = function (id) {
|
||
let angle = $(`#${id}`).attr("angle") - 0;
|
||
angle += 90;
|
||
$(`#${id}`).css("transform", `rotate(${angle}deg)`);
|
||
$(`#${id}`).attr("angle", angle);
|
||
}
|
||
|
||
function addBackground() {
|
||
// 类型改变
|
||
fabric.Image.fromURL(soonAsset('bg_front_1.png'), function (image) {
|
||
let width = $("#canvas1").width()
|
||
|
||
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) {
|
||
image.scale(1, 1);
|
||
background_image1 = image;
|
||
background_image = background_image1;
|
||
window.background_image1 = background_image1;
|
||
window.background_image = background_image;
|
||
canvas.add(image);
|
||
canvas.renderAll();
|
||
//pre_objs1.push(JSON.stringify(objs1));
|
||
//pre_json1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor"]));
|
||
|
||
recordObjs1.push(JSON.stringify(objs1));
|
||
recordJson1.push(canvas1.toJSON(TO_JSON_PROPERTIES));
|
||
});
|
||
// 类型改变
|
||
fabric.Image.fromURL(soonAsset('bg_back.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();
|
||
//pre_objs2.push(JSON.stringify(objs2));
|
||
//pre_json2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor"]));
|
||
|
||
recordObjs2.push(JSON.stringify(objs2));
|
||
recordJson2.push(canvas2.toJSON(TO_JSON_PROPERTIES));
|
||
|
||
var file = typeof window.soonResolveOpenFileKey === 'function'
|
||
? window.soonResolveOpenFileKey(GetFile)
|
||
: (GetFile().get('file') || '');
|
||
if (file && file != 'empty') {
|
||
if (typeof window.soonConsumeOpenMeta === 'function') {
|
||
window.soonConsumeOpenMeta(file);
|
||
}
|
||
window.openFile(file);
|
||
}
|
||
});
|
||
});
|
||
is_bgi_add = 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", soonAsset("lock20.png"))//已锁定
|
||
} else {
|
||
$("#lock_img").attr("src", soonAsset("unlock20.png"))//未锁定
|
||
}
|
||
};
|
||
// 向后兼容
|
||
function updateControls() {
|
||
return window.updateControls();
|
||
}
|
||
function selectObject(target, index) {
|
||
updateControls();
|
||
|
||
$("#base_control").show();
|
||
//$("#text_x").val((target.left - background_image.left).toFixed(1));
|
||
//$("#text_y").val((target.top - background_image.top).toFixed(1));
|
||
//$("#text_w").val(parseFloat(target.get("scaleX") * target.get("width")).toFixed(1));
|
||
//$("#text_h").val(parseFloat(target.get("scaleY") * target.get("height")).toFixed(1));
|
||
//$("#text_r").val(target.get("angle").toFixed(0));
|
||
}
|
||
|
||
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();
|
||
window.recordState();
|
||
//OpenDialog();
|
||
//function OpenDialog()
|
||
//{
|
||
// dialog.showOpenDialog({
|
||
// title: "请选择图片",
|
||
// buttonLabel: "确认",
|
||
// filters: [
|
||
// { name: 'Image File Type', extensions: ['png', 'jpg'] },
|
||
// ]
|
||
// }).then(result => {
|
||
// if(result.canceled)return;
|
||
// //console.log(result.filePaths)
|
||
// fabric.Image.fromURL(result.filePaths[0], function(image) {
|
||
// image.left = pointer.x;
|
||
// image.top = pointer.y;
|
||
//
|
||
// canvas.add(image);
|
||
// objs.push({type: 1});
|
||
// canvas.renderAll();
|
||
// updateList();
|
||
// window.recordState();
|
||
// } );
|
||
// }).catch(err => {
|
||
// console.log(err)
|
||
// })
|
||
//}
|
||
}
|
||
|
||
function selectPic(target, index) {
|
||
//透明度
|
||
|
||
$("#pic_control").show();
|
||
$("#set_bg").show();
|
||
let activeObj = canvas.getActiveObject();
|
||
if (typeof pic_op !== 'undefined' && pic_op.setValue) {
|
||
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 + "%");
|
||
}
|
||
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
// $("#pic_black").prop("checked", objs[idx-1].black ? 'checked' : '');
|
||
|
||
$("#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 addOutPic(pointer) {
|
||
fabric.Image.fromURL(soonAsset('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.on("selected",function(){//test well
|
||
// console.log(222);
|
||
//});
|
||
image.setSrc(image.toDataURL(), function (image) {
|
||
canvas.add(image);
|
||
objs.push({ type: 2, name: resName("OUT_PIC_"), black: false });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
});
|
||
});
|
||
}
|
||
|
||
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());
|
||
$("#out_pic_field_name").val(objs[idx - 1].name);
|
||
// $("#out_pic_black").prop("checked", objs[idx-1].black ? 'checked' : '');
|
||
|
||
|
||
$("#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 addText(pointer) {
|
||
let text = new fabric.Text('TEXT', {//源:文本
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
fill: "#000000",
|
||
textBackgroundColor: "",
|
||
fontStyle: "normal",
|
||
fontWeight: "normal",
|
||
fontSize: 100,
|
||
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();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectText(target, index) {
|
||
//console.log(canvas.getActiveObject().get("fontFamily"));
|
||
$("#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 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: 100,
|
||
underline: false,
|
||
linethrough: false,
|
||
lockScalingX: true,
|
||
lockScalingY: true,
|
||
fontFamily: 'Arial',
|
||
originX: 'center',
|
||
// originY: 'center',
|
||
angle: angleDeg,
|
||
flipped: pointer.y > canvas.height / 2 ? true : false
|
||
});
|
||
// distance = getDisdance(pointer.x+text.width/2, pointer.y, canvas.width/2,canvas.height/2)
|
||
// text.set({diameter: distance*2})
|
||
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 });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectCircleText() {
|
||
// 检查是否有选中对象
|
||
if (!canvas || !canvas.getActiveObject()) return;
|
||
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
$("#circle_text_control").show();
|
||
$("#circle_text_color").val(canvas.getActiveObject().get("fill"));
|
||
$("#circle_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor"));
|
||
|
||
// 检查 circle_text_size 是否存在且有 setValue 方法
|
||
if (typeof circle_text_size !== 'undefined' && circle_text_size && typeof circle_text_size.setValue === 'function') {
|
||
circle_text_size.setValue(canvas.getActiveObject().get("fontSize") - 10);
|
||
} else {
|
||
$("#circle_text_size").next().children(".layui-slider-bar").css("width", ((canvas.getActiveObject().get("fontSize") - 10 - 10) / (120 - 10)) * 100 + "%");
|
||
$("#circle_text_size").next().children(".layui-slider-handle").css("left", ((canvas.getActiveObject().get("fontSize") - 10 - 10) / (120 - 10)) * 100 + "%");
|
||
}
|
||
|
||
// 检查 circle_text_diameter 是否存在且有 setValue 方法
|
||
if (typeof circle_text_diameter !== 'undefined' && circle_text_diameter && typeof circle_text_diameter.setValue === 'function') {
|
||
circle_text_diameter.setValue(canvas.getActiveObject().get("diameter") - 500);
|
||
} else {
|
||
$("#circle_text_diameter").next().children(".layui-slider-bar").css("width", ((canvas.getActiveObject().get("diameter") - 500 - 500) / (4000 - 500)) * 100 + "%");
|
||
$("#circle_text_diameter").next().children(".layui-slider-handle").css("left", ((canvas.getActiveObject().get("diameter") - 500 - 500) / (4000 - 500)) * 100 + "%");
|
||
}
|
||
$("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : '');
|
||
$("#circle_text_text").val(canvas.getActiveObject().get("text"));
|
||
$("#circle_text_font_family").val(canvas.getActiveObject().get("fontFamily"));
|
||
|
||
if (canvas.getActiveObject().get("underline") == true) {
|
||
$("#circle_text_underline").addClass("ui-radio-acitve")
|
||
} else {
|
||
$("#circle_text_underline").removeClass("ui-radio-acitve")
|
||
}
|
||
if (canvas.getActiveObject().get("fontWeight") == "bold") {
|
||
$("#circle_text_bold").addClass("ui-radio-acitve")
|
||
} else {
|
||
$("#circle_text_bold").removeClass("ui-radio-acitve")
|
||
}
|
||
if (canvas.getActiveObject().get("fontStyle") == "italic") {
|
||
$("#circle_text_italics").addClass("ui-radio-acitve")
|
||
} else {
|
||
$("#circle_text_italics").removeClass("ui-radio-acitve")
|
||
}
|
||
if (canvas.getActiveObject().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 addOutText(pointer) {
|
||
//MERGE_TEXT_1
|
||
let text = new fabric.Text("MERGE TEXT", {//源:合成文本
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
fill: "#000000",
|
||
fontStyle: "normal",
|
||
fontWeight: "normal",
|
||
fontSize: 100,
|
||
textBackgroundColor: "",
|
||
underline: false,
|
||
linethrough: false,
|
||
fontFamily: 'Arial',
|
||
textAlign: 'center'
|
||
});
|
||
|
||
//text.on({
|
||
// 'scaling': function(e) {
|
||
// var obj = this,
|
||
// w = obj.width * obj.scaleX,
|
||
// h = obj.height * obj.scaleY;
|
||
//
|
||
// obj.set({
|
||
// 'height' : h,
|
||
// 'width' : w,
|
||
// 'scaleX' : 1,
|
||
// 'scaleY' : 1
|
||
// });
|
||
// }
|
||
//});
|
||
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();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectOutText(target, index) {
|
||
//color
|
||
//size
|
||
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());
|
||
$("#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 addRect(pointer) {
|
||
let rect = new fabric.Rect({
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
fill: '',
|
||
stroke: "#ff0000",
|
||
strokeWidth: 10,
|
||
width: 300,
|
||
height: 300
|
||
});
|
||
canvas.add(rect);
|
||
objs.push({ type: 5, black: false });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectRect(target, index) {
|
||
//color
|
||
$("#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 + "%");
|
||
}
|
||
let a = canvas.getActiveObject().getCoords();
|
||
$("#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 addCircle(pointer) {
|
||
let circle = new fabric.Circle({
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
fill: '',
|
||
stroke: '#ff0000',
|
||
strokeWidth: 10,
|
||
radius: 150,
|
||
strokeUniform: true,
|
||
});
|
||
canvas.add(circle);
|
||
objs.push({ type: 6, black: false });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectCircle(target, index) {
|
||
//border-color
|
||
//color
|
||
$("#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 addLine(pointer) {
|
||
let line = new fabric.Line([100, 200, 400, 200], {
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
stroke: '#ff0000',
|
||
strokeWidth: 10,
|
||
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();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectLine(target, index) {
|
||
//color
|
||
//weight
|
||
$("#line_control").show();
|
||
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
$("#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 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;
|
||
image.scale(2.4, 2.4)
|
||
canvas.add(image);
|
||
objs.push({ type: 8, name: resName("QR_CODE_"), color: "#000000", val: "https://www.cardsoon.com", black: false });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
});
|
||
}
|
||
|
||
function selectQrCode(target, index) {
|
||
//字段名
|
||
//background-color
|
||
//color
|
||
$("#qrcode_Controll").show();
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
$("#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 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;
|
||
image.scale(2.4, 2.4)
|
||
canvas.add(image);
|
||
objs.push({ type: 9, name: resName("BAR_CODE_"), val: "123456789", color: "#000000", fontSize: 18, font: "Arial", show: false, black: false });//show是取消文字显示
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
});
|
||
}
|
||
|
||
function selectBarCode(target, index) {
|
||
//字段名
|
||
//background-color
|
||
//color
|
||
$("#barcode_controll").show();
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
$("#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 addCounter(pointer) {
|
||
let text = new fabric.Text('0', {
|
||
left: pointer.x,
|
||
top: pointer.y,
|
||
fontSize: 100,
|
||
fill: "#000000",
|
||
});
|
||
canvas.add(text);
|
||
objs.push({ type: 10, name: resName("COUNTER_"), start: 0, add: 1, black: false });
|
||
canvas.renderAll();
|
||
updateList();
|
||
window.recordState();
|
||
}
|
||
|
||
function selectCounter(target, index) {
|
||
//起始
|
||
//终止
|
||
//增量
|
||
$("#counter_control").show();
|
||
|
||
|
||
let idx = getIndex(canvas.getActiveObject());
|
||
$("#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();
|
||
|
||
}
|
||
|
||
// 将 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 ?
|
||
`<img src="${soonAsset('lock20.png')}" style="width:14px; height:14px; margin-left:5px; opacity:0.7;" title="已锁定">` :
|
||
`<img src="${soonAsset('unlock20.png')}" style="width:14px; height:14px; margin-left:5px; opacity:0.5;" title="未锁定">`;
|
||
|
||
str = `<div style="${rowStyle}">
|
||
<div style="${leftStyle}">
|
||
<img src="${soonAsset(iconName)}" id="obj${i}" style="width:16px; margin-right:5px;">
|
||
<span title="${nameText}">${nameText}</span>
|
||
${lockIcon}
|
||
</div>
|
||
<span style="${indexStyle}">${displayIndex}</span>
|
||
</div>` + 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);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 将 selectObj 附加到 window 对象,确保全局可访问
|
||
// 标记是否通过对象列表选中(用于区分鼠标点击和列表点击)
|
||
window._selectingFromList = false;
|
||
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
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 = [];
|
||
//next_objs = [];
|
||
//recordObjs.push(JSON.stringify(objs));
|
||
//recordJson.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]));
|
||
++step.val;
|
||
for (let i = step.val; i < recordObjs.length; i++) {
|
||
recordObjs.pop();
|
||
recordJson.pop();
|
||
}
|
||
//recordObjs.splice(step.val, 1, JSON.stringify(objs))
|
||
//recordJson.splice(step.val, 1, canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]))
|
||
recordObjs.push(JSON.stringify(objs))
|
||
j = canvas.toJSON(TO_JSON_PROPERTIES);
|
||
j.objects[0].hoverCursor = "default";
|
||
recordJson.push(j);
|
||
}
|
||
|
||
// 将 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) {
|
||
let temp_objs = canvas.getObjects();
|
||
let i = 0;
|
||
for (let res of temp_objs) {
|
||
if (target == res) {
|
||
return objs[i].type;
|
||
}
|
||
i++;
|
||
}
|
||
}
|
||
|
||
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 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. 获取索引
|
||
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 数组下标
|
||
|
||
if (objs[i]) {
|
||
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;
|
||
let idx = getIndex(obj);
|
||
// 圆形文本特殊处理
|
||
if (idx > 0 && idx <= objs.length && objs[idx - 1] && 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(distance * 2 - 500);
|
||
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 属性列表,不包含锁定相关属性(锁定状态不应被保存)
|
||
// 统一的 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 drawActiveObjectBorder(currentCanvas, ctx) {
|
||
if (!currentCanvas || !ctx) return;
|
||
const active = currentCanvas.getActiveObject();
|
||
if (!active) return;
|
||
// 辅助线不需要额外描边
|
||
if (active.isGuideLine) return;
|
||
|
||
const coords = active.getCoords(true);
|
||
if (!coords || coords.length === 0) return;
|
||
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.strokeStyle = '#9013FE';
|
||
// 根据当前缩放比例调整线宽,避免看起来太虚
|
||
let lineWidth = 2;
|
||
if (typeof zoom === 'number' && zoom > 0) {
|
||
lineWidth = 2 / zoom; // 目标是屏幕上大约 2px 宽
|
||
}
|
||
ctx.lineWidth = lineWidth;
|
||
// 使用实线边框,提高清晰度
|
||
if (ctx.setLineDash) {
|
||
ctx.setLineDash([]);
|
||
}
|
||
ctx.moveTo(coords[0].x, coords[0].y);
|
||
for (let i = 1; i < coords.length; i++) {
|
||
ctx.lineTo(coords[i].x, coords[i].y);
|
||
}
|
||
ctx.closePath();
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
|
||
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));
|
||
}
|
||
updateRulerGuides(e.e);
|
||
});
|
||
|
||
// 2. 鼠标移出
|
||
currentCanvas.on("mouse:out", () => {
|
||
$("#ruler-guide-h").hide(); $("#ruler-guide-v").hide();
|
||
});
|
||
|
||
// 3. 选中处理
|
||
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 false; // 完全排除辅助线
|
||
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();
|
||
}
|
||
});
|
||
|
||
// 4. 鼠标松开 (添加对象)
|
||
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", "");
|
||
});
|
||
|
||
// 5. 对象移动 (只保留圆形文本逻辑,去掉了 checkOutOfBounds)
|
||
currentCanvas.on("object:moving", (e) => {
|
||
// 阻止背景图(索引0)被移动(如果已锁定)
|
||
let movingObj = e.target;
|
||
let idx = getIndex(movingObj);
|
||
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", () => { window.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;
|
||
let idx = getIndex(event.target);
|
||
if (idx <= 0) return;
|
||
let objData = objs[idx - 1];
|
||
// 安全检查:确保 objData 存在且有 type 属性
|
||
if (!objData || typeof objData.type === 'undefined') {
|
||
// 更新对象列表以同步锁状态
|
||
if (typeof window.updateList === 'function') {
|
||
window.updateList();
|
||
}
|
||
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();
|
||
}
|
||
window.recordState();
|
||
// 更新对象列表以同步锁状态
|
||
if (typeof window.updateList === 'function') {
|
||
window.updateList();
|
||
}
|
||
});
|
||
// ==================================================
|
||
// 8. 【完美修复版】渲染后绘制:外部蒙版 + 中心圆孔 (修复三角形Bug)
|
||
// ==================================================
|
||
currentCanvas.on('after:render', function () {
|
||
const ctx = currentCanvas.getContext();
|
||
let bgObj = (currentCanvas === canvas1) ? background_image1 : background_image2;
|
||
|
||
if (bgObj) {
|
||
const width = currentCanvas.width;
|
||
const height = currentCanvas.height;
|
||
|
||
// 1. 计算圆心
|
||
const cx = bgObj.left + (bgObj.width * bgObj.scaleX) / 2;
|
||
const cy = bgObj.top + (bgObj.height * bgObj.scaleY) / 2;
|
||
|
||
// 2. 计算半径
|
||
// 外半径 (光盘大小)
|
||
const r_outer = (bgObj.width * bgObj.scaleX) / 2;
|
||
|
||
// 内半径 (中心孔)
|
||
const dpi = 600;
|
||
const mmToPx = dpi / 25.4;
|
||
let holeDiameterMm = 24; // 默认 15mm
|
||
|
||
// 切换版本判断 (15mm vs 38mm)
|
||
if (currentCanvas === canvas1 && typeof bg_version !== 'undefined' && bg_version === 2) {
|
||
holeDiameterMm = 42;
|
||
}
|
||
|
||
// 比例计算法:确保孔径随缩放适配
|
||
// r_inner = r_outer * (孔径 / 光盘标准直径120mm)
|
||
const r_inner = r_outer * (holeDiameterMm / 120);
|
||
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
|
||
// A. 绘制全屏矩形 (顺时针)
|
||
ctx.rect(0, 0, width, height);
|
||
|
||
// B. 挖掉外圆 (逆时针)
|
||
// 【关键修复】画圆前,必须把笔移动到圆的起点 (cx + r, cy)
|
||
// 否则 Canvas 会画一条线连过来,形成奇怪的三角形
|
||
ctx.moveTo(cx + r_outer, cy);
|
||
ctx.arc(cx, cy, r_outer, 0, Math.PI * 2, true);
|
||
|
||
// C. 填补内圆 (顺时针)
|
||
// 【关键修复】同理,把笔移动到内圆的起点
|
||
ctx.moveTo(cx + r_inner, cy);
|
||
ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false);
|
||
|
||
ctx.closePath();
|
||
ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩
|
||
ctx.fill();
|
||
ctx.restore();
|
||
}
|
||
|
||
// 在遮罩之后重新绘制辅助线,确保辅助线在最上层
|
||
const objects = currentCanvas.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;
|
||
}
|
||
|
||
// 使用 Fabric 对象的内置渲染方法
|
||
obj._render(ctx);
|
||
ctx.restore();
|
||
}
|
||
});
|
||
|
||
// 最后再补绘当前选中对象的边框,确保不会被遮罩层挡住
|
||
drawActiveObjectBorder(currentCanvas, ctx);
|
||
});
|
||
});
|
||
}
|
||
|
||
function initRulers() {
|
||
const dpi = 600;
|
||
const mmToPx = dpi / 25.4;
|
||
const rulerHeight = 20;
|
||
const rulerWidth = 20;
|
||
|
||
const canvasWrapper = $('.canvas-wrapper');
|
||
const canvasBox = $('.canvas_box');
|
||
|
||
// 确保容器定位
|
||
if (canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative');
|
||
|
||
// 初始化尺寸
|
||
let canvasWidth = canvasWrapper.width() || 800;
|
||
let canvasHeight = canvasWrapper.height() || 600;
|
||
|
||
const $rulerH = $('#ruler-h');
|
||
const $rulerV = $('#ruler-v');
|
||
|
||
$rulerH.attr('width', canvasWidth).attr('height', rulerHeight);
|
||
$rulerV.attr('width', rulerWidth).attr('height', canvasHeight);
|
||
|
||
// --- 1. 绑定拖拽事件 (核心新功能) ---
|
||
// 定义临时拖拽线 DOM
|
||
let $dragLine = null;
|
||
|
||
// 通用拖拽开始函数
|
||
const startDragGuide = (type, e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
// 防止重复调用:如果正在拖拽,直接返回
|
||
if ($dragLine && $dragLine.length > 0) {
|
||
return;
|
||
}
|
||
|
||
// 创建临时显示的线
|
||
$dragLine = $('<div class="guide-line-drag"></div>');
|
||
$('.canvas-wrapper').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
|
||
$rulerH.off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e));
|
||
$rulerV.off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e));
|
||
|
||
// --- 2. 在画布中创建辅助线 ---
|
||
function createFabricGuide(type, domX, domY) {
|
||
// domX 和 domY 是相对于 canvas-wrapper 的坐标
|
||
// 需要转换为相对于 canvas_box 的坐标,然后再转换为 Fabric 画布坐标
|
||
|
||
// 获取 canvas_box 相对于 canvas-wrapper 的位置
|
||
let boxPosition = canvasBox.position();
|
||
let boxMarginLeft = parseFloat(canvasBox.css('margin-left')) || 0;
|
||
let boxMarginTop = parseFloat(canvasBox.css('margin-top')) || 0;
|
||
|
||
// 计算相对于 canvas_box 的坐标(减去 box 在 wrapper 中的位置和 margin)
|
||
let relX = domX - boxPosition.left - boxMarginLeft;
|
||
let relY = domY - boxPosition.top - boxMarginTop;
|
||
|
||
// 转为 Fabric 坐标 (除以 zoom)
|
||
let canvasX = relX / zoom;
|
||
let canvasY = relY / zoom;
|
||
|
||
// 获取画布的实际尺寸(用于决定辅助线的长度)
|
||
let canvasWidth = canvas.width || 2000;
|
||
let canvasHeight = canvas.height || 2000;
|
||
// 使用与画布尺寸相关的长度,避免“整块区域都像辅助线”,同时保证能横穿整个画布
|
||
let lineExtent = Math.max(canvasWidth, canvasHeight) * 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',
|
||
// design1 中改回边框命中模型,保证更容易点中拖拽
|
||
perPixelTargetFind: false,
|
||
targetFindTolerance: 10,
|
||
padding: 10,
|
||
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.add(guideLine);
|
||
canvas.setActiveObject(guideLine);
|
||
canvas.renderAll();
|
||
}
|
||
|
||
// --- 3. 绘制标尺 (核心绘制循环) ---
|
||
function updateAndDraw() {
|
||
// 每次重新获取尺寸,确保使用最新值
|
||
// 使用多种方法尝试获取实际尺寸,确保准确
|
||
let currentCanvasWidth = canvasWrapper.width() || canvasWrapper.outerWidth() || canvasWrapper[0].getBoundingClientRect().width || 800;
|
||
let currentCanvasHeight = canvasWrapper.height() || canvasWrapper.outerHeight() || canvasWrapper[0].getBoundingClientRect().height || 600;
|
||
|
||
// 如果还是获取不到,尝试使用父容器的尺寸
|
||
if (!currentCanvasWidth || currentCanvasWidth === 0) {
|
||
const parent = canvasWrapper.parent();
|
||
currentCanvasWidth = parent.width() || parent[0].getBoundingClientRect().width || 800;
|
||
}
|
||
if (!currentCanvasHeight || currentCanvasHeight === 0) {
|
||
const parent = canvasWrapper.parent();
|
||
currentCanvasHeight = parent.height() || parent[0].getBoundingClientRect().height || 600;
|
||
}
|
||
|
||
// 更新标尺尺寸
|
||
$rulerH.attr('width', currentCanvasWidth).attr('height', rulerHeight);
|
||
$rulerV.attr('width', rulerWidth).attr('height', currentCanvasHeight);
|
||
|
||
const pxPerMm = zoom * mmToPx;
|
||
|
||
let boxMarginLeft = parseFloat(canvasBox.css('margin-left')) || 0;
|
||
let boxMarginTop = parseFloat(canvasBox.css('margin-top')) || 0;
|
||
const rulerHeightOffset = 20; // ruler 的高度
|
||
|
||
// 原点在标尺的左上角(画布的左上角位置,考虑margin)
|
||
// 标尺0点 = boxMarginLeft(水平)和 boxMarginTop + rulerHeightOffset(垂直)
|
||
let originX = boxMarginLeft;
|
||
let originY = boxMarginTop + rulerHeightOffset;
|
||
|
||
const ctxH = document.getElementById('ruler-h').getContext('2d', { willReadFrequently: true });
|
||
const ctxV = document.getElementById('ruler-v').getContext('2d', { willReadFrequently: true });
|
||
|
||
// 绘制水平标尺
|
||
function drawHorizontal() {
|
||
ctxH.clearRect(0, 0, currentCanvasWidth, rulerHeight);
|
||
|
||
ctxH.strokeStyle = '#666';
|
||
ctxH.lineWidth = 1;
|
||
ctxH.beginPath();
|
||
ctxH.moveTo(0, 19);
|
||
ctxH.lineTo(currentCanvasWidth, 19);
|
||
ctxH.stroke();
|
||
|
||
ctxH.fillStyle = '#888';
|
||
ctxH.font = '9px Arial';
|
||
ctxH.textAlign = 'left';
|
||
ctxH.textBaseline = 'top';
|
||
|
||
const startX = 0;
|
||
const endX = currentCanvasWidth;
|
||
|
||
// 绘制主要刻度(每10mm)
|
||
// 从0点向左绘制负值刻度,确保覆盖到startX
|
||
for (let mm = -10; originX + mm * pxPerMm >= startX; mm -= 10) {
|
||
const x = originX + mm * pxPerMm;
|
||
|
||
ctxH.beginPath();
|
||
ctxH.moveTo(x, 5);
|
||
ctxH.lineTo(x, 20);
|
||
ctxH.stroke();
|
||
|
||
ctxH.fillText(mm + 'mm', x + 2, 2);
|
||
}
|
||
// 从0点向右绘制正值刻度,确保覆盖到endX
|
||
for (let mm = 0; ; mm += 10) {
|
||
const x = originX + mm * pxPerMm;
|
||
if (x > endX) break;
|
||
|
||
ctxH.beginPath();
|
||
ctxH.moveTo(x, 5);
|
||
ctxH.lineTo(x, 20);
|
||
ctxH.stroke();
|
||
|
||
ctxH.fillText(mm + 'mm', x + 2, 2);
|
||
}
|
||
|
||
// 绘制次要刻度(每1mm)
|
||
// 从0点向左绘制负值刻度,确保覆盖到startX
|
||
for (let mm = -1; originX + mm * pxPerMm >= startX; mm -= 1) {
|
||
const x = originX + mm * pxPerMm;
|
||
if (mm % 10 === 0) continue;
|
||
|
||
ctxH.beginPath();
|
||
ctxH.moveTo(x, 12);
|
||
ctxH.lineTo(x, 20);
|
||
ctxH.stroke();
|
||
}
|
||
// 从0点向右绘制正值刻度,确保覆盖到endX
|
||
for (let mm = 0; ; mm += 1) {
|
||
const x = originX + mm * pxPerMm;
|
||
if (x > endX) break;
|
||
if (mm % 10 === 0) continue;
|
||
|
||
ctxH.beginPath();
|
||
ctxH.moveTo(x, 12);
|
||
ctxH.lineTo(x, 20);
|
||
ctxH.stroke();
|
||
}
|
||
}
|
||
|
||
// 绘制垂直标尺
|
||
function drawVertical() {
|
||
ctxV.clearRect(0, 0, rulerWidth, currentCanvasHeight);
|
||
|
||
ctxV.strokeStyle = '#666';
|
||
ctxV.lineWidth = 1;
|
||
ctxV.beginPath();
|
||
ctxV.moveTo(19, 0);
|
||
ctxV.lineTo(19, currentCanvasHeight);
|
||
ctxV.stroke();
|
||
|
||
ctxV.fillStyle = '#888';
|
||
ctxV.font = '9px Arial';
|
||
ctxV.textAlign = 'center';
|
||
ctxV.textBaseline = 'middle';
|
||
|
||
const startY = 0;
|
||
const endY = currentCanvasHeight;
|
||
|
||
// 绘制主要刻度(每10mm)
|
||
// 从0点向上绘制负值刻度,确保覆盖到startY
|
||
for (let mm = -10; originY + mm * pxPerMm >= startY; mm -= 10) {
|
||
const y = originY + mm * pxPerMm;
|
||
|
||
ctxV.beginPath();
|
||
ctxV.moveTo(5, y);
|
||
ctxV.lineTo(20, y);
|
||
ctxV.stroke();
|
||
|
||
ctxV.save();
|
||
ctxV.translate(8, y + 2);
|
||
ctxV.rotate(-Math.PI / 2);
|
||
ctxV.fillText(mm + 'mm', 0, 0);
|
||
ctxV.restore();
|
||
}
|
||
// 从0点向下绘制正值刻度,确保覆盖到endY
|
||
for (let mm = 0; ; mm += 10) {
|
||
const y = originY + mm * pxPerMm;
|
||
if (y > endY) break;
|
||
|
||
ctxV.beginPath();
|
||
ctxV.moveTo(5, y);
|
||
ctxV.lineTo(20, y);
|
||
ctxV.stroke();
|
||
|
||
ctxV.save();
|
||
ctxV.translate(8, y + 2);
|
||
ctxV.rotate(-Math.PI / 2);
|
||
ctxV.fillText(mm + 'mm', 0, 0);
|
||
ctxV.restore();
|
||
}
|
||
|
||
// 绘制次要刻度(每1mm)
|
||
// 从0点向上绘制负值刻度,确保覆盖到startY
|
||
for (let mm = -1; originY + mm * pxPerMm >= startY; mm -= 1) {
|
||
const y = originY + mm * pxPerMm;
|
||
if (mm % 10 === 0) continue;
|
||
|
||
ctxV.beginPath();
|
||
ctxV.moveTo(12, y);
|
||
ctxV.lineTo(20, y);
|
||
ctxV.stroke();
|
||
}
|
||
// 从0点向下绘制正值刻度,确保覆盖到endY
|
||
for (let mm = 0; ; mm += 1) {
|
||
const y = originY + mm * pxPerMm;
|
||
if (y > endY) break;
|
||
if (mm % 10 === 0) continue;
|
||
|
||
ctxV.beginPath();
|
||
ctxV.moveTo(12, y);
|
||
ctxV.lineTo(20, y);
|
||
ctxV.stroke();
|
||
}
|
||
}
|
||
|
||
drawHorizontal();
|
||
drawVertical();
|
||
}
|
||
|
||
canvas1.on('after:render', updateAndDraw);
|
||
canvas2.on('after:render', updateAndDraw);
|
||
$(window).resize(() => setTimeout(updateAndDraw, 100));
|
||
updateAndDraw();
|
||
}
|
||
|
||
// 辅助函数:更新光标位置
|
||
function updateRulerGuides(nativeEvent) {
|
||
if (!nativeEvent) return;
|
||
const wrapper = $('.canvas-wrapper');
|
||
const offset = wrapper.offset();
|
||
if (!offset) return;
|
||
$("#ruler-guide-h").css({ left: (nativeEvent.pageX - offset.left) + 'px', display: 'block' });
|
||
$("#ruler-guide-v").css({ top: (nativeEvent.pageY - offset.top) + 'px', display: 'block' });
|
||
}
|
||
|
||
window.copySelection = function copySelection() {
|
||
const activeObjects = canvas.getActiveObjects();
|
||
|
||
if (!activeObjects || activeObjects.length === 0) {
|
||
layer.msg(language_str("selectPic") || "请先选择对象");
|
||
return;
|
||
}
|
||
|
||
// 【核心修复】
|
||
// 遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据
|
||
clipboardData.data = activeObjects.map(obj => {
|
||
let idx = getIndex(obj); // 获取对象在画布中的索引
|
||
|
||
// 获取业务属性 (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;
|
||
}
|
||
|
||
// 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. 刷新列表和历史记录
|
||
updateList();
|
||
window.recordState();
|
||
|
||
// 5. 高亮左侧列表最后一项 (即新粘贴的项)
|
||
if (objs.length > 0) selectObj(objs.length - 1);
|
||
|
||
// 6. 增加下次粘贴的偏移量 (让用户知道粘贴了新东西)
|
||
clipboardData.offset += 10;
|
||
});
|
||
}
|
||
|
||
// 将 onStateRestored 附加到 window 对象,确保全局可访问
|
||
window.onStateRestored = function onStateRestored() {
|
||
// 1. 重新获取背景图引用 (因为旧对象被销毁了)
|
||
// 注意:canvas 是全局变量,指向当前激活的画板
|
||
if (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();
|
||
}
|
||
}
|