feat: 裁剪组裁剪跟随选择组移动

This commit is contained in:
bighuixiang
2025-07-14 01:00:23 +08:00
parent 96e13cb22a
commit 24e9ba8ae5
80 changed files with 2052 additions and 4292 deletions

View File

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