2025-06-22 15:20:08 +08:00
|
|
|
|
<script setup>
|
2026-01-16 10:29:03 +08:00
|
|
|
|
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"); // 默认显示红绿图示例 canvasEditor redGreenExample
|
2026-01-16 15:16:33 +08:00
|
|
|
|
const clothingMinIOPath =
|
|
|
|
|
|
"aida-users/24299/sketchboard/female/Outwear/32d2485c-90fa-43d0-bc31-e59aaea466ba.png";
|
2026-01-16 10:29:03 +08:00
|
|
|
|
const clothingImageUrl = "/src/assets/images/canvas/xiangao.png";
|
|
|
|
|
|
const clothingImageUrlInit = "/src/assets/images/canvas/xiangaofenge.png";
|
|
|
|
|
|
|
|
|
|
|
|
const imageData = [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "风景照片",
|
|
|
|
|
|
type: "风景",
|
|
|
|
|
|
imgList: [
|
|
|
|
|
|
{
|
|
|
|
|
|
url: "https://www.minio-api.aida.com.hk/aida-users/83/printboard/7a0cccfc-1e3c-4c38-af65-d6179f21a267.png?response-content-type=image%2Fpng&response-content-disposition=inline&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20250723%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250723T025046Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=3612d6c168207bebdae2fa7c7c2c86ea0f48358b99b8b41dec9e2ff15e197e15",
|
|
|
|
|
|
name: "山景",
|
|
|
|
|
|
},
|
|
|
|
|
|
{ url: "/src/assets/work/2.PNG", name: "海景" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "人物照片",
|
|
|
|
|
|
type: "人物",
|
|
|
|
|
|
imgList: [
|
|
|
|
|
|
{ url: "/src/assets/work/3.PNG", name: "肖像1" },
|
|
|
|
|
|
{ url: "/src/assets/work/5.PNG", name: "肖像2" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "衣服照片",
|
|
|
|
|
|
type: "衣服",
|
|
|
|
|
|
imgList: [
|
|
|
|
|
|
{ url: "/src/assets/work/3.PNG", name: "肖像1" },
|
|
|
|
|
|
{ url: "/src/assets/work/5.PNG", name: "肖像2" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "裤子照片",
|
|
|
|
|
|
type: "裤子",
|
|
|
|
|
|
imgList: [
|
|
|
|
|
|
{ url: "/src/assets/work/IMG_0001.PNG", name: "肖像1" },
|
|
|
|
|
|
{ url: "/src/assets/work/IMG_0008.PNG", name: "肖像2" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const handleImageSelect = (selectedImage) => {
|
|
|
|
|
|
console.log("选中的图片:", selectedImage);
|
|
|
|
|
|
console.log(selectedImage);
|
|
|
|
|
|
// selectedImage 包含:url, name, categoryName, categoryType, originalItem
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 切换视图
|
|
|
|
|
|
function switchView(view) {
|
|
|
|
|
|
currentView.value = view;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 定义编辑器配置
|
|
|
|
|
|
const editorConfig = {
|
|
|
|
|
|
width: 800,
|
|
|
|
|
|
height: 600,
|
|
|
|
|
|
backgroundColor: "#ffffff", // 画布背景色
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const exportImage = async () => {
|
|
|
|
|
|
if (canvasEditor.value) {
|
|
|
|
|
|
const base64 = await canvasEditor.value.exportImage({
|
|
|
|
|
|
isContainFixed: true, // 是否导出底图
|
|
|
|
|
|
isContainFixedOther: true, // 是否导出其他固定图层
|
|
|
|
|
|
isContainBg: false, // 是否导出背景
|
|
|
|
|
|
isEnhanceImg: false, // 是否导出增强图片
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟下载图片
|
|
|
|
|
|
const link = document.createElement("a");
|
|
|
|
|
|
link.href = base64;
|
|
|
|
|
|
link.download = "canvas_image.png"; // 设置下载文件名
|
|
|
|
|
|
document.body.appendChild(link);
|
|
|
|
|
|
link.click(); // 触发下载
|
|
|
|
|
|
document.body.removeChild(link); // 下载后移除链接元素
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// 导出颜色图层
|
|
|
|
|
|
const exportColorLayer = async () => {
|
|
|
|
|
|
if (canvasEditor.value) {
|
|
|
|
|
|
const colorLayer = await canvasEditor.value.exportColorLayer();
|
|
|
|
|
|
console.log("导出颜色图层:", colorLayer);
|
|
|
|
|
|
// 模拟下载图片
|
|
|
|
|
|
const link = document.createElement("a");
|
|
|
|
|
|
link.href = colorLayer.base64;
|
|
|
|
|
|
link.download = "canvas_image.png"; // 设置下载文件名
|
|
|
|
|
|
document.body.appendChild(link);
|
|
|
|
|
|
link.click(); // 触发下载
|
|
|
|
|
|
document.body.removeChild(link); // 下载后移除链接元素
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 导出所有信息
|
|
|
|
|
|
const exportExtraInfo = async () => {
|
|
|
|
|
|
if (canvasEditor.value) {
|
|
|
|
|
|
const extraInfo = await canvasEditor.value.exportExtraInfo();
|
|
|
|
|
|
console.log("==========导出信息:", extraInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新其他图层颜色
|
|
|
|
|
|
const updateOtherLayersColor = async () => {
|
|
|
|
|
|
const obj = {
|
|
|
|
|
|
color: { rgba: { r: 255, g: 255, b: 0, a: 1 } },
|
|
|
|
|
|
};
|
|
|
|
|
|
await canvasEditor?.value?.updateOtherLayers?.(obj);
|
|
|
|
|
|
};
|
|
|
|
|
|
// 更新其他图层印花
|
|
|
|
|
|
const updateOtherLayersPrint = async () => {
|
|
|
|
|
|
// document.querySelector(".app-container").style.width = "50vw"
|
|
|
|
|
|
const obj = {
|
|
|
|
|
|
printObject: {
|
|
|
|
|
|
prints: [
|
|
|
|
|
|
{
|
|
|
|
|
|
ifSingle: true,
|
|
|
|
|
|
level2Type: "Pattern",
|
|
|
|
|
|
designType: "Library",
|
|
|
|
|
|
path: "/src/assets/images/canvas/yinhua1.jpg",
|
|
|
|
|
|
location: [250, 780],
|
|
|
|
|
|
scale: [0.3, 0.4],
|
|
|
|
|
|
angle: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
await canvasEditor?.value?.updateOtherLayers?.(obj);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const changeCanvas = (command) => {
|
|
|
|
|
|
console.log(command);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const changeImageUrl = "/src/assets/work/1.PNG";
|
|
|
|
|
|
const loadImageUrlToLayer = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const layerToLayer1 = canvasEditor?.value?.layers?.[0]?.id;
|
|
|
|
|
|
const layerId = await canvasEditor?.value?.addImageToLayer?.(
|
|
|
|
|
|
changeImageUrl,
|
|
|
|
|
|
{
|
|
|
|
|
|
layerId: layerToLayer1, // 指定添加到的图层ID
|
|
|
|
|
|
imageMode: "contains", // 设置图片包含在画布内
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
console.log("新图层ID:", layerId);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("加载图片到图层失败:", error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 自定义工具方法
|
|
|
|
|
|
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 exportJSON = () => {
|
|
|
|
|
|
console.log("导出JSON");
|
|
|
|
|
|
// 实现导出JSON逻辑
|
|
|
|
|
|
const json = canvasEditor.value.getJSON();
|
|
|
|
|
|
// 导出JSON文件
|
|
|
|
|
|
const blob = new Blob([json], { type: "application/json" });
|
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
a.download = "canvas_project.json";
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
};
|
|
|
|
|
|
// 复制JSON
|
|
|
|
|
|
const copyJSON = () => {
|
|
|
|
|
|
console.log("复制JSON");
|
|
|
|
|
|
// 实现复制JSON逻辑
|
|
|
|
|
|
const json = canvasEditor.value.getJSON();
|
|
|
|
|
|
// 复制JSON到剪贴板
|
|
|
|
|
|
navigator.clipboard.writeText(json);
|
|
|
|
|
|
};
|
|
|
|
|
|
const getLayers = () => {
|
|
|
|
|
|
console.log("==========layers", canvasEditor.value?.layers);
|
|
|
|
|
|
};
|
|
|
|
|
|
// 处理自定义工具点击
|
|
|
|
|
|
const handleCustomToolClick = (tool) => {
|
|
|
|
|
|
tool.action();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const changeFixedImage = () => {
|
|
|
|
|
|
canvasEditor.value.changeFixedImage(changeImageUrl, {
|
|
|
|
|
|
imageMode: "contains", // 设置底图包含在画布内
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const canvasInit = () => {
|
|
|
|
|
|
console.log("画布初始化完成");
|
|
|
|
|
|
// 可以在这里执行一些初始化逻辑
|
|
|
|
|
|
// canvasEditor.value.changeFixedImage(clothingImageUrlInit, {
|
|
|
|
|
|
// imageMode: "contains", // 设置底图包含在画布内
|
|
|
|
|
|
// });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const frontBackChange = (value) => {};
|
|
|
|
|
|
|
|
|
|
|
|
// 自定义工具配置相关
|
|
|
|
|
|
const customToolsList = ref([
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "exportColorLayer",
|
|
|
|
|
|
title: "导出颜色图层",
|
|
|
|
|
|
action: exportColorLayer,
|
|
|
|
|
|
label: "导颜",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "exportExtraInfo",
|
|
|
|
|
|
title: "导出印花颜色等信息",
|
|
|
|
|
|
action: exportExtraInfo,
|
|
|
|
|
|
label: "导E",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "updateExtraInfo_color",
|
|
|
|
|
|
title: "更新颜色",
|
|
|
|
|
|
action: updateOtherLayersColor,
|
|
|
|
|
|
label: "更C",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "updateExtraInfo_print",
|
|
|
|
|
|
title: "更新印花",
|
|
|
|
|
|
action: updateOtherLayersPrint,
|
|
|
|
|
|
label: "更P",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
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",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "loadImageUrlToLayer",
|
|
|
|
|
|
title: "添加画布图片",
|
|
|
|
|
|
action: loadImageUrlToLayer,
|
|
|
|
|
|
label: "🎨",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "redGreenExample",
|
|
|
|
|
|
title: "红绿图模式",
|
|
|
|
|
|
action: () => switchView("redGreenExample"),
|
|
|
|
|
|
label: "红",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "canvasEditor",
|
|
|
|
|
|
title: "普通模式",
|
|
|
|
|
|
action: () => switchView("canvasEditor"),
|
|
|
|
|
|
label: "普",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "changeFixedImage",
|
|
|
|
|
|
title: "更换底图",
|
|
|
|
|
|
action: changeFixedImage,
|
|
|
|
|
|
label: "更",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "exportJSON",
|
|
|
|
|
|
title: "导出JSON",
|
|
|
|
|
|
action: exportJSON,
|
|
|
|
|
|
label: "导J",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "copyJSON",
|
|
|
|
|
|
title: "复制JSON",
|
|
|
|
|
|
action: copyJSON,
|
|
|
|
|
|
label: "复J",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "getLayers",
|
|
|
|
|
|
title: "查询图层",
|
|
|
|
|
|
action: getLayers,
|
|
|
|
|
|
label: "查L",
|
|
|
|
|
|
class: "export-btn",
|
|
|
|
|
|
},
|
|
|
|
|
|
]);
|
2026-01-19 16:57:11 +08:00
|
|
|
|
const canvasLoadJsonSuccess = () => {
|
|
|
|
|
|
console.log("画布加载JSON成功");
|
|
|
|
|
|
canvasEditor.value?.updateOtherLayers({
|
|
|
|
|
|
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: [1.2, 1.6],
|
|
|
|
|
|
angle: 0,
|
|
|
|
|
|
object: {
|
|
|
|
|
|
top: 600,
|
|
|
|
|
|
left: 800,
|
|
|
|
|
|
scaleX: 0.5,
|
|
|
|
|
|
scaleY: 0.5,
|
|
|
|
|
|
opacity: 1,
|
|
|
|
|
|
angle: 45,
|
|
|
|
|
|
flipX: false,
|
|
|
|
|
|
flipY: false,
|
|
|
|
|
|
blendMode: "multiply",
|
|
|
|
|
|
gapX: 0,
|
|
|
|
|
|
gapY: 0,
|
|
|
|
|
|
},
|
2026-01-16 10:29:03 +08:00
|
|
|
|
},
|
2026-01-19 16:57:11 +08:00
|
|
|
|
// {
|
|
|
|
|
|
// ifSingle: true,
|
|
|
|
|
|
// level2Type: "Pattern",
|
|
|
|
|
|
// designType: "Library",
|
|
|
|
|
|
// path: "/src/assets/images/canvas/yinhua1.jpg",
|
|
|
|
|
|
// location: [550, 650],
|
|
|
|
|
|
// scale: [0.15, 0.2],
|
|
|
|
|
|
// angle: 0,
|
|
|
|
|
|
// },
|
|
|
|
|
|
// {
|
|
|
|
|
|
// ifSingle: true,
|
|
|
|
|
|
// level2Type: "Pattern",
|
|
|
|
|
|
// designType: "Library",
|
|
|
|
|
|
// path: "/src/assets/images/canvas/yinhua1.jpg",
|
|
|
|
|
|
// location: [700, 400],
|
|
|
|
|
|
// scale: [0.1, 0.133],
|
|
|
|
|
|
// angle: 0,
|
|
|
|
|
|
// },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
2026-01-16 10:29:03 +08:00
|
|
|
|
};
|
2025-06-22 15:20:08 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-01-16 10:29:03 +08:00
|
|
|
|
<div class="app-container-example">
|
|
|
|
|
|
<!-- 模拟左侧导航栏 -->
|
|
|
|
|
|
<!-- <div
|
2025-07-23 21:50:19 +08:00
|
|
|
|
class="app-wrapper-btns"
|
|
|
|
|
|
:class="{ hide: !isShowLeft }"
|
|
|
|
|
|
@click="isShowLeft = !isShowLeft"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="app-btn">收起/展开</div>
|
2025-08-24 17:11:04 +08:00
|
|
|
|
</div> -->
|
2026-01-16 10:29:03 +08:00
|
|
|
|
<!-- 内容区域 -->
|
|
|
|
|
|
<div class="app-content">
|
|
|
|
|
|
<!-- 红绿图模式示例 -->
|
|
|
|
|
|
<RedGreenModeExample
|
|
|
|
|
|
v-if="currentView === 'redGreenExample'"
|
|
|
|
|
|
key="redGreenExample"
|
|
|
|
|
|
@trigger-red-green-mouseup="frontBackChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
</RedGreenModeExample>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 普通画布编辑器 -->
|
|
|
|
|
|
<CanvasEditor
|
|
|
|
|
|
ref="canvasEditor"
|
|
|
|
|
|
key="canvasEditor"
|
|
|
|
|
|
v-if="currentView === 'canvasEditor'"
|
2026-01-16 15:16:33 +08:00
|
|
|
|
:clothingMinIOPath="clothingMinIOPath"
|
2026-01-16 10:29:03 +08:00
|
|
|
|
:clothingImageUrl="clothingImageUrl"
|
|
|
|
|
|
:clothingImageUrl2="clothingImageUrlInit"
|
|
|
|
|
|
:config="editorConfig"
|
|
|
|
|
|
:clothing-image-opts="{
|
|
|
|
|
|
imageMode: 'contains', // 设置底图包含在画布内
|
|
|
|
|
|
}"
|
|
|
|
|
|
@change-canvas="changeCanvas"
|
|
|
|
|
|
@canvas-init="canvasInit"
|
|
|
|
|
|
showFixedLayer
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #existsImageList>
|
|
|
|
|
|
<ExistsImageList
|
|
|
|
|
|
:list="imageData"
|
|
|
|
|
|
@select="handleImageSelect"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 使用插槽添加自定义工具栏按钮 -->
|
|
|
|
|
|
<template #customToolsBottom="{ toolButtonProps }">
|
|
|
|
|
|
<!-- 分隔线 -->
|
|
|
|
|
|
<div class="tool-separator"></div>
|
|
|
|
|
|
<!-- 自定义工具按钮 -->
|
|
|
|
|
|
<ToolButton
|
|
|
|
|
|
v-for="tool in customToolsList"
|
|
|
|
|
|
:key="tool.id"
|
|
|
|
|
|
:tool="tool"
|
|
|
|
|
|
:active-tool="toolButtonProps.activeTool"
|
|
|
|
|
|
@click="handleCustomToolClick"
|
|
|
|
|
|
tipBody
|
|
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</CanvasEditor>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-06-22 15:20:08 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
2026-01-16 10:29:03 +08:00
|
|
|
|
* {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.canvas-wrapper-btns {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
html,
|
|
|
|
|
|
body {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-container {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
// height: 100vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-container-example {
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-wrapper-btns {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 200px;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
&.app-btn {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
&.hide {
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.view-switcher {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.switch-btn {
|
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #007bff;
|
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
border-color: #007bff;
|
|
|
|
|
|
background: #007bff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-header {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
right: 10px;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.95);
|
|
|
|
|
|
padding: 8px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.app-content {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 自定义工具按钮样式 */
|
|
|
|
|
|
.tool-separator {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 1px;
|
|
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
|
|
margin: 8px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.custom-tool-btn {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 3.5rem;
|
|
|
|
|
|
height: 3.5rem;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font-size: 1.6rem;
|
|
|
|
|
|
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;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
writing-mode: vertical-rl; /* 竖直排列 */
|
|
|
|
|
|
text-orientation: upright; /* 保持文字正常显示 */ // left: 100%;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
top: -0.8rem;
|
|
|
|
|
|
transform: translate(-50%, -100%);
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.9);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
padding: 0.8rem 0.4rem;
|
|
|
|
|
|
border-radius: 0.4rem;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
font-size: 1.2rem;
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tool-tooltip:before {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
transform: translate(-50%, 100%);
|
|
|
|
|
|
border-width: 0.5rem;
|
|
|
|
|
|
border-style: solid;
|
|
|
|
|
|
border-color: rgba(0, 0, 0, 0.9) transparent transparent transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 深色模式适配 */
|
|
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
|
|
|
|
.app-header {
|
|
|
|
|
|
background: rgba(45, 45, 45, 0.95);
|
|
|
|
|
|
|
|
|
|
|
|
.view-switcher .switch-btn {
|
|
|
|
|
|
background: #3d3d3d;
|
|
|
|
|
|
border-color: #555;
|
|
|
|
|
|
color: #e0e0e0;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #007bff;
|
|
|
|
|
|
background: #444;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
background: #007bff;
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-22 15:20:08 +08:00
|
|
|
|
</style>
|