略略略略略略略略略略略略

This commit is contained in:
李志鹏
2026-01-08 14:29:10 +08:00
parent 567ae02c48
commit 4e0faed88e
17 changed files with 229 additions and 152 deletions

View File

@@ -287,7 +287,7 @@ const canDeleteComputed = computed(() => {
:is-child="isChild"
:is-active="layer.id === activeLayerId"
:is-selected="isLayerSelected(layer.id)"
:is-multi-select-mode="isMultiSelectMode"
:is-multi-select-mode="isMultiSelectMode && !layer.specialType"
:is-editing="editingLayerId === layer.id"
:editing-name="editingLayerName"
:can-delete="
@@ -296,7 +296,7 @@ const canDeleteComputed = computed(() => {
:expanded-group-ids="expandedGroupIds"
@click="(...args) => forwardEvent('layer-click', ...args)"
@double-click="(...args) => forwardEvent('layer-double-click', ...args)"
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
@context-menu="(...args) => !layer.specialType && forwardEvent('context-menu', ...args)"
@checkbox-change="(...args) => forwardEvent('checkbox-change', ...args)"
@toggle-visibility="(...args) => forwardEvent('toggle-visibility', ...args)"
@toggle-lock="(...args) => forwardEvent('toggle-lock', ...args)"
@@ -337,7 +337,7 @@ const canDeleteComputed = computed(() => {
:expanded-group-ids="expandedGroupIds"
:isChild="true"
:parentLayerId="layer.id"
:group-name="groupName"
:group-name="layer.specialType || groupName"
@layer-click="(...args) => forwardEvent('layer-click', ...args)"
@layer-double-click="(...args) => forwardEvent('layer-double-click', ...args)"
@context-menu="(...args) => forwardEvent('context-menu', ...args)"
@@ -385,17 +385,10 @@ const canDeleteComputed = computed(() => {
<style scoped lang="less">
// 从父组件的样式文件中继承相关样式
.layers-list {
flex: 1;
overflow-y: auto;
.sortable-layers {
min-height: 20px;
}
// .layer-group {
// // margin-bottom: 1px;
// }
.child-layers {
position: relative;
padding-left: 20px;

View File

@@ -1240,6 +1240,12 @@ async function handleCrossLevelMove(moveData) {
}
try {
const layer = findLayerRecursively(layers.value, layerId).layer;
const toLayer = findLayerRecursively(layers.value, toParentId).layer;
if(layer?.specialType || toLayer?.specialType) {
console.warn("当前图层不可移动到外部");
return;
}
// 如果有命令管理器,使用命令模式
if (commandManager) {
console.log("📝 使用命令模式执行跨层级移动");
@@ -1593,46 +1599,48 @@ async function moveGroupToGroup(draggedLayer, fromParentId, toParentId, newIndex
<small>{{ $t('Canvas.Hint') }}</small>
</div>
<div class="layers-list-container">
<!-- 图层列表组件 -->
<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"
@cross-level-move="handleCrossLevelMove"
@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"
/>
<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"
@cross-level-move="handleCrossLevelMove"
@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>
<!-- 固定层背景层和固定层 -->
<div v-if="fixedLayers.length > 0" class="fixed-layers">
<!-- 遍历固定层 -->

View File

@@ -11,9 +11,9 @@
flex-direction: column;
user-select: none;
z-index: 6;
overflow-y: auto;
width: 100%;
// max-height: 70vh;
overflow: hidden;
-webkit-overflow-scrolling: touch;
}
@@ -161,12 +161,12 @@
font-size: 1.1rem;
}
}
.layers-list-container{
overflow-y: auto;
}
// 图层列表
.layers-list {
position: relative;
flex: 1;
overflow-y: auto;
}
// 图层项样式

View File

@@ -179,7 +179,11 @@
import { ref, onMounted, watch, onUnmounted, reactive } from "vue";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { OperationType, SpecialLayerId } from "../../utils/layerHelper";
import {
OperationType,
SpecialLayerId,
SpecialType,
} from "../../utils/layerHelper";
import { loadImageUrlToLayer } from "../../utils/imageHelper";
import {
calculateRotatedTopLeftDeg,
@@ -280,6 +284,9 @@
const getActiveObject = (e) => {
console.log("==========切换激活对象", e, activeObjects);
activeObjects.value = [...e.selected];
// .filter((v) =>
// v.specialType ? v.specialType === SpecialType.REPEAT_O : true
// );// 过滤出印花对象
activeObjects.value.forEach((v) => {
v.layer = props.layerManager.getLayerById(v.layerId);
});

View File

@@ -8,8 +8,8 @@
flex-direction: column;
user-select: none;
z-index: 6;
overflow-y: auto;
width: 100%;
overflow: hidden;
-webkit-overflow-scrolling: touch;
}
.layers-header {
@@ -132,10 +132,11 @@
color: #666;
font-size: 1.1rem;
}
.layers-list-container {
overflow-y: auto;
}
.layers-list {
position: relative;
flex: 1;
overflow-y: auto;
}
.layer-item {
position: relative;

View File

@@ -1,5 +1,5 @@
<template>
<div class="angle-tool">
<div class="angle-tool" :disabled="disabled">
<div
ref="dishRef"
class="dish"
@@ -11,7 +11,13 @@
</div>
</div>
<div class="input">
<input type="number" v-model="angle" @input="onInput" @change="onChange" />
<input
type="number"
v-model="angle"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
</div>
</template>
@@ -25,14 +31,22 @@
type: Number,
default: 0,
},
disabled: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["change", "input"]);
const angle = ref(props.angle);
watch(() => props.angle, (value) => {
angle.value = value;
});
watch(
() => props.angle,
(value) => {
angle.value = value;
}
);
const dishRef = ref<HTMLDivElement>();
const mousedown = (e: MouseEvent | TouchEvent) => {
if (props.disabled) return;
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const { left, top, width, height } =
@@ -56,9 +70,10 @@
document.addEventListener("mouseup", mouseup);
document.addEventListener("touchend", mouseup);
};
const onInput = () => emit("input", angle.value);
const onInput = () => !props.disabled && emit("input", angle.value);
var changeTime: any = null;
const onChange = () => {
if (props.disabled) return;
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", angle.value), 500);
};
@@ -79,10 +94,17 @@
display: flex;
align-items: center;
width: 100%;
--color: #000;
&[disabled="true"] {
--color: #b2b2b2;
> .dish {
cursor: not-allowed;
}
}
> .dish {
width: 24px;
height: 24px;
border: 1px solid #000;
border: 1px solid var(--color);
border-radius: 50%;
cursor: pointer;
> .pointer {
@@ -98,7 +120,7 @@
transform: translate(-50%, 0);
width: 35%;
height: 35%;
background-color: #000;
background-color: var(--color);
border-radius: 50%;
}
}
@@ -106,7 +128,7 @@
> .input {
margin-left: 5px;
font-size: 14px;
color: #000;
color: var(--color);
flex: 1;
// min-width: 45px;
// max-width: 80px;

View File

@@ -5,6 +5,7 @@
@change="change"
:defaultValue="defaultValue"
@dropdownVisibleChange="dropdownVisibleChange"
:disabled="disabled"
>
<a-select-option
v-for="v in list"
@@ -21,6 +22,10 @@
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
defaultValue: {
default: "",
},

View File

@@ -1,5 +1,5 @@
<template>
<div class="slider">
<div class="slider" :disabled="disabled">
<div class="input-range">
<span
class="tip"
@@ -16,6 +16,7 @@
:step="props.step"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
<div class="input" v-show="isInput">
@@ -27,6 +28,7 @@
:step="props.step"
@input="onInput"
@change="onChange"
:disabled="disabled"
/>
</div>
</div>
@@ -35,6 +37,10 @@
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
disabled: {
type: Boolean,
default: false,
},
value: {
type: Number,
default: 0,
@@ -66,9 +72,10 @@
() => props.value,
(v) => (value.value = v)
);
const onInput = () => emit("input", Number(value.value));
const onInput = () => !props.disabled && emit("input", Number(value.value));
var changeTime: any = null;
const onChange = () => {
if (props.disabled) return;
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", Number(value.value)), 500);
};