feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -145,11 +145,7 @@ const handleItemMouseEnter = (item, index, event) => {
|
||||
// 处理鼠标在菜单项内移动
|
||||
const handleItemMouseMove = (item, index, event) => {
|
||||
// 如果当前菜单项有子菜单但子菜单未显示,则显示子菜单
|
||||
if (
|
||||
item.children &&
|
||||
item.children.length > 0 &&
|
||||
hoveredItem.value !== index
|
||||
) {
|
||||
if (item.children && item.children.length > 0 && hoveredItem.value !== index) {
|
||||
const element = event.target.closest(".context-menu-item");
|
||||
showSubmenu(item, index, element);
|
||||
}
|
||||
@@ -261,10 +257,7 @@ onUnmounted(() => {
|
||||
>
|
||||
<template v-for="(item, index) in items" :key="index">
|
||||
<!-- 分隔线 -->
|
||||
<div
|
||||
v-if="item.type === 'divider'"
|
||||
class="context-menu-divider"
|
||||
></div>
|
||||
<div v-if="item.type === 'divider'" class="context-menu-divider"></div>
|
||||
|
||||
<!-- 菜单项 -->
|
||||
<div
|
||||
@@ -294,38 +287,24 @@ onUnmounted(() => {
|
||||
<span class="context-menu-shortcut" v-if="item.shortcut">
|
||||
{{ item.shortcut }}
|
||||
</span>
|
||||
<span
|
||||
class="context-menu-arrow"
|
||||
v-if="item.children && item.children.length > 0"
|
||||
>
|
||||
<span class="context-menu-arrow" v-if="item.children && item.children.length > 0">
|
||||
<SvgIcon name="CRight" size="12" />
|
||||
</span>
|
||||
|
||||
<!-- 子菜单 -->
|
||||
<transition name="context-submenu">
|
||||
<div
|
||||
v-if="
|
||||
item.children &&
|
||||
item.children.length > 0 &&
|
||||
hoveredItem === index
|
||||
"
|
||||
v-if="item.children && item.children.length > 0 && hoveredItem === index"
|
||||
class="context-submenu"
|
||||
:class="{
|
||||
'submenu-left':
|
||||
submenuPositions.get(index)?.direction === 'left',
|
||||
'submenu-left': submenuPositions.get(index)?.direction === 'left',
|
||||
}"
|
||||
@mouseenter="handleSubmenuMouseEnter(index)"
|
||||
@mouseleave="handleSubmenuMouseLeave"
|
||||
>
|
||||
<template
|
||||
v-for="(subItem, subIndex) in item.children"
|
||||
:key="subIndex"
|
||||
>
|
||||
<template v-for="(subItem, subIndex) in item.children" :key="subIndex">
|
||||
<!-- 子菜单分隔线 -->
|
||||
<div
|
||||
v-if="subItem.type === 'divider'"
|
||||
class="context-menu-divider"
|
||||
></div>
|
||||
<div v-if="subItem.type === 'divider'" class="context-menu-divider"></div>
|
||||
|
||||
<!-- 子菜单项 -->
|
||||
<div
|
||||
@@ -342,9 +321,7 @@ onUnmounted(() => {
|
||||
:name="subItem.icon"
|
||||
size="14"
|
||||
:style="{
|
||||
transform: subItem.inverIcon
|
||||
? `rotate(90deg)`
|
||||
: 'none',
|
||||
transform: subItem.inverIcon ? `rotate(90deg)` : 'none',
|
||||
}"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -250,13 +250,7 @@ function handleTouchEnd(event) {
|
||||
|
||||
function handleUpdateChildLayers(newChildren) {
|
||||
// 更新当前组图层的children数组
|
||||
console.log(
|
||||
"更新子图层顺序:",
|
||||
"父图层ID:",
|
||||
props.layer.id,
|
||||
"新顺序:",
|
||||
newChildren
|
||||
);
|
||||
console.log("更新子图层顺序:", "父图层ID:", props.layer.id, "新顺序:", newChildren);
|
||||
emit("update-child-layers", props.layer.id, newChildren);
|
||||
}
|
||||
|
||||
@@ -383,21 +377,13 @@ function findParentLayerId() {
|
||||
>
|
||||
<!-- 拖拽手柄 -->
|
||||
<div class="layer-drag-handle" :title="$t('拖拽排序')">
|
||||
<SvgIcon
|
||||
v-if="!isHidenDragHandle"
|
||||
:name="isChild ? 'CSort' : 'CSort'"
|
||||
:size="32"
|
||||
></SvgIcon>
|
||||
<SvgIcon v-if="!isHidenDragHandle" :name="isChild ? 'CSort' : 'CSort'" :size="32"></SvgIcon>
|
||||
</div>
|
||||
|
||||
<!-- 图层头部 -->
|
||||
<div class="layer-header">
|
||||
<!-- 多选复选框 -->
|
||||
<div
|
||||
v-if="isMultiSelectMode && !isChild"
|
||||
class="layer-checkbox"
|
||||
@click.stop
|
||||
>
|
||||
<div v-if="isMultiSelectMode && !isChild" class="layer-checkbox" @click.stop>
|
||||
<Checkbox :checked="isSelected" @change="handleCheckboxChange" />
|
||||
</div>
|
||||
|
||||
@@ -453,12 +439,7 @@ function findParentLayerId() {
|
||||
>
|
||||
<SvgIcon name="CLock" :size="18"></SvgIcon>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="status-icon"
|
||||
:title="$t('未锁定')"
|
||||
@click.stop="handleToggleLock"
|
||||
>
|
||||
<span v-else class="status-icon" :title="$t('未锁定')" @click.stop="handleToggleLock">
|
||||
<SvgIcon name="CUnLock" :size="18"></SvgIcon>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -96,8 +96,7 @@ const handleRootLayersSort = (event) => {
|
||||
|
||||
// 获取被拖拽的图层ID
|
||||
const draggedLayerId =
|
||||
item.getAttribute("data-layer-id") ||
|
||||
props.sortableRootLayers[oldIndex]?.id;
|
||||
item.getAttribute("data-layer-id") || props.sortableRootLayers[oldIndex]?.id;
|
||||
|
||||
if (!draggedLayerId) {
|
||||
console.error("❌ 无法获取被拖拽的图层ID");
|
||||
@@ -150,25 +149,14 @@ const handleRootLayersSort = (event) => {
|
||||
if (props.isChild) {
|
||||
// 子图层事件处理
|
||||
// 确保排序只影响当前组图层的children,而不是全局layers
|
||||
emit(
|
||||
"child-layers-sort",
|
||||
event,
|
||||
props.sortableRootLayers,
|
||||
props.parentLayerId
|
||||
);
|
||||
emit("child-layers-sort", event, props.sortableRootLayers, props.parentLayerId);
|
||||
} else {
|
||||
emit("root-layers-sort", event);
|
||||
}
|
||||
};
|
||||
|
||||
// 验证跨层级移动的有效性
|
||||
const validateCrossLevelMove = (
|
||||
layerId,
|
||||
fromType,
|
||||
toType,
|
||||
fromParentId,
|
||||
toParentId
|
||||
) => {
|
||||
const validateCrossLevelMove = (layerId, fromType, toType, fromParentId, toParentId) => {
|
||||
// 查找图层
|
||||
const layer = findLayerInHierarchy(layerId, fromType, fromParentId);
|
||||
|
||||
@@ -250,9 +238,7 @@ const handleDragRemove = (event) => {
|
||||
const canDeleteComputed = computed(() => {
|
||||
// 如果是子图层,检查父图层是否可以删除
|
||||
if (props.isChild) {
|
||||
const parentLayer = props.layers.find(
|
||||
(layer) => layer.id === props.parentLayerId
|
||||
);
|
||||
const parentLayer = props.layers.find((layer) => layer.id === props.parentLayerId);
|
||||
return parentLayer?.children?.length > 1;
|
||||
}
|
||||
// 否则直接返回根图层的可删除状态
|
||||
@@ -305,23 +291,14 @@ const canDeleteComputed = computed(() => {
|
||||
:is-editing="editingLayerId === layer.id"
|
||||
:editing-name="editingLayerName"
|
||||
:can-delete="
|
||||
canDeleteComputed &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
!layer.locked
|
||||
canDeleteComputed && !layer.isBackground && !layer.isFixed && !layer.locked
|
||||
"
|
||||
:expanded-group-ids="expandedGroupIds"
|
||||
@click="(...args) => forwardEvent('layer-click', ...args)"
|
||||
@double-click="
|
||||
(...args) => forwardEvent('layer-double-click', ...args)
|
||||
"
|
||||
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
||||
@checkbox-change="
|
||||
(...args) => forwardEvent('checkbox-change', ...args)
|
||||
"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-visibility', ...args)
|
||||
"
|
||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||
@delete="(...args) => forwardEvent('delete', ...args)"
|
||||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||||
@@ -330,18 +307,10 @@ const canDeleteComputed = computed(() => {
|
||||
@touch-start="(...args) => forwardEvent('touch-start', ...args)"
|
||||
@touch-move="(...args) => forwardEvent('touch-move', ...args)"
|
||||
@touch-end="(...args) => forwardEvent('touch-end', ...args)"
|
||||
@update:editing-name="
|
||||
(...args) => forwardEvent('update:editing-name', ...args)
|
||||
"
|
||||
@toggle-group-expanded="
|
||||
(...args) => forwardEvent('toggle-group-expanded', ...args)
|
||||
"
|
||||
@toggle-child-visibility="
|
||||
(...args) => forwardEvent('toggle-child-visibility', ...args)
|
||||
"
|
||||
@toggle-child-lock="
|
||||
(...args) => forwardEvent('toggle-child-lock', ...args)
|
||||
"
|
||||
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
|
||||
@toggle-group-expanded="(...args) => forwardEvent('toggle-group-expanded', ...args)"
|
||||
@toggle-child-visibility="(...args) => forwardEvent('toggle-child-visibility', ...args)"
|
||||
@toggle-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
|
||||
@delete-child="(...args) => forwardEvent('delete-child', ...args)"
|
||||
@rename-child="(...args) => forwardEvent('rename-child', ...args)"
|
||||
/>
|
||||
@@ -370,16 +339,10 @@ const canDeleteComputed = computed(() => {
|
||||
:parentLayerId="layer.id"
|
||||
:group-name="groupName"
|
||||
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
|
||||
@layer-double-click="
|
||||
(...args) => forwardEvent('layer-double-click', ...args)
|
||||
"
|
||||
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
|
||||
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
|
||||
@checkbox-change="
|
||||
(...args) => forwardEvent('checkbox-change', ...args)
|
||||
"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-visibility', ...args)
|
||||
"
|
||||
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
|
||||
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
|
||||
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
|
||||
@delete="(...args) => forwardEvent('delete', ...args)"
|
||||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||||
@@ -388,33 +351,17 @@ const canDeleteComputed = computed(() => {
|
||||
@touch-start="(...args) => forwardEvent('touch-start', ...args)"
|
||||
@touch-move="(...args) => forwardEvent('touch-move', ...args)"
|
||||
@touch-end="(...args) => forwardEvent('touch-end', ...args)"
|
||||
@update:editing-name="
|
||||
(...args) => forwardEvent('update:editing-name', ...args)
|
||||
"
|
||||
@root-layers-sort="
|
||||
(...args) => forwardEvent('root-layers-sort', ...args)
|
||||
"
|
||||
@child-layers-sort="
|
||||
(...args) => forwardEvent('child-layers-sort', ...args)
|
||||
"
|
||||
@cross-level-move="
|
||||
(...args) => forwardEvent('cross-level-move', ...args)
|
||||
"
|
||||
@select-child-layer="
|
||||
(...args) => forwardEvent('select-child-layer', ...args)
|
||||
"
|
||||
@start-child-layer-edit="
|
||||
(...args) => forwardEvent('start-child-layer-edit', ...args)
|
||||
"
|
||||
@child-context-menu="
|
||||
(...args) => forwardEvent('child-context-menu', ...args)
|
||||
"
|
||||
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
|
||||
@root-layers-sort="(...args) => forwardEvent('root-layers-sort', ...args)"
|
||||
@child-layers-sort="(...args) => forwardEvent('child-layers-sort', ...args)"
|
||||
@cross-level-move="(...args) => forwardEvent('cross-level-move', ...args)"
|
||||
@select-child-layer="(...args) => forwardEvent('select-child-layer', ...args)"
|
||||
@start-child-layer-edit="(...args) => forwardEvent('start-child-layer-edit', ...args)"
|
||||
@child-context-menu="(...args) => forwardEvent('child-context-menu', ...args)"
|
||||
@toggle-child-visibility="
|
||||
(...args) => forwardEvent('toggle-child-visibility', ...args)
|
||||
"
|
||||
@toggle-child-lock="
|
||||
(...args) => forwardEvent('toggle-child-lock', ...args)
|
||||
"
|
||||
@toggle-child-lock="(...args) => forwardEvent('toggle-child-lock', ...args)"
|
||||
@finish-child-layer-edit="
|
||||
(...args) => forwardEvent('finish-child-layer-edit', ...args)
|
||||
"
|
||||
@@ -424,9 +371,7 @@ const canDeleteComputed = computed(() => {
|
||||
@child-layer-edit-keydown="
|
||||
(...args) => forwardEvent('child-layer-edit-keydown', ...args)
|
||||
"
|
||||
@toggle-group-expanded="
|
||||
(...args) => forwardEvent('toggle-group-expanded', ...args)
|
||||
"
|
||||
@toggle-group-expanded="(...args) => forwardEvent('toggle-group-expanded', ...args)"
|
||||
@delete-child="(...args) => forwardEvent('delete-child', ...args)"
|
||||
@rename-child="(...args) => forwardEvent('rename-child', ...args)"
|
||||
/>
|
||||
@@ -517,7 +462,9 @@ const canDeleteComputed = computed(() => {
|
||||
// 跨层级拖拽目标区域高亮
|
||||
.sortable-layers {
|
||||
position: relative;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
border-color 0.2s ease;
|
||||
border-radius: 4px;
|
||||
min-height: 40px; // 确保空组也有足够的拖拽区域
|
||||
|
||||
|
||||
@@ -72,26 +72,21 @@ const contextMenuItems = ref([]);
|
||||
// 计算属性:可排序的根级图层(排除背景层和固定层)
|
||||
const sortableRootLayers = computed(() => {
|
||||
if (!layers) return [];
|
||||
return layers.value.filter(
|
||||
(layer) => !layer.parentId && !layer.isFixed && !layer.isBackground
|
||||
);
|
||||
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);
|
||||
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)
|
||||
);
|
||||
return sortableRootLayers.value.filter((layer) => selectedLayerIds.value.includes(layer.id));
|
||||
});
|
||||
|
||||
// 计算属性:获取当前是否激活子图层
|
||||
@@ -224,9 +219,7 @@ function toggleLayerSelection(layer, event) {
|
||||
}
|
||||
|
||||
const isShift = event.shiftKey;
|
||||
const layerIndex = sortableRootLayers.value.findIndex(
|
||||
(l) => l.id === layer.id
|
||||
);
|
||||
const layerIndex = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
||||
console.log("isShift", isShift);
|
||||
|
||||
if (isShift && lastSelectedIndex.value !== -1) {
|
||||
@@ -386,9 +379,7 @@ async function ungroupSelectedLayer() {
|
||||
|
||||
try {
|
||||
const childLayerIds = await layerManager?.ungroupLayers(groupLayer.id);
|
||||
console.log(
|
||||
`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`
|
||||
);
|
||||
console.log(`✅ 成功解组图层组: ${groupLayer.name}, 子图层: ${childLayerIds}`);
|
||||
|
||||
// 清除选择状态
|
||||
clearSelection();
|
||||
@@ -406,9 +397,7 @@ function deleteSelectedLayers() {
|
||||
}
|
||||
|
||||
// 检查是否包含不能删除的图层
|
||||
const undeletableLayers = selectedLayers.filter(
|
||||
(layer) => layer.isBackground || layer.isFixed
|
||||
);
|
||||
const undeletableLayers = selectedLayers.filter((layer) => layer.isBackground || layer.isFixed);
|
||||
|
||||
if (undeletableLayers.length > 0) {
|
||||
console.warn("选择的图层中包含背景层或固定层,无法删除");
|
||||
@@ -417,10 +406,7 @@ function deleteSelectedLayers() {
|
||||
|
||||
// 检查删除后是否还有足够的普通图层
|
||||
const remainingNormalLayers = layers.value.filter(
|
||||
(layer) =>
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed &&
|
||||
!selectedLayerIds.value.includes(layer.id)
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
|
||||
).length;
|
||||
|
||||
if (remainingNormalLayers < 1) {
|
||||
@@ -460,9 +446,7 @@ function startEditing(layer) {
|
||||
|
||||
// 下一帧聚焦输入框并选中文本
|
||||
nextTick(() => {
|
||||
const inputElement = document.querySelector(
|
||||
`input[data-layer-id="${layer.id}"]`
|
||||
);
|
||||
const inputElement = document.querySelector(`input[data-layer-id="${layer.id}"]`);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
inputElement.select();
|
||||
@@ -494,41 +478,41 @@ function handleEditKeydown(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图层缩略图URL
|
||||
function getLayerThumbnail(layerId) {
|
||||
if (props.thumbnailManager) {
|
||||
return props.thumbnailManager.getLayerThumbnail(layerId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 获取图层缩略图URL - 弃用
|
||||
// function getLayerThumbnail(layerId) {
|
||||
// if (props.thumbnailManager) {
|
||||
// return props.thumbnailManager.getLayerThumbnail(layerId);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// 获取图层类型图标
|
||||
function getLayerTypeIcon(layer) {
|
||||
if (!layer) return "🖼️";
|
||||
// function getLayerTypeIcon(layer) {
|
||||
// if (!layer) return "🖼️";
|
||||
|
||||
if (isGroupLayer(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 "⬤";
|
||||
}
|
||||
}
|
||||
// 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 "🖼️";
|
||||
}
|
||||
// return "🖼️";
|
||||
// }
|
||||
|
||||
// 获取图层的子图层
|
||||
function getChildLayers(parentId) {
|
||||
@@ -557,22 +541,22 @@ const toggleGroupExpanded = (groupId) => {
|
||||
expandedGroupIds.value = new Set(expandedGroupIds.value);
|
||||
};
|
||||
|
||||
// 渲染单个图层项(递归组件)
|
||||
function renderLayerItem(layer, index) {
|
||||
if (!layer) return null;
|
||||
// // 渲染单个图层项(递归组件)
|
||||
// function renderLayerItem(layer, index) {
|
||||
// if (!layer) return null;
|
||||
|
||||
const isGroup = isGroupLayerType(layer);
|
||||
const children = isGroup ? getChildLayers(layer.id) : [];
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
// return {
|
||||
// id: layer.id,
|
||||
// name: layer.name,
|
||||
// isGroup: isGroup,
|
||||
// children: children,
|
||||
// fabricObject: layer.fabricObject,
|
||||
// visible: layer.visible,
|
||||
// };
|
||||
// }
|
||||
|
||||
// 处理图层点击事件
|
||||
function handleLayerClick(layer, event) {
|
||||
@@ -580,12 +564,7 @@ function handleLayerClick(layer, event) {
|
||||
event.stopPropagation();
|
||||
|
||||
// 如果按住修饰键,执行多选逻辑
|
||||
if (
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.shiftKey ||
|
||||
isMultiSelectMode.value
|
||||
) {
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey || isMultiSelectMode.value) {
|
||||
toggleLayerSelection(layer, event);
|
||||
} else {
|
||||
// 普通点击:进入单选模式
|
||||
@@ -595,11 +574,7 @@ function handleLayerClick(layer, event) {
|
||||
// 如果不是多选模式,才可激活图层
|
||||
// 1.如果是组,则设置组下的第一个子图层为活动图层
|
||||
// 2.否则直接设置活动图层
|
||||
if (
|
||||
isGroupLayerType(layer) &&
|
||||
layer.children &&
|
||||
layer.children.length > 0
|
||||
) {
|
||||
if (isGroupLayerType(layer) && layer.children && layer.children.length > 0) {
|
||||
// 如果是组图层,设置第一个子图层为活动图层
|
||||
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
|
||||
setActiveLayer(layer.children[0].id, { parentId: layer.id });
|
||||
@@ -609,9 +584,7 @@ function handleLayerClick(layer, event) {
|
||||
layerManager?.updateLayersObjectsInteractivity();
|
||||
}
|
||||
}
|
||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex(
|
||||
(l) => l.id === layer.id
|
||||
);
|
||||
lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,10 +648,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "栅格化图层",
|
||||
icon: "CPicture",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canRasterizeLayer?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
rasterizeLayer(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -688,10 +658,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "导出图层",
|
||||
icon: "CExport",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canRasterizeLayer?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canRasterizeLayer?.(layer.id),
|
||||
action: () => {
|
||||
exportLayerToImage(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -755,10 +722,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "置顶",
|
||||
icon: "CBottom",
|
||||
inverIcon: true, // 倒置图标
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToTop?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerToTop(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -767,10 +731,7 @@ function buildContextMenuItems(layer) {
|
||||
{
|
||||
label: "向上移动",
|
||||
icon: "CUp",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToTop?.(layer.id),
|
||||
disabled: layer.isBackground || layer.isFixed || !layerManager?.canMoveToTop?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerUp(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -780,9 +741,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "向下移动",
|
||||
icon: "CDown",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToBottom?.(layer.id),
|
||||
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerDown(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -792,9 +751,7 @@ function buildContextMenuItems(layer) {
|
||||
label: "置底",
|
||||
icon: "CBottom",
|
||||
disabled:
|
||||
layer.isBackground ||
|
||||
layer.isFixed ||
|
||||
!layerManager?.canMoveToBottom?.(layer.id),
|
||||
layer.isBackground || layer.isFixed || !layerManager?.canMoveToBottom?.(layer.id),
|
||||
action: () => {
|
||||
moveLayerToBottom(layer.id);
|
||||
hideContextMenu();
|
||||
@@ -856,18 +813,13 @@ function canDeleteLayers() {
|
||||
if (selectedLayers.length === 0) return false;
|
||||
|
||||
// 检查是否包含不能删除的图层
|
||||
const undeletableLayers = selectedLayers.filter(
|
||||
(layer) => layer.isBackground || layer.isFixed
|
||||
);
|
||||
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)
|
||||
(layer) => !layer.isBackground && !layer.isFixed && !selectedLayerIds.value.includes(layer.id)
|
||||
).length;
|
||||
|
||||
return remainingNormalLayers >= 1;
|
||||
@@ -904,9 +856,7 @@ function startChildLayerEdit(childLayer) {
|
||||
childLayer.tempName = childLayer.name;
|
||||
|
||||
nextTick(() => {
|
||||
const inputElement = document.querySelector(
|
||||
`input[data-child-layer-id="${childLayer.id}"]`
|
||||
);
|
||||
const inputElement = document.querySelector(`input[data-child-layer-id="${childLayer.id}"]`);
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
inputElement.select();
|
||||
@@ -943,8 +893,7 @@ function handleChildLayerEditKeydown(event, childLayer) {
|
||||
|
||||
// 选中子图层
|
||||
function selectChildLayer(layerId, parentId) {
|
||||
if (!isMultiSelectMode.value)
|
||||
layerManager?.setActiveLayer(layerId, { parentId });
|
||||
if (!isMultiSelectMode.value) layerManager?.setActiveLayer(layerId, { parentId });
|
||||
}
|
||||
|
||||
// 子图层右键菜单处理
|
||||
@@ -991,8 +940,7 @@ function buildChildLayerContextMenuItems(childLayer) {
|
||||
{
|
||||
label: childLayer.visible ? "隐藏图层" : "显示图层",
|
||||
icon: childLayer.visible ? "CUnEye" : "CEye",
|
||||
action: () =>
|
||||
toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
|
||||
action: () => toggleChildLayerVisibility(childLayer.id, childLayer.parentId),
|
||||
},
|
||||
{ type: "divider" },
|
||||
// 移出组
|
||||
@@ -1045,9 +993,7 @@ async function handleRootLayersSort(event) {
|
||||
|
||||
try {
|
||||
layerManager?.reorderLayers(oldIndex, newIndex, layerId);
|
||||
console.log(
|
||||
`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
|
||||
);
|
||||
console.log(`✅ 图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
|
||||
} catch (error) {
|
||||
console.error("❌ 图层排序命令执行失败:", error);
|
||||
emit("reorder-layers", {
|
||||
@@ -1085,12 +1031,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
|
||||
const layerToMove = childrenLayers[oldIndex];
|
||||
|
||||
if (!layerToMove) {
|
||||
console.error(
|
||||
"❌ 找不到要移动的子图层,oldIndex:",
|
||||
oldIndex,
|
||||
"childLayers:",
|
||||
childrenLayers
|
||||
);
|
||||
console.error("❌ 找不到要移动的子图层,oldIndex:", oldIndex, "childLayers:", childrenLayers);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1105,9 +1046,7 @@ async function handleChildLayersSort(event, childrenLayers, parentId) {
|
||||
|
||||
try {
|
||||
layerManager?.moveLayerToIndex({ parentId, oldIndex, newIndex, layerId });
|
||||
console.log(
|
||||
`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`
|
||||
);
|
||||
console.log(`✅ 子图层排序命令执行成功: ${layerToMove.name} (${oldIndex} -> ${newIndex})`);
|
||||
} catch (error) {
|
||||
console.error("❌ 子图层排序命令执行失败:", error);
|
||||
emit("reorder-child-layers", {
|
||||
@@ -1168,11 +1107,7 @@ async function mergeGroupLayer(groupId) {
|
||||
const childLayerId = await layerManager.mergeGroupLayers(groupId);
|
||||
if (childLayerId) {
|
||||
const groupLayer = layers.value.find((l) => l.id === groupId);
|
||||
console.log(
|
||||
`✅ 成功合并组图层: ${
|
||||
groupLayer?.name || groupId
|
||||
}, 生成 ${childLayerId} 图层`
|
||||
);
|
||||
console.log(`✅ 成功合并组图层: ${groupLayer?.name || groupId}, 生成 ${childLayerId} 图层`);
|
||||
} else {
|
||||
console.warn("合并组图层失败");
|
||||
}
|
||||
@@ -1284,8 +1219,7 @@ async function handleCrossLevelMove(moveData) {
|
||||
目标图层不是组图层: "只能将图层移动到组图层中",
|
||||
};
|
||||
|
||||
const userMessage =
|
||||
errorMessages[error.message] || `移动失败: ${error.message}`;
|
||||
const userMessage = errorMessages[error.message] || `移动失败: ${error.message}`;
|
||||
|
||||
// 这里可以触发一个全局的错误提示组件
|
||||
// 暂时使用console.warn,实际项目中应该替换为适当的提示方式
|
||||
@@ -1315,9 +1249,7 @@ async function executeDirectMove(moveData) {
|
||||
} 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
|
||||
);
|
||||
draggedLayer = sourceParent.children.find((child) => child.id === layerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1385,9 +1317,7 @@ async function moveRootToGroup(draggedLayer, toParentId, newIndex) {
|
||||
}
|
||||
|
||||
// 从顶级图层数组中移除
|
||||
const rootIndex = layers.value.findIndex(
|
||||
(layer) => layer.id === draggedLayer.id
|
||||
);
|
||||
const rootIndex = layers.value.findIndex((layer) => layer.id === draggedLayer.id);
|
||||
if (rootIndex !== -1) {
|
||||
layers.value.splice(rootIndex, 1);
|
||||
}
|
||||
@@ -1432,9 +1362,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
|
||||
} else {
|
||||
// 回退方案:手动更新图层关系
|
||||
// 从源父图层的children中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
}
|
||||
@@ -1461,12 +1389,7 @@ async function moveGroupToRoot(draggedLayer, fromParentId, newIndex) {
|
||||
}
|
||||
|
||||
// 在不同组之间移动
|
||||
async function moveGroupToGroup(
|
||||
draggedLayer,
|
||||
fromParentId,
|
||||
toParentId,
|
||||
newIndex
|
||||
) {
|
||||
async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex) {
|
||||
console.log("🔄 在不同组间移动:", {
|
||||
layerId: draggedLayer.id,
|
||||
fromParentId,
|
||||
@@ -1488,18 +1411,11 @@ async function moveGroupToGroup(
|
||||
|
||||
// 使用 layerManager 的方法在组间移动
|
||||
if (layerManager?.moveLayerBetweenGroups) {
|
||||
await layerManager.moveLayerBetweenGroups(
|
||||
draggedLayer.id,
|
||||
fromParentId,
|
||||
toParentId,
|
||||
newIndex
|
||||
);
|
||||
await layerManager.moveLayerBetweenGroups(draggedLayer.id, fromParentId, toParentId, newIndex);
|
||||
} else {
|
||||
// 回退方案:手动更新图层关系
|
||||
// 从源父图层中移除
|
||||
const childIndex = sourceParent.children.findIndex(
|
||||
(child) => child.id === draggedLayer.id
|
||||
);
|
||||
const childIndex = sourceParent.children.findIndex((child) => child.id === draggedLayer.id);
|
||||
if (childIndex !== -1) {
|
||||
sourceParent.children.splice(childIndex, 1);
|
||||
|
||||
@@ -1585,19 +1501,11 @@ async function moveGroupToGroup(
|
||||
>
|
||||
<SvgIcon name="CPlusTop" size="16"></SvgIcon>
|
||||
</div>
|
||||
<div
|
||||
class="add-layer-btn action-btn"
|
||||
@click="addLayer"
|
||||
:title="$t('添加图层')"
|
||||
>
|
||||
<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('全选图层')"
|
||||
>
|
||||
<div class="select-all-btn action-btn" @click="selectAllLayers" :title="$t('全选图层')">
|
||||
<SvgIcon name="CCheckbox" size="16"></SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1668,15 +1576,11 @@ async function moveGroupToGroup(
|
||||
:editing-name="editingLayerName"
|
||||
:can-delete="false"
|
||||
:isHidenDragHandle="true"
|
||||
@toggle-visibility="
|
||||
(...args) => forwardEvent('toggle-layer-visibility', ...args)
|
||||
"
|
||||
@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)
|
||||
"
|
||||
@update:editing-name="(...args) => forwardEvent('update:editing-name', ...args)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user