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 @@
+
+
+
+
+
+
{{ t(tool.title) }}
+
+
+
+
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", // 设置底图包含在画布内
+ });
};
-
-
-
-
- 红绿图模式示例
-
-
- 普通画布编辑器
-
-
-
+ >
+
{
:clothing-image-opts="{
imageMode: 'contains', // 设置底图包含在画布内
}"
+ @change-canvas="changeCanvas"
+ isFixedErasable
+ showFixedLayer
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -216,6 +290,65 @@ body {
height: 100vh;
}
+/* 自定义工具按钮样式 */
+.tool-separator {
+ width: 100%;
+ height: 1px;
+ background-color: #e0e0e0;
+ margin: 8px 0;
+}
+
+.custom-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;
+}
+
+.custom-tool-btn:hover {
+ background-color: #f0f0f0;
+}
+
+.custom-tool-btn:hover .tool-tooltip {
+ display: block;
+}
+
+.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;
+}
+
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.app-header {