de/frontend/src/components/Editor/index.vue
2021-03-25 19:16:32 +08:00

308 lines
8.1 KiB
Vue

<template>
<div
id="editor"
class="editor"
:class="{ edit: isEdit }"
:style="{
width: changeStyleWithScale(canvasStyleData.width) + 'px',
height: changeStyleWithScale(canvasStyleData.height) + 'px',
}"
@contextmenu="handleContextMenu"
@mousedown="handleMouseDown"
>
<!-- 网格线 -->
<Grid />
<!--页面组件列表展示-->
<Shape
v-for="(item, index) in componentData"
:key="item.id"
:default-style="item.style"
:style="getShapeStyle(item.style)"
:active="item === curComponent"
:element="item"
:index="index"
:class="{ lock: item.isLock }"
>
<component
:is="item.component"
v-if="item.component != 'v-text'"
:id="'component' + item.id"
class="component"
:style="getComponentStyle(item.style)"
:prop-value="item.propValue"
:element="item"
/>
<component
:is="item.component"
v-else
:id="'component' + item.id"
class="component"
:style="getComponentStyle(item.style)"
:prop-value="item.propValue"
:element="item"
@input="handleInput"
/>
</Shape>
<!-- 右击菜单 -->
<ContextMenu />
<!-- 标线 -->
<MarkLine />
<!-- 选中区域 -->
<Area v-show="isShowArea" :start="start" :width="width" :height="height" />
</div>
</template>
<script>
import { mapState } from 'vuex'
import Shape from './Shape'
import { getStyle, getComponentRotatedStyle } from '@/utils/style'
import { $ } from '@/utils/utils'
import ContextMenu from './ContextMenu'
import MarkLine from './MarkLine'
import Area from './Area'
import eventBus from '@/utils/eventBus'
import Grid from './Grid'
import { changeStyleWithScale } from '@/utils/translate'
export default {
components: { Shape, ContextMenu, MarkLine, Area, Grid },
props: {
isEdit: {
type: Boolean,
default: true
}
},
data() {
return {
editorX: 0,
editorY: 0,
start: { // 选中区域的起点
x: 0,
y: 0
},
width: 0,
height: 0,
isShowArea: false
}
},
computed: mapState([
'componentData',
'curComponent',
'canvasStyleData',
'editor'
]),
mounted() {
// 获取编辑器元素
this.$store.commit('getEditor')
eventBus.$on('hideArea', () => {
this.hideArea()
})
},
methods: {
changeStyleWithScale,
handleMouseDown(e) {
// 如果没有选中组件 在画布上点击时需要调用 e.preventDefault() 防止触发 drop 事件
if (!this.curComponent || (this.curComponent.component != 'v-text' && this.curComponent.component != 'rect-shape')) {
e.preventDefault()
}
this.hideArea()
// 获取编辑器的位移信息,每次点击时都需要获取一次。主要是为了方便开发时调试用。
const rectInfo = this.editor.getBoundingClientRect()
this.editorX = rectInfo.x
this.editorY = rectInfo.y
const startX = e.clientX
const startY = e.clientY
this.start.x = startX - this.editorX
this.start.y = startY - this.editorY
// 展示选中区域
this.isShowArea = true
const move = (moveEvent) => {
debugger
this.width = Math.abs(moveEvent.clientX - startX)
this.height = Math.abs(moveEvent.clientY - startY)
if (moveEvent.clientX < startX) {
this.start.x = moveEvent.clientX - this.editorX
}
if (moveEvent.clientY < startY) {
this.start.y = moveEvent.clientY - this.editorY
}
}
const up = (e) => {
document.removeEventListener('mousemove', move)
document.removeEventListener('mouseup', up)
if (e.clientX == startX && e.clientY == startY) {
this.hideArea()
return
}
this.createGroup()
}
document.addEventListener('mousemove', move)
document.addEventListener('mouseup', up)
},
hideArea() {
this.isShowArea = 0
this.width = 0
this.height = 0
},
createGroup() {
// 获取选中区域的组件数据
const areaData = this.getSelectArea()
if (areaData.length <= 1) {
this.hideArea()
return
}
// 根据选中区域和区域中每个组件的位移信息来创建 Group 组件
// 要遍历选择区域的每个组件,获取它们的 left top right bottom 信息来进行比较
let top = Infinity; let left = Infinity
let right = -Infinity; let bottom = -Infinity
areaData.forEach(component => {
let style = {}
if (component.component == 'Group') {
component.propValue.forEach(item => {
const rectInfo = $(`#component${item.id}`).getBoundingClientRect()
style.left = rectInfo.left - this.editorX
style.top = rectInfo.top - this.editorY
style.right = rectInfo.right - this.editorX
style.bottom = rectInfo.bottom - this.editorY
if (style.left < left) left = style.left
if (style.top < top) top = style.top
if (style.right > right) right = style.right
if (style.bottom > bottom) bottom = style.bottom
})
} else {
style = getComponentRotatedStyle(component.style)
}
if (style.left < left) left = style.left
if (style.top < top) top = style.top
if (style.right > right) right = style.right
if (style.bottom > bottom) bottom = style.bottom
})
this.start.x = left
this.start.y = top
this.width = right - left
this.height = bottom - top
// 设置选中区域位移大小信息和区域内的组件数据
this.$store.commit('setAreaData', {
style: {
left,
top,
width: this.width,
height: this.height
},
components: areaData
})
},
getSelectArea() {
const result = []
// 区域起点坐标
const { x, y } = this.start
// 计算所有的组件数据,判断是否在选中区域内
this.componentData.forEach(component => {
if (component.isLock) return
const { left, top, width, height } = component.style
if (x <= left && y <= top && (left + width <= x + this.width) && (top + height <= y + this.height)) {
result.push(component)
}
})
// 返回在选中区域内的所有组件
return result
},
handleContextMenu(e) {
e.stopPropagation()
e.preventDefault()
// 计算菜单相对于编辑器的位移
let target = e.target
let top = e.offsetY
let left = e.offsetX
while (target instanceof SVGElement) {
target = target.parentNode
}
while (!target.className.includes('editor')) {
left += target.offsetLeft
top += target.offsetTop
target = target.parentNode
}
this.$store.commit('showContextMenu', { top, left })
},
getShapeStyle(style) {
const result = {};
['width', 'height', 'top', 'left', 'rotate'].forEach(attr => {
if (attr != 'rotate') {
result[attr] = style[attr] + 'px'
} else {
result.transform = 'rotate(' + style[attr] + 'deg)'
}
})
return result
},
getComponentStyle(style) {
return getStyle(style, ['top', 'left', 'width', 'height', 'rotate'])
},
handleInput(element, value) {
// 根据文本组件高度调整 shape 高度
this.$store.commit('setShapeStyle', { height: this.getTextareaHeight(element, value) })
},
getTextareaHeight(element, text) {
let { lineHeight, fontSize, height } = element.style
if (lineHeight === '') {
lineHeight = 1.5
}
const newHeight = (text.split('<br>').length - 1) * lineHeight * fontSize
return height > newHeight ? height : newHeight
}
}
}
</script>
<style lang="scss" scoped>
.editor {
position: relative;
background: #fff;
margin: auto;
.lock {
opacity: .5;
}
}
.edit {
.component {
outline: none;
width: 100%;
height: 100%;
}
}
</style>