合并画布代码

This commit is contained in:
X1627315083
2025-06-18 11:05:23 +08:00
parent 903c0ebdf5
commit 9c7fae36eb
118 changed files with 23633 additions and 8201 deletions

View File

@@ -0,0 +1,514 @@
<script setup>
import { ref, nextTick, computed, inject } from "vue";
import { Checkbox } from "ant-design-vue";
import { VueDraggable } from "vue-draggable-plus";
import SvgIcon from "../../../SvgIcon/index.vue";
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="16"
></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="thumbnailUrl"
:src="thumbnailUrl"
class="layer-thumbnail"
:alt="$t('图层预览')"
/>
<span v-else class="layer-type-icon">{{
getLayerTypeIcon(layer)
}}</span>
</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>