合并画布代码

This commit is contained in:
X1627315083
2025-06-18 11:05:23 +08:00
parent 903c0ebdf5
commit 9c7fae36eb
118 changed files with 23633 additions and 8201 deletions

View File

@@ -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>