174 lines
5.4 KiB
Vue
174 lines
5.4 KiB
Vue
<!-- 主画布 -->
|
|
<script setup lang="ts">
|
|
import { createNode, getMaterialComponent } from '@/materials'
|
|
import type { MaterialSchema } from '@/schema/materials'
|
|
import { useEditorStore } from '@/stores/editor'
|
|
import { storeToRefs } from 'pinia'
|
|
import Moveable from 'vue3-moveable'
|
|
import { VueSelecto } from 'vue3-selecto'
|
|
import SketchRuler from 'vue3-sketch-ruler'
|
|
import 'vue3-sketch-ruler/lib/style.css'
|
|
import { useCanvasRuler } from './composables/useCanvasRuler'
|
|
import { useMoveable } from './composables/useMoveable'
|
|
import { useSelection } from './composables/useSelection'
|
|
|
|
defineOptions({
|
|
name: 'CanvasRoot',
|
|
})
|
|
|
|
const editorStore = useEditorStore()
|
|
const { nodes, canvas } = storeToRefs(editorStore)
|
|
|
|
const moveableRef = useTemplateRef('moveableRef')
|
|
const stageRef = useTemplateRef('stageRef')
|
|
// canvas容器的ref
|
|
const canvasRootRef = ref<HTMLElement | null>(null)
|
|
|
|
const { RULER_THICK, scale, lines, rulerCanvasSize, viewportSize, palette, onZoomChange } =
|
|
useCanvasRuler(moveableRef, canvasRootRef)
|
|
const { onDrag, onResize, onDragGroup, onResizeGroup, onStart, onEnd } = useMoveable(moveableRef)
|
|
const { selectedTarget, onSelect, onClearSelect, onSelectEnd } = useSelection(stageRef, moveableRef)
|
|
|
|
/** 设计稿白底:宽高和背景色立刻跟 store */
|
|
const canvasStyle = computed(() => {
|
|
return {
|
|
width: canvas.value.width,
|
|
height: canvas.value.height,
|
|
backgroundColor: canvas.value.backgroundColor,
|
|
}
|
|
})
|
|
|
|
// 获取节点的样式
|
|
const getNodeStyle = (node: MaterialSchema, zIndex: number) => {
|
|
return {
|
|
width: `${node.layout.width}px`,
|
|
height: `${node.layout.height}px`,
|
|
left: `${node.layout.x}px`,
|
|
top: `${node.layout.y}px`,
|
|
zIndex: zIndex + 1,
|
|
}
|
|
}
|
|
|
|
// 拖拽放置节点
|
|
const onDrop = (e: DragEvent) => {
|
|
const data = e.dataTransfer?.getData('schema')
|
|
if (data) {
|
|
const node = createNode(JSON.parse(data))
|
|
node.layout.x = e.offsetX - node.layout.width / 2
|
|
node.layout.y = e.offsetY - node.layout.height / 2
|
|
|
|
editorStore.addNode(node)
|
|
editorStore.selectNode(node.id)
|
|
}
|
|
}
|
|
|
|
// 右键菜单操作映射
|
|
const commendMap = {
|
|
copy: () => editorStore.selectedNode && editorStore.copyNode(editorStore.selectedNode),
|
|
remove: () => editorStore.selectedNode && editorStore.removeNode(editorStore.selectedNode),
|
|
moveBottom: () => editorStore.selectedNode && editorStore.moveTop(editorStore.selectedNode), // 列表渲染时反转了,这里也反转操作
|
|
moveTop: () => editorStore.selectedNode && editorStore.moveBottom(editorStore.selectedNode),
|
|
toggleLock: () => {
|
|
if (editorStore.selectedNode) {
|
|
editorStore.toggleLock(editorStore.selectedNode)
|
|
}
|
|
selectedTarget.value = []
|
|
},
|
|
}
|
|
|
|
// 右键菜单操作
|
|
const onCommand = (command: keyof typeof commendMap) => {
|
|
commendMap[command]?.()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="canvasRootRef" class="relative h-full w-full min-h-0 min-w-0 overflow-hidden isolate">
|
|
<SketchRuler
|
|
v-if="viewportSize.width && viewportSize.height"
|
|
:palette="palette"
|
|
:thick="RULER_THICK"
|
|
:width="viewportSize.width"
|
|
:height="viewportSize.height"
|
|
:canvas-width="rulerCanvasSize.width"
|
|
:canvas-height="rulerCanvasSize.height"
|
|
:scale="scale"
|
|
:lines="lines"
|
|
@zoomchange="onZoomChange"
|
|
>
|
|
<div
|
|
ref="stageRef"
|
|
class="relative"
|
|
:style="{
|
|
width: `${canvasStyle.width}px`,
|
|
height: `${canvasStyle.height}px`,
|
|
backgroundColor: canvasStyle.backgroundColor,
|
|
}"
|
|
@dragover.prevent
|
|
@drop="onDrop"
|
|
@mousedown.self="onClearSelect"
|
|
>
|
|
<el-dropdown
|
|
v-for="(node, index) in nodes"
|
|
:key="node.id"
|
|
trigger="contextmenu"
|
|
@command="onCommand"
|
|
>
|
|
<div
|
|
:data-node-id="node.id"
|
|
:data-node-locked="node.locked"
|
|
class="canvas-node absolute"
|
|
:style="getNodeStyle(node, index)"
|
|
@mousedown="onSelect(node)"
|
|
>
|
|
<component :is="getMaterialComponent(node.type)" :schema="node" />
|
|
</div>
|
|
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item command="copy">复制</el-dropdown-item>
|
|
<el-dropdown-item command="remove">移除</el-dropdown-item>
|
|
<el-dropdown-item command="moveTop">置顶</el-dropdown-item>
|
|
<el-dropdown-item command="moveBottom">置底</el-dropdown-item>
|
|
<el-dropdown-item command="toggleLock">
|
|
{{ node.locked ? '解锁' : '锁定' }}
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
</div>
|
|
</SketchRuler>
|
|
|
|
<VueSelecto
|
|
v-if="stageRef"
|
|
:container="stageRef"
|
|
:dragContainer="stageRef"
|
|
:select-from-inside="false"
|
|
:selectableTargets="['.canvas-node']"
|
|
toggle-continue-select="shift"
|
|
@selectEnd="onSelectEnd"
|
|
/>
|
|
<Moveable
|
|
ref="moveableRef"
|
|
:target="selectedTarget"
|
|
:draggable="true"
|
|
:resizable="true"
|
|
:origin="false"
|
|
@drag="onDrag"
|
|
@drag-start="onStart"
|
|
@drag-end="onEnd"
|
|
@drag-group="onDragGroup"
|
|
@drag-group-start="onStart"
|
|
@drag-group-end="onEnd"
|
|
@resize="onResize"
|
|
@resize-start="onStart"
|
|
@resize-end="onEnd"
|
|
@resize-group="onResizeGroup"
|
|
@resize-group-start="onStart"
|
|
@resize-group-end="onEnd"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped></style>
|