Merge branch 'dev_vite' of http://18.167.251.121:10003/aidlab/aida_front into dev_vite
This commit is contained in:
@@ -52,6 +52,7 @@ export class FillRepeatCommand extends Command {
|
||||
console.warn("当前对象不能平铺", object.type);
|
||||
return false;
|
||||
}
|
||||
console.log("===========", object.toObject(["id", "layerId", "layerName"]))
|
||||
this.oldObjects = object;
|
||||
const img = await new Promise((resolve, reject) => {
|
||||
if (object.type === "rect") {
|
||||
@@ -79,6 +80,8 @@ export class FillRepeatCommand extends Command {
|
||||
source: FillSourceToBase64(img),
|
||||
gapX: 0,
|
||||
gapY: 0,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
};
|
||||
const bgObject = this.canvasManager.getBackgroundLayerObject();
|
||||
const pattern = new fabric.Pattern({
|
||||
@@ -274,6 +277,8 @@ export class FillRepeatGapChangeCommand extends Command {
|
||||
const image = new Image();
|
||||
image.src = object.fill_.source;
|
||||
await image.decode();
|
||||
object.fill_.width = image.width;
|
||||
object.fill_.height = image.height;
|
||||
// 创建透明 Canvas
|
||||
const tcanvas = document.createElement('canvas');
|
||||
tcanvas.width = image.width + object.fill_.gapX;
|
||||
|
||||
@@ -524,6 +524,7 @@ export class RemoveLayerCommand extends Command {
|
||||
this.layerId = options.layerId;
|
||||
this.activeLayerId = options.activeLayerId;
|
||||
this.layerManager = options.layerManager || null;
|
||||
this.IsOnlyLayer = this.layers.value.filter((v => !v.isFixed && !v.isFixedOther && !v.isBackground)).length <= 1
|
||||
|
||||
// 查找要删除的图层
|
||||
this.layerIndex = this.layers.value.findIndex(
|
||||
@@ -600,7 +601,9 @@ export class RemoveLayerCommand extends Command {
|
||||
);
|
||||
// 从图层列表中删除
|
||||
this.layers.value.splice(this.layerIndex, 1);
|
||||
|
||||
if(this.IsOnlyLayer){
|
||||
this.addCmd = await this.layerManager?.createLayer?.(null, LayerType.EMPTY, {}, false);
|
||||
}
|
||||
// 如果删除的是当前活动图层,需要更新活动图层
|
||||
if (this.isActiveLayer) {
|
||||
// 查找最近的非背景层作为新的活动图层
|
||||
@@ -633,6 +636,9 @@ export class RemoveLayerCommand extends Command {
|
||||
async undo() {
|
||||
// 恢复图层到原位置
|
||||
if (this.layerIndex !== -1 && this.removedLayer) {
|
||||
if(this.IsOnlyLayer && this.addCmd){
|
||||
this.addCmd?.undo?.();
|
||||
}
|
||||
this.layers.value.splice(this.layerIndex, 0, this.removedLayer);
|
||||
|
||||
// 使用优化渲染批处理恢复真实对象到画布
|
||||
@@ -650,7 +656,6 @@ export class RemoveLayerCommand extends Command {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
this.canvas.renderAll();
|
||||
|
||||
@@ -4286,24 +4291,28 @@ export class RemoveChildLayerCommand extends Command {
|
||||
}
|
||||
// 恢复子图层到原位置
|
||||
this.parentLayer.children.splice(this.childIndex, 0, this.removedChild);
|
||||
optimizeCanvasRendering(this.canvas, async () => {
|
||||
this.originalObjects.forEach((obj) => {
|
||||
// 恢复对象到画布
|
||||
this.canvas.add(obj);
|
||||
// 恢复对象的图层信息
|
||||
obj.layerId = this.layerId;
|
||||
obj.layerName = this.removedChild.name;
|
||||
obj.setCoords(); // 更新坐标
|
||||
});
|
||||
await new Promise((resolve) => {
|
||||
optimizeCanvasRendering(this.canvas, async () => {
|
||||
this.originalObjects.forEach((obj) => {
|
||||
// 恢复对象到画布
|
||||
this.canvas.add(obj);
|
||||
// 恢复对象的图层信息
|
||||
obj.layerId = this.layerId;
|
||||
obj.layerName = this.removedChild.name;
|
||||
obj.setCoords(); // 更新坐标
|
||||
});
|
||||
|
||||
// 如果是原活动图层,恢复活动图层
|
||||
if (this.isActiveLayer) {
|
||||
this.activeLayerId.value = this.layerId;
|
||||
}
|
||||
// 如果是原活动图层,恢复活动图层
|
||||
if (this.isActiveLayer) {
|
||||
this.activeLayerId.value = this.layerId;
|
||||
}
|
||||
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
// 重新渲染画布
|
||||
await this.layerManager?.updateLayersObjectsInteractivity(false);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
@@ -4499,6 +4508,9 @@ export class SetColorLayerFillCommand extends Command {
|
||||
this.layer = this.layerManager?.getLayerById(this.object.layerId);
|
||||
this.newFill = options.newFill;
|
||||
this.oldFill = JSON.parse(JSON.stringify(this.object.fill));
|
||||
this.layer.blendMode = "multiply";
|
||||
this.object.set("globalCompositeOperation", "multiply");
|
||||
this.object.set("originColor", options.originColor);
|
||||
}
|
||||
|
||||
async execute(isUndo = false) {
|
||||
|
||||
@@ -364,14 +364,13 @@ const clickColor = () => {
|
||||
const fill = layerObject.value.fill;
|
||||
if (fill) {
|
||||
const obj = fillToPallet(fill);
|
||||
console.log("===========:", obj);
|
||||
palletPanel(obj).then((res) => {
|
||||
console.log("===========:", res);
|
||||
const cmd = new SetColorLayerFillCommand({
|
||||
canvas: canvasManager.canvas,
|
||||
layerManager: layerManager,
|
||||
object: layerObject.value,
|
||||
newFill: palletToFill(res),
|
||||
originColor: res,
|
||||
});
|
||||
layerManager.commandManager.execute(cmd);
|
||||
});
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
* 显示面板
|
||||
*/
|
||||
function show() {
|
||||
if (activeObjects.length === 0) return;
|
||||
if (activeObjects.value.length === 0) return;
|
||||
visible.value = true;
|
||||
closePanel.value = true;
|
||||
}
|
||||
@@ -276,14 +276,14 @@
|
||||
visible.value = false;
|
||||
}
|
||||
// 获取当前选中的对象
|
||||
const activeObjects = reactive([]);
|
||||
const activeObjects = ref([]);
|
||||
const getActiveObject = (e) => {
|
||||
console.log("==========切换激活对象", e, activeObjects);
|
||||
activeObjects.splice(0, activeObjects.length, ...e.selected);
|
||||
activeObjects.forEach((v) => {
|
||||
activeObjects.value = [...e.selected];
|
||||
activeObjects.value.forEach((v) => {
|
||||
v.layer = props.layerManager.getLayerById(v.layerId);
|
||||
});
|
||||
if (activeObjects.length === 0) {
|
||||
if (activeObjects.value.length === 0) {
|
||||
close();
|
||||
} else {
|
||||
show();
|
||||
@@ -291,7 +291,7 @@
|
||||
};
|
||||
//取消当前选中
|
||||
const cancelSelect = () => {
|
||||
activeObjects.splice(0, activeObjects.length);
|
||||
activeObjects.value = [];
|
||||
close();
|
||||
};
|
||||
const lastSelectLayerId = inject("lastSelectLayerId");
|
||||
@@ -537,7 +537,7 @@
|
||||
// 更新选中对象属性
|
||||
const updateActiveObjects = (arrs, keys, isNumber = true) => {
|
||||
arrs.forEach((v) => {
|
||||
activeObjects.forEach((item) => {
|
||||
activeObjects.value.forEach((item) => {
|
||||
if (item.id === v.id) {
|
||||
keys.forEach(
|
||||
(key) => (item[key] = isNumber ? Number(v[key]) : v[key])
|
||||
@@ -545,6 +545,7 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
activeObjects.value = [...activeObjects.value];
|
||||
};
|
||||
|
||||
// 旋转对象时更新角度
|
||||
|
||||
@@ -58,6 +58,7 @@ const emit = defineEmits([
|
||||
"changeCanvas", // 画布变更事件
|
||||
"canvasInit", // 画布初始化事件
|
||||
"trigger-library", // 触发打开Library选择图片事件
|
||||
"before-unmount-export-extra-info", // 组件卸载前导出额外信息事件
|
||||
]);
|
||||
|
||||
const props = defineProps({
|
||||
@@ -544,12 +545,15 @@ watchEffect(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
onBeforeUnmount(async () => {
|
||||
// if (import.meta.hot) {
|
||||
// // 热更新 ?
|
||||
// console.log("onBeforeUnmount 开发环境热更新不卸载组件...");
|
||||
// return; // 开发环境下不卸载组件
|
||||
// }
|
||||
const extraInfo = await canvasManager.exportExtraInfo();
|
||||
emit("before-unmount-export-extra-info", extraInfo);
|
||||
|
||||
console.log("onBeforeUnmount 组件卸载,清理资源...");
|
||||
canvasManager?.dispose?.();
|
||||
commandManager?.dispose?.();
|
||||
@@ -884,6 +888,7 @@ const changeCanvas = async (command) => {
|
||||
...command, // 传递完整的命令数据
|
||||
};
|
||||
emit("changeCanvas", commandData);
|
||||
canvasManager.changeCanvas(commandData);
|
||||
if ((command.canUndo || command.canRedo) && props.enabledRedGreenMode) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
@@ -1008,6 +1013,7 @@ defineExpose({
|
||||
exportImage: ({
|
||||
isContainBg = false, // 是否包含背景图层
|
||||
isContainFixed = false, // 是否包含固定图层
|
||||
isContainFixedOther = false, // 是否包含其他固定图层
|
||||
isCropByBg = false, // 是否使用背景大小裁剪 // 如果为true,则导出时裁剪到背景图层大小
|
||||
layerId = "", // 导出具体图层ID
|
||||
layerIdArray = [], // 导出多个图层ID数组
|
||||
@@ -1017,6 +1023,7 @@ defineExpose({
|
||||
return canvasManager.exportImage({
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther,
|
||||
isCropByBg,
|
||||
layerId,
|
||||
layerIdArray,
|
||||
@@ -1046,6 +1053,14 @@ defineExpose({
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出所有信息
|
||||
* @returns {Object} 包含所有图层信息的对象
|
||||
*/
|
||||
exportExtraInfo: () => {
|
||||
return canvasManager.exportExtraInfo();
|
||||
},
|
||||
|
||||
/**
|
||||
* 拖拽排序图层
|
||||
* @param {number} oldIndex 原索引
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
createLayer,
|
||||
LayerType,
|
||||
SpecialLayerId,
|
||||
BlendMode,
|
||||
} from "../utils/layerHelper";
|
||||
import { ObjectMoveCommand } from "../commands/ObjectCommands";
|
||||
import { AnimationManager } from "./animation/AnimationManager";
|
||||
@@ -29,7 +30,10 @@ import {
|
||||
palletToFill,
|
||||
fillToCssStyle,
|
||||
calculateRotatedTopLeftDeg,
|
||||
calculateCenterPoint,
|
||||
createPatternTransform,
|
||||
getTransformScaleAngle,
|
||||
base64ToCanvas,
|
||||
} from "../utils/helper";
|
||||
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
||||
import { isFunction } from "lodash-es";
|
||||
@@ -40,7 +44,7 @@ import {
|
||||
} from "../utils/layerUtils";
|
||||
import { imageModeHandler } from "../utils/imageHelper";
|
||||
import { getObjectAlphaToCanvas } from "../utils/objectHelper";
|
||||
import { AddLayerCommand } from "../commands/LayerCommands";
|
||||
import { AddLayerCommand, RemoveLayerCommand } from "../commands/LayerCommands";
|
||||
import { fa, id } from "element-plus/es/locales.mjs";
|
||||
import i18n from "@/lang/index.ts";
|
||||
const {t} = i18n.global;
|
||||
@@ -564,15 +568,14 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
// 更新颜色层信息
|
||||
const fixedLayerObj = this.getFixedLayerObject();
|
||||
const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
|
||||
if(colorObject && fixedLayerObj){
|
||||
await this.setColorObjectInfo(colorObject, fixedLayerObj);
|
||||
if(colorObject){
|
||||
await this.setObjecCliptInfo(colorObject);
|
||||
}
|
||||
const groupLayer = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
|
||||
if(groupLayer && fixedLayerObj){
|
||||
if(groupLayer){
|
||||
const groupRect = new fabric.Rect({});
|
||||
await this.setColorObjectInfo(groupRect, fixedLayerObj);
|
||||
await this.setObjecCliptInfo(groupRect);
|
||||
groupLayer.clippingMask = groupRect.toObject();
|
||||
}
|
||||
|
||||
@@ -807,6 +810,13 @@ export class CanvasManager {
|
||||
|
||||
return layerObjectByLayerId;
|
||||
}
|
||||
getObjectsByIds(ids){
|
||||
const objects = this.canvas.getObjects().filter((obj) => {
|
||||
return ids.includes(obj.id);
|
||||
});
|
||||
return objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新蒙层位置
|
||||
* @param {Object} backgroundLayerObject 背景层对象
|
||||
@@ -908,6 +918,7 @@ export class CanvasManager {
|
||||
* @param {Object} options 导出选项
|
||||
* @param {Boolean} options.isContainBg 是否包含背景图层
|
||||
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
||||
* @param {String} options.layerId 导出具体图层ID
|
||||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||||
@@ -934,6 +945,7 @@ export class CanvasManager {
|
||||
options.restoreOpacityInRedGreen !== undefined
|
||||
? options.restoreOpacityInRedGreen
|
||||
: false, // 默认在红绿图模式下恢复透明度
|
||||
excludedLayers: [SpecialLayerId.SPECIAL_GROUP],
|
||||
};
|
||||
|
||||
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
||||
@@ -948,7 +960,7 @@ export class CanvasManager {
|
||||
const normalLayerIds =
|
||||
this.layers?.value
|
||||
?.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther && layer.visible
|
||||
)
|
||||
?.map((layer) => layer.id) || [];
|
||||
|
||||
@@ -964,6 +976,22 @@ export class CanvasManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出印花元素颜色信息
|
||||
* @returns {Object}
|
||||
*/
|
||||
async exportExtraInfo() {
|
||||
// 导出颜色图层信息
|
||||
const color = await this.exportColorLayer().catch(() => (null));
|
||||
// 导出印花和元素图层信息
|
||||
const printTrimsData = await this.exportPrintTrimsLayers().catch(() => ({prints: null, trims: null}));
|
||||
|
||||
return {
|
||||
color,
|
||||
...printTrimsData,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出颜色图层
|
||||
* @returns {Object} 导出的颜色图层数据URL
|
||||
@@ -978,13 +1006,12 @@ export class CanvasManager {
|
||||
console.warn("颜色图层不存在,请确保已添加颜色图层");
|
||||
return Promise.reject("颜色图层不存在");
|
||||
}
|
||||
const color = fillToCssStyle(object.fill)
|
||||
const css = fillToCssStyle(object.fill)
|
||||
const canvas = new fabric.StaticCanvas();
|
||||
canvas.setDimensions({
|
||||
width: object.width,
|
||||
height: object.height,
|
||||
backgroundColor: null,
|
||||
// enableRetinaScaling: true,
|
||||
imageSmoothingEnabled: true,
|
||||
});
|
||||
const cloneObject = await new Promise((resolve, reject) => {
|
||||
@@ -1005,9 +1032,80 @@ export class CanvasManager {
|
||||
quality: 1,
|
||||
});
|
||||
canvas.clear();
|
||||
return {color, base64};
|
||||
const color = object.originColor;
|
||||
return {css, base64, color};
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出印花和元素图层
|
||||
*/
|
||||
async exportPrintTrimsLayers() {
|
||||
const object = this.layerManager.getLayerById(SpecialLayerId.SPECIAL_GROUP);
|
||||
if(!object) return Promise.reject("印花和元素图层组不存在");
|
||||
const ids = object.children.map((v) => v.id);
|
||||
const objects = this.getObjectsByIds(ids).filter((v) => !!v.sourceData);
|
||||
const fixedLayerObj = this.getFixedLayerObject();
|
||||
if(!fixedLayerObj) return Promise.reject("固定图层不存在");
|
||||
const flWidth = fixedLayerObj.width
|
||||
const flHeight = fixedLayerObj.height
|
||||
const flTop = fixedLayerObj.top
|
||||
const flLeft = fixedLayerObj.left
|
||||
const flScaleX = fixedLayerObj.scaleX
|
||||
const flScaleY = fixedLayerObj.scaleY
|
||||
const prints = [];
|
||||
const trims = [];
|
||||
objects.forEach((v) => {
|
||||
const obj = {
|
||||
ifSingle: v.sourceData.ifSingle,
|
||||
level2Type: v.sourceData.level2Type,
|
||||
designType: v.sourceData.designType,
|
||||
path: v.sourceData.path,
|
||||
minIOPath: v.sourceData.minIOPath,
|
||||
location: [0, 0],
|
||||
scale: [0, 0],
|
||||
angle: v.angle,
|
||||
name: v.sourceData.name,
|
||||
priority: v.sourceData.priority,
|
||||
}
|
||||
if(obj.ifSingle){
|
||||
let left = (v.left - (flLeft - flWidth * flScaleX / 2));
|
||||
let top = (v.top - (flTop - flHeight * flScaleY / 2));
|
||||
let width = (v.width * v.scaleX);
|
||||
let height = (v.height * v.scaleY);
|
||||
let {x:cx, y:cy} = calculateCenterPoint(width, height, left, top, v.angle);
|
||||
let x = (cx-width/2) / flScaleX;
|
||||
let y = (cy-height/2) / flScaleY;
|
||||
obj.location = [x, y];
|
||||
obj.scale = [(v.width * v.scaleX) / (flWidth * flScaleX), (v.height * v.scaleY) / (flHeight * flScaleY)];
|
||||
}else{
|
||||
let fill = v.fill;
|
||||
let fill_ = v.fill_;
|
||||
if(!fill || !fill_) return;
|
||||
let {scale, angle} = getTransformScaleAngle(fill.patternTransform);
|
||||
let scaleX = scale * 5 * v.fill_.width / flWidth;
|
||||
let scaleY = scale * 5 * v.fill_.height / flHeight;
|
||||
let scaleXY = flWidth > flHeight ? scaleX : scaleY;
|
||||
|
||||
let left = fill.offsetX + v.fill_.width * scale / 2;
|
||||
let top = fill.offsetY + v.fill_.height * scale / 2;
|
||||
|
||||
obj.scale = [scaleXY, scaleXY];
|
||||
obj.angle = angle;
|
||||
obj.location = [left, top];
|
||||
}
|
||||
if(obj.level2Type === "Pattern"){
|
||||
prints.push(obj);
|
||||
}else if(obj.level2Type === "Embroidery"){
|
||||
trims.push(obj);
|
||||
}
|
||||
})
|
||||
// prints.sort((a, b) => a.ifSingle ? 1 : -1);
|
||||
prints.forEach((v, i) => v.priority = i + 1);
|
||||
trims.forEach((v, i) => v.priority = i + 1);
|
||||
return {prints, trims};
|
||||
}
|
||||
|
||||
|
||||
dispose() {
|
||||
// 释放导出管理器资源
|
||||
if (this.exportManager) {
|
||||
@@ -1105,37 +1203,55 @@ export class CanvasManager {
|
||||
// this.canvas.discardActiveObject();
|
||||
this.canvas.renderAll();
|
||||
|
||||
|
||||
// 排除颜色图层和特殊组图层
|
||||
const excludedLayers = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP];
|
||||
this.layers.value.forEach((layer) => {
|
||||
if(excludedLayers.includes(layer.id)){
|
||||
excludedLayers.push(...layer.children?.map((child) => child.id));
|
||||
}
|
||||
})
|
||||
|
||||
const canvas = this.canvas.toJSON([
|
||||
"id",
|
||||
"type",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"isBackground",
|
||||
"isLocked",
|
||||
"isVisible",
|
||||
"isFixed",
|
||||
"parentId",
|
||||
"eraser",
|
||||
"eraserable",
|
||||
"erasable",
|
||||
"customType",
|
||||
"fill_",
|
||||
"scaleX",
|
||||
"scaleY",
|
||||
"top",
|
||||
"left",
|
||||
"width",
|
||||
"height",
|
||||
]);
|
||||
canvas.objects = canvas.objects.filter((v) => !excludedLayers.includes(v.layerId));
|
||||
|
||||
const simplifyLayersData = simplifyLayers(
|
||||
JSON.parse(JSON.stringify(this.layers.value))
|
||||
JSON.parse(JSON.stringify(this.layers.value)),
|
||||
excludedLayers
|
||||
);
|
||||
const data = JSON.stringify({
|
||||
canvas: this.canvas.toJSON([
|
||||
"id",
|
||||
"type",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"isBackground",
|
||||
"isLocked",
|
||||
"isVisible",
|
||||
"isFixed",
|
||||
"parentId",
|
||||
"eraser",
|
||||
"eraserable",
|
||||
"erasable",
|
||||
"customType",
|
||||
"fill_",
|
||||
]),
|
||||
const data = {
|
||||
canvas,
|
||||
layers: simplifyLayersData, // 简化图层数据
|
||||
// layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
|
||||
version: "1.0", // 添加版本信息
|
||||
timestamp: new Date().toISOString(), // 添加时间戳
|
||||
canvasWidth: this.canvasWidth.value,
|
||||
canvasHeight: this.canvasHeight.value,
|
||||
canvasColor: this.canvasColor.value,
|
||||
activeLayerId: this.layerManager?.activeLayerId?.value,
|
||||
});
|
||||
};
|
||||
console.log("获取画布JSON数据...", data);
|
||||
return data;
|
||||
return JSON.stringify(data);
|
||||
} catch (error) {
|
||||
console.error("获取画布JSON失败:", error);
|
||||
throw new Error("获取画布JSON失败");
|
||||
@@ -1282,32 +1398,46 @@ export class CanvasManager {
|
||||
if (!otherData) return console.warn("otherData 为空不需要添加");
|
||||
const otherData_ = JSON.parse(JSON.stringify(otherData));
|
||||
console.log("==========创建其他图层", otherData_);
|
||||
|
||||
// 删除颜色图层和特殊组图层
|
||||
const ids = [SpecialLayerId.COLOR, SpecialLayerId.SPECIAL_GROUP];
|
||||
this.layers.value = this.layers.value.filter((layer) => {
|
||||
if(ids.includes(layer.id)){
|
||||
ids.push(...layer.children?.map((child) => child.id));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
this.canvas.getObjects().forEach((v) => ids.includes(v.id) && this.canvas.remove(v))
|
||||
|
||||
|
||||
// 创建颜色图层
|
||||
await this.createColorLayer(otherData_.color);
|
||||
|
||||
if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)){
|
||||
console.warn("画布中已存在印花和元素组图层");
|
||||
}else{
|
||||
const printTrimsLayers = [];// 印花和元素图层
|
||||
const singleLayers = [];// 平铺图层
|
||||
otherData_?.printObject?.prints?.forEach((print, index) => {
|
||||
print.name = t("Canvas.Print") + (index + 1);
|
||||
if(print.ifSingle){
|
||||
printTrimsLayers.unshift({...print});
|
||||
}else{
|
||||
singleLayers.unshift({...print});
|
||||
}
|
||||
})
|
||||
otherData_?.trims?.prints?.forEach((print, index) => {
|
||||
print.name = t("Canvas.Elements") + (index + 1);
|
||||
const printTrimsLayers = [];// 印花和元素图层
|
||||
const singleLayers = [];// 平铺图层
|
||||
otherData_?.printObject?.prints?.forEach((print, index) => {
|
||||
print.name = t("Canvas.Print") + (index + 1);
|
||||
if(print.ifSingle){
|
||||
printTrimsLayers.unshift({...print});
|
||||
})
|
||||
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
|
||||
}
|
||||
}else{
|
||||
singleLayers.unshift({...print});
|
||||
}
|
||||
})
|
||||
otherData_?.trims?.prints?.forEach((trims, index) => {
|
||||
trims.name = t("Canvas.Elements") + (index + 1);
|
||||
printTrimsLayers.unshift({...trims});
|
||||
})
|
||||
await this.createPrintTrimsLayers(printTrimsLayers, singleLayers);
|
||||
|
||||
await this.changeCanvas();
|
||||
}
|
||||
|
||||
async setColorObjectInfo(colorRect, fixedLayerObj){
|
||||
colorRect.set({
|
||||
// 设置画布对象的裁剪信息
|
||||
async setObjecCliptInfo(tagObject, data){
|
||||
const fixedLayerObj = this.getFixedLayerObject();
|
||||
if(!fixedLayerObj) return console.warn("固定图层为空");
|
||||
tagObject.set({
|
||||
top: fixedLayerObj.top,
|
||||
left: fixedLayerObj.left,
|
||||
width: fixedLayerObj.width,
|
||||
@@ -1322,7 +1452,7 @@ export class CanvasManager {
|
||||
if(imageUrl){
|
||||
object = await new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(imageUrl, (imgObject) => {
|
||||
colorRect.set({
|
||||
tagObject.set({
|
||||
width: imgObject.width,
|
||||
height: imgObject.height,
|
||||
});
|
||||
@@ -1330,20 +1460,21 @@ export class CanvasManager {
|
||||
}, { crossOrigin: "anonymous" });
|
||||
});
|
||||
}
|
||||
const canvas = getObjectAlphaToCanvas(object);
|
||||
const canvas = getObjectAlphaToCanvas(object, data);
|
||||
const transparentMask = new fabric.Image(canvas, {
|
||||
top: 0,
|
||||
left: 0,
|
||||
originX: fixedLayerObj.originX,
|
||||
originY: fixedLayerObj.originY,
|
||||
});
|
||||
colorRect.set('clipPath', transparentMask);
|
||||
tagObject.set('clipPath', transparentMask);
|
||||
}
|
||||
async createColorLayer(color){
|
||||
if(!color) return console.warn("颜色为空不需要添加");
|
||||
if(findLayer(this.layers.value, SpecialLayerId.COLOR)) return console.warn("画布中已存在颜色图层");
|
||||
// if(findLayer(this.layers.value, SpecialLayerId.COLOR)) {
|
||||
// return console.warn("画布中已存在颜色图层");
|
||||
// }
|
||||
console.log("==========添加颜色图层", color, this.layers.value.length)
|
||||
const fixedLayerObj = this.getFixedLayerObject();
|
||||
// 创建颜色图层对象
|
||||
const colorRect = new fabric.Rect({
|
||||
id: SpecialLayerId.COLOR,
|
||||
@@ -1351,8 +1482,13 @@ export class CanvasManager {
|
||||
layerName: t("Canvas.color"),
|
||||
isVisible: true,
|
||||
isLocked: true,
|
||||
selectable: false,
|
||||
hasControls: false,
|
||||
hasBorders: false,
|
||||
globalCompositeOperation: BlendMode.MULTIPLY,
|
||||
originColor: color,
|
||||
});
|
||||
await this.setColorObjectInfo(colorRect, fixedLayerObj);
|
||||
await this.setObjecCliptInfo(colorRect);
|
||||
const gradientObj = palletToFill(color);
|
||||
const gradient = new fabric.Gradient({
|
||||
type: 'linear',
|
||||
@@ -1370,6 +1506,7 @@ export class CanvasManager {
|
||||
locked: colorRect.isLocked,
|
||||
opacity: 1.0,
|
||||
isFixedOther: true,
|
||||
blendMode: BlendMode.MULTIPLY,
|
||||
fabricObjects: [colorRect.toObject(["id", "layerId", "layerName"])],
|
||||
})
|
||||
const groupIndex = this.layers.value.findIndex(layer => layer.isFixed || layer.isBackground);
|
||||
@@ -1378,6 +1515,9 @@ export class CanvasManager {
|
||||
|
||||
// 创建印花和元素图层
|
||||
async createPrintTrimsLayers(printTrimsLayers, singleLayers){
|
||||
// if(findLayer(this.layers.value, SpecialLayerId.SPECIAL_GROUP)) {
|
||||
// return console.warn("画布中已存在印花和元素组图层");
|
||||
// }
|
||||
console.log("==========添加印花和元素图层组", printTrimsLayers, singleLayers)
|
||||
const fixedLayerObj = this.getFixedLayerObject();
|
||||
const flWidth = fixedLayerObj.width
|
||||
@@ -1389,24 +1529,24 @@ export class CanvasManager {
|
||||
const children = [];
|
||||
// 添加印花和元素图层
|
||||
for(let index = 0; index < printTrimsLayers.length; index++){
|
||||
let print = printTrimsLayers[index];
|
||||
let item = printTrimsLayers[index];
|
||||
let id = generateId("layer_image_");
|
||||
let name = print.name;
|
||||
let name = item.name;
|
||||
let image = await new Promise(resolve => {
|
||||
fabric.Image.fromURL(print.path, (fabricImage)=>{
|
||||
const left = flLeft - flWidth * flScaleX / 2 + (print.location?.[0] || 0) * flScaleX
|
||||
const top = flTop - flHeight * flScaleY / 2 + (print.location?.[1] || 0) * flScaleY
|
||||
const scaleX = flWidth * (print.scale?.[0] || 1) / fabricImage.width * flScaleX
|
||||
const scaleY = flHeight * (print.scale?.[1] || 1) / fabricImage.height * flScaleY
|
||||
fabric.Image.fromURL(item.path, (fabricImage)=>{
|
||||
const left = flLeft - flWidth * flScaleX / 2 + (item.location?.[0] || 0) * flScaleX
|
||||
const top = flTop - flHeight * flScaleY / 2 + (item.location?.[1] || 0) * flScaleY
|
||||
const scaleX = flWidth * (item.scale?.[0] || 1) / fabricImage.width * flScaleX
|
||||
const scaleY = flHeight * (item.scale?.[1] || 1) / fabricImage.height * flScaleY
|
||||
const {x, y} = calculateRotatedTopLeftDeg(
|
||||
fabricImage.width * scaleX,
|
||||
fabricImage.height * scaleY,
|
||||
left,
|
||||
top,
|
||||
0,
|
||||
print.angle || 0
|
||||
item.angle || 0
|
||||
)
|
||||
const angle = print.angle || 0
|
||||
const angle = item.angle || 0
|
||||
fabricImage.set({
|
||||
left: x,
|
||||
top: y,
|
||||
@@ -1419,6 +1559,7 @@ export class CanvasManager {
|
||||
selectable: true,
|
||||
hasControls: true,
|
||||
hasBorders: true,
|
||||
sourceData: item,
|
||||
});
|
||||
resolve(fabricImage);
|
||||
}, { crossOrigin: "anonymous" });
|
||||
@@ -1437,11 +1578,11 @@ export class CanvasManager {
|
||||
};
|
||||
// 添加平铺图层
|
||||
for(let index = 0; index < singleLayers.length; index++){
|
||||
let print = singleLayers[index];
|
||||
let item = singleLayers[index];
|
||||
let id = generateId("layer_image_");
|
||||
let name = print.name;
|
||||
let name = item.name;
|
||||
let image = await new Promise(resolve => {
|
||||
fabric.Image.fromURL(print.path, (fabricImage)=>{
|
||||
fabric.Image.fromURL(item.path, (fabricImage)=>{
|
||||
const imgElement = fabricImage.getElement();
|
||||
const tcanvas = document.createElement('canvas');
|
||||
tcanvas.width = imgElement.width;
|
||||
@@ -1452,12 +1593,11 @@ export class CanvasManager {
|
||||
resolve(tcanvas);
|
||||
}, { crossOrigin: "anonymous" });
|
||||
})
|
||||
console.log("==========添加平铺图层", fixedLayerObj.width,image.width)
|
||||
let scaleX = fixedLayerObj.width / image.width * (print.scale?.[0] || 1) / 5;
|
||||
let scaleY = fixedLayerObj.height / image.height * (print.scale?.[1] || 1) / 5;
|
||||
let scaleX = fixedLayerObj.width / image.width * (item.scale?.[0] || 1) / 5;
|
||||
let scaleY = fixedLayerObj.height / image.height * (item.scale?.[1] || 1) / 5;
|
||||
let scale = fixedLayerObj.width > fixedLayerObj.height ? scaleX : scaleY;
|
||||
let left = (print.location?.[0] || 0) - image.width * scale / 2
|
||||
let top = (print.location?.[1] || 0) - image.height * scale / 2
|
||||
let left = (item.location?.[0] || 0) - image.width * scale / 2
|
||||
let top = (item.location?.[1] || 0) - image.height * scale / 2
|
||||
let rect = new fabric.Rect({
|
||||
id: id,
|
||||
layerId: id,
|
||||
@@ -1470,13 +1610,21 @@ export class CanvasManager {
|
||||
scaleY: fixedLayerObj.scaleY,
|
||||
originX: fixedLayerObj.originX,
|
||||
originY: fixedLayerObj.originY,
|
||||
sourceData: item,
|
||||
fill: new fabric.Pattern({
|
||||
source: image,
|
||||
repeat: "repeat",
|
||||
patternTransform: createPatternTransform(scale, print.angle || 0),
|
||||
patternTransform: createPatternTransform(scale, item.angle || 0),
|
||||
offsetX: left, // 水平偏移
|
||||
offsetY: top, // 垂直偏移
|
||||
}),
|
||||
fill_ : {
|
||||
source: item.path,
|
||||
gapX: 0,
|
||||
gapY: 0,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
}
|
||||
});
|
||||
this.canvas.add(rect);
|
||||
let layer = createLayer({
|
||||
@@ -1503,7 +1651,7 @@ export class CanvasManager {
|
||||
children.push(layer);
|
||||
}
|
||||
const groupRect = new fabric.Rect({});
|
||||
await this.setColorObjectInfo(groupRect, fixedLayerObj);
|
||||
await this.setObjecCliptInfo(groupRect);
|
||||
// 插入组图层
|
||||
const groupIndex = this.layers.value.findIndex(layer => layer.isFixedOther || layer.isFixed || layer.isBackground);
|
||||
const groupLayer = createLayer({
|
||||
@@ -1521,6 +1669,35 @@ export class CanvasManager {
|
||||
this.layers.value.splice(groupIndex, 0, groupLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画布事件变更后
|
||||
*/
|
||||
async changeCanvas(){
|
||||
// const fixedLayerObj = this.getFixedLayerObject();
|
||||
// if(!fixedLayerObj) return console.warn("固定图层对象不存在", fixedLayerObj)
|
||||
// const colorObject = this.getLayerObjectById(SpecialLayerId.COLOR);
|
||||
// if(colorObject){
|
||||
// const ids = this.layerManager.getBlendModeLayerIds(SpecialLayerId.SPECIAL_GROUP);
|
||||
// if(ids.length === 0){
|
||||
// ids.unshift(SpecialLayerId.SPECIAL_GROUP);
|
||||
// await this.setObjecCliptInfo(colorObject);
|
||||
// this.canvas.renderAll();
|
||||
// return;
|
||||
// }
|
||||
// const base64 = await this.exportManager.exportImage({layerIdArray2: ids, isEnhanceImg: true});
|
||||
// if(!base64) return console.warn("导出图片失败", base64)
|
||||
// const canvas = await base64ToCanvas(base64, fixedLayerObj.scaleX * 2, true);
|
||||
// const ctx = canvas.getContext('2d');
|
||||
// const width = fixedLayerObj.width;
|
||||
// const height = fixedLayerObj.height;
|
||||
// const x = (canvas.width - width) / 2;
|
||||
// const y = (canvas.height - height) / 2;
|
||||
// const data = ctx.getImageData(x, y, width, height);
|
||||
// await this.setObjecCliptInfo(colorObject, data);
|
||||
// this.canvas.renderAll();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放红绿图模式内容以适应当前画布大小
|
||||
* 确保衣服底图和红绿图永远在画布内可见
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { findObjectById } from "../utils/helper";
|
||||
import { createRasterizedImage } from "../utils/selectionToImage";
|
||||
import { OperationType, SpecialLayerId } from "../utils/layerHelper";
|
||||
import { OperationType, SpecialLayerId } from "../utils/layerHelper";
|
||||
|
||||
/**
|
||||
* 图片导出管理器
|
||||
@@ -19,32 +19,38 @@ export class ExportManager {
|
||||
* @param {Object} options 导出选项
|
||||
* @param {Boolean} options.isContainBg 是否包含背景图层
|
||||
* @param {Boolean} options.isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} options.isContainFixedOther 是否包含其他固定图层
|
||||
* @param {Boolean} options.isCropByBg 是否使用背景大小裁剪
|
||||
* @param {String} options.layerId 导出具体图层ID
|
||||
* @param {Array} options.layerIdArray 导出多个图层ID数组
|
||||
* @param {Array} options.layerIdArray2 导出多个图层ID数组2
|
||||
* @param {String} options.expPicType 导出图片类型 (png/jpg/svg)
|
||||
* @param {Boolean} options.restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||
* @param {Boolean} options.isEnhanceImg 是否是增强图片
|
||||
* @param {Boolean} options.isEnhanceImg 是否是增强图片
|
||||
* @param {Array} options.excludedLayers 排除的图层ID数组
|
||||
* @returns {String} 导出的图片数据URL
|
||||
*/
|
||||
async exportImage(options = {}) {
|
||||
const {
|
||||
isContainBg = false,
|
||||
isContainFixed = false,
|
||||
isContainFixedOther = false, // 是否包含其他固定图层
|
||||
isCropByBg = false, // 是否使用背景大小裁剪
|
||||
layerId = "",
|
||||
layerIdArray = [],
|
||||
layerIdArray2 = null,
|
||||
expPicType = "png",
|
||||
restoreOpacityInRedGreen = true,
|
||||
isEnhanceImg, // 是否是增强图片
|
||||
excludedLayers = [], // 排除的图层ID数组
|
||||
} = options;
|
||||
try {
|
||||
// 查找颜色图层并隐藏
|
||||
const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR);
|
||||
if (colorLayer && colorLayer.visible) {
|
||||
colorLayer.visible = false;
|
||||
await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
}
|
||||
// const colorLayer = this.layerManager.getLayerById(SpecialLayerId.COLOR);
|
||||
// if (colorLayer && colorLayer.visible) {
|
||||
// colorLayer.visible = false;
|
||||
// await this.layerManager?.updateLayersObjectsInteractivity();
|
||||
// }
|
||||
|
||||
// 检查是否为红绿图模式
|
||||
const isRedGreenMode = this.layerManager?.isInRedGreenMode?.() || false;
|
||||
@@ -67,6 +73,7 @@ export class ExportManager {
|
||||
expPicType,
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen,
|
||||
isCropByBg,
|
||||
@@ -79,10 +86,13 @@ export class ExportManager {
|
||||
expPicType,
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen,
|
||||
isCropByBg,
|
||||
isEnhanceImg, // 是否是增强图片
|
||||
layerIdArray2,
|
||||
excludedLayers, // 排除的图层ID数组
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("导出图片失败:", error);
|
||||
@@ -155,6 +165,7 @@ export class ExportManager {
|
||||
* @param {String} expPicType 导出类型
|
||||
* @param {Boolean} isContainBg 是否包含背景图层
|
||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
||||
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
||||
@@ -167,6 +178,7 @@ export class ExportManager {
|
||||
expPicType,
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen,
|
||||
isCropByBg, // 是否使用背景大小裁剪
|
||||
@@ -180,7 +192,8 @@ export class ExportManager {
|
||||
const objectsToExport = this._collectObjectsByLayerOrder(
|
||||
layerIdArray,
|
||||
isContainBg,
|
||||
isContainFixed
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
);
|
||||
|
||||
if (objectsToExport.length === 0) {
|
||||
@@ -212,10 +225,12 @@ export class ExportManager {
|
||||
* @param {String} expPicType 导出类型
|
||||
* @param {Boolean} isContainBg 是否包含背景图层
|
||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||
* @param {Boolean} isRedGreenMode 是否为红绿图模式
|
||||
* @param {Boolean} restoreOpacityInRedGreen 红绿图模式下是否恢复透明度为1
|
||||
* @param {Boolean} isCropByBg 是否使用背景大小裁剪
|
||||
* @param {Boolean} isEnhanceImg 是否是增强图片
|
||||
* @param {Array} layerIdArray 导出多个图层ID数组2
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
@@ -223,16 +238,21 @@ export class ExportManager {
|
||||
expPicType,
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen,
|
||||
isCropByBg, // 是否使用背景大小裁剪
|
||||
isEnhanceImg, // 是否是增强图片
|
||||
isEnhanceImg, // 是否是增强图片
|
||||
layerIdArray, // 导出所有图层
|
||||
excludedLayers, // 排除的图层ID数组
|
||||
) {
|
||||
// 按图层顺序收集对象(从底到顶)
|
||||
const objectsToExport = this._collectObjectsByLayerOrder(
|
||||
null, // 导出所有图层
|
||||
layerIdArray, // 导出所有图层
|
||||
isContainBg,
|
||||
isContainFixed,
|
||||
isContainFixedOther, // 是否包含其他固定图层
|
||||
excludedLayers,
|
||||
);
|
||||
|
||||
if (objectsToExport.length === 0) {
|
||||
@@ -288,10 +308,11 @@ export class ExportManager {
|
||||
/**
|
||||
* 从图层收集对象(优化版本 - 通过ID查找画布中的真实对象)
|
||||
* @param {Object} layer 图层对象
|
||||
* @param {Boolean} isChildren 是否递归收集子图层的对象
|
||||
* @returns {Array} 画布中的真实对象数组
|
||||
* @private
|
||||
*/
|
||||
_collectObjectsFromLayer(layer) {
|
||||
_collectObjectsFromLayer(layer, isChildren = true) {
|
||||
if (!layer) {
|
||||
return [];
|
||||
}
|
||||
@@ -320,10 +341,10 @@ export class ExportManager {
|
||||
}
|
||||
|
||||
// 递归收集子图层的对象
|
||||
if (layer.children && layer.children.length > 0) {
|
||||
if (isChildren && layer.children && layer.children.length > 0) {
|
||||
for (let i = layer.children.length - 1; i >= 0; i--) {
|
||||
const childLayer = layer.children[i];
|
||||
const childObjects = this._collectObjectsFromLayer(childLayer);
|
||||
const childObjects = this._collectObjectsFromLayer(childLayer, isChildren);
|
||||
realObjects.push(...childObjects);
|
||||
}
|
||||
}
|
||||
@@ -389,12 +410,14 @@ export class ExportManager {
|
||||
* @param {Array|null} layerIdArray 图层ID数组,null表示所有图层
|
||||
* @param {Boolean} isContainBg 是否包含背景图层
|
||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||
* @param {Array} excludedLayers 排除的图层ID数组
|
||||
* @returns {Array} 按正确顺序排列的真实对象数组
|
||||
* @private
|
||||
*/
|
||||
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed) {
|
||||
_collectObjectsByLayerOrder(layerIdArray, isContainBg, isContainFixed, isContainFixedOther, excludedLayers) {
|
||||
const objectsToExport = [];
|
||||
const allLayers = this._getAllLayersFlattened(); // 获取扁平化的图层列表
|
||||
const allLayers = this._getAllLayersFlattened(excludedLayers); // 获取扁平化的图层列表
|
||||
|
||||
// 图层数组是从顶到底的顺序,需要反向遍历以获得从底到顶的渲染顺序
|
||||
for (let i = allLayers.length - 1; i >= 0; i--) {
|
||||
@@ -404,11 +427,11 @@ export class ExportManager {
|
||||
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
|
||||
|
||||
// 检查图层类型过滤条件
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed))
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther))
|
||||
continue;
|
||||
|
||||
if (layer.visible) {
|
||||
const layerObjects = this._collectObjectsFromLayer(layer);
|
||||
const layerObjects = this._collectObjectsFromLayer(layer, false);
|
||||
objectsToExport.push(...layerObjects);
|
||||
}
|
||||
}
|
||||
@@ -417,15 +440,19 @@ export class ExportManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扁平化的图层列表(包含子图层)
|
||||
* 获取扁平化的图层列表(包含子图层),排除指定的图层
|
||||
* @param {Array} excludedLayers 排除的图层ID数组
|
||||
* @returns {Array} 扁平化的图层数组
|
||||
* @private
|
||||
*/
|
||||
_getAllLayersFlattened() {
|
||||
_getAllLayersFlattened(excludedLayers) {
|
||||
const flattenedLayers = [];
|
||||
const rootLayers = this._getAllLayers();
|
||||
|
||||
const flattenLayer = (layer) => {
|
||||
// 检查是否在排除列表中
|
||||
if (excludedLayers && excludedLayers.includes(layer.id)) return;
|
||||
|
||||
flattenedLayers.push(layer);
|
||||
|
||||
// 递归处理子图层
|
||||
@@ -440,7 +467,6 @@ export class ExportManager {
|
||||
for (const layer of rootLayers) {
|
||||
flattenLayer(layer);
|
||||
}
|
||||
|
||||
return flattenedLayers;
|
||||
}
|
||||
|
||||
@@ -1019,10 +1045,11 @@ export class ExportManager {
|
||||
* @param {Object} layer 图层对象
|
||||
* @param {Boolean} isContainBg 是否包含背景图层
|
||||
* @param {Boolean} isContainFixed 是否包含固定图层
|
||||
* @param {Boolean} isContainFixedOther 是否包含其他固定图层
|
||||
* @returns {Boolean} 是否应该包含
|
||||
* @private
|
||||
*/
|
||||
_shouldIncludeLayer(layer, isContainBg, isContainFixed) {
|
||||
_shouldIncludeLayer(layer, isContainBg, isContainFixed, isContainFixedOther) {
|
||||
if (!layer) return false;
|
||||
|
||||
// 检查背景图层
|
||||
@@ -1035,6 +1062,11 @@ export class ExportManager {
|
||||
return isContainFixed;
|
||||
}
|
||||
|
||||
// 检查其他固定图层
|
||||
if (layer.isFixedOther) {
|
||||
return isContainFixedOther;
|
||||
}
|
||||
|
||||
// 普通图层总是包含
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -524,15 +524,16 @@ export class LayerManager {
|
||||
* @param {string} name 图层名称
|
||||
* @param {string} type 图层类型
|
||||
* @param {Object} options 额外选项
|
||||
* @param {boolean} isCmd 是否创建命令
|
||||
* @returns {string} 新创建的图层ID
|
||||
*/
|
||||
async createLayer(name = null, type = LayerType.EMPTY, options = {}) {
|
||||
async createLayer(name = null, type = LayerType.EMPTY, options = {}, isCmd = true) {
|
||||
// 生成唯一ID
|
||||
const layerId = options.id || options.layerId || generateId("layer_");
|
||||
|
||||
// 计算普通图层数量(非背景、非固定)
|
||||
const normalLayersCount = this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !layer.isFixedOther
|
||||
).length;
|
||||
// 计算插入位置,如果没有指定insertIndex,则根据当前选中图层决定插入位置
|
||||
// 添加到图层列表
|
||||
@@ -544,7 +545,7 @@ export class LayerManager {
|
||||
// 创建新图层
|
||||
const newLayer = createLayer({
|
||||
id: layerId,
|
||||
name: name || `图层 ${normalLayersCount + 1}`,
|
||||
name: name || this.t("Canvas.EmptyLayer"),
|
||||
type: type,
|
||||
visible: true,
|
||||
locked: false,
|
||||
@@ -573,13 +574,13 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
if (this.commandManager) {
|
||||
if (isCmd && this.commandManager) {
|
||||
await this.commandManager.execute(command);
|
||||
} else {
|
||||
} else{
|
||||
await command.execute();
|
||||
}
|
||||
|
||||
return layerId;
|
||||
return isCmd ? layerId : command;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -973,7 +974,7 @@ export class LayerManager {
|
||||
})
|
||||
// const normalLayers = this.layers.value.filter((l) => !l.isBackground && !l.isFixed && !l.isFixedOther);
|
||||
console.log("普通图层:", normalLayers)
|
||||
if (isChild ? parentLength <= 1 : normalLayers.length <= 1) {
|
||||
if (isChild ? parentLength <= 1 : false) {//normalLayers.length <= 1
|
||||
console.warn("不能删除唯一的普通图层");
|
||||
message.warning(this.t("Canvas.cannotDeleteOnlyLayer"));
|
||||
return false;
|
||||
@@ -3436,4 +3437,22 @@ export class LayerManager {
|
||||
|
||||
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取印花和颜色图层设置了blendMode的图层ID
|
||||
* @returns {string[]} - 包含blendMode的图层ID数组
|
||||
*/
|
||||
getBlendModeLayerIds() {
|
||||
const blendModeLayerIds = [];
|
||||
this.layers.value.forEach(layer => {
|
||||
if(layer.id === SpecialLayerId.SPECIAL_GROUP){
|
||||
layer.children.forEach(child => {
|
||||
if(child.visible && child.blendMode && child.blendMode !== BlendMode.NORMAL){
|
||||
blendModeLayerIds.push(child.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return blendModeLayerIds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,6 +919,26 @@ export function calculateRotatedTopLeftDeg(
|
||||
return { x: newX, y: newY };
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据左上角坐标计算中心点坐标
|
||||
* @param {number} W - 宽度
|
||||
* @param {number} H - 高度
|
||||
* @param {number} currentX - 当前左上角x坐标
|
||||
* @param {number} currentY - 当前左上角y坐标
|
||||
* @param {number} currentAngleDeg - 当前角度(度)
|
||||
* @returns {Object} 中心点坐标 {x, y}
|
||||
*/
|
||||
export function calculateCenterPoint(W, H, currentX, currentY, currentAngleDeg) {
|
||||
const currentAngle = (currentAngleDeg * Math.PI) / 180;
|
||||
const cosCurrent = Math.cos(currentAngle);
|
||||
const sinCurrent = Math.sin(currentAngle);
|
||||
const Cx = currentX + (W / 2) * cosCurrent - (H / 2) * sinCurrent;
|
||||
const Cy = currentY + (W / 2) * sinCurrent + (H / 2) * cosCurrent;
|
||||
return { x: Cx, y: Cy };
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建缩放+旋转的变换矩阵
|
||||
* @param {number} scale - 缩放比例
|
||||
@@ -959,3 +979,32 @@ export function getTransformScaleAngle(Transform) {
|
||||
const angle = Math.round(Math.atan2(b, a) * 180 / Math.PI);
|
||||
return { scale, angle };
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片转换为canvas
|
||||
* @param {String} base64 - 图片base64编码
|
||||
* @param {Number} scale - 缩放比例
|
||||
* @param {Boolean} sr - 缩放反转,默认false
|
||||
* @returns {Promise<HTMLCanvasElement>} canvas元素
|
||||
*/
|
||||
export async function base64ToCanvas(base64, scale = 1, sr = false) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.src = base64;
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.onload = () => {
|
||||
image.width = image.width;
|
||||
image.height = image.height;
|
||||
const canvas = document.createElement('canvas');
|
||||
const width = (sr ? image.width / scale : image.width * scale);
|
||||
const height = sr ? image.height / scale : image.height * scale;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.drawImage(image, 0, 0, width, height);
|
||||
resolve(canvas);
|
||||
};
|
||||
image.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,15 +155,19 @@ export function validateLayerAssociations(layers, canvasObjects) {
|
||||
/**
|
||||
* 简化layers对象属性,只保留必要的属性
|
||||
* @param {Array} layers 图层数组
|
||||
* @param {Array} excludedLayers 排除的图层ID数组
|
||||
* @returns {Array} 简化后的图层数组
|
||||
*/
|
||||
export function simplifyLayers(layers) {
|
||||
export function simplifyLayers(layers, excludedLayers = []) {
|
||||
if (!layers || !isArray(layers)) {
|
||||
console.warn("simplifyLayers 请传入有效的图层数组:", layers);
|
||||
return [];
|
||||
}
|
||||
|
||||
return layers.map((layer) => {
|
||||
// 检查是否在排除列表中
|
||||
if (excludedLayers && excludedLayers.includes(layer.id)) return null;
|
||||
|
||||
const simplifiedLayer = {
|
||||
id: layer.id,
|
||||
name: layer.name,
|
||||
@@ -211,10 +215,11 @@ export function simplifyLayers(layers) {
|
||||
fill: layer?.fill || null,
|
||||
fillColor: layer.fillColor,
|
||||
selectObject: layer.selectObject,
|
||||
blendMode: layer.blendMode || null,
|
||||
};
|
||||
|
||||
return simplifiedLayer;
|
||||
});
|
||||
}).filter((layer) => !!layer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,11 +61,14 @@ export async function restoreFabricObject(serializedObject, canvas) {
|
||||
|
||||
/**
|
||||
* 获取对象黑白通道画布
|
||||
* @param {fabric.Object} object - 要处理的 fabric 对象
|
||||
* @param {ImageData} revData - 相反的ImageData,白通道的相同位置是否为透明,revData为白色为透明,黑色为不透明
|
||||
* @returns {HTMLCanvasElement|null} 包含黑白通道的画布,或 null 如果失败
|
||||
*/
|
||||
export function getObjectAlphaToCanvas(object) {
|
||||
export function getObjectAlphaToCanvas(object, revData) {
|
||||
const image = object.getElement();
|
||||
const { width, height } = image;
|
||||
if(!width || !height){
|
||||
if (!width || !height) {
|
||||
console.warn("对象没有元素");
|
||||
return null;
|
||||
}
|
||||
@@ -80,12 +83,23 @@ export function getObjectAlphaToCanvas(object) {
|
||||
const g = data.data[i + 1];
|
||||
const b = data.data[i + 2];
|
||||
const a = data.data[i + 3];
|
||||
const revR = revData?.data[i + 0] || 0;
|
||||
const revG = revData?.data[i + 1] || 0;
|
||||
const revB = revData?.data[i + 2] || 0;
|
||||
const revA = revData?.data[i + 3] || 0;
|
||||
if (r || g || b || a) {
|
||||
data.data[i + 0] = 255;
|
||||
data.data[i + 1] = 255;
|
||||
data.data[i + 2] = 255;
|
||||
data.data[i + 3] = 255;
|
||||
}else{
|
||||
if (revR || revG || revB || revA) {
|
||||
data.data[i + 0] = 0;
|
||||
data.data[i + 1] = 0;
|
||||
data.data[i + 2] = 0;
|
||||
data.data[i + 3] = 0;
|
||||
} else {
|
||||
data.data[i + 0] = 255;
|
||||
data.data[i + 1] = 255;
|
||||
data.data[i + 2] = 255;
|
||||
data.data[i + 3] = 255;
|
||||
}
|
||||
} else {
|
||||
data.data[i + 0] = 0;
|
||||
data.data[i + 1] = 0;
|
||||
data.data[i + 2] = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 栅格化帮助
|
||||
import { fabric } from "fabric-with-all";
|
||||
|
||||
import { SpecialLayerId } from "./layerHelper";
|
||||
/**
|
||||
* 创建栅格化图像 - 重构版本
|
||||
* 采用复制原对象+裁剪路径的方式,保持原始质量和准确位置
|
||||
@@ -184,10 +184,16 @@ const createClippedDataURLByCanvas = async ({
|
||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
// const optimizedBounds = calculateOptimizedBounds(
|
||||
// clippingObject,
|
||||
// fabricObjects
|
||||
// );
|
||||
const optimizedBounds = {
|
||||
left: clippingObject.left - clippingObject.width / 2,
|
||||
top: clippingObject.top - clippingObject.height / 2,
|
||||
width: clippingObject.width,
|
||||
height: clippingObject.height,
|
||||
}
|
||||
|
||||
// 使用高分辨率以保证质量
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
@@ -685,6 +691,16 @@ const cloneObjectAsync = (obj) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
obj.clone((cloned) => {
|
||||
if (cloned) {
|
||||
cloned.set({
|
||||
scaleX: obj.scaleX,
|
||||
scaleY: obj.scaleY,
|
||||
top: obj.top,
|
||||
left: obj.left,
|
||||
width: obj.width,
|
||||
height: obj.height,
|
||||
zoomX: obj.zoomX,
|
||||
zoomY: obj.zoomY,
|
||||
})
|
||||
resolve(cloned);
|
||||
} else {
|
||||
reject(new Error("对象克隆失败"));
|
||||
@@ -839,9 +855,8 @@ const renderContentToImage = async ({
|
||||
});
|
||||
|
||||
// 克隆并添加所有需要渲染的对象
|
||||
for (const obj of fabricObjects) {
|
||||
const clonedObj = await cloneObjectAsync(obj);
|
||||
|
||||
for (let obj of fabricObjects) {
|
||||
let clonedObj = await cloneObjectAsync(obj);
|
||||
// 调整对象位置:将选区左上角作为新的原点(0,0)
|
||||
clonedObj.set({
|
||||
left: (clonedObj.left - selectionBounds.left) * qualityMultiplier,
|
||||
@@ -853,19 +868,19 @@ const renderContentToImage = async ({
|
||||
});
|
||||
|
||||
// 如果有裁剪路径,也需要调整裁剪路径
|
||||
if (clonedObj.clipPath) {
|
||||
if (clonedObj.clipPath && obj.id !== SpecialLayerId.COLOR) {
|
||||
clonedObj.clipPath.set({
|
||||
left:
|
||||
(clonedObj.clipPath.left - selectionBounds.left) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier,
|
||||
left: (clonedObj.clipPath.left - selectionBounds.left) * qualityMultiplier,
|
||||
top: (clonedObj.clipPath.top - selectionBounds.top) * qualityMultiplier,
|
||||
scaleX: (clonedObj.clipPath.scaleX || 1) * qualityMultiplier,
|
||||
scaleY: (clonedObj.clipPath.scaleY || 1) * qualityMultiplier,
|
||||
});
|
||||
clonedObj.clipPath.setCoords(); // 更新裁剪路径坐标
|
||||
}
|
||||
|
||||
// if(obj.globalCompositeOperation === "multiply"){
|
||||
// clonedObj.clipPath = null;
|
||||
// }
|
||||
console.log("==========", obj.id, obj.layerName);
|
||||
contentCanvas.add(clonedObj);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import ToolButton from "@/component/Canvas/ExistsImageList/ToolButton.vue";
|
||||
const canvasEditor = ref();
|
||||
const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample
|
||||
|
||||
const clothingImageUrl = "https://www.minio-api.aida.com.hk/aida-collection-element/24299/Printboard/4eba03bd-4367-4c69-b1a3-3f3177a1be1f.jpg?response-content-type=image%2Fjpeg&response-content-disposition=inline&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20251217%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251217T081126Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=b8223ba37625370c9716024536a08ce1ee5c5a7aaefc47d9baf8bd1e0e4d2d91";
|
||||
const clothingImageUrlInit = "/src/assets/work/5.PNG";
|
||||
const clothingImageUrl = "/src/assets/images/canvas/xiangao.png";
|
||||
const clothingImageUrlInit = "/src/assets/images/canvas/xiangaofenge.png";
|
||||
|
||||
const imageData = [
|
||||
{
|
||||
@@ -71,8 +71,10 @@ const editorConfig = {
|
||||
const exportImage = async () => {
|
||||
if (canvasEditor.value) {
|
||||
const base64 = await canvasEditor.value.exportImage({
|
||||
isContainFixed: true, // 是否导出底图
|
||||
isContainFixed: false, // 是否导出底图
|
||||
isContainFixedOther: false, // 是否导出其他固定图层
|
||||
isContainBg: false, // 是否导出背景
|
||||
isEnhanceImg: false, // 是否导出增强图片
|
||||
});
|
||||
|
||||
// 模拟下载图片
|
||||
@@ -99,6 +101,16 @@ const exportColorLayer = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 导出所有信息
|
||||
const exportExtraInfo = async () => {
|
||||
if (canvasEditor.value) {
|
||||
const extraInfo = await canvasEditor.value.exportExtraInfo();
|
||||
console.log("==========导出信息:", extraInfo);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
const changeCanvas = (command) => {
|
||||
console.log(command);
|
||||
};
|
||||
@@ -194,12 +206,19 @@ const frontBackChange =(value)=>{
|
||||
// 自定义工具配置相关
|
||||
const customToolsList = ref([
|
||||
{
|
||||
id: "exportPNG",
|
||||
id: "exportColorLayer",
|
||||
title: "导出颜色图层",
|
||||
action: exportColorLayer,
|
||||
label: "导颜",
|
||||
class: "export-btn",
|
||||
},
|
||||
{
|
||||
id: "exportExtraInfo",
|
||||
title: "导出印花颜色等信息",
|
||||
action: exportExtraInfo,
|
||||
label: "导E",
|
||||
class: "export-btn",
|
||||
},
|
||||
{
|
||||
id: "exportPNG",
|
||||
title: "导出PNG", //导出画布图片
|
||||
@@ -272,6 +291,40 @@ const customToolsList = ref([
|
||||
class: "export-btn",
|
||||
},
|
||||
]);
|
||||
const otherData = {
|
||||
color: {rgba: {r:255,g:0,b:0,a:1}},
|
||||
printObject: {
|
||||
prints: [
|
||||
{
|
||||
ifSingle: false,
|
||||
level2Type: "Pattern",
|
||||
designType: "Library",
|
||||
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||
location: [250, 780],
|
||||
scale: [0.5 * 0.7, 0.272541 * 0.7],
|
||||
angle: 0,
|
||||
},
|
||||
{
|
||||
ifSingle: true,
|
||||
level2Type: "Pattern",
|
||||
designType: "Library",
|
||||
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||
location: [250, 780],
|
||||
scale: [0.5 * 0.7, 0.272541 * 0.7],
|
||||
angle: 0,
|
||||
},
|
||||
{
|
||||
ifSingle: true,
|
||||
level2Type: "Pattern",
|
||||
designType: "Library",
|
||||
path: "/src/assets/images/canvas/yinhua1.jpg",
|
||||
location: [300, 500],
|
||||
scale: [0.5 * 0.4, 0.272541 * 0.4],
|
||||
angle: 0,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -299,8 +352,10 @@ const customToolsList = ref([
|
||||
ref="canvasEditor"
|
||||
key="canvasEditor"
|
||||
v-if="currentView === 'canvasEditor'"
|
||||
:clothingImageUrl="clothingImageUrl"
|
||||
:clothingImageUrl2="clothingImageUrlInit"
|
||||
:otherData="otherData"
|
||||
:config="editorConfig"
|
||||
:clothingImageUrl="clothingImageUrl"
|
||||
:clothing-image-opts="{
|
||||
imageMode: 'contains', // 设置底图包含在画布内
|
||||
}"
|
||||
|
||||
Reference in New Issue
Block a user