1362 lines
37 KiB
Vue
1362 lines
37 KiB
Vue
<script setup>
|
||
import { computed, ref, nextTick, inject } from "vue";
|
||
import { findLayerRecursively, isGroupLayer } from "../../utils/layerHelper";
|
||
import ContextMenu from "./ContextMenu.vue";
|
||
import LayerItem from "./LayerItem.vue";
|
||
import LayersList from "./LayersList.vue"; // 引入 LayersList 组件
|
||
// // 导入命令类
|
||
// import {
|
||
// ReorderLayersCommand,
|
||
// // ReorderChildLayersCommand,
|
||
// GroupLayersCommand,
|
||
// UngroupLayersCommand,
|
||
// } from "../../commands/LayerCommands";
|
||
import { generateId } from "../../utils/helper";
|
||
|
||
const props = defineProps({
|
||
// layers: Array,
|
||
activeLayerId: String,
|
||
activeElementId: String,
|
||
thumbnailManager: Object, // 添加缩略图管理器属性
|
||
showFixedLayer: {
|
||
type: Boolean,
|
||
default: false, // 默认不显示固定层
|
||
},
|
||
});
|
||
|
||
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) => {
|
||
if (props.showFixedLayer)
|
||
return !layer.parentId && (layer.isFixed || layer.isBackground);
|
||
return !layer.parentId && layer.isBackground; // 只显示背景层,不显示固定层 - 固定层用来做红绿图模式 和 放模特
|
||
});
|
||
});
|
||
|
||
// 计算属性:获取当前选中的图层
|
||
const selectedLayers = computed(() => {
|
||
return sortableRootLayers.value.filter((layer) =>
|
||
selectedLayerIds.value.includes(layer.id)
|
||
);
|
||
});
|
||
|
||
// 计算属性:检查是否有选中的图层可以分组
|
||
const 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();
|
||
},
|
||
},
|
||
// 栅格化图层
|
||
{
|
||
label: "导出图层",
|
||
icon: "CExport",
|
||
disabled:
|
||
layer.isBackground ||
|
||
layer.isFixed ||
|
||
!layerManager?.canRasterizeLayer?.(layer.id),
|
||
action: () => {
|
||
exportLayerToImage(layer.id);
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
{ type: "divider" },
|
||
// 分组操作 - 带子菜单
|
||
{
|
||
label: "分组操作",
|
||
icon: "CGroup",
|
||
children: [
|
||
{
|
||
label: "创建组",
|
||
icon: "CCreateGroup",
|
||
disabled: !canGroupLayers.value,
|
||
action: () => {
|
||
groupSelectedLayers();
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
{
|
||
label: "解组",
|
||
icon: "CCancelGroup",
|
||
disabled: !canUngroupLayers.value,
|
||
action: () => {
|
||
ungroupSelectedLayer();
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
// {
|
||
// label: "合并组", // 不需要了 同栅格化功能一样了
|
||
// icon: "CMergeGroup",
|
||
// disabled: !isGroupLayer || isMultiple,
|
||
// action: () => {
|
||
// mergeGroupLayer(layer.id);
|
||
// hideContextMenu();
|
||
// },
|
||
// },
|
||
],
|
||
},
|
||
// 图层操作 - 带子菜单
|
||
{
|
||
label: "图层操作",
|
||
icon: "CLayout",
|
||
children: [
|
||
// // 锁定/解锁
|
||
// {
|
||
// label: layer.locked ? "解锁图层" : "锁定图层",
|
||
// icon: layer.locked ? "CUnLock" : "CLock",
|
||
// disabled: !canToggleLock.value,
|
||
// action: () => toggleSelectedLayersLock(),
|
||
// },
|
||
// // 显示/隐藏
|
||
// {
|
||
// label: layer.visible ? "隐藏图层" : "显示图层",
|
||
// icon: layer.visible ? "CUnEye" : "CEye",
|
||
// action: () => toggleSelectedLayersVisibility(),
|
||
// },
|
||
// { type: "divider" },
|
||
{
|
||
label: "置顶",
|
||
icon: "CBottom",
|
||
inverIcon: true, // 倒置图标
|
||
disabled:
|
||
layer.isBackground ||
|
||
layer.isFixed ||
|
||
!layerManager?.canMoveToTop?.(layer.id),
|
||
action: () => {
|
||
moveLayerToTop(layer.id);
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
{
|
||
label: "向上移动",
|
||
icon: "CUp",
|
||
disabled:
|
||
layer.isBackground ||
|
||
layer.isFixed ||
|
||
!layerManager?.canMoveToTop?.(layer.id),
|
||
action: () => {
|
||
moveLayerUp(layer.id);
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
{
|
||
label: "向下移动",
|
||
icon: "CDown",
|
||
disabled:
|
||
layer.isBackground ||
|
||
layer.isFixed ||
|
||
!layerManager?.canMoveToBottom?.(layer.id),
|
||
action: () => {
|
||
moveLayerDown(layer.id);
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
{
|
||
label: "置底",
|
||
icon: "CBottom",
|
||
disabled:
|
||
layer.isBackground ||
|
||
layer.isFixed ||
|
||
!layerManager?.canMoveToBottom?.(layer.id),
|
||
action: () => {
|
||
moveLayerToBottom(layer.id);
|
||
hideContextMenu();
|
||
},
|
||
},
|
||
],
|
||
},
|
||
].filter((item) => item !== null);
|
||
}
|
||
|
||
// 右键菜单操作方法
|
||
function 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) {
|
||
console.log(`✅ 成功栅格化图层: ${layerId}`);
|
||
} else {
|
||
console.warn("栅格化图层失败");
|
||
}
|
||
} catch (error) {
|
||
console.error("栅格化图层时发生错误:", error);
|
||
}
|
||
}
|
||
|
||
// 导出图层
|
||
async function exportLayerToImage(layerId) {
|
||
if (!layerManager?.rasterizeLayer) {
|
||
console.warn("导出图层功能不可用");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const success = await layerManager.exportLayerToImage(layerId);
|
||
if (success) {
|
||
console.log(`✅ 成功导出图层: ${layerId}`);
|
||
} else {
|
||
console.warn("导出图层失败");
|
||
}
|
||
} catch (error) {
|
||
console.error("导出图层时发生错误:", error);
|
||
}
|
||
}
|
||
|
||
// 合并组图层
|
||
async function mergeGroupLayer(groupId) {
|
||
if (!layerManager?.mergeGroupLayers) {
|
||
console.warn("合并组功能不可用");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const childLayerId = await layerManager.mergeGroupLayers(groupId);
|
||
if (childLayerId) {
|
||
const groupLayer = layers.value.find((l) => l.id === groupId);
|
||
console.log(
|
||
`✅ 成功合并组图层: ${
|
||
groupLayer?.name || groupId
|
||
}, 生成 ${childLayerId} 图层`
|
||
);
|
||
} else {
|
||
console.warn("合并组图层失败");
|
||
}
|
||
} catch (error) {
|
||
console.error("合并组图层时发生错误:", error);
|
||
}
|
||
}
|
||
|
||
// 置顶图层
|
||
function moveLayerToTop(layerId) {
|
||
if (!layerManager?.moveLayer) {
|
||
console.warn("移动图层功能不可用");
|
||
return;
|
||
}
|
||
|
||
const success = layerManager.moveLayer(layerId, "toTop");
|
||
if (success) {
|
||
const layer = layers.value.find((l) => l.id === layerId);
|
||
console.log(`✅ 成功置顶图层: ${layer?.name || layerId}`);
|
||
} else {
|
||
console.warn("置顶图层失败");
|
||
}
|
||
}
|
||
|
||
// 置底图层
|
||
function moveLayerToBottom(layerId) {
|
||
if (!layerManager?.moveLayer) {
|
||
console.warn("移动图层功能不可用");
|
||
return;
|
||
}
|
||
|
||
const success = layerManager.moveLayer(layerId, "toBottom");
|
||
if (success) {
|
||
const layer = layers.value.find((l) => l.id === layerId);
|
||
console.log(`✅ 成功置底图层: ${layer?.name || layerId}`);
|
||
} else {
|
||
console.warn("置底图层失败");
|
||
}
|
||
}
|
||
|
||
// 事件转发方法
|
||
const forwardEvent = (eventName, ...args) => {
|
||
emit(eventName, ...args);
|
||
};
|
||
</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">
|
||
<!-- 遍历固定层 -->
|
||
<!-- :thumbnail-url="getLayerThumbnail(layer.id)" -->
|
||
|
||
<LayerItem
|
||
v-for="layer in fixedLayers"
|
||
:key="layer.id"
|
||
:layer="layer"
|
||
:is-child="false"
|
||
:is-active="layer.id === activeLayerId"
|
||
:is-selected="false"
|
||
:is-multi-select-mode="false"
|
||
:is-editing="editingLayerId === layer.id"
|
||
:editing-name="editingLayerName"
|
||
:can-delete="false"
|
||
:isHidenDragHandle="true"
|
||
@toggle-visibility="
|
||
(...args) => forwardEvent('toggle-layer-visibility', ...args)
|
||
"
|
||
@edit-confirm="(...args) => forwardEvent('edit-confirm', ...args)"
|
||
@edit-cancel="(...args) => forwardEvent('edit-cancel', ...args)"
|
||
@edit-keydown="(...args) => forwardEvent('edit-keydown', ...args)"
|
||
@update:editing-name="
|
||
(...args) => forwardEvent('update:editing-name', ...args)
|
||
"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 右键菜单 -->
|
||
<ContextMenu
|
||
:visible="contextMenuVisible"
|
||
:position="contextMenuPosition"
|
||
:items="contextMenuItems"
|
||
@close="hideContextMenu"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="less">
|
||
@import "./layersPanel.less";
|
||
</style>
|