合并画布代码
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
defineAsyncComponent,
|
||||
shallowRef,
|
||||
provide,
|
||||
defineExpose,
|
||||
} from "vue";
|
||||
import { CanvasManager } from "./managers/CanvasManager";
|
||||
import { LayerManager } from "./managers/LayerManager";
|
||||
@@ -21,14 +22,14 @@ import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
||||
// 导入封装的组件
|
||||
import ToolsSidebar from "./components/ToolsSidebar.vue";
|
||||
import HeaderMenu from "./components/HeaderMenu.vue";
|
||||
import LayersPanel from "./components/LayersPanel.vue";
|
||||
import LayersPanel from "./components/LayersPanel/LayersPanel.vue";
|
||||
import BrushControlPanel from "./components/BrushControlPanel.vue";
|
||||
import TextEditorPanel from "./components/TextEditorPanel.vue"; // 引入文本编辑面板
|
||||
import LiquifyPanel from "./components/LiquifyPanel.vue"; // 引入液化编辑面板
|
||||
import SelectionPanel from "./components/SelectionPanel.vue"; // 引入选区面板
|
||||
import { OperationType } from "./utils/layerHelper.js";
|
||||
import { ToolManager } from "./managers/ToolManager.js";
|
||||
//import { fabric } from "fabric-with-all";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { uploadImageAndCreateLayer } from "./utils/imageHelper.js";
|
||||
// import MinimapPanel from "./components/MinimapPanel.vue";
|
||||
const KeyboardShortcutHelp = defineAsyncComponent(() =>
|
||||
@@ -70,13 +71,17 @@ const currentZoom = ref(100);
|
||||
const canvasWidth = ref(CanvasConfig.width);
|
||||
const canvasHeight = ref(CanvasConfig.height);
|
||||
const canvasColor = ref(CanvasConfig.backgroundColor);
|
||||
const layerWidth = ref(CanvasConfig.layerWidth); // 假设侧边栏宽度为 250px
|
||||
const layerWidth = ref(CanvasConfig.layerWidth);
|
||||
const brushSize = ref(CanvasConfig.brushSize); // 画笔大小
|
||||
const canvasManagerLoaded = ref(false); // 画布是否加载完成
|
||||
|
||||
// 红绿图模式状态
|
||||
const isRedGreenMode = ref(false);
|
||||
|
||||
const isShowLayerPanel = ref(true); // 是否显示图层面板
|
||||
|
||||
provide("isShowLayerPanel", isShowLayerPanel); // 提供红绿图模式状态给子组件
|
||||
|
||||
// 小地图设置
|
||||
// const minimapEnabled = ref(true);
|
||||
// const minimapManager = ref(null);
|
||||
@@ -110,7 +115,9 @@ function toggleShortcutHelp() {
|
||||
function handleToolSelect(tool) {
|
||||
activeTool.value = tool;
|
||||
// toolManager.setActiveTool(tool); // 更新工具管理器中的当前工具 普通模式,不可撤回操作
|
||||
toolManager.setToolWithCommand(tool); // 命令模式 可撤回操作
|
||||
toolManager.setToolWithCommand(tool, {
|
||||
undoable: props.enabledRedGreenMode ? false : true, // 普通模式下工具选择不可撤销
|
||||
}); // 命令模式 可撤回操作
|
||||
}
|
||||
|
||||
function toggleMinimap(enabled) {
|
||||
@@ -127,9 +134,10 @@ onMounted(async () => {
|
||||
canvasHeight.value = canvasContainerRef.value.clientWidth;
|
||||
canvasWidth.value = canvasContainerRef.value.clientHeight;
|
||||
}
|
||||
|
||||
// 创建管理器实例
|
||||
canvasManager = new CanvasManager(canvasRef.value, {
|
||||
width: canvasContainerRef.value.clientWidth - layerWidth.value, // 初始化的时候需要减去侧边栏宽度
|
||||
width: canvasContainerRef.value.clientWidth,
|
||||
height: canvasContainerRef.value.clientHeight,
|
||||
// backgroundColor: canvasColor.value,
|
||||
currentZoom,
|
||||
@@ -139,7 +147,6 @@ onMounted(async () => {
|
||||
canvasColor,
|
||||
enabledRedGreenMode: props.enabledRedGreenMode,
|
||||
});
|
||||
console.log(canvasManager,canvasManager.thumbnailManager)
|
||||
canvasManager.canvas.activeLayerId = activeLayerId;
|
||||
canvasManager.canvas.activeElementId = activeElementId;
|
||||
|
||||
@@ -155,6 +162,7 @@ onMounted(async () => {
|
||||
canvasWidth: canvasWidth.value,
|
||||
canvasHeight: canvasHeight.value,
|
||||
backgroundColor: canvasColor.value,
|
||||
isRedGreenMode: props.enabledRedGreenMode,
|
||||
layers,
|
||||
activeLayerId,
|
||||
canvasManager, // 添加对 canvasManager 的引用
|
||||
@@ -202,6 +210,7 @@ onMounted(async () => {
|
||||
layerManager.setToolManager(toolManager); // 将工具管理器传递给图层管理器
|
||||
canvasManager.setToolManager(toolManager); // 将工具管理器传递给画布管理器
|
||||
canvasManager.setLayerManager(layerManager);
|
||||
canvasManager.setCommandManager(commandManager); // 将命令管理器传递给画布管理器
|
||||
|
||||
// 初始化快捷键管理器
|
||||
keyboardManager = new KeyboardManager({
|
||||
@@ -369,13 +378,18 @@ function updateCanvasSize() {
|
||||
const containerWidth = canvasContainerRef.value.clientWidth;
|
||||
const containerHeight = canvasContainerRef.value.clientHeight;
|
||||
|
||||
// 如果启用了红绿图模式,使用 layerManager 的缩放方法
|
||||
if (props.enabledRedGreenMode && layerManager) {
|
||||
layerManager.resizeCanvasWithScale(containerWidth, containerHeight);
|
||||
} else {
|
||||
// 普通模式下,更新画布大小,这会同时重置视图和居中所有元素
|
||||
canvasManager.setCanvasSize(containerWidth, containerHeight);
|
||||
}
|
||||
// 普通模式下,更新画布大小,这会同时重置视图和居中所有元素
|
||||
canvasManager.setCanvasSize(containerWidth, containerHeight);
|
||||
|
||||
// // 如果启用了红绿图模式,使用 layerManager 的缩放方法
|
||||
// if (props.enabledRedGreenMode && layerManager) {
|
||||
// layerManager.resizeCanvasWithScale(containerWidth, containerHeight, {
|
||||
// undoable: false, // 可撤销操作
|
||||
// });
|
||||
// } else {
|
||||
// // 普通模式下,更新画布大小,这会同时重置视图和居中所有元素
|
||||
// canvasManager.setCanvasSize(containerWidth, containerHeight);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,15 +402,17 @@ function addLayer() {
|
||||
}
|
||||
|
||||
function setActiveLayer(layerId) {
|
||||
if (activeElementId.value && canvasManager && canvasManager.canvas) {
|
||||
if (layerId !== activeLayerId.value) {
|
||||
layerManager.setActiveLayer(layerId, {
|
||||
undoable: true, // 可撤销
|
||||
});
|
||||
|
||||
const activeObject = canvasManager.canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
canvasManager.canvas.discardActiveObject();
|
||||
canvasManager.canvas.renderAll();
|
||||
}
|
||||
activeElementId.value = null;
|
||||
}
|
||||
layerManager.setActiveLayer(layerId);
|
||||
}
|
||||
|
||||
function toggleLayerVisibility(layerId) {
|
||||
@@ -570,9 +586,9 @@ defineExpose({
|
||||
getCanvasManager: () => canvasManager, // 获取画布管理器实例
|
||||
canvasManagerLoaded,
|
||||
// 加载新数据到画布
|
||||
loadJSON: (json) => {
|
||||
loadJSON: (json, calllBack) => {
|
||||
try {
|
||||
if (json) canvasManager?.loadJSON?.(json);
|
||||
if (json) canvasManager?.loadJSON?.(json, calllBack);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("加载画布JSON失败:", error);
|
||||
@@ -611,42 +627,133 @@ defineExpose({
|
||||
expPicType,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 移动图层位置
|
||||
* @param {string} layerId 图层ID
|
||||
* @param {string} direction 移动方向,'up'或'down'
|
||||
* @returns {boolean} 是否移动成功
|
||||
*/
|
||||
moveLayer(layerId, direction) {
|
||||
if (!layerManager) return false;
|
||||
const result = layerManager.moveLayer(layerId, direction);
|
||||
|
||||
// 使用高级排序重建画布顺序
|
||||
if (result) {
|
||||
layerManager.forceRebuildCanvasOrder();
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* 拖拽排序图层
|
||||
* @param {number} oldIndex 原索引
|
||||
* @param {number} newIndex 新索引
|
||||
* @param {string} layerId 图层ID
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
reorderLayers(oldIndex, newIndex, layerId) {
|
||||
if (!layerManager) return false;
|
||||
|
||||
// 优先使用高级排序功能
|
||||
if (layerManager.layerSort) {
|
||||
return layerManager.advancedReorderLayers(oldIndex, newIndex, layerId);
|
||||
} else {
|
||||
// 降级到基础排序
|
||||
return layerManager.reorderLayers(oldIndex, newIndex, layerId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 智能排序图层
|
||||
* 根据对象类型和位置自动调整图层顺序
|
||||
* @param {Array<string>} targetLayerIds 要排序的图层ID数组,null表示排序所有普通图层
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
smartSortLayers(targetLayerIds = null) {
|
||||
if (!layerManager) return false;
|
||||
return layerManager.smartSortLayers(targetLayerIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* 优化图层结构
|
||||
* 清理空图层、重新排序等
|
||||
* @returns {Object} 优化结果统计
|
||||
*/
|
||||
optimizeLayerStructure() {
|
||||
if (!layerManager)
|
||||
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
|
||||
return layerManager.optimizeLayerStructure();
|
||||
},
|
||||
|
||||
/**
|
||||
* 强制重建画布对象顺序
|
||||
* 当图层顺序发生变化后调用此方法确保画布对象顺序正确
|
||||
*/
|
||||
forceRebuildCanvasOrder() {
|
||||
if (!layerManager) return;
|
||||
layerManager.forceRebuildCanvasOrder();
|
||||
},
|
||||
|
||||
/**
|
||||
* 验证画布对象顺序是否正确
|
||||
* @returns {boolean} 顺序是否正确
|
||||
*/
|
||||
validateObjectOrder() {
|
||||
if (!layerManager) return true;
|
||||
return layerManager.validateObjectOrder();
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量重新排序多个图层
|
||||
* @param {Array} reorderOperations 排序操作数组 [{layerId, oldIndex, newIndex}]
|
||||
* @returns {boolean} 是否全部操作成功
|
||||
*/
|
||||
batchReorderLayers(reorderOperations) {
|
||||
if (!layerManager) return false;
|
||||
return layerManager.batchReorderLayers(reorderOperations);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 头部菜单组件 -->
|
||||
<HeaderMenu
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeTool="activeTool"
|
||||
:canvasWidth="canvasWidth"
|
||||
:canvasHeight="canvasHeight"
|
||||
:canvasColor="canvasColor"
|
||||
:brushSize="brushSize"
|
||||
@update:canvasWidth="canvasWidth = $event"
|
||||
@update:canvasHeight="canvasHeight = $event"
|
||||
@update:canvasColor="canvasColor = $event"
|
||||
@update:brushSize="brushSize = $event"
|
||||
@canvas-size-change="updateCanvasSize"
|
||||
@canvas-color-change="updateCanvasColor"
|
||||
/>
|
||||
<div class="header-menu">
|
||||
<HeaderMenu
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeTool="activeTool"
|
||||
:canvasWidth="canvasWidth"
|
||||
:canvasHeight="canvasHeight"
|
||||
:canvasColor="canvasColor"
|
||||
:brushSize="brushSize"
|
||||
:enabledRedGreenMode="enabledRedGreenMode"
|
||||
@update:canvasWidth="canvasWidth = $event"
|
||||
@update:canvasHeight="canvasHeight = $event"
|
||||
@update:canvasColor="canvasColor = $event"
|
||||
@update:brushSize="brushSize = $event"
|
||||
@canvas-size-change="updateCanvasSize"
|
||||
@canvas-color-change="updateCanvasColor"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- :minimapEnabled="minimapEnabled" -->
|
||||
<!-- 工具栏组件 -->
|
||||
<ToolsSidebar
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeTool="activeTool"
|
||||
:isRedGreenMode="isRedGreenMode"
|
||||
@tool-selected="handleToolSelect"
|
||||
@red-green-tool-selected="handleRedGreenToolSelect"
|
||||
@toggle-red-green-mode="toggleRedGreenMode"
|
||||
@trigger-image-upload="triggerImageUpload"
|
||||
@add-text="handleAddText"
|
||||
@zoom-in="zoomIn"
|
||||
@zoom-out="zoomOut"
|
||||
/>
|
||||
<div style="min-width: 58px">
|
||||
<ToolsSidebar
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeTool="activeTool"
|
||||
:isRedGreenMode="isRedGreenMode"
|
||||
@tool-selected="handleToolSelect"
|
||||
@red-green-tool-selected="handleRedGreenToolSelect"
|
||||
@toggle-red-green-mode="toggleRedGreenMode"
|
||||
@trigger-image-upload="triggerImageUpload"
|
||||
@add-text="handleAddText"
|
||||
@zoom-in="zoomIn"
|
||||
@zoom-out="zoomOut"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="canvas-container"
|
||||
@@ -665,14 +772,14 @@ defineExpose({
|
||||
|
||||
<!-- 文本编辑面板 -->
|
||||
<TextEditorPanel
|
||||
v-if="canvasManagerLoaded"
|
||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||
:canvas="canvasManager?.canvas"
|
||||
:commandManager="commandManager"
|
||||
/>
|
||||
|
||||
<!-- 液化编辑面板 -->
|
||||
<LiquifyPanel
|
||||
v-if="canvasManagerLoaded"
|
||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||
:canvas="canvasManager?.canvas"
|
||||
:commandManager="commandManager"
|
||||
:liquifyManager="liquifyManager"
|
||||
@@ -682,7 +789,7 @@ defineExpose({
|
||||
|
||||
<!-- 选区面板 -->
|
||||
<SelectionPanel
|
||||
v-if="canvasManagerLoaded"
|
||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||
:canvas="canvasManager?.canvas"
|
||||
:commandManager="commandManager"
|
||||
:selectionManager="selectionManager"
|
||||
@@ -705,22 +812,29 @@ defineExpose({
|
||||
</div>
|
||||
|
||||
<!-- 图层面板组件 -->
|
||||
<LayersPanel
|
||||
class="layers-panel"
|
||||
:style="{ width: layerWidth + 'px' }"
|
||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||
:activeLayerId="activeLayerId"
|
||||
:activeElementId="activeElementId"
|
||||
:thumbnailManager="canvasManager?.thumbnailManager"
|
||||
@add-layer="addLayer"
|
||||
@set-active-layer="setActiveLayer"
|
||||
@toggle-layer-visibility="toggleLayerVisibility"
|
||||
@move-layer-up="moveLayerUp"
|
||||
@move-layer-down="moveLayerDown"
|
||||
@remove-layer="removeLayer"
|
||||
@layers-reorder="handleLayersReorder"
|
||||
@child-layers-reorder="handleChildLayersReorder"
|
||||
/>
|
||||
<!-- v-if="canvasManagerLoaded && !enabledRedGreenMode" -->
|
||||
|
||||
<transition name="fade">
|
||||
<div
|
||||
class="layers-panel"
|
||||
v-if="isShowLayerPanel && !enabledRedGreenMode"
|
||||
>
|
||||
<LayersPanel
|
||||
v-if="canvasManagerLoaded"
|
||||
:activeLayerId="activeLayerId"
|
||||
:activeElementId="activeElementId"
|
||||
:thumbnailManager="canvasManager.thumbnailManager"
|
||||
@add-layer="addLayer"
|
||||
@set-active-layer="setActiveLayer"
|
||||
@toggle-layer-visibility="toggleLayerVisibility"
|
||||
@move-layer-up="moveLayerUp"
|
||||
@move-layer-down="moveLayerDown"
|
||||
@remove-layer="removeLayer"
|
||||
@layers-reorder="handleLayersReorder"
|
||||
@child-layers-reorder="handleChildLayersReorder"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="footer-actions">
|
||||
@@ -769,6 +883,15 @@ defineExpose({
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 77;
|
||||
& > .header-menu {
|
||||
height: 52px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@@ -972,10 +1095,33 @@ button:hover {
|
||||
}
|
||||
|
||||
.layers-panel {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 10px;
|
||||
transition: width 0.3s ease;
|
||||
background: #fff;
|
||||
width: 250px;
|
||||
flex: none;
|
||||
width: 350px;
|
||||
max-height: 85vh;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
|
||||
backdrop-filter: blur(2px); /* 添加模糊效果 */
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
background-color: rgba(255, 255, 255, 0.95); /* 改为白色背景 */
|
||||
z-index: 1000; /* 确保面板在最上层 */
|
||||
border: 1px solid #e0e0e0;
|
||||
/* 添加指向整个面板的倒三角 */
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
right: 6px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid rgba(255, 255, 255, 0.95); /* 与面板背景色一致 */
|
||||
filter: drop-shadow(0 -1px 1px rgba(0, 0, 0, 0.05));
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
/* 添加触控设备的样式调整 */
|
||||
@media (pointer: coarse) {
|
||||
@@ -1016,4 +1162,15 @@ button:hover {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
// 淡入淡出动画
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user