Files
my-v0/src/editor/canvas/index.vue
T

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>