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

1327 lines
36 KiB
Vue
Raw Normal View History

2025-06-18 11:05:23 +08:00
<script setup>
import { computed, ref, nextTick, inject } from "vue";
2025-06-22 13:52:28 +08:00
import { findLayerRecursively, isGroupLayer } from "../../utils/layerHelper";
2025-06-18 11:05:23 +08:00
import SvgIcon from "../../../SvgIcon/index.vue";
import ContextMenu from "./ContextMenu.vue";
import LayerItem from "./LayerItem.vue";
import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
// // 导入命令类
// import {
// ReorderLayersCommand,
// // ReorderChildLayersCommand,
// GroupLayersCommand,
// UngroupLayersCommand,
// } from "../../commands/LayerCommands";
import { generateId } from "../../utils/helper";
const props = defineProps({
// layers: Array,
activeLayerId: String,
activeElementId: String,
thumbnailManager: Object, // 添加缩略图管理器属性
});
const emit = defineEmits([
"add-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 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 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) => !layer.parentId && (layer.isFixed || layer.isBackground)
(layer) => !layer.parentId && layer.isBackground // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
);
});
// 计算属性:获取当前选中的图层
const selectedLayers = computed(() => {
return sortableRootLayers.value.filter((layer) =>
selectedLayerIds.value.includes(layer.id)
);
});
// 计算属性:检查是否有选中的图层可以分组
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 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 {
// 普通点击:进入单选模式
// selectedLayerIds.value = [layer.id];
// isMultiSelectMode.value = false;
if (!isMultiSelectMode.value) {
// 如果不是多选模式,才可激活图层
// 1.如果是组,则设置组下的第一个子图层为活动图层
// 2.否则直接设置活动图层
if (
isGroupLayerType(layer) &&
layer.children &&
layer.children.length > 0
) {
// 如果是组图层,设置第一个子图层为活动图层
setActiveLayer(layer.children[0].id, { parentId: layer.id });
} else {
// 否则直接设置当前图层为活动图层
setActiveLayer(layer.id);
}
}
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: "CPicture",
disabled:
layer.isBackground ||
layer.isFixed ||
!layerManager?.canRasterizeLayer?.(layer.id),
action: () => {
rasterizeLayer(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();
},
},
2025-06-22 13:52:28 +08:00
// {
// label: "合并组", // 不需要了 同栅格化功能一样了
// icon: "CMergeGroup",
// disabled: !isGroupLayer || isMultiple,
// action: () => {
// mergeGroupLayer(layer.id);
// hideContextMenu();
// },
// },
2025-06-18 11:05:23 +08:00
],
},
// 图层操作 - 带子菜单
{
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 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();
}
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) {
2025-06-22 13:52:28 +08:00
console.log(`✅ 成功栅格化图层: ${layerId}`);
2025-06-18 11:05:23 +08:00
} 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("置底图层失败");
}
}
2025-06-22 13:52:28 +08:00
// 事件转发方法
const forwardEvent = (eventName, ...args) => {
emit(eventName, ...args);
};
2025-06-18 11:05:23 +08:00
</script>
<template>
<div class="layers-panel-inner" @click="handlePanelClick">
<div class="layers-header">
<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
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"
@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">
<!-- 遍历固定层 -->
2025-06-22 13:52:28 +08:00
<!-- :thumbnail-url="getLayerThumbnail(layer.id)" -->
2025-06-18 11:05:23 +08:00
<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="
2025-06-22 13:52:28 +08:00
(...args) => forwardEvent('toggle-layer-visibility', ...args)
2025-06-18 11:05:23 +08:00
"
@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>