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

511 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, nextTick, computed, inject } from "vue";
import { Checkbox } from "ant-design-vue";
import { VueDraggable } from "vue-draggable-plus";
import { isGroupLayer } from "../../utils/layerHelper";
// 设置组件名称,用于递归渲染
defineOptions({
name: "LayerItem",
});
const props = defineProps({
layer: {
type: Object,
required: true,
},
isChild: {
type: Boolean,
default: false,
},
isActive: {
type: Boolean,
default: false,
},
isSelected: {
type: Boolean,
default: false,
},
isMultiSelectMode: {
type: Boolean,
default: false,
},
isEditing: {
type: Boolean,
default: false,
},
editingName: {
type: String,
default: "",
},
canDelete: {
type: Boolean,
default: true,
},
// thumbnailUrl: {
// type: String,
// default: null,
// },
isHidenDragHandle: {
type: Boolean,
default: false,
},
expandedGroupIds: {
type: Set,
default: () => new Set(),
},
});
const emit = defineEmits([
"click",
"double-click",
"context-menu",
"checkbox-change",
"toggle-visibility",
"toggle-lock",
"delete",
"edit-confirm",
"edit-cancel",
"edit-keydown",
"touch-start",
"touch-move",
"touch-end",
"child-layers-sort",
"update-child-layers",
"toggle-group-expanded",
// 新增子图层专用事件
"toggle-child-visibility",
"toggle-child-lock",
"delete-child",
"rename-child",
// v-model相关事件
"update:editingName",
]);
const layerManager = inject("layerManager", null);
// 计算属性
const isGroupLayerType = computed(() => {
return isGroupLayer(props.layer);
});
// 计算属性:检查组图层是否展开
const isGroupExpanded = computed(() => {
return props.expandedGroupIds.has(props.layer.id);
});
// 获取子图层
const childLayers = computed(() => {
if (!isGroupLayerType.value) return [];
// 优先使用 layer.children 属性
if (props.layer.children && Array.isArray(props.layer.children)) {
return props.layer.children;
}
return [];
});
// 切换组图层展开/收起状态
const toggleGroupExpanded = () => {
emit("toggle-group-expanded", props.layer.id);
};
// 获取图层类型图标
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 getLayerTypeText(layerType) {
const typeMap = {
EMPTY: "空图层",
TEXT: "文本",
IMAGE: "图片",
SHAPE: "形状",
GROUP: "组合",
BACKGROUND: "背景",
FIXED: "固定",
};
return typeMap[layerType] || "未知";
}
// 事件处理
function handleClick(event) {
emit("click", props.layer, event);
}
function handleDoubleClick(event) {
emit("double-click", props.layer, event);
}
function handleContextMenu(event) {
emit("context-menu", event, props.layer);
}
function handleCheckboxChange(event) {
emit("checkbox-change", props.layer.id, event);
}
function handleToggleVisibility() {
if (props.isChild) {
// 子图层需要传递父图层ID - 从父级组件获取
const parentId = props.layer.parentId || findParentLayerId();
emit("toggle-child-visibility", props.layer.id, parentId);
} else {
// 一级图层
emit("toggle-visibility", props.layer.id);
}
}
function handleToggleLock() {
if (props.isChild) {
// 子图层需要传递父图层ID - 从父级组件获取
const parentId = props.layer.parentId || findParentLayerId();
emit("toggle-child-lock", props.layer.id, parentId);
} else {
// 一级图层
emit("toggle-lock", props.layer);
}
}
function handleDelete() {
if (!props.canDelete) {
console.warn("当前图层无法删除:", props.layer.id);
return;
}
if (props.isChild) {
// 子图层删除需要传递父图层ID
const parentId = props.layer.parentId || findParentLayerId();
if (parentId) {
emit("delete-child", props.layer.id, parentId);
} else {
console.warn("无法找到子图层的父图层ID:", props.layer.id);
}
} else {
// 一级图层删除
emit("delete", props.layer.id);
}
}
function handleEditConfirm() {
if (props.isChild) {
// 子图层重命名需要传递父图层ID
const parentId = props.layer.parentId || findParentLayerId();
if (props.editingName && props.editingName.trim() && parentId) {
emit("rename-child", props.layer.id, parentId, props.editingName.trim());
} else if (!parentId) {
console.warn("无法找到子图层的父图层ID:", props.layer.id);
}
// 发送编辑取消事件,清理编辑状态
emit("edit-cancel");
} else {
// 一级图层重命名
emit("edit-confirm");
}
}
function handleEditCancel() {
emit("edit-cancel");
}
function handleEditKeydown(event) {
emit("edit-keydown", event); // 修复事件名称,从 "edit-keyboard" 改为 "edit-keydown"
}
function handleTouchStart(event) {
emit("touch-start", event, props.layer);
}
function handleTouchMove(event) {
emit("touch-move", event);
}
function handleTouchEnd(event) {
emit("touch-end", event);
}
function handleUpdateChildLayers(newChildren) {
// 更新当前组图层的children数组
console.log(
"更新子图层顺序:",
"父图层ID:",
props.layer.id,
"新顺序:",
newChildren
);
emit("update-child-layers", props.layer.id, newChildren);
}
// 子图层递归事件处理
function handleChildClick(childLayer, event) {
emit("click", childLayer, event);
}
function handleChildDoubleClick(childLayer, event) {
emit("double-click", childLayer, event);
}
function handleChildContextMenu(event, childLayer) {
emit("context-menu", event, childLayer);
}
function handleChildToggleVisibility(childLayerId) {
emit("toggle-visibility", childLayerId);
}
function handleChildToggleLock(childLayer) {
emit("toggle-lock", childLayer);
}
// 动画钩子函数
function onEnter(el) {
// 设置初始状态
el.style.height = "0";
el.style.opacity = "0";
el.style.paddingTop = "0";
el.style.paddingBottom = "0";
el.style.marginTop = "0";
el.style.marginBottom = "0";
el.style.overflow = "hidden";
// 强制重排
el.offsetHeight;
// 获取最终高度
el.style.height = "auto";
const finalHeight = el.scrollHeight;
el.style.height = "0";
// 执行动画
requestAnimationFrame(() => {
el.style.transition = "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
el.style.height = finalHeight + "px";
el.style.opacity = "1";
el.style.paddingTop = "";
el.style.paddingBottom = "";
el.style.marginTop = "";
el.style.marginBottom = "";
});
}
function onLeave(el) {
// 设置当前高度
el.style.height = el.scrollHeight + "px";
el.style.overflow = "hidden";
// 强制重排
el.offsetHeight;
// 执行收起动画
el.style.transition = "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)";
el.style.height = "0";
el.style.opacity = "0";
el.style.paddingTop = "0";
el.style.paddingBottom = "0";
el.style.marginTop = "0";
el.style.marginBottom = "0";
}
// 查找父图层ID的辅助方法 - 增强版本
function findParentLayerId() {
// 首先检查 layer 对象是否已经有 parentId 属性
if (props.layer.parentId) {
return props.layer.parentId;
}
// 如果没有,尝试从 layerManager 中查找
if (layerManager && layerManager.layers) {
for (const layer of layerManager.layers.value) {
if (
layer.children &&
Array.isArray(layer.children) &&
layer.children.some((child) => child.id === props.layer.id)
) {
return layer.id;
}
}
}
console.warn("无法找到图层的父图层:", props.layer.id);
return null;
}
</script>
<template>
<div>
<!-- 主图层项 -->
<div
:class="[
'layer-item',
{
'child-layer': isChild,
active: isActive,
selected: isSelected,
'group-layer': isGroupLayerType,
editing: isEditing,
'multi-select-mode': isMultiSelectMode,
invisible: !layer.visible,
locked: layer.locked,
'fixed-layer': layer.isBackground || layer.isFixed,
},
]"
@click="handleClick"
@dblclick="handleDoubleClick"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@contextmenu.prevent="handleContextMenu"
>
<!-- 拖拽手柄 -->
<div class="layer-drag-handle" :title="$t('拖拽排序')">
<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
>
<Checkbox :checked="isSelected" @change="handleCheckboxChange" />
</div>
<!-- 图层预览图标 -->
<div class="layer-review">
<img
v-if="layer.thumbnailUrl"
:src="layer.thumbnailUrl"
class="layer-thumbnail"
:alt="$t('图层预览')"
/>
</div>
<!-- 图层名称 -->
<div class="layer-name-container" :title="layer.name">
<div class="layer-name-wrapper">
<span v-if="!isEditing" class="layer-name text-ellipsis">
{{ layer.name }}
</span>
<input
v-else
:value="editingName"
:data-layer-id="layer.id"
:data-child-layer-id="isChild ? layer.id : undefined"
class="layer-name-input"
@blur="handleEditConfirm"
@keydown="handleEditKeydown"
@click.stop
@input="$emit('update:editingName', $event.target.value)"
/>
</div>
</div>
<!-- 图层操作按钮 -->
<div class="layer-actions" v-if="!(isGroupLayerType && !isChild)">
<!-- 可见性切换 -->
<div
class="visibility-btn"
@click.stop="handleToggleVisibility"
:title="$t('显示/隐藏图层')"
>
<SvgIcon v-if="layer.visible" name="CEye" :size="16"></SvgIcon>
<SvgIcon v-else name="CUnEye" :size="16"></SvgIcon>
</div>
<!-- 锁定状态 -->
<span
v-if="layer.locked"
class="status-icon locked"
:class="{ disabled: layer.isBackground || layer.isFixed }"
:title="$t('锁定')"
@click.stop="handleToggleLock"
>
<SvgIcon name="CLock" :size="18"></SvgIcon>
</span>
<span
v-else
class="status-icon"
:title="$t('未锁定')"
@click.stop="handleToggleLock"
>
<SvgIcon name="CUnLock" :size="18"></SvgIcon>
</span>
<!-- 删除按钮 -->
<div
class="delete-btn"
:class="{ disabled: !canDelete }"
:title="$t('删除图层')"
@click.stop="handleDelete"
>
<SvgIcon name="CDelete" size="14"></SvgIcon>
</div>
</div>
<!-- 组图层展开/收起图标 -->
<div
v-if="isGroupLayerType && !isChild"
class="group-expand-icon"
@click.stop="toggleGroupExpanded"
@dblclick.stop=""
:title="isGroupExpanded ? $t('收起组') : $t('展开组')"
>
<SvgIcon
name="CRight"
:size="12"
:style="{
transform: isGroupExpanded ? 'rotate(45deg)' : 'rotate(0deg)',
transition: 'transform 0.2s',
}"
/>
</div>
<!-- 图层状态指示器 -->
<!-- <div v-if="!isChild" class="layer-status">
<span
v-if="isGroupLayerType"
class="status-icon group"
:title="$t('组图层')"
>
📁
</span>
</div> -->
</div>
</div>
</div>
</template>
<style scoped lang="less">
@import "./layersPanel.less";
</style>