123
This commit is contained in:
@@ -1,28 +1,64 @@
|
||||
<template>
|
||||
<div class="layer-item">
|
||||
<div class="layer-item" @click="onClickLayer">
|
||||
<div class="drag"><svg-icon name="dc-drag" size="18" /></div>
|
||||
<div class="thumb"></div>
|
||||
<div class="name">
|
||||
<div @dblclick="editName = true" v-if="!editName">{{ layer.name }}</div>
|
||||
<input type="text" v-model="layer.name" v-else @blur="editName = false" />
|
||||
<div @dblclick="onClickEditName" v-if="!editName">
|
||||
{{ layer.info.name || '未命名图层' }}
|
||||
</div>
|
||||
<input
|
||||
v-else
|
||||
type="text"
|
||||
ref="nameInputRef"
|
||||
:value="layer.info.name"
|
||||
@blur="onChangeName"
|
||||
@keyup.enter="onChangeName"
|
||||
/>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<span><svg-icon name="dc-show" size="15" /></span>
|
||||
<span><svg-icon name="dc-delete" size="13" /></span>
|
||||
<span @click="onClickShowHide"
|
||||
><svg-icon :name="layer.visible ? 'dc-show' : 'dc-hide'" size="15"
|
||||
/></span>
|
||||
<span @click="onClickDelete"><svg-icon name="dc-delete" size="13" /></span>
|
||||
<span><svg-icon name="dc-down_arrow" size="11" /></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { ref, inject, nextTick } from 'vue'
|
||||
const layerManager = inject('layerManager') as any
|
||||
const editName = ref(false)
|
||||
const props = defineProps({
|
||||
layer: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
const editName = ref(false)
|
||||
const nameInputRef = ref(null)
|
||||
const onClickEditName = () => {
|
||||
editName.value = true
|
||||
nextTick(() => {
|
||||
nameInputRef.value.focus()
|
||||
})
|
||||
}
|
||||
const onChangeName = (e) => {
|
||||
const value = e.target.value
|
||||
editName.value = false
|
||||
const name = props.layer.info.name
|
||||
if (name !== value) {
|
||||
layerManager.setLayerNameByID(props.layer.info.id, value)
|
||||
}
|
||||
}
|
||||
const onClickShowHide = () => {
|
||||
layerManager.setLayerVisibleByID(props.layer.info.id, !props.layer.visible)
|
||||
}
|
||||
const onClickDelete = () => {
|
||||
layerManager.deleteLayerByID(props.layer.info.id)
|
||||
}
|
||||
const onClickLayer = () => {
|
||||
layerManager.setActiveID(props.layer.info.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -39,11 +75,11 @@
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
&:not([draging='true']) {
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: #ededed;
|
||||
}
|
||||
&:not([draging='true']):hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
&.active {
|
||||
background-color: #ededed !important;
|
||||
}
|
||||
&.drag {
|
||||
opacity: 0;
|
||||
@@ -51,7 +87,7 @@
|
||||
&.ghost,
|
||||
&.chosen {
|
||||
box-shadow: inset 0 0 5px #aaa;
|
||||
background-color: #ededed;
|
||||
background-color: #ededed !important;
|
||||
}
|
||||
> .drag {
|
||||
padding: 0.3rem;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<div class="content">
|
||||
<VueDraggable
|
||||
:model-value="sortableRootLayers"
|
||||
:model-value="list"
|
||||
@start="handleDragStart"
|
||||
@end="handleDragEnd"
|
||||
class="sortable-layers"
|
||||
@@ -36,10 +36,11 @@
|
||||
:scroll-speed="10"
|
||||
>
|
||||
<layer-item
|
||||
v-for="layer in sortableRootLayers"
|
||||
:key="layer.id"
|
||||
v-for="layer in list"
|
||||
:key="layer.info.id"
|
||||
:layer="layer"
|
||||
:draging="draging"
|
||||
:class="{ active: layer.info.id === layerManager.activeID.value }"
|
||||
/>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
@@ -51,20 +52,16 @@
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import layerItem from './layer-item.vue'
|
||||
const draging = ref(false)
|
||||
const sortableRootLayers = ref([
|
||||
{ id: 1, name: 'layer1' },
|
||||
{ id: 2, name: 'layer2' },
|
||||
{ id: 3, name: 'layer3' }
|
||||
])
|
||||
|
||||
const layerManager = inject('layerManager') as any
|
||||
const list = computed(() => layerManager.layers.value)
|
||||
const handleDragStart = () => {
|
||||
draging.value = true
|
||||
}
|
||||
const handleDragEnd = (event) => {
|
||||
draging.value = false
|
||||
const { from, to, oldIndex, newIndex, item } = event
|
||||
console.log('🔄 拖拽结束事件:', event)
|
||||
sortableRootLayers.value.splice(newIndex, 0, sortableRootLayers.value.splice(oldIndex, 1)[0])
|
||||
const { from, to, oldIndex, newIndex, data } = event
|
||||
console.log('oldIndex', data)
|
||||
layerManager.dragSort(data.info.id, newIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -107,6 +104,7 @@
|
||||
> .content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 20rem;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<layer-panel />
|
||||
<details-panel />
|
||||
<header-tools />
|
||||
<header-tools @export="exportCanvas" />
|
||||
<zoom
|
||||
:zoom="canvasManager.currentZoom.value / 100"
|
||||
:step="0.1"
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
// 管理器
|
||||
import { StateManager } from './manager/StateManager'
|
||||
import { LayerManager } from './manager/LayerManager'
|
||||
import { EventManager } from './manager/EventManager'
|
||||
import { CanvasManager } from './manager/CanvasManager'
|
||||
import { ToolManager } from './manager/ToolManager'
|
||||
@@ -50,6 +51,11 @@
|
||||
stateManager.setManager({ canvasManager, canvasRef })
|
||||
provide('canvasManager', canvasManager)
|
||||
|
||||
// 图层管理器
|
||||
const layerManager = new LayerManager({ stateManager, canvasManager })
|
||||
stateManager.setManager({ layerManager })
|
||||
provide('layerManager', layerManager)
|
||||
|
||||
// 事件管理器
|
||||
const eventManager = new EventManager({ stateManager })
|
||||
stateManager.setManager({ eventManager })
|
||||
@@ -90,6 +96,9 @@
|
||||
})
|
||||
canvasManager.resetZoom()
|
||||
}
|
||||
const exportCanvas = () => {
|
||||
console.log(canvasManager.getBitObjects())
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import '@vue-flow/core/dist/style.css';
|
||||
|
||||
@@ -13,7 +13,7 @@ export class AnimationManager {
|
||||
constructor(canvas, options = {}) {
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.canvas = canvas;
|
||||
this.currentZoom = options.currentZoom || { value: 100 };
|
||||
this.currentZoom = options.currentZoom;
|
||||
|
||||
// 动画相关属性
|
||||
this._zoomAnimation = null;
|
||||
|
||||
@@ -15,6 +15,9 @@ interface CanvasInitOptions {
|
||||
}
|
||||
export class CanvasManager {
|
||||
stateManager: any
|
||||
layerManager: any
|
||||
animationManager: any
|
||||
eventManager: any
|
||||
deviceInfo: any
|
||||
canvas: any
|
||||
canvasViewWidth: number
|
||||
@@ -22,8 +25,6 @@ export class CanvasManager {
|
||||
canvasWidth: number
|
||||
canvasHeight: number
|
||||
currentZoom: any
|
||||
animationManager: any
|
||||
eventManager: any
|
||||
constructor(options) {
|
||||
this.stateManager = options.stateManager;
|
||||
this.deviceInfo = detectDeviceType();
|
||||
@@ -34,6 +35,7 @@ export class CanvasManager {
|
||||
this.canvasViewHeight = options.canvasViewHeight || 1080
|
||||
}
|
||||
initCanvas(options: CanvasInitOptions) {
|
||||
this.layerManager = this.stateManager.layerManager
|
||||
this.setCanvasViewSize(options)
|
||||
this.canvasWidth = options.canvasWidth || 750
|
||||
this.canvasHeight = options.canvasHeight || 600
|
||||
@@ -62,7 +64,11 @@ export class CanvasManager {
|
||||
top: 20,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#f00'
|
||||
fill: '#f00',
|
||||
info: {
|
||||
id: 'rect1',
|
||||
name: 'rect1',
|
||||
}
|
||||
})
|
||||
this.canvas.add(rect)
|
||||
//创建圆形
|
||||
@@ -70,9 +76,37 @@ export class CanvasManager {
|
||||
left: 200,
|
||||
top: 200,
|
||||
radius: 50,
|
||||
fill: '#0f0'
|
||||
fill: '#0f0',
|
||||
info: {
|
||||
id: 'circle',
|
||||
name: 'circle',
|
||||
}
|
||||
})
|
||||
this.canvas.add(circle)
|
||||
// 文字
|
||||
const text = new fabric.Text('Hello World', {
|
||||
left: 300,
|
||||
top: 300,
|
||||
fontSize: 24,
|
||||
fill: '#000',
|
||||
info: {
|
||||
id: 'text1',
|
||||
name: 'text',
|
||||
}
|
||||
})
|
||||
this.canvas.add(text)
|
||||
// 文字
|
||||
const text2 = new fabric.Text('Hello World', {
|
||||
left: 300,
|
||||
top: 300,
|
||||
fontSize: 24,
|
||||
fill: '#000',
|
||||
info: {
|
||||
id: 'text2',
|
||||
name: 'tex2t',
|
||||
}
|
||||
})
|
||||
this.canvas.add(text2)
|
||||
this.animationManager = new AnimationManager(this.canvas, {
|
||||
currentZoom: this.currentZoom,
|
||||
canvasManager: this,
|
||||
@@ -81,9 +115,9 @@ export class CanvasManager {
|
||||
defaultDuration: 0.3, // 缩短默认动画时间
|
||||
});
|
||||
this.setupCanvasEvents()
|
||||
this.stateManager.toolManager.setTool(OperationType.PAN)
|
||||
|
||||
|
||||
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
||||
this.layerManager.updateLayers()
|
||||
this.layerManager.setActiveID(text2.info.id)
|
||||
}
|
||||
setupCanvasEvents() {
|
||||
// 创建画布事件管理器
|
||||
@@ -98,7 +132,36 @@ export class CanvasManager {
|
||||
resetZoom() {
|
||||
this.animationManager.resetZoom()
|
||||
}
|
||||
getObjects() {
|
||||
return this.canvas.getObjects() || []
|
||||
}
|
||||
getObjectById(id: string) {
|
||||
return this.getObjects().find((item: any) => item.info.id === id)
|
||||
}
|
||||
renderAll() {
|
||||
this.canvas.renderAll()
|
||||
}
|
||||
|
||||
deleteObjectById(id: string) {
|
||||
const object = this.getObjectById(id)
|
||||
if (object) {
|
||||
this.canvas.remove(object)
|
||||
this.layerManager.updateLayers()
|
||||
this.renderAll()
|
||||
}
|
||||
}
|
||||
// 拖拽排序
|
||||
dragSort(id, newIndex) {
|
||||
this.canvas.moveTo(this.getObjectById(id), newIndex)
|
||||
this.layerManager.updateLayers()
|
||||
this.renderAll()
|
||||
}
|
||||
getBitObjects() {
|
||||
return this.getObjects().map(v => {
|
||||
const object = v.toJSON("info");
|
||||
return object
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
46
src/components/Canvas/DepthCanvas/manager/LayerManager.ts
Normal file
46
src/components/Canvas/DepthCanvas/manager/LayerManager.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export class LayerManager {
|
||||
stateManager: any
|
||||
canvasManager: any
|
||||
layers: any
|
||||
activeID: any
|
||||
constructor(options) {
|
||||
this.stateManager = options.stateManager;
|
||||
this.canvasManager = options.canvasManager;
|
||||
this.layers = ref([])
|
||||
this.activeID = ref("")
|
||||
}
|
||||
setActiveID(id: string) { this.activeID.value = id }
|
||||
getLayerByID(id) {
|
||||
return this.layers.value.find((item: any) => item.info.id === id)
|
||||
}
|
||||
setLayerNameByID(id, name: string) {
|
||||
const layer = this.getLayerByID(id)
|
||||
if (layer) {
|
||||
layer.info.name = name
|
||||
this.canvasManager.renderAll()
|
||||
}
|
||||
}
|
||||
setLayerVisibleByID(id, visible: boolean) {
|
||||
const layer = this.getLayerByID(id)
|
||||
if (layer) {
|
||||
layer.set({
|
||||
visible: visible
|
||||
})
|
||||
this.canvasManager.renderAll()
|
||||
}
|
||||
}
|
||||
deleteLayerByID(id) {
|
||||
this.canvasManager.deleteObjectById(id)
|
||||
}
|
||||
// 拖拽排序
|
||||
dragSort(id, newIndex) {
|
||||
const index = Math.abs(this.layers.value.length - newIndex - 1)
|
||||
this.canvasManager.dragSort(id, index)
|
||||
}
|
||||
// 更新图层列表
|
||||
updateLayers() {
|
||||
this.layers.value = this.canvasManager.getObjects().reverse()
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,15 @@ export class StateManager {
|
||||
historyIndex: any
|
||||
|
||||
// 管理器
|
||||
eventManager: any
|
||||
canvasManager: any
|
||||
layerManager: any
|
||||
eventManager: any
|
||||
toolManager: any
|
||||
// 设置管理器
|
||||
setManager(options) {
|
||||
options.eventManager && (this.eventManager = options.eventManager)
|
||||
options.canvasManager && (this.canvasManager = options.canvasManager)
|
||||
options.layerManager && (this.layerManager = options.layerManager)
|
||||
options.toolManager && (this.toolManager = options.toolManager)
|
||||
}
|
||||
constructor(options) {
|
||||
|
||||
@@ -603,12 +603,12 @@ export class CanvasEventManager {
|
||||
handleDragEnd(opt, isTouch = false) {
|
||||
if (this.canvas.isDragging) {
|
||||
// 使用动画管理器处理惯性效果
|
||||
if (this.lastMousePositions.length > 1 && opt && opt.e) {
|
||||
this.animationManager.applyInertiaEffect(
|
||||
this.lastMousePositions,
|
||||
isTouch
|
||||
);
|
||||
}
|
||||
// if (this.lastMousePositions.length > 1 && opt && opt.e) {
|
||||
// this.animationManager.applyInertiaEffect(
|
||||
// this.lastMousePositions,
|
||||
// isTouch
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
this.canvas.isDragging = false;
|
||||
|
||||
Reference in New Issue
Block a user