Files
aida_front/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue

1671 lines
48 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { computed, ref, nextTick, inject, watch } from "vue";
import { findLayerRecursively, isGroupLayer } from "../../utils/layerHelper";
import ContextMenu from "./ContextMenu.vue";
import LayerItem from "./LayerItem.vue";
import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
import { createCrossLevelMoveCommand } from "../../commands/CrossLevelMoveCommands";
// // 导入命令类
// import {
// ReorderLayersCommand,
// // ReorderChildLayersCommand,
// GroupLayersCommand,
// UngroupLayersCommand,
// } from "../../commands/LayerCommands";
import { findObjectById, generateId } from "../../utils/helper";
import { throttle } from "lodash-es";
const props = defineProps({
// layers: Array,
activeLayerId: String,
activeElementId: String,
thumbnailManager: Object, // 添加缩略图管理器属性
showFixedLayer: {
type: Boolean,
default: false, // 默认不显示固定层
},
});
const emit = defineEmits([
"add-layer",
"add-top-layer",
"add-group-layer",
"set-active-layer",
"toggle-layer-visibility",
"move-layer-up",
"move-layer-down",
"remove-layer",
"reorder-layers", // 新增:图层重新排序事件
"reorder-child-layers", // 新增:子图层重新排序事件
]);
const layers = inject("layers", []);
const lastSelectLayerId = inject("lastSelectLayerId", null);
const layerManager = inject("layerManager", {});
// 注入命令管理器
const commandManager = inject("commandManager", null);
// 编辑状态管理
const editingLayerId = ref(null);
const editingLayerName = ref("");
// 多选状态管理
const selectedLayerIds = ref([]);
const isMultiSelectMode = ref(false);
const lastSelectedIndex = ref(-1);
// 组图层展开/收起状态管理
const expandedGroupIds = ref(new Set());
// 长按检测(移动端)
const touchTimer = ref(null);
const touchStartPos = ref({ x: 0, y: 0 });
const LONG_PRESS_DELAY = 500; // 长按触发时间(毫秒)
const TOUCH_MOVE_THRESHOLD = 10; // 触摸移动阈值(像素)
// 右键菜单状态管理
const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ x: 0, y: 0 });
const contextMenuLayer = ref(null);
const contextMenuItems = ref([]);
// 颜色填充相关
const currLayerId = ref(null); // 当前图层ID
const fillColor = ref("#ffffff"); // 默认填充颜色
const fillColorRef = ref(null);
// 计算属性:可排序的根级图层(排除背景层和固定层)
const sortableRootLayers = computed(() => {
if (!layers) return [];
return layers.value.filter((layer) => !layer.parentId && !layer.isFixed && !layer.isBackground);
});
// 计算属性:不可排序的固定图层(背景层和固定层)
const fixedLayers = computed(() => {
if (!layers) return [];
return layers.value.filter((layer) => {
if (props.showFixedLayer) return !layer.parentId && (layer.isFixed || layer.isBackground);
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
});
});
// 计算属性:获取当前选中的图层
const selectedLayers = computed(() => {
return sortableRootLayers.value.filter((layer) => selectedLayerIds.value.includes(layer.id));
});
// 计算属性:获取当前是否激活子图层
const isChildLayerActive = computed(() => {
const { parent } = findLayerRecursively(layers.value, props.activeLayerId);
if (parent) return true; // 如果有父图层,则表示是子图层
return false; // 否则是根图层
});
// 计算属性:检查是否有选中的图层可以分组
const canGroupLayers = computed(() => {
// 直接基于 selectedLayerIds 和 sortableRootLayers 计算,确保响应式
if (selectedLayerIds.value.length < 2) return false;
const selectedLayers = sortableRootLayers.value.filter((layer) =>
selectedLayerIds.value.includes(layer.id)
);
return selectedLayers.every(
(layer) => !layer.isBackground && !layer.isFixed && !isGroupLayerType(layer)
);
});
// 计算属性:检查是否有选中的组图层可以解组
const canUngroupLayers = computed(() => {
if (selectedLayerIds.value.length !== 1) return false;
const selectedLayers = sortableRootLayers.value.filter((layer) =>
selectedLayerIds.value.includes(layer.id)
);
return selectedLayers.length === 1 && isGroupLayerType(selectedLayers[0]);
});
// 计算属性:可以复制的图层
const canCopyLayers = computed(() => {
if (selectedLayerIds.value.length === 0) return false;
const selectedLayers = sortableRootLayers.value.filter((layer) =>
selectedLayerIds.value.includes(layer.id)
);
return selectedLayers.every((layer) => !layer.isBackground && !layer.isFixed);
});
// 计算属性:可以锁定/解锁的图层
const canToggleLock = computed(() => {
if (selectedLayerIds.value.length === 0) return false;
const selectedLayers = sortableRootLayers.value.filter((layer) =>
selectedLayerIds.value.includes(layer.id)
);
return selectedLayers.every((layer) => !layer.isBackground && !layer.isFixed);
});
function addLayer() {
emit("add-layer");
}
function addTopLayer() {
emit("add-top-layer");
}
function addGroupLayer() {
emit("add-group-layer");
}
function setActiveLayer(layerId) {
emit("set-active-layer", layerId);
}
function toggleLayerVisibility(layerId) {
emit("toggle-layer-visibility", layerId);
}
function moveLayerUp(layerId) {
emit("move-layer-up", layerId);
}
function moveLayerDown(layerId) {
emit("move-layer-down", layerId);
}
function removeLayer(layerId, isDisabled = false) {
if (isDisabled) return;
emit("remove-layer", layerId);
}
function renameLayer(layerId, newName) {
// emit("rename-layer", layerId, newName);
layerManager.renameLayer(layerId, newName);
}
// 修复复选框点击逻辑,支持单击取消或选中,不退出多选模式
const handleCheckboxClick = (layerId, event) => {
// 阻止事件冒泡,避免触发父元素的点击事件
if (event) {
event.stopPropagation();
event.preventDefault();
}
const index = selectedLayerIds.value.indexOf(layerId);
if (index > -1) {
selectedLayerIds.value.splice(index, 1); // 取消选中
} else {
selectedLayerIds.value.push(layerId); // 选中
}
// 更新多选模式状态
isMultiSelectMode.value = selectedLayerIds.value.length > 0;
// 如果有选中的图层,设置最后一个为活动图层
// if (selectedLayerIds.value.length > 0) {
// setActiveLayer(selectedLayerIds.value[selectedLayerIds.value.length - 1]);
// }
};
// 检查图层是否被选中
const isLayerSelected = (layerId) => {
return selectedLayerIds.value.includes(layerId);
};
// 多选相关方法
function toggleLayerSelection(layer, event) {
console.log("layer, event", layer, event);
// 防止在编辑模式下触发选择
if (editingLayerId.value === layer.id) {
return;
}
const isShift = event.shiftKey;
const layerIndex = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
console.log("isShift", isShift);
if (isShift && lastSelectedIndex.value !== -1) {
// Shift + 点击:范围选择
selectLayerRange(lastSelectedIndex.value, layerIndex);
} else {
isMultiSelectMode.value = true;
// 普通点击或Ctrl/Cmd + 点击:切换单个图层选择状态
const index = selectedLayerIds.value.indexOf(layer.id);
if (index > -1) {
selectedLayerIds.value.splice(index, 1);
} else {
selectedLayerIds.value.push(layer.id);
}
lastSelectedIndex.value = layerIndex;
// 设置活动图层为最后选中的图层
// if (selectedLayerIds.value.includes(layer.id)) {
// setActiveLayer(layer.id);
// } else if (selectedLayerIds.value.length > 0) {
// // 如果取消选中当前活动图层,设置最后一个选中的图层为活动图层
// setActiveLayer(selectedLayerIds.value[selectedLayerIds.value.length - 1]);
// }
}
// 始终保持多选模式状态
// isMultiSelectMode.value = selectedLayerIds.value.length > 0;
}
function selectLayerRange(startIndex, endIndex) {
const start = Math.min(startIndex, endIndex);
const end = Math.max(startIndex, endIndex);
selectedLayerIds.value = [];
for (let i = start; i <= end; i++) {
if (sortableRootLayers.value[i]) {
selectedLayerIds.value.push(sortableRootLayers.value[i].id);
}
}
isMultiSelectMode.value = selectedLayerIds.value.length > 1;
}
function selectAllLayers() {
selectedLayerIds.value = [];
sortableRootLayers.value.forEach((layer) => {
if (!layer.isBackground && !layer.isFixed) {
selectedLayerIds.value.push(layer.id);
}
});
isMultiSelectMode.value = selectedLayerIds.value.length > 1;
}
function clearSelection() {
selectedLayerIds.value = [];
isMultiSelectMode.value = false;
lastSelectedIndex.value = -1;
}
// 保留原函数作为向后兼容,但现在直接返回计算属性的值
function getSelectedLayers() {
return selectedLayers.value;
}
// 移动端长按多选支持
function handleTouchStart(event, layer) {
if (editingLayerId.value === layer.id) return;
const touch = event.touches[0];
touchStartPos.value = { x: touch.clientX, y: touch.clientY };
touchTimer.value = setTimeout(() => {
// 长按触发多选
handleLongPress(layer);
}, LONG_PRESS_DELAY);
}
function handleTouchMove(event) {
if (touchTimer.value) {
const touch = event.touches[0];
const deltaX = Math.abs(touch.clientX - touchStartPos.value.x);
const deltaY = Math.abs(touch.clientY - touchStartPos.value.y);
// 如果移动距离超过阈值,取消长按
if (deltaX > TOUCH_MOVE_THRESHOLD || deltaY > TOUCH_MOVE_THRESHOLD) {
clearTimeout(touchTimer.value);
touchTimer.value = null;
}
}
}
function handleTouchEnd() {
if (touchTimer.value) {
clearTimeout(touchTimer.value);
touchTimer.value = null;
}
}
function handleLongPress(layer) {
// 长按进入多选模式
if (!selectedLayerIds.value.includes(layer.id)) {
selectedLayerIds.value.push(layer.id);
}
isMultiSelectMode.value = true;
// 提供触觉反馈(如果支持)
if (navigator.vibrate) {
navigator.vibrate(50);
}
}
// 分组操作
async function groupSelectedLayers() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length < 2) {
console.warn("至少需要选择两个图层才能分组");
return;
}
// 检查是否包含不能分组的图层
const invalidLayers = selectedLayers.filter(
(layer) => layer.isBackground || layer.isFixed || isGroupLayerType(layer)
);
if (invalidLayers.length > 0) {
console.warn("选择的图层中包含背景层、固定层或组图层,无法分组");
return;
}
try {
const layerIds = selectedLayers.map((layer) => layer.id);
const groupName = generateId("GroupLayer_");
await layerManager?.groupLayers(layerIds, groupName);
console.log(`✅ 成功创建图层组: ${groupName}`);
// 清除选择状态
clearSelection();
} catch (error) {
console.error("❌ 创建图层组失败:", error);
}
}
// 解组操作
async function ungroupSelectedLayer() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length !== 1) {
console.warn("只能选择一个组图层进行解组");
return;
}
const groupLayer = selectedLayers[0];
if (!isGroupLayerType(groupLayer)) {
console.warn("选择的图层不是组图层");
return;
}
try {
const childLayerIds = await layerManager?.ungroupLayers(groupLayer.id);
console.log(`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`);
// 清除选择状态
clearSelection();
} catch (error) {
console.error("❌ 解组图层失败:", error);
}
}
// 删除选中的图层
function deleteSelectedLayers() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length === 0) {
console.warn("没有选择要删除的图层");
return;
}
// 检查是否包含不能删除的图层
const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
if (undeletableLayers.length > 0) {
console.warn("选择的图层中包含背景层或固定层,无法删除");
return;
}
// 检查删除后是否还有足够的普通图层
const remainingNormalLayers = layers.value.filter(
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
).length;
if (remainingNormalLayers < 1) {
console.warn("不能删除所有普通图层");
return;
}
// 确认删除
if (selectedLayers.length > 1) {
if (!confirm(`确定要删除选中的 ${selectedLayers.length} 个图层吗?`)) {
return;
}
}
// 删除图层
selectedLayers.forEach((layer) => {
removeLayer(layer.id);
});
// 清除选择状态
clearSelection();
}
// 双击重命名相关方法
function handleLayerDoubleClick(layer) {
// 不允许重命名背景层和固定层
if (layer.isBackground || layer.isFixed) {
return;
}
startEditing(layer);
}
function startEditing(layer) {
editingLayerId.value = layer.id;
editingLayerName.value = layer.name;
// 下一帧聚焦输入框并选中文本
nextTick(() => {
const inputElement = document.querySelector(`input[data-layer-id="${layer.id}"]`);
if (inputElement) {
inputElement.focus();
inputElement.select();
}
});
}
function confirmEdit() {
if (editingLayerId.value && editingLayerName.value.trim()) {
const trimmedName = editingLayerName.value.trim();
renameLayer(editingLayerId.value, trimmedName);
}
cancelEdit();
}
function cancelEdit() {
editingLayerId.value = null;
editingLayerName.value = "";
}
function handleEditKeydown(event) {
if (event.key === "Enter") {
event.preventDefault();
confirmEdit();
} else if (event.key === "Escape") {
event.preventDefault();
cancelEdit();
}
}
// 获取图层缩略图URL - 弃用
// function getLayerThumbnail(layerId) {
// if (props.thumbnailManager) {
// return props.thumbnailManager.getLayerThumbnail(layerId);
// }
// return null;
// }
// 获取图层类型图标
// function getLayerTypeIcon(layer) {
// if (!layer) return "🖼️";
// if (isGroupLayer(layer)) {
// return "📁";
// }
// if (layer.fabricObject) {
// switch (layer.fabricObject.type) {
// case "image":
// return "🖼️";
// case "text":
// return "📝";
// case "rect":
// return "▢";
// case "circle":
// return "⬤";
// case "path":
// return "✎";
// default:
// return "⬤";
// }
// }
// return "🖼️";
// }
// 获取图层的子图层
function getChildLayers(parentId) {
if (!layers || !parentId) return [];
return layers.value.filter((layer) => layer.parentId === parentId);
}
// 检查图层是否为分组
function isGroupLayerType(layer) {
return isGroupLayer(layer);
}
// 计算属性:检查组图层是否展开
const isGroupExpanded = (groupId) => {
return expandedGroupIds.value.has(groupId);
};
// 切换组图层展开/收起状态
const toggleGroupExpanded = (groupId) => {
if (expandedGroupIds.value.has(groupId)) {
expandedGroupIds.value.delete(groupId);
} else {
expandedGroupIds.value.add(groupId);
}
// 触发响应式更新
expandedGroupIds.value = new Set(expandedGroupIds.value);
};
// // 渲染单个图层项(递归组件)
// function renderLayerItem(layer, index) {
// if (!layer) return null;
// const isGroup = isGroupLayerType(layer);
// const children = isGroup ? getChildLayers(layer.id) : [];
// return {
// id: layer.id,
// name: layer.name,
// isGroup: isGroup,
// children: children,
// fabricObject: layer.fabricObject,
// visible: layer.visible,
// };
// }
// 处理图层点击事件
function handleLayerClick(layer, event) {
// 阻止事件冒泡
event.stopPropagation();
// 如果按住修饰键,执行多选逻辑
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
toggleLayerSelection(layer, event);
} else {
lastSelectLayerId.value = layer.id; // 更新最后选中的图层ID
// 普通点击:进入单选模式
// selectedLayerIds.value = [layer.id];
// isMultiSelectMode.value = false;
if (!isMultiSelectMode.value) {
// 如果不是多选模式,才可激活图层
// 1.如果是组,则设置组下的第一个子图层为活动图层
// 2.否则直接设置活动图层
if (isGroupLayerType(layer) && layer.children && layer.children.length > 0) {
// 如果是组图层,设置第一个子图层为活动图层
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
setActiveLayer(layer.children[0].id, { parentId: layer.id });
} else {
// 否则直接设置当前图层为活动图层
setActiveLayer(layer.id);
layerManager?.updateLayersObjectsInteractivity();
}
}
lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
}
}
// 右键菜单相关方法
function showContextMenu(event, layer) {
event.preventDefault();
event.stopPropagation();
// 如果右键的图层不在选中列表中,先选中它
if (!selectedLayerIds.value.includes(layer.id)) {
selectedLayerIds.value = [layer.id];
isMultiSelectMode.value = false;
setActiveLayer(layer.id);
}
contextMenuLayer.value = layer;
contextMenuPosition.value = { x: event.clientX, y: event.clientY };
// 构建菜单项
buildContextMenuItems(layer);
contextMenuVisible.value = true;
}
function hideContextMenu() {
contextMenuVisible.value = false;
contextMenuLayer.value = null;
contextMenuItems.value = [];
}
function buildContextMenuItems(layer) {
const selectedLayers = getSelectedLayers();
const isMultiple = selectedLayers.length > 1;
const isGroupLayer = isGroupLayerType(layer);
contextMenuItems.value = [
// 重命名
{
label: "重命名",
icon: "CFont",
disabled: isMultiple || layer.isBackground || layer.isFixed,
action: () => startEditing(layer),
},
{ type: "divider" },
// 复制
{
label: isMultiple ? `复制 ${selectedLayers.length} 个图层` : "复制图层",
icon: "CPaste",
disabled: !canCopyLayers.value,
action: () => copySelectedLayers(),
},
// 删除
{
label: isMultiple ? `删除 ${selectedLayers.length} 个图层` : "删除图层",
icon: "CDelete",
disabled: layer.isBackground || layer.isFixed || !canDeleteLayers(),
danger: true,
action: () => deleteSelectedLayers(),
},
// 组合图层
// {
// label: "填充图层",
// icon: "CThemeColor",
// disabled:
// layer.isBackground ||
// layer.isFixed ||
// !layerManager?.canRasterizeLayer?.(layer.id) ||
// layer.isGroup ||
// layer?.children?.length > 0, // 如果是组图层或有子图层则禁用
// action: () => {
// // 调用浏览器原生颜色选择器
// fillColorRef.value.click();
// currLayerId.value = layer.id;
// // // 监听颜色选择器的变化
// // fillColorRef.value.addEventListener("change", () => {
// // const selectedColor = fillColor.value;
// // layerManager
// // .fillLayerBackground(layer.id, selectedColor)
// // .then(() => {
// // console.log(`✅ 已填充图层 ${layer.name} 背景颜色: ${selectedColor}`);
// // })
// // .catch((error) => {
// // console.error(`❌ 填充图层 ${layer.name} 背景颜色失败:`, error);
// // });
// // });
// // 隐藏右键菜单
// hideContextMenu();
// },
// },
// 组合图层
{
label: "组合图层",
icon: "CPicture",
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
action: () => {
rasterizeLayer(layer.id);
hideContextMenu();
},
},
// 导出图层
{
label: "导出图层",
icon: "CExport",
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
action: () => {
exportLayerToImage(layer.id);
hideContextMenu();
},
},
{ type: "divider" },
// 分组操作 - 带子菜单
{
label: "分组操作",
icon: "CGroup",
children: [
{
label: "创建组",
icon: "CCreateGroup",
disabled: !canGroupLayers.value,
action: () => {
groupSelectedLayers();
hideContextMenu();
},
},
{
label: "解组",
icon: "CCancelGroup",
disabled: !canUngroupLayers.value,
action: () => {
ungroupSelectedLayer();
hideContextMenu();
},
},
// {
// label: "合并组", // 不需要了 同组合功能一样了
// icon: "CMergeGroup",
// disabled: !isGroupLayer || isMultiple,
// action: () => {
// mergeGroupLayer(layer.id);
// hideContextMenu();
// },
// },
],
},
// 图层操作 - 带子菜单
{
label: "图层操作",
icon: "CLayout",
children: [
// // 锁定/解锁
// {
// label: layer.locked ? "解锁图层" : "锁定图层",
// icon: layer.locked ? "CUnLock" : "CLock",
// disabled: !canToggleLock.value,
// action: () => toggleSelectedLayersLock(),
// },
// // 显示/隐藏
// {
// label: layer.visible ? "隐藏图层" : "显示图层",
// icon: layer.visible ? "CUnEye" : "CEye",
// action: () => toggleSelectedLayersVisibility(),
// },
// { type: "divider" },
{
label: "置顶",
icon: "CBottom",
inverIcon: true, // 倒置图标
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
action: () => {
moveLayerToTop(layer.id);
hideContextMenu();
},
},
{
label: "向上移动",
icon: "CUp",
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
action: () => {
moveLayerUp(layer.id);
hideContextMenu();
},
},
{
label: "向下移动",
icon: "CDown",
disabled:
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
action: () => {
moveLayerDown(layer.id);
hideContextMenu();
},
},
{
label: "置底",
icon: "CBottom",
disabled:
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
action: () => {
moveLayerToBottom(layer.id);
hideContextMenu();
},
},
],
},
].filter((item) => item !== null);
}
// 填充颜色选择器
function fillColorChange() {
layerManager.fillLayerBackground(currLayerId.value, fillColor.value, true);
}
// 右键菜单操作方法
function copySelectedLayers() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length === 0) return;
if (selectedLayers.length === 1) {
layerManager.copyLayer(selectedLayers[0].id);
console.log(`✅ 已复制图层: ${selectedLayers[0].name}`);
} else {
// 多个图层复制逻辑(如果需要支持)
console.log(`✅ 已复制 ${selectedLayers.length} 个图层`);
}
hideContextMenu();
}
// watch(fillColor, (newColor) => {
// // 使用loadash-es 节流函数
// // 使用throttle节流 控制更新评率
// if (currLayerId.value) {
// throttle(() => {
// if (layerManager && layerManager.fillLayerBackground) {
// layerManager
// .fillLayerBackground(currLayerId.value, newColor, false)
// .then(() => {
// console.log(`✅ 已填充图层 ${currLayerId.value} 背景颜色: ${newColor}`);
// })
// .catch((error) => {
// console.error(`❌ 填充图层 ${currLayerId.value} 背景颜色失败:`, error);
// });
// }
// }, 300)(); // 每300毫秒执行一次
// }
// });
function toggleSelectedLayersLock() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length === 0) return;
selectedLayers.forEach((layer) => {
if (!layer.isBackground && !layer.isFixed) {
layerManager.toggleLayerLock(layer.id);
}
});
hideContextMenu();
}
function toggleSelectedLayersLockByLayer(layer) {
if (!layer.isBackground && !layer.isFixed) {
layerManager.toggleLayerLock(layer.id);
}
}
function toggleSelectedLayersVisibility() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length === 0) return;
selectedLayers.forEach((layer) => {
layerManager.toggleLayerVisibility(layer.id);
});
hideContextMenu();
}
function canDeleteLayers() {
const selectedLayers = getSelectedLayers();
if (selectedLayers.length === 0) return false;
// 检查是否包含不能删除的图层
const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
if (undeletableLayers.length > 0) return false;
// 检查删除后是否还有足够的普通图层
const remainingNormalLayers = layers.value.filter(
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
).length;
return remainingNormalLayers >= 1;
}
// 子图层操作方法
function toggleChildLayerVisibility(childLayerId, parentId) {
if (layerManager.toggleChildLayerVisibility) {
layerManager.toggleChildLayerVisibility(childLayerId, parentId);
}
}
function toggleChildLayerLock(childLayerId, parentId) {
if (layerManager.toggleChildLayerLock) {
layerManager.toggleChildLayerLock(childLayerId, parentId);
}
}
function deleteChildLayer(childLayerId, parentId) {
if (layerManager.removeChildLayer) {
layerManager.removeChildLayer(childLayerId, parentId);
}
}
function renameChildLayer(childLayerId, parentId, newName) {
if (layerManager.renameChildLayer) {
layerManager.renameChildLayer(childLayerId, parentId, newName);
}
}
// 子图层编辑相关方法
function startChildLayerEdit(childLayer) {
childLayer.editing = true;
childLayer.tempName = childLayer.name;
nextTick(() => {
const inputElement = document.querySelector(`input[data-child-layer-id="${childLayer.id}"]`);
if (inputElement) {
inputElement.focus();
inputElement.select();
}
});
}
function finishChildLayerEdit(childLayer) {
if (childLayer.tempName && childLayer.tempName.trim()) {
const trimmedName = childLayer.tempName.trim();
renameLayer(childLayer.id, trimmedName);
childLayer.name = trimmedName;
}
childLayer.editing = false;
childLayer.tempName = "";
}
function cancelChildLayerEdit(childLayer) {
childLayer.editing = false;
childLayer.tempName = "";
}
// 子图层编辑按键处理
function handleChildLayerEditKeydown(event, childLayer) {
if (event.key === "Enter") {
event.preventDefault();
finishChildLayerEdit(childLayer);
} else if (event.key === "Escape") {
event.preventDefault();
cancelChildLayerEdit(childLayer);
}
}
// 选中子图层
function selectChildLayer(layerId, parentId) {
if (!isMultiSelectMode.value) layerManager?.setActiveLayer(layerId, { parentId });
}
// 子图层右键菜单处理
function showChildLayerContextMenu(event, childLayer) {
event.preventDefault();
event.stopPropagation();
// 先选中子图层
selectChildLayer(childLayer.id, childLayer.parentId);
// 构建子图层菜单项
contextMenuLayer.value = childLayer;
contextMenuPosition.value = { x: event.clientX, y: event.clientY };
buildChildLayerContextMenuItems(childLayer);
contextMenuVisible.value = true;
// 点击其他地方关闭菜单
nextTick(() => {
document.addEventListener("click", hideContextMenu, { once: true });
document.addEventListener("contextmenu", hideContextMenu, { once: true });
});
}
function buildChildLayerContextMenuItems(childLayer) {
contextMenuItems.value = [
// 重命名
{
label: "重命名",
icon: "CFont",
disabled: childLayer.isBackground || childLayer.isFixed,
action: () => startChildLayerEdit(childLayer),
},
{ type: "divider" },
// 锁定/解锁
{
label: childLayer.locked ? "解锁图层" : "锁定图层",
icon: childLayer.locked ? "CUnLock" : "CLock",
disabled: childLayer.isBackground || childLayer.isFixed,
action: () => toggleChildLayerLock(childLayer.id),
},
// 显示/隐藏
{
label: childLayer.visible ? "隐藏图层" : "显示图层",
icon: childLayer.visible ? "CUnEye" : "CEye",
action: () => toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
},
{ type: "divider" },
// 移出组
{
label: "移出组",
icon: "CPlus",
disabled: false,
action: () => moveChildLayerOutOfGroup(childLayer.id),
},
].filter((item) => item !== null);
}
// 将子图层移出组
function moveChildLayerOutOfGroup(childLayerId) {
console.log(`移出组操作: ${childLayerId}`);
// TODO: 实现将子图层移出组的逻辑
hideContextMenu();
}
// 点击空白区域清除选择
function handlePanelClick(event) {
// 如果点击的是面板空白区域,清除多选状态
if (event.target === event.currentTarget) {
if (isMultiSelectMode.value) {
clearSelection();
}
}
}
// 处理根级图层拖拽排序
async function handleRootLayersSort(event) {
console.log("🔄 图层拖拽排序开始:", { event });
const { newIndex, oldIndex } = event;
const layerId = sortableRootLayers.value[oldIndex]?.id;
if (!commandManager) {
console.warn("命令管理器未注入,使用回退方案");
emit("reorder-layers", {
oldIndex: oldIndex,
newIndex: newIndex,
layerId,
});
return;
}
const layerToMove = sortableRootLayers.value[oldIndex];
if (!layerToMove) {
console.error("找不到要移动的图层");
return;
}
try {
layerManager?.reorderLayers(oldIndex, newIndex, layerId);
console.log(`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
} catch (error) {
console.error("❌ 图层排序命令执行失败:", error);
emit("reorder-layers", {
oldIndex: oldIndex,
newIndex: newIndex,
layerId: layerToMove.id,
});
}
}
// 处理子图层拖拽排序
async function handleChildLayersSort(event, childrenLayers, parentId) {
console.log("🔄 子图层拖拽排序开始:", { event, parentId });
const { newIndex, oldIndex } = event;
const layerId = childrenLayers?.[oldIndex]?.id;
if (!commandManager) {
console.warn("命令管理器未注入,使用回退方案");
emit("reorder-child-layers", {
parentId,
oldIndex,
newIndex,
layerId,
});
return;
}
// 获取父图层
const parentLayer = layers.value.find((layer) => layer.id === parentId);
if (!parentLayer) {
console.error("❌ 找不到父图层:", parentId);
return;
}
// 获取父图层的子图层列表
const layerToMove = childrenLayers[oldIndex];
if (!layerToMove) {
console.error("❌ 找不到要移动的子图层oldIndex:", oldIndex, "childLayers:", childrenLayers);
return;
}
console.log("📝 子图层排序详情:", {
parentId,
parentLayerName: parentLayer.name,
layerToMove: layerToMove.name,
oldIndex,
newIndex,
totalChildLayers: childrenLayers.length,
});
try {
layerManager?.moveLayerToIndex({ parentId, oldIndex, newIndex, layerId });
console.log(`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
} catch (error) {
console.error("❌ 子图层排序命令执行失败:", error);
emit("reorder-child-layers", {
parentId,
oldIndex,
newIndex,
layerId: layerToMove.id,
});
}
}
// 组合图层
async function rasterizeLayer(layerId) {
if (!layerManager?.rasterizeLayer) {
console.warn("组合功能不可用");
return;
}
try {
const success = await layerManager.rasterizeLayer(layerId);
if (success) {
console.log(`✅ 成功组合图层: ${layerId}`);
} else {
console.warn("组合图层失败");
}
} catch (error) {
console.error("组合图层时发生错误:", error);
}
}
// 导出图层
async function exportLayerToImage(layerId) {
if (!layerManager?.rasterizeLayer) {
console.warn("导出图层功能不可用");
return;
}
try {
const success = await layerManager.exportLayerToImage(layerId);
if (success) {
console.log(`✅ 成功导出图层: ${layerId}`);
} else {
console.warn("导出图层失败");
}
} catch (error) {
console.error("导出图层时发生错误:", error);
}
}
// 合并组图层
async function mergeGroupLayer(groupId) {
if (!layerManager?.mergeGroupLayers) {
console.warn("合并组功能不可用");
return;
}
try {
const childLayerId = await layerManager.mergeGroupLayers(groupId);
if (childLayerId) {
const groupLayer = layers.value.find((l) => l.id === groupId);
console.log(`✅ 成功合并组图层: ${groupLayer?.name || groupId}, 生成 ${childLayerId} 图层`);
} else {
console.warn("合并组图层失败");
}
} catch (error) {
console.error("合并组图层时发生错误:", error);
}
}
// 置顶图层
function moveLayerToTop(layerId) {
if (!layerManager?.moveLayer) {
console.warn("移动图层功能不可用");
return;
}
const success = layerManager.moveLayer(layerId, "toTop");
if (success) {
const layer = layers.value.find((l) => l.id === layerId);
console.log(`✅ 成功置顶图层: ${layer?.name || layerId}`);
} else {
console.warn("置顶图层失败");
}
}
// 置底图层
function moveLayerToBottom(layerId) {
if (!layerManager?.moveLayer) {
console.warn("移动图层功能不可用");
return;
}
const success = layerManager.moveLayer(layerId, "toBottom");
if (success) {
const layer = layers.value.find((l) => l.id === layerId);
console.log(`✅ 成功置底图层: ${layer?.name || layerId}`);
} else {
console.warn("置底图层失败");
}
}
// 事件转发方法
const forwardEvent = (eventName, ...args) => {
emit(eventName, ...args);
};
// 处理跨层级移动
async function handleCrossLevelMove(moveData) {
console.log("🔀 处理跨层级移动:", moveData);
const {
layerId,
fromContainerType,
toContainerType,
fromParentId,
toParentId,
newIndex,
oldIndex,
} = moveData;
// 基本验证
if (!layerId) {
console.error("❌ 缺少图层ID");
return;
}
try {
// 如果有命令管理器,使用命令模式
if (commandManager) {
console.log("📝 使用命令模式执行跨层级移动");
const command = createCrossLevelMoveCommand({
layers,
layerManager,
canvas: layerManager?.canvas,
layerId,
fromContainerType,
toContainerType,
fromParentId,
toParentId,
newIndex,
oldIndex,
});
// 执行命令
const result = await commandManager.executeCommand(command);
if (result) {
console.log("✅ 跨层级移动命令执行成功");
// 清除选择状态
clearSelection();
} else {
console.warn("⚠️ 跨层级移动命令执行失败");
}
return result;
} else {
// 回退方案:直接执行移动逻辑
console.log("📝 使用回退方案执行跨层级移动");
return await executeDirectMove(moveData);
}
} catch (error) {
console.error("❌ 跨层级移动失败:", error);
// 可以在这里添加用户友好的错误提示
const errorMessages = {
找不到要移动的图层: "无法找到要移动的图层,请刷新页面后重试",
背景层和固定层不能移动: "背景层和固定层无法移动",
不能将图层移动到自己的子层级中: "无法将图层移动到自己的子层级中",
目标图层不是组图层: "只能将图层移动到组图层中",
};
const userMessage = errorMessages[error.message] || `移动失败: ${error.message}`;
// 这里可以触发一个全局的错误提示组件
// 暂时使用console.warn实际项目中应该替换为适当的提示方式
console.warn("用户提示:", userMessage);
}
}
// 直接执行移动的回退方案(保持原有逻辑)
async function executeDirectMove(moveData) {
const {
layerId,
fromContainerType,
toContainerType,
fromParentId,
toParentId,
newIndex,
oldIndex,
} = moveData;
// 查找被拖拽的图层
let draggedLayer = null;
let sourceParent = null;
// 根据源容器类型查找图层
if (fromContainerType === "root") {
draggedLayer = layers.value.find((layer) => layer.id === layerId);
} else if (fromContainerType === "child" && fromParentId) {
sourceParent = layers.value.find((layer) => layer.id === fromParentId);
if (sourceParent && sourceParent.children) {
draggedLayer = sourceParent.children.find((child) => child.id === layerId);
}
}
if (!draggedLayer) {
throw new Error("找不到要移动的图层");
}
// 检查是否允许移动(背景层和固定层不能移动)
if (draggedLayer.isBackground || draggedLayer.isFixed) {
throw new Error("背景层和固定层不能移动");
}
// 情况1: 从顶级图层移动到组图层内
if (fromContainerType === "root" && toContainerType === "child") {
await moveRootToGroup(draggedLayer, toParentId, newIndex);
}
// 情况2: 从组图层内移动到顶级图层
else if (fromContainerType === "child" && toContainerType === "root") {
await moveGroupToRoot(draggedLayer, fromParentId, newIndex);
}
// 情况3: 在不同组之间移动
else if (
fromContainerType === "child" &&
toContainerType === "child" &&
fromParentId !== toParentId
) {
await moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex);
}
// 刷新画布渲染
// if (layerManager?.canvas) {
// layerManager.canvas.renderAll();
// }
await layerManager?.updateLayersObjectsInteractivity?.();
// 清除选择状态
clearSelection();
console.log("✅ 跨层级移动完成");
return true;
}
// 从顶级图层移动到组图层内
async function moveRootToGroup(draggedLayer, toParentId, newIndex) {
console.log("📥 顶级图层移动到组内:", {
layerId: draggedLayer.id,
toParentId,
newIndex,
});
// 找到目标父图层
const targetParent = layers.value.find((layer) => layer.id === toParentId);
if (!targetParent) {
throw new Error(`找不到目标父图层: ${toParentId}`);
}
// 检查目标父图层是否为组
if (!isGroupLayerType(targetParent)) {
throw new Error("目标图层不是组图层");
}
// 确保父图层有children数组
if (!targetParent.children) {
targetParent.children = [];
}
// 从顶级图层数组中移除
const rootIndex = layers.value.findIndex((layer) => layer.id === draggedLayer.id);
if (rootIndex !== -1) {
layers.value.splice(rootIndex, 1);
}
// 使用 layerManager 的方法移动图层到组内
if (layerManager?.moveLayerToGroup) {
await layerManager.moveLayerToGroup(draggedLayer.id, toParentId, newIndex);
} else {
// 回退方案:手动更新图层关系
draggedLayer.parentId = toParentId;
targetParent.children.splice(newIndex, 0, draggedLayer);
// 处理 fabricObject 的层级关系
if (draggedLayer.fabricObject && targetParent.fabricObject) {
if (layerManager?.canvas) {
layerManager.canvas.remove(draggedLayer.fabricObject);
}
targetParent.fabricObject.add(draggedLayer.fabricObject);
}
}
console.log("✅ 成功将图层移动到组内");
}
// 从组图层内移动到顶级图层
async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
console.log("📤 组内图层移动到顶级:", {
layerId: draggedLayer.id,
fromParentId,
newIndex,
});
// 找到源父图层
const sourceParent = layers.value.find((layer) => layer.id === fromParentId);
if (!sourceParent || !sourceParent.children) {
throw new Error(`找不到源父图层或其children数组: ${fromParentId}`);
}
// 使用 layerManager 的方法移动图层到顶级
if (layerManager?.moveLayerToRoot) {
await layerManager.moveLayerToRoot(draggedLayer.id, newIndex);
} else {
// 回退方案:手动更新图层关系
// 从源父图层的children中移除
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1);
}
// 处理 fabricObject 的层级关系
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
sourceParent.fabricObject.remove(draggedLayer.fabricObject);
if (layerManager?.canvas) {
layerManager.canvas.add(draggedLayer.fabricObject);
}
}
// 清除图层的parentId
delete draggedLayer.parentId;
// 计算在顶级图层中的插入位置
const targetIndex = Math.min(newIndex, layers.value.length);
// 将图层添加到顶级图层的指定位置
layers.value.splice(targetIndex, 0, draggedLayer);
}
console.log("✅ 成功将图层移动到顶级");
}
// 在不同组之间移动
async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex) {
console.log("🔄 在不同组间移动:", {
layerId: draggedLayer.id,
fromParentId,
toParentId,
newIndex,
});
// 找到源父图层和目标父图层
const sourceParent = layers.value.find((layer) => layer.id === fromParentId);
const targetParent = layers.value.find((layer) => layer.id === toParentId);
if (!sourceParent || !targetParent) {
throw new Error("找不到源父图层或目标父图层");
}
if (!isGroupLayerType(targetParent)) {
throw new Error("目标图层不是组图层");
}
// 使用 layerManager 的方法在组间移动
if (layerManager?.moveLayerBetweenGroups) {
await layerManager.moveLayerBetweenGroups(draggedLayer.id, fromParentId, toParentId, newIndex);
} else {
// 回退方案:手动更新图层关系
// 从源父图层中移除
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
if (childIndex !== -1) {
sourceParent.children.splice(childIndex, 1);
// 从源父组的fabricObject中移除
if (draggedLayer.fabricObject && sourceParent.fabricObject) {
sourceParent.fabricObject.remove(draggedLayer.fabricObject);
}
}
// 更新图层的parentId
draggedLayer.parentId = toParentId;
// 确保目标父图层有children数组
if (!targetParent.children) {
targetParent.children = [];
}
// 将图层添加到目标组
targetParent.children.splice(newIndex, 0, draggedLayer);
// 将图层的fabricObject添加到目标父组中
if (draggedLayer.fabricObject && targetParent.fabricObject) {
targetParent.fabricObject.add(draggedLayer.fabricObject);
}
}
console.log("✅ 成功在不同组间移动图层");
}
</script>
<template>
<div class="layers-panel-inner" @click="handlePanelClick">
<div class="layers-header">
<!-- 颜色填充选择组件 -->
<!-- <input
class="fillColor-input"
v-model="fillColor"
ref="fillColorRef"
type="color"
@change="fillColorChange"
style="width: 0; height: 0; opacity: 0"
/> -->
<h3>
{{ $t("图层") }}
{{ selectedLayerIds.length > 0 ? `(${selectedLayerIds.length})` : "" }}
</h3>
<div class="layer-actions-group">
<!-- 多选操作按钮组 -->
<div v-if="isMultiSelectMode" class="multi-select-actions">
<div
class="group-btn action-btn"
:class="{ disabled: !canGroupLayers }"
@click="groupSelectedLayers"
:title="$t('创建组')"
>
<SvgIcon name="CCreateGroup" size="20"></SvgIcon>
</div>
<div
class="ungroup-btn action-btn"
:class="{ disabled: !canUngroupLayers }"
@click="ungroupSelectedLayer"
:title="$t('解组')"
>
<SvgIcon name="CCancelGroup" size="20"></SvgIcon>
</div>
<div
class="delete-selected-btn action-btn"
@click="deleteSelectedLayers"
:title="$t('删除选中图层')"
>
<SvgIcon name="CDelete" size="16"></SvgIcon>
</div>
<div
class="clear-selection-btn action-btn"
@click="clearSelection"
:title="$t('清除选择')"
>
<SvgIcon name="CCloseNo" size="14"></SvgIcon>
</div>
</div>
<!-- 常规操作按钮 -->
<div v-else class="normal-actions">
<div
v-if="isChildLayerActive"
class="add-layer-btn action-btn"
@click="addTopLayer"
:title="$t('添加顶级图层')"
>
<SvgIcon name="CPlusTop" size="16"></SvgIcon>
</div>
<div class="add-layer-btn action-btn" @click="addLayer" :title="$t('添加图层')">
<SvgIcon name="CPlus" size="16"></SvgIcon>
</div>
<div class="select-all-btn action-btn" @click="selectAllLayers" :title="$t('全选图层')">
<SvgIcon name="CCheckbox" size="16"></SvgIcon>
</div>
</div>
</div>
</div>
<!-- 多选提示条 -->
<div v-if="isMultiSelectMode" class="multi-select-info">
<span>已选择 {{ selectedLayerIds.length }} 个图层</span>
<small>提示按住 Ctrl/Cmd 多选Shift 范围选择长按进入多选模式</small>
</div>
<!-- 图层列表组件 -->
<LayersList
:layers="layers"
:active-layer-id="activeLayerId"
:sortable-root-layers="sortableRootLayers"
:fixed-layers="fixedLayers"
:selected-layer-ids="selectedLayerIds"
:is-multi-select-mode="isMultiSelectMode"
:editing-layer-id="editingLayerId"
:editing-layer-name="editingLayerName"
:thumbnail-manager="thumbnailManager"
:expanded-group-ids="expandedGroupIds"
:isChild="false"
group-name="layers-root"
@layer-click="handleLayerClick"
@layer-double-click="handleLayerDoubleClick"
@context-menu="showContextMenu"
@checkbox-change="handleCheckboxClick"
@toggle-visibility="toggleLayerVisibility"
@toggle-lock="toggleSelectedLayersLockByLayer"
@delete="removeLayer"
@edit-confirm="confirmEdit"
@edit-cancel="cancelEdit"
@edit-keydown="handleEditKeydown"
@touch-start="handleTouchStart"
@touch-move="handleTouchMove"
@touch-end="handleTouchEnd"
@update:editing-name="editingLayerName = $event"
@root-layers-sort="handleRootLayersSort"
@child-layers-sort="handleChildLayersSort"
@cross-level-move="handleCrossLevelMove"
@select-child-layer="selectChildLayer"
@start-child-layer-edit="startChildLayerEdit"
@child-context-menu="showChildLayerContextMenu"
@toggle-group-expanded="toggleGroupExpanded"
@toggle-child-visibility="toggleChildLayerVisibility"
@toggle-child-lock="toggleChildLayerLock"
@delete-child="deleteChildLayer"
@rename-child="renameChildLayer"
/>
<!-- 固定层背景层和固定层 -->
<div v-if="fixedLayers.length > 0" class="fixed-layers">
<!-- 遍历固定层 -->
<!-- :thumbnail-url="getLayerThumbnail(layer.id)" -->
<LayerItem
v-for="layer in fixedLayers"
:key="layer.id"
:layer="layer"
:is-child="false"
:is-active="layer.id === activeLayerId"
:is-selected="false"
:is-multi-select-mode="false"
:is-editing="editingLayerId === layer.id"
:editing-name="editingLayerName"
:can-delete="false"
:isHidenDragHandle="true"
@toggle-visibility="(...args) => forwardEvent('toggle-layer-visibility', ...args)"
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
@edit-cancel="(...args) => forwardEvent('edit-cancel', ...args)"
@edit-keydown="(...args) => forwardEvent('edit-keydown', ...args)"
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
/>
</div>
<!-- 右键菜单 -->
<ContextMenu
:visible="contextMenuVisible"
:position="contextMenuPosition"
:items="contextMenuItems"
@close="hideContextMenu"
/>
</div>
</template>
<style scoped lang="less">
@import "./layersPanel.less";
</style>