diff --git a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js index fe351b8d..50a5559f 100644 --- a/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/BackgroundCommands.js @@ -33,7 +33,11 @@ export class CreateBackgroundLayerCommand extends Command { const bgObject = this._createBackgroundObject(); // 将背景对象添加到图层中 - this.backgroundLayer.fabricObject = bgObject; + this.backgroundLayer.fabricObject = bgObject.toObject([ + "id", + "layerId", + "type", + ]); // 添加图层到最底部 this.layers.value.push(this.backgroundLayer); diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js index af6cf4ea..685888f5 100644 --- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js @@ -3581,7 +3581,11 @@ export class ChangeFixedImageCommand extends Command { restoredImage.setCoords(); // 更新引用 - this.targetLayer.fabricObject = restoredImage; + this.targetLayer.fabricObject = restoredImage.toObject([ + "id", + "layerId", + "type", + ]); this.layerManager.updateLayerObject( this.targetLayer.id, restoredImage diff --git a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js index 6c68b54f..e27eeeb4 100644 --- a/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js +++ b/src/component/Canvas/CanvasEditor/commands/RedGreenCommands.js @@ -358,7 +358,7 @@ export class BatchInitializeRedGreenModeCommand extends Command { this.canvas.add(object); this.canvas.sendToBack(object); - backgroundLayer.fabricObject = object; + backgroundLayer.fabricObject = object.toObject(["id", "layerId", "type"]); } else { // 更新现有背景对象大小 object.set({ diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue index 605b0079..42ed71c8 100644 --- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue +++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue @@ -18,6 +18,10 @@ const props = defineProps({ activeLayerId: String, activeElementId: String, thumbnailManager: Object, // 添加缩略图管理器属性 + showFixedLayer: { + type: Boolean, + default: false, // 默认不显示固定层 + }, }); const emit = defineEmits([ @@ -74,10 +78,11 @@ const sortableRootLayers = computed(() => { // 计算属性:不可排序的固定图层(背景层和固定层) const fixedLayers = computed(() => { if (!layers) return []; - return layers.value.filter( - // (layer) => !layer.parentId && (layer.isFixed || layer.isBackground) - (layer) => !layer.parentId && layer.isBackground // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特 - ); + return layers.value.filter((layer) => { + if (props.showFixedLayer) + return !layer.parentId && (layer.isFixed || layer.isBackground); + return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特 + }); }); // 计算属性:获取当前选中的图层 diff --git a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue index aa96dcb9..ec521f17 100644 --- a/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue +++ b/src/component/Canvas/CanvasEditor/components/ToolsSidebar.vue @@ -1,6 +1,7 @@ @@ -333,67 +332,6 @@ onUnmounted(() => { height: 100%; } -.tool-btn { - position: relative; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - background: none; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; - color: #333; - transition: all 0.2s ease; -} - -.tool-btn:hover { - background-color: #f0f0f0; -} - -.tool-btn:hover .tool-tooltip { - display: block; -} - -.tool-btn.active { - background-color: #e6f7ff; - color: #1890ff; -} - -.tool-btn.disabled { - cursor: not-allowed; - color: #e0e0e0; -} - -.tool-tooltip { - display: none; - position: absolute; - left: 100%; - top: 50%; - transform: translateY(-50%); - background-color: rgba(0, 0, 0, 0.7); - color: white; - padding: 4px 8px; - border-radius: 4px; - margin-left: 8px; - white-space: nowrap; - font-size: 12px; - z-index: 10; -} - -.tool-tooltip:before { - content: ""; - position: absolute; - top: 50%; - right: 100%; - margin-top: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent rgba(0, 0, 0, 0.7) transparent transparent; -} - .red-green-mode { background-color: #fff4f4; } diff --git a/src/component/Canvas/CanvasEditor/index.vue b/src/component/Canvas/CanvasEditor/index.vue index 3faa912b..9b581fc5 100644 --- a/src/component/Canvas/CanvasEditor/index.vue +++ b/src/component/Canvas/CanvasEditor/index.vue @@ -85,6 +85,11 @@ const props = defineProps({ type: Boolean, default: false, // 是否允许擦除背景图层 }, + + showFixedLayer: { + type: Boolean, + default: false, // 是否显示固定图层 + }, }); // 引用和状态 @@ -676,6 +681,7 @@ const changeCanvas = (command) => { // 提供外部ref实例方法 defineExpose({ layers, // 图层数据 + activeTool, // 当前选中的工具 getCanvasManager: () => canvasManager, // 获取画布管理器实例 // type : isBackground isFixed flag: 是否可擦除图层 setFixedLayerErasable: ({ type = "isFixed", flag = false }) => { @@ -862,7 +868,12 @@ defineExpose({ @zoom-in="zoomIn" @zoom-out="zoomOut" @undo-redo-status-changed="changeCanvas" - /> + > + + +
{ - return new Promise((resolve) => { - const newLayer = { - ...layerData, - fabricObjects: [], - children: layerData.children || [], - }; - - // 如果有序列化的对象,恢复它们 - if ( - layerData.serializedObjects && - Array.isArray(layerData.serializedObjects) - ) { - fabric.util.enlivenObjects(layerData.serializedObjects, (objects) => { - objects.forEach((obj) => { - // 关联到图层 - obj.layerId = newLayer.id; - obj.layerName = newLayer.name; - - // 添加到画布 - this.canvas.add(obj); - - // 添加到图层 - newLayer.fabricObjects.push(obj); - }); - - resolve(newLayer); - }); - } else if ( - layerData.isBackground && - layerData.serializedBackgroundObject - ) { - // 恢复背景对象 - fabric.util.enlivenObjects( - [layerData.serializedBackgroundObject], - ([bgObject]) => { - if (bgObject) { - bgObject.layerId = newLayer.id; - bgObject.layerName = newLayer.name; - this.canvas.add(bgObject); - newLayer.fabricObject = bgObject; - } - - resolve(newLayer); - } - ); - } else { - resolve(newLayer); - } - }); - }); - - // 等待所有图层加载完成 - Promise.all(promises).then((layers) => { - // 更新图层列表 - this.layers.value = layers; - - // 设置活动图层 - if (data.activeLayerId) { - const activeLayer = layers.find( - (layer) => layer.id === data.activeLayerId - ); - if (activeLayer && !activeLayer.isBackground && !activeLayer.locked) { - this.activeLayerId.value = data.activeLayerId; - } else { - // 查找第一个非背景、非锁定的图层 - const firstNormalLayer = layers.find( - (layer) => !layer.isBackground && !layer.locked - ); - if (firstNormalLayer) { - this.activeLayerId.value = firstNormalLayer.id; - } - } - } - - // 确保至少有一个背景图层和一个普通图层 - this.initializeLayers(); - - // 更新对象交互性 - this.updateLayersObjectsInteractivity(); - - // 重新排列对象 - this._rearrangeObjects(); - }); - - return true; - } - /** * 复制图层数据到剪贴板 * @param {string} layerId 要复制的图层ID diff --git a/src/component/Canvas/CanvasEditor/utils/canvasFactory.js b/src/component/Canvas/CanvasEditor/utils/canvasFactory.js index 292d3f9c..2f1b9587 100644 --- a/src/component/Canvas/CanvasEditor/utils/canvasFactory.js +++ b/src/component/Canvas/CanvasEditor/utils/canvasFactory.js @@ -12,6 +12,8 @@ export const createCanvas = (elementId, options = {}) => { enableRetinaScaling: true, preserveObjectStacking: true, // 保持对象堆叠顺序 // skipOffscreen: true, // 跳过离屏渲染 + imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿 + imageSmoothingQuality: "high", // 设置高质量图像平滑 ...options, }); @@ -24,6 +26,9 @@ export const createCanvas = (elementId, options = {}) => { export const createStaticCanvas = (elementId, options = {}) => { const canvas = new fabric.StaticCanvas(elementId, { enableRetinaScaling: canvasConfig.enableRetinaScaling, + imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿 + imageSmoothingQuality: "high", // 设置高质量图像平滑 + skipOffscreen: false, // 不跳过离屏渲染 ...options, }); diff --git a/src/component/Canvas/CanvasEditor/utils/layerUtils.js b/src/component/Canvas/CanvasEditor/utils/layerUtils.js index e67ac824..0c681510 100644 --- a/src/component/Canvas/CanvasEditor/utils/layerUtils.js +++ b/src/component/Canvas/CanvasEditor/utils/layerUtils.js @@ -51,7 +51,6 @@ export function buildLayerAssociations(layer, canvasObjects) { */ export function restoreObjectLayerAssociations(layers, canvasObjects) { if (!layers || !canvasObjects || !isArray(canvasObjects)) return; - layers.forEach((layer) => { buildLayerAssociations(layer, canvasObjects); // 处理子图层 @@ -242,7 +241,11 @@ export function restoreLayers(simplifiedLayers, canvasObjects) { if (fabricObj) { fabricObj.layerId = layer.id; fabricObj.layerName = layer.name; - restoredLayer.fabricObject = fabricObj; + restoredLayer.fabricObject = fabricObj.toObject([ + "id", + "layerId", + "type", + ]); } else { restoredLayer.fabricObject = null; } @@ -258,7 +261,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) { if (fabricObj) { fabricObj.layerId = layer.id; fabricObj.layerName = layer.name; - return fabricObj; + return fabricObj.toObject(["id", "layerId", "type"]); } return null; }) diff --git a/src/component/Canvas/CanvasEditor/utils/rasterizedImage.js b/src/component/Canvas/CanvasEditor/utils/rasterizedImage.js index f5932aa0..8b19c94a 100644 --- a/src/component/Canvas/CanvasEditor/utils/rasterizedImage.js +++ b/src/component/Canvas/CanvasEditor/utils/rasterizedImage.js @@ -1,8 +1,9 @@ // 栅格化帮助 import { fabric } from "fabric-with-all"; +import { createStaticCanvas } from "./canvasFactory"; /** * 创建栅格化图像 - * 使用增强版栅格化方法,不受原始画布变换影响 + * 使用组对象方式,避免边界计算误差 * @returns {Promise} 栅格化后的图像对象 * @private */ @@ -11,7 +12,7 @@ export const createRasterizedImage = async ({ fabricObjects = [], // 要栅格化的对象列表 - 按顺序 必填 maskObject = null, // 用于裁剪的对象 - 可选 trimWhitespace = true, // 是否裁剪空白区域 - trimPadding = 1, // 裁剪边距 + trimPadding = 0, // 裁剪边距 quality = 1.0, // 图像质量 format = "png", // 图像格式 scaleFactor = 2, // 高清倍数 - 默认是画布的高清倍数 @@ -33,22 +34,12 @@ export const createRasterizedImage = async ({ currentZoom ); - scaleFactor = Math.min(scaleFactor, 3); // 最大不能大于3 - console.log(`高清倍数: ${scaleFactor}, 当前缩放: ${currentZoom}`); - // 计算绝对边界框(原始尺寸)和相对边界框(当前缩放后的尺寸) - const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects); - - console.log("📏 绝对边界框:", absoluteBounds); - console.log("📏 相对边界框:", relativeBounds); - - // 使用绝对边界框创建高质量的离屏渲染 - const rasterizedImage = await createOffscreenRasterization({ + // 使用组对象方式创建栅格化图像 + const rasterizedImage = await createRasterizedImageWithGroup({ canvas, objects: fabricObjects, - absoluteBounds, - relativeBounds, scaleFactor, maskObject, trimWhitespace, @@ -80,8 +71,6 @@ export const createRasterizedImage = async ({ type: "rasterized", rasterizedAt: new Date().toISOString(), objectCount: fabricObjects.length, - absoluteBounds, - relativeBounds, originalZoom: currentZoom, }, }); @@ -97,84 +86,13 @@ export const createRasterizedImage = async ({ }; /** - * 计算对象的绝对边界框和相对边界框 - * @param {Array} fabricObjects fabric对象数组 - * @returns {Object} 包含绝对边界框和相对边界框的对象 - */ -const calculateBounds = (fabricObjects) => { - if (fabricObjects.length === 0) { - console.warn("⚠️ 没有对象,无法计算边界框"); - return { absoluteBounds: null, relativeBounds: null }; - } - - let absoluteBounds = null; - let relativeBounds = null; - - fabricObjects.forEach((obj, index) => { - // 获取相对边界框(考虑画布缩放和平移) - const relativeBound = obj.getBoundingRect(); - // 获取绝对边界框(原始大小和位置) - const absoluteBound = obj.getBoundingRect(true, true); - - console.log(`对象 ${obj.id || index} 边界框比较:`, { - relative: relativeBound, - absolute: absoluteBound, - scaleX: obj.scaleX, - scaleY: obj.scaleY, - }); - - // 计算绝对边界框的累积范围 - if (!absoluteBounds) { - absoluteBounds = { ...absoluteBound }; - } else { - const right = Math.max( - absoluteBounds.left + absoluteBounds.width, - absoluteBound.left + absoluteBound.width - ); - const bottom = Math.max( - absoluteBounds.top + absoluteBounds.height, - absoluteBound.top + absoluteBound.height - ); - - absoluteBounds.left = Math.min(absoluteBounds.left, absoluteBound.left); - absoluteBounds.top = Math.min(absoluteBounds.top, absoluteBound.top); - absoluteBounds.width = right - absoluteBounds.left; - absoluteBounds.height = bottom - absoluteBounds.top; - } - - // 计算相对边界框的累积范围 - if (!relativeBounds) { - relativeBounds = { ...relativeBound }; - } else { - const right = Math.max( - relativeBounds.left + relativeBounds.width, - relativeBound.left + relativeBound.width - ); - const bottom = Math.max( - relativeBounds.top + relativeBounds.height, - relativeBound.top + relativeBound.height - ); - - relativeBounds.left = Math.min(relativeBounds.left, relativeBound.left); - relativeBounds.top = Math.min(relativeBounds.top, relativeBound.top); - relativeBounds.width = right - relativeBounds.left; - relativeBounds.height = bottom - relativeBounds.top; - } - }); - - return { absoluteBounds, relativeBounds }; -}; - -/** - * 创建离屏栅格化渲染 + * 使用组对象方式创建栅格化图像 * @param {Object} options 渲染选项 * @returns {Promise} 栅格化后的图像对象 */ -const createOffscreenRasterization = async ({ +const createRasterizedImageWithGroup = async ({ canvas, objects, - absoluteBounds, - relativeBounds, scaleFactor, maskObject, trimWhitespace, @@ -185,49 +103,71 @@ const createOffscreenRasterization = async ({ isReturenDataURL, }) => { try { - // 创建离屏画布,使用绝对尺寸以保证高质量 - const offscreenCanvas = new fabric.Canvas(); + // 创建离屏画布 + const offscreenCanvas = createStaticCanvas(); - // 设置离屏画布尺寸为绝对边界框大小,并应用高清倍数 - const canvasWidth = Math.ceil(absoluteBounds.width * scaleFactor); - const canvasHeight = Math.ceil(absoluteBounds.height * scaleFactor); + // 克隆所有对象 + const clonedObjects = []; + for (const obj of objects) { + const clonedObj = await cloneObjectAsync(obj); + clonedObj.set({ + select: false, + evented: false, + hasControls: false, + }); + clonedObjects.push(clonedObj); + } + + // 创建组对象 + const group = new fabric.Group(clonedObjects, { + originX: "left", + originY: "top", + }); + + // 获取组的绝对边界框 + const groupBounds = group.getBoundingRect(true, true); + console.log("📏 组边界框:", groupBounds); + + // 设置离屏画布尺寸,使用组的边界大小 + const canvasWidth = Math.ceil(groupBounds.width * scaleFactor); + const canvasHeight = Math.ceil(groupBounds.height * scaleFactor); offscreenCanvas.setDimensions({ width: canvasWidth, height: canvasHeight, + hasControls: false, }); - // 设置离屏画布的缩放,确保对象以原始尺寸渲染 - // offscreenCanvas.setZoom(scaleFactor); - console.log( `🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}` ); - // 克隆对象到离屏画布 - const clonedObjects = []; - for (const obj of objects) { - const clonedObj = await cloneObjectAsync(obj); + // 调整组的位置,让它位于画布的左上角 + group.set({ + left: 0, + top: 0, + }); - // 调整对象位置,相对于绝对边界框的左上角 - // const absoluteObjBounds = obj.getBoundingRect(true, true); - clonedObj.set({ - left: clonedObj.left - absoluteBounds.left, - top: clonedObj.top - absoluteBounds.top, - }); + // 取消对象激活 + group.set({ + selectable: false, // 禁用组的选择 + evented: false, // 禁用组的事件 + }); - clonedObjects.push(clonedObj); - offscreenCanvas.add(clonedObj); + // 将组添加到离屏画布 + offscreenCanvas.add(group); + + // 设置离屏画布的缩放 + offscreenCanvas.setZoom(scaleFactor); + + // 如果有遮罩对象,应用遮罩 + if (maskObject) { + await applyMaskToCanvas(offscreenCanvas, maskObject, groupBounds); } // 渲染离屏画布 offscreenCanvas.renderAll(); - // 如果有遮罩对象,应用遮罩 - if (maskObject) { - await applyMaskToCanvas(offscreenCanvas, maskObject, absoluteBounds); - } - // 生成图像数据 const dataURL = offscreenCanvas.toDataURL({ format, @@ -236,22 +176,30 @@ const createOffscreenRasterization = async ({ }); if (isReturenDataURL) { + // 清理离屏画布 + offscreenCanvas.dispose(); return dataURL; // 如果需要返回DataURL } + // 清理离屏画布 offscreenCanvas.dispose(); // 创建fabric.Image对象 const fabricImage = await createFabricImageFromDataURL(dataURL); - // // 应用变换到fabric图像 + // 设置图像的位置和缩放,使其与原始组的位置和大小匹配 fabricImage.set({ - ...absoluteBounds, + scaleX: 1 / scaleFactor, // 由于我们生成的图像是高清版本,需要缩放回原始大小 + scaleY: 1 / scaleFactor, + left: groupBounds.left + groupBounds.width / 2, // 设置为组中心点 + top: groupBounds.top + groupBounds.height / 2, // 设置为组中心点 + originX: "center", + originY: "center", }); return fabricImage; } catch (error) { - console.error("离屏栅格化失败:", error); + console.error("组对象栅格化失败:", error); throw error; } }; @@ -290,37 +238,6 @@ const createFabricImageFromDataURL = (dataURL) => { }); }; -/** - * 计算栅格化图像在当前画布上的正确变换 - * @param {Object} params 计算参数 - * @returns {Object} 变换属性对象 - */ -const calculateImageTransform = ({ - absoluteBounds, - relativeBounds, - currentZoom, - scaleFactor, - imageWidth, - imageHeight, -}) => { - // 计算缩放比例:相对尺寸 / 绝对尺寸 - const scaleX = relativeBounds.width / absoluteBounds.width; - const scaleY = relativeBounds.height / absoluteBounds.height; - - // 由于我们生成的图像是基于绝对尺寸的高清版本,需要考虑scaleFactor - const finalScaleX = scaleX / scaleFactor; - const finalScaleY = scaleY / scaleFactor; - - return { - left: relativeBounds.left + relativeBounds.width / 2, // 设置为中心点 - top: relativeBounds.top + relativeBounds.height / 2, // 设置为中心点 - // scaleX: finalScaleX, - // scaleY: finalScaleY, - originX: "center", - originY: "center", - }; -}; - /** * 应用遮罩到画布(如果需要) * @param {fabric.Canvas} canvas 目标画布 @@ -333,7 +250,26 @@ const applyMaskToCanvas = async (canvas, maskObject, bounds) => { console.log("应用遮罩功能待实现"); }; +/** + * 获取对象组的边界框 + * @param {Array} fabricObjects fabric对象数组 + * @returns {Object} 边界框信息 + */ export const getObjectsBounds = (fabricObjects) => { - const { absoluteBounds } = calculateBounds(fabricObjects); - return absoluteBounds; + if (fabricObjects.length === 0) { + return null; + } + + // 创建临时组来获取准确的边界框 + const tempGroup = new fabric.Group([...fabricObjects], { + originX: "left", + originY: "top", + }); + + const bounds = tempGroup.getBoundingRect(true, true); + + // 清理临时组 + tempGroup.destroy(); + + return bounds; }; diff --git a/src/component/Canvas/ExistsImageList/ToolButton.vue b/src/component/Canvas/ExistsImageList/ToolButton.vue new file mode 100644 index 00000000..ff93eb5a --- /dev/null +++ b/src/component/Canvas/ExistsImageList/ToolButton.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/src/component/Canvas/canvasExample.vue b/src/component/Canvas/canvasExample.vue index d3e232fa..2096baf7 100644 --- a/src/component/Canvas/canvasExample.vue +++ b/src/component/Canvas/canvasExample.vue @@ -3,25 +3,14 @@ import { ref } from "vue"; import CanvasEditor from "./CanvasEditor/index.vue"; import RedGreenModeExample from "./RedGreenModeExample.vue"; import ExistsImageList from "@/component/Canvas/ExistsImageList/index.vue"; +import ToolButton from "@/component/Canvas/ExistsImageList/ToolButton.vue"; // 当前显示的组件 const canvasEditor = ref(); -const currentView = ref("canvasEditor"); // 默认显示红绿图示例 +const currentView = ref("canvasEditor"); // 默认显示红绿图示例 canvasEditor redGreenExample const clothingImageUrl = "/src/assets/work/3.PNG"; -// 切换视图 -function switchView(view) { - currentView.value = view; -} - -// 定义编辑器配置 -const editorConfig = { - width: 800, - height: 600, - backgroundColor: "#f8f8f8", -}; - const imageData = [ { name: "风景照片", @@ -62,70 +51,118 @@ const handleImageSelect = (selectedImage) => { // selectedImage 包含:url, name, categoryName, categoryType, originalItem }; -const getJSON = () => { +// 切换视图 +function switchView(view) { + currentView.value = view; +} + +// 定义编辑器配置 +const editorConfig = { + width: 800, + height: 600, + backgroundColor: "#ffffff", // 画布背景色 +}; + +const exportImage = () => { if (canvasEditor.value) { - const json = canvasEditor.value.getJSON(); - console.log("获取的JSON数据:", json); - localStorage.setItem("redGreenModeJSON", json); + canvasEditor.value.exportImage({ + isContainFixed: true, // 是否导出底图 + isContainBg: false, // 是否导出背景 + }); } }; -const loadJSON = () => { - if (canvasEditor.value) { - const currentJSON = localStorage.getItem("redGreenModeJSON"); - canvasEditor.value.loadJSON(currentJSON); - console.log("加载的JSON数据:", currentJSON); +const changeCanvas = (command) => { + console.log(command); +}; + +const loadImageUrlToLayer = async () => { + try { + const imageUrl = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABrQAAAZNCAYAAACENbGaAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3Yt208YaBtCflkuhhXLv+z/e4X6HQiFnfbYmTIwDsRM7srS91ixJY8WW9kjjVh8jXS"; + const layerId = await canvasEditor?.value?.addImageToLayer?.(imageUrl); + console.log("新图层ID:", layerId); + } catch (error) { + console.error("加载图片到图层失败:", error); } }; +// 自定义工具配置相关 +const customToolsList = ref([ + { + id: "exportPNG", + title: "导出PNG", //导出画布图片 + action: exportAsPNG, + icon: { name: "CExport", size: "24" }, + class: "export-btn", + }, + { + id: "saveCanvas", + title: "保存画布", + action: saveCanvas, + icon: { name: "CBottom", size: "24" }, + class: "save-btn", + }, + + { + id: "readCanvas", + title: "读取画布", + action: canvasProject, + icon: { name: "CMiniMap", size: "24" }, + class: "clear-btn", + }, +]); + +// 自定义工具方法 +function exportAsPNG() { + console.log("导出PNG"); + // 实现导出PNG逻辑 + exportImage(); +} + +function saveCanvas() { + console.log("保存项目"); + // 实现保存画布逻辑 + const json = canvasEditor.value.getJSON(); + localStorage.setItem("canvasProject", json); +} + +function canvasProject() { + console.log("读取项目"); + // 实现读取画布逻辑 + const json = localStorage.getItem("canvasProject"); + if (json) { + console.log("读取的项目JSON:", JSON.parse(json)?.layers); + canvasEditor.value.loadJSON(json); + } else { + console.warn("没有找到保存的画布项目"); + } +} + +// 处理自定义工具点击 +const handleCustomToolClick = (tool) => { + tool.action(); +}; + +const changeImageUrl = "/src/assets/work/1.PNG"; + const changeFixedImage = () => { - canvasEditor.value.changeFixedImage(clothingImageUrl); + canvasEditor.value.changeFixedImage(changeImageUrl, { + imageMode: "contains", // 设置底图包含在画布内 + }); };