Commit eecd65504f53e3acb1c953c7aa4dcfce906364a8

Authored by ly525
1 parent 25dbd43c

feat(editor): support move plugin on cawqnvas(zh: 画布上元素支持移动)

front-end/h5/src/components/core/editor/canvas/edit.js
  1 +import Shape from '../../support/shape'
1 export default { 2 export default {
2 - props: ['elements', 'handleElementClick'], 3 + props: ['elements', 'editingElement', 'handleClickElementProp', 'handleClickCanvasProp'],
3 methods: { 4 methods: {
4 /** 5 /**
5 * #!zh: renderCanvas 渲染中间画布 6 * #!zh: renderCanvas 渲染中间画布
@@ -10,17 +11,38 @@ export default { @@ -10,17 +11,38 @@ export default {
10 */ 11 */
11 renderCanvas (h, elements) { 12 renderCanvas (h, elements) {
12 return ( 13 return (
13 - <div style={{ height: '100%' }}> 14 + <div
  15 + style={{ height: '100%', position: 'relative' }}
  16 + class="canvas-editor-wrapper"
  17 + onClick={this.handleClickCanvasProp}
  18 + >
14 { 19 {
15 elements.map((element, index) => { 20 elements.map((element, index) => {
  21 + const style = element.getStyle()
16 const data = { 22 const data = {
17 - style: element.getStyle(), 23 + style,
  24 + class: 'element-on-edit-canvas', // TODO 添加为何添加 class 的原因:与 handleClickCanvas 配合
18 props: element.pluginProps, // #6 #3 25 props: element.pluginProps, // #6 #3
19 nativeOn: { 26 nativeOn: {
20 - click: () => this.handleElementClick(element) 27 + // 高亮当前点击的元素
  28 + // click: () => this.handleClickElementProp(element)
21 } 29 }
22 } 30 }
23 - return h(element.name, data) 31 + return (
  32 + <Shape
  33 + element={element}
  34 + editingElement={this.editingElement}
  35 + active={this.editingElement === element}
  36 + handleMousedownProp={() => {
  37 + // 在 shape 上面添加 mousedown,而非 plugin 本身添加 onClick 的原因:
  38 + // 在 mousedown 的时候,即可激活 editingElement(当前选中元素)
  39 + // 这样,就不用等到鼠标抬起的时候,也就是 plugin 的 onClick 生效的时候,才给选中的元素添加边框等选中效果
  40 + this.handleClickElementProp(element)
  41 + }}
  42 + >
  43 + {h(element.name, data)}
  44 + </Shape>
  45 + )
24 }) 46 })
25 } 47 }
26 </div> 48 </div>
front-end/h5/src/components/core/editor/index.js
@@ -54,6 +54,11 @@ export default { @@ -54,6 +54,11 @@ export default {
54 }, 54 },
55 setCurrentEditingElement (element) { 55 setCurrentEditingElement (element) {
56 this.editingElement = element 56 this.editingElement = element
  57 + },
  58 + handleClickCanvas (e) {
  59 + if (!e.target.classList.contains('element-on-edit-canvas')) {
  60 + this.editingElement = null
  61 + }
57 } 62 }
58 }, 63 },
59 render (h) { 64 render (h) {
@@ -104,7 +109,15 @@ export default { @@ -104,7 +109,15 @@ export default {
104 </div> 109 </div>
105 <div class='canvas-wrapper'> 110 <div class='canvas-wrapper'>
106 {/* { this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) } */} 111 {/* { this.isPreviewMode ? this.renderPreview(h, this.elements) : this.renderCanvas(h, this.elements) } */}
107 - { this.isPreviewMode ? <RenderPreviewCanvas elements={this.elements}/> : <RenderEditCanvas elements={this.elements} handleElementClick={this.setCurrentEditingElement} /> } 112 + { this.isPreviewMode
  113 + ? <RenderPreviewCanvas elements={this.elements}/>
  114 + : <RenderEditCanvas
  115 + elements={this.elements}
  116 + handleClickElementProp={this.setCurrentEditingElement}
  117 + handleClickCanvasProp={this.handleClickCanvas}
  118 + editingElement={this.editingElement}
  119 + />
  120 + }
108 </div> 121 </div>
109 </a-layout-content> 122 </a-layout-content>
110 </a-layout> 123 </a-layout>
front-end/h5/src/components/core/models/element.js
@@ -16,13 +16,15 @@ class Element { @@ -16,13 +16,15 @@ class Element {
16 this.name = ele.name 16 this.name = ele.name
17 this.editorConfig = ele.editorConfig || {} 17 this.editorConfig = ele.editorConfig || {}
18 this.pluginProps = {} 18 this.pluginProps = {}
  19 + this.commonStyle = {}
19 this.init() 20 this.init()
20 } 21 }
21 22
22 init () { 23 init () {
  24 + const commonStyle = this.commonStyle
23 // init common props 25 // init common props
24 Object.keys(defaultProps).forEach(key => { 26 Object.keys(defaultProps).forEach(key => {
25 - this[key] = defaultProps[key] 27 + commonStyle[key] = defaultProps[key]
26 }) 28 })
27 29
28 // init prop of plugin 30 // init prop of plugin
@@ -39,15 +41,16 @@ class Element { @@ -39,15 +41,16 @@ class Element {
39 41
40 getStyle () { 42 getStyle () {
41 const pluginProps = this.pluginProps 43 const pluginProps = this.pluginProps
  44 + const commonStyle = this.commonStyle
42 let style = { 45 let style = {
43 - top: `${pluginProps.top || this.top}px`,  
44 - left: `${pluginProps.left || this.left}px`,  
45 - width: `${pluginProps.width || this.width}px`,  
46 - height: `${pluginProps.height || this.height}px`,  
47 - fontSize: `${pluginProps.fontSize || this.fontSize}px`,  
48 - color: pluginProps.color || this.color,  
49 - backgroundColor: pluginProps.backgroundColor || this.backgroundColor,  
50 - textAlign: pluginProps.textAlign || this.textAlign 46 + top: `${pluginProps.top || commonStyle.top}px`,
  47 + left: `${pluginProps.left || commonStyle.left}px`,
  48 + width: `${pluginProps.width || commonStyle.width}px`,
  49 + height: `${pluginProps.height || commonStyle.height}px`,
  50 + fontSize: `${pluginProps.fontSize || commonStyle.fontSize}px`,
  51 + color: pluginProps.color || commonStyle.color,
  52 + backgroundColor: pluginProps.backgroundColor || commonStyle.backgroundColor,
  53 + textAlign: pluginProps.textAlign || commonStyle.textAlign
51 } 54 }
52 return style 55 return style
53 } 56 }
front-end/h5/src/components/core/styles/index.scss
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 // background: rgba(255,255,255,.2); 8 // background: rgba(255,255,255,.2);
9 margin: 16px 28px 16px 0; 9 margin: 16px 28px 16px 0;
10 float: left; 10 float: left;
11 - 11 +
12 line-height: 31px; 12 line-height: 31px;
13 text-align: center; 13 text-align: center;
14 color: white; 14 color: white;
@@ -28,6 +28,22 @@ @@ -28,6 +28,22 @@
28 } 28 }
29 } 29 }
30 30
  31 + .canvas-editor-wrapper {
  32 + .element--editing {
  33 + outline: 1px dashed #70c0ff !important;
  34 + }
  35 +
  36 + // !#zh 控制缩放大小的圆点
  37 + .shape__scale-point {
  38 + position: absolute;
  39 + background: #fff;
  40 + border: 1px solid rgb(89, 199, 249);
  41 + width: 6px;
  42 + height: 6px;
  43 + z-index: 1;
  44 + border-radius: 50%;
  45 + }
  46 + }
31 } 47 }
32 48
33 49
front-end/h5/src/components/core/support/shape.js 0 → 100644
  1 +/**
  2 + * #!zh: 上下左右 对应的 东南西北
  3 + * #!en: top(north)、bottom(south)、left(west)、right(east)
  4 + */
  5 +const directionKey = {
  6 + t: 'n',
  7 + b: 's',
  8 + l: 'w',
  9 + r: 'e'
  10 +}
  11 +
  12 +// #!zh: 四个边角、两条中线上的点
  13 +const points = ['lt', 'rt', 'lb', 'rb', 'l', 'r', 't', 'b']
  14 +
  15 +export default {
  16 + props: ['element', 'active', 'editingElement', 'handleMousedownProp'],
  17 + methods: {
  18 + /**
  19 + * 通过方位计算样式,主要是 top、left、鼠标样式
  20 + */
  21 + getPointStyle (point, isWrapElement = true) {
  22 + // const eleStyle = this.numbericElementStyle
  23 + const pos = this.element.commonStyle
  24 + const top = pos.top - 4 // !#zh 减4是为了让元素能够处于 border 的中间
  25 + const left = pos.left - 4
  26 + const height = pos.height
  27 + const width = pos.width
  28 + let hasT = /t/.test(point)
  29 + let hasB = /b/.test(point)
  30 + let hasL = /l/.test(point)
  31 + let hasR = /r/.test(point)
  32 + let newLeft = 0
  33 + let newTop = 0
  34 + if (point.length === 2) {
  35 + newLeft = hasL ? 0 : width
  36 + newTop = hasT ? 0 : height
  37 + } else {
  38 + // !#zh 上下点,宽度固定在中间
  39 + if (hasT || hasB) {
  40 + newLeft = width / 2
  41 + newTop = hasT ? 0 : height
  42 + }
  43 + // !#zh 左右点,高度固定在中间
  44 + if (hasL || hasR) {
  45 + newLeft = hasL ? 0 : width
  46 + newTop = height / 2
  47 + }
  48 + }
  49 + const style = {
  50 + left: `${newLeft + (isWrapElement ? 0 : left)}px`,
  51 + top: `${newTop + (isWrapElement ? 0 : top)}px`,
  52 + cursor: point.split('').reverse().map(m => directionKey[m]).join('') + '-resize'
  53 + }
  54 + return style
  55 + },
  56 + /**
  57 + * !#zh 主要目的是:阻止冒泡
  58 + */
  59 + handleWrapperClick (e) {
  60 + e.stopPropagation()
  61 + e.preventDefault()
  62 + },
  63 + mousedownForMark (point, downEvent) {
  64 + downEvent.stopPropagation()
  65 + downEvent.preventDefault() // Let's stop this event.
  66 + // let eleStyle = this.numbericElementStyle
  67 + const pos = this.element.commonStyle
  68 + let height = pos.height
  69 + let width = pos.width
  70 + let top = pos.top
  71 + let left = pos.left
  72 + let startX = downEvent.clientX
  73 + let startY = downEvent.clientY
  74 + let move = moveEvent => {
  75 + let currX = moveEvent.clientX
  76 + let currY = moveEvent.clientY
  77 + let disY = currY - startY
  78 + let disX = currX - startX
  79 + let hasT = /t/.test(point)
  80 + let hasB = /b/.test(point)
  81 + let hasL = /l/.test(point)
  82 + let hasR = /r/.test(point)
  83 + let newHeight = +height + (hasT ? -disY : hasB ? disY : 0)
  84 + let newWidth = +width + (hasL ? -disX : hasR ? disX : 0)
  85 + pos.height = newHeight > 0 ? newHeight : 0
  86 + pos.width = newWidth > 0 ? newWidth : 0
  87 + pos.left = +left + (hasL ? disX : 0)
  88 + pos.top = +top + (hasT ? disY : 0)
  89 + }
  90 + let up = () => {
  91 + document.removeEventListener('mousemove', move)
  92 + document.removeEventListener('mouseup', up)
  93 + }
  94 + document.addEventListener('mousemove', move)
  95 + document.addEventListener('mouseup', up)
  96 + },
  97 + /**
  98 + * !#zh 给 当前选中元素 添加鼠标移动相关事件
  99 + *
  100 + * @param {Object} element
  101 + * @param {mouseEvent} e
  102 + */
  103 + mousedownForElement (e, element) {
  104 + const pos = element.commonStyle
  105 + let startY = e.clientY
  106 + let startX = e.clientX
  107 + let startTop = pos.top
  108 + let startLeft = pos.left
  109 +
  110 + let move = moveEvent => {
  111 + // !#zh 移动的时候,不需要向后代元素传递事件,只需要单纯的移动就OK
  112 + moveEvent.stopPropagation()
  113 + moveEvent.preventDefault()
  114 +
  115 + let currX = moveEvent.clientX
  116 + let currY = moveEvent.clientY
  117 + pos.top = currY - startY + startTop
  118 + pos.left = currX - startX + startLeft
  119 + }
  120 +
  121 + let up = moveEvent => {
  122 + document.removeEventListener('mousemove', move, true)
  123 + document.removeEventListener('mouseup', up, true)
  124 + }
  125 + document.addEventListener('mousemove', move, true)
  126 + document.addEventListener('mouseup', up, true)
  127 + // TODO add comment
  128 + return true
  129 + },
  130 + handleMousedown (e) {
  131 + if (this.handleMousedownProp) {
  132 + this.handleMousedownProp()
  133 + this.mousedownForElement(e, this.element)
  134 + }
  135 + }
  136 + },
  137 + render (h) {
  138 + return (
  139 + <div
  140 + onClick={this.handleWrapperClick}
  141 + onMousedown={this.handleMousedown}
  142 + style={{ ...this.element.getStyle(), position: 'absolute' }}
  143 + >
  144 + {
  145 + this.active &&
  146 + points.map(point => {
  147 + const pointStyle = this.getPointStyle(point)
  148 + return (
  149 + <div
  150 + key={point}
  151 + data-point={point}
  152 + style={pointStyle}
  153 + class="shape__scale-point"
  154 + onMousedown={this.mousedownForMark.bind(this, point)}
  155 + ></div>
  156 + )
  157 + })
  158 + }
  159 + {this.$slots.default}
  160 + </div>
  161 + )
  162 + }
  163 +}