feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -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