Commit 67b129aef0b73dcb1a988466dbdf72e23c1f62e7
Committed by
小小鲁班
1 parent
f002c516
feat: support drag&drop element from the left panel to the canvas; !#: 支持从左侧元素列表中拖拽元素至中间画布
Showing
4 changed files
with
173 additions
and
61 deletions
front-end/h5/src/components/core/editor/drag-mixin.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Author: ly525 | ||
| 3 | + * @Date: 2020-05-17 17:21:04 | ||
| 4 | + * @LastEditors: ly525 | ||
| 5 | + * @LastEditTime: 2020-05-24 18:09:23 | ||
| 6 | + * @FilePath: /luban-h5/front-end/h5/src/components/core/editor/drag-mixin.js | ||
| 7 | + * @Github: https://github.com/ly525/luban-h5 | ||
| 8 | + * @Copyright 2018 - 2019 luban-h5. All Rights Reserved | ||
| 9 | + * @Description: | ||
| 10 | + * 组件拖拽至画布功能 | ||
| 11 | + * 其中部分代码参考自:https://github.com/hakubox/haku-form-design,已经征得作者同意,目的是后续考虑做 tab 之类的嵌套容器 | ||
| 12 | + */ | ||
| 13 | + | ||
| 14 | +let dragDom = null | ||
| 15 | + | ||
| 16 | +let dragConfig = { | ||
| 17 | + isPreDrag: false, // 准备拖拽 | ||
| 18 | + isDrag: false, // 正式拖拽 | ||
| 19 | + origin: { | ||
| 20 | + clientY: 0, // 鼠标按下时候时候值 | ||
| 21 | + clientX: 0, | ||
| 22 | + layerX: 0, // 鼠标.x 相对于元素左上角.left 的偏移 | ||
| 23 | + layerY: 0 // 鼠标.y 相对于元素左上角.top 的偏移 | ||
| 24 | + } | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +class Drag { | ||
| 28 | + constructor (options) { | ||
| 29 | + this.mousedown = options.mousedown | ||
| 30 | + this.mousemove = options.mousemove | ||
| 31 | + this.mouseup = options.mouseup | ||
| 32 | + | ||
| 33 | + this._mousedown = this._mousedown.bind(this) | ||
| 34 | + this._mousemove = this._mousemove.bind(this) | ||
| 35 | + this._mouseup = this._mouseup.bind(this) | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + start (e) { | ||
| 39 | + this._mousedown(e) | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + _mousedown (e) { | ||
| 43 | + this.mousedown(e) | ||
| 44 | + this.toggleListener('add') | ||
| 45 | + } | ||
| 46 | + | ||
| 47 | + _mousemove (e) { | ||
| 48 | + console.log('mousemove') | ||
| 49 | + this.mousemove(e) | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + _mouseup (e) { | ||
| 53 | + console.log('mouseup') | ||
| 54 | + this.mouseup(e) | ||
| 55 | + this.toggleListener('remove') | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + toggleListener (action) { | ||
| 59 | + document[`${action}EventListener`]('mousemove', this._mousemove) | ||
| 60 | + document[`${action}EventListener`]('mouseup', this._mouseup) | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +export default { | ||
| 65 | + data () { | ||
| 66 | + return { | ||
| 67 | + | ||
| 68 | + } | ||
| 69 | + }, | ||
| 70 | + methods: { | ||
| 71 | + /** | ||
| 72 | + * | ||
| 73 | + * @param {*} element | ||
| 74 | + * @param {*} e | ||
| 75 | + */ | ||
| 76 | + handleDragStartFromMixin (element, e) { | ||
| 77 | + // https://developer.mozilla.org/zh-CN/docs/Web/API/event.button | ||
| 78 | + // 0 为 左键点击. | ||
| 79 | + if (e.button !== 0) return | ||
| 80 | + if (dragDom) { | ||
| 81 | + document.body.removeChild(dragDom) | ||
| 82 | + dragDom = null | ||
| 83 | + } | ||
| 84 | + this.dragElement = element | ||
| 85 | + dragDom = e.target.cloneNode(true) | ||
| 86 | + document.body.appendChild(dragDom) | ||
| 87 | + | ||
| 88 | + new Drag({ | ||
| 89 | + mousedown: this.mousedown, | ||
| 90 | + mousemove: this.mousemove, | ||
| 91 | + mouseup: this.mouseup | ||
| 92 | + }).start(e) | ||
| 93 | + }, | ||
| 94 | + /** | ||
| 95 | + * | ||
| 96 | + * @param {*} e | ||
| 97 | + */ | ||
| 98 | + mousedown (e) { | ||
| 99 | + // 鼠标.x 相对于元素左上角 的偏移 | ||
| 100 | + const { layerX, layerY } = e | ||
| 101 | + dragConfig.origin.layerX = layerX | ||
| 102 | + dragConfig.origin.layerY = layerY | ||
| 103 | + dragConfig.origin.clientX = e.clientX | ||
| 104 | + dragConfig.origin.clientY = e.clientY | ||
| 105 | + | ||
| 106 | + dragDom.style.position = 'absolute' | ||
| 107 | + dragDom.style.left = e.clientX - layerX + 'px' | ||
| 108 | + dragDom.style.top = e.clientY - layerY + 'px' | ||
| 109 | + dragDom.classList.add('dragging-dom-ele', 'hidden') | ||
| 110 | + | ||
| 111 | + dragConfig.isPreDrag = true | ||
| 112 | + }, | ||
| 113 | + /** 组件拖拽中 */ | ||
| 114 | + mousemove (e) { | ||
| 115 | + dragDom.classList.remove('hidden') | ||
| 116 | + const { layerX, layerY } = dragConfig.origin | ||
| 117 | + dragDom.style.left = e.clientX - layerX + 'px' | ||
| 118 | + dragDom.style.top = e.clientY - layerY + 'px' | ||
| 119 | + }, | ||
| 120 | + mouseup (e) { | ||
| 121 | + const { layerX, layerY } = dragConfig.origin | ||
| 122 | + document.body.removeChild(dragDom) | ||
| 123 | + dragDom = null | ||
| 124 | + | ||
| 125 | + const canvasWrapper = document.querySelector('.canvas-wrapper') | ||
| 126 | + const position = canvasWrapper.getBoundingClientRect() | ||
| 127 | + this.dragElement && this.clone({ | ||
| 128 | + ...this.dragElement, | ||
| 129 | + customStyle: { | ||
| 130 | + left: e.clientX - layerX - position.left, | ||
| 131 | + top: e.clientY - layerY - position.top | ||
| 132 | + } | ||
| 133 | + }) | ||
| 134 | + } | ||
| 135 | + }, | ||
| 136 | + updated () { | ||
| 137 | + console.log('updated') | ||
| 138 | + } | ||
| 139 | +} |
front-end/h5/src/components/core/editor/index.js
| @@ -22,26 +22,7 @@ import LangSelect from '@/components/common/header/LangSelect.vue' | @@ -22,26 +22,7 @@ import LangSelect from '@/components/common/header/LangSelect.vue' | ||
| 22 | import Feedback from '@/components/common/feedback/index' | 22 | import Feedback from '@/components/common/feedback/index' |
| 23 | import AdjustLineV from '@/components/core/support/adjust-line/vertical' | 23 | import AdjustLineV from '@/components/core/support/adjust-line/vertical' |
| 24 | 24 | ||
| 25 | -// const sidebarMenus = [ | ||
| 26 | -// { | ||
| 27 | -// i18nLabel: 'editor.sidebar.components', | ||
| 28 | -// label: '组件列表', | ||
| 29 | -// value: 'pluginList', | ||
| 30 | -// antIcon: 'bars' | ||
| 31 | -// }, | ||
| 32 | -// { | ||
| 33 | -// i18nLabel: 'editor.sidebar.pages', | ||
| 34 | -// label: '页面管理', | ||
| 35 | -// value: 'pageManagement', | ||
| 36 | -// antIcon: 'snippets' | ||
| 37 | -// }, | ||
| 38 | -// { | ||
| 39 | -// i18nLabel: 'editor.sidebar.templates', | ||
| 40 | -// label: '免费模板', | ||
| 41 | -// value: 'freeTemplate', | ||
| 42 | -// antIcon: 'appstore' | ||
| 43 | -// } | ||
| 44 | -// ] | 25 | +import DragMixin from './drag-mixin' |
| 45 | 26 | ||
| 46 | const fixedTools = [ | 27 | const fixedTools = [ |
| 47 | { | 28 | { |
| @@ -103,6 +84,7 @@ const fixedTools = [ | @@ -103,6 +84,7 @@ const fixedTools = [ | ||
| 103 | ] | 84 | ] |
| 104 | 85 | ||
| 105 | export default { | 86 | export default { |
| 87 | + mixins: [DragMixin], | ||
| 106 | name: 'Editor', | 88 | name: 'Editor', |
| 107 | components: { | 89 | components: { |
| 108 | LogoOfHeader, | 90 | LogoOfHeader, |
| @@ -184,7 +166,11 @@ export default { | @@ -184,7 +166,11 @@ export default { | ||
| 184 | <strong>{ this.$t('editor.tip.click') }</strong>{ this.$t('editor.tip.click') } | 166 | <strong>{ this.$t('editor.tip.click') }</strong>{ this.$t('editor.tip.click') } |
| 185 | </i18n> | 167 | </i18n> |
| 186 | </div> | 168 | </div> |
| 187 | - <RenderShortcutsPanel pluginsList={this.pluginsList} handleClickShortcut={this.clone} /> | 169 | + <RenderShortcutsPanel |
| 170 | + pluginsList={this.pluginsList} | ||
| 171 | + handleClickShortcut={this.clone} | ||
| 172 | + handleDragStart={this.handleDragStartFromMixin} | ||
| 173 | + /> | ||
| 188 | </a-tab-pane> | 174 | </a-tab-pane> |
| 189 | <a-tab-pane key='page-manager' tab={this.$t('editor.sidebar.pages')}> | 175 | <a-tab-pane key='page-manager' tab={this.$t('editor.sidebar.pages')}> |
| 190 | <RenderPageManager | 176 | <RenderPageManager |
| @@ -202,46 +188,6 @@ export default { | @@ -202,46 +188,6 @@ export default { | ||
| 202 | </a-tab-pane> | 188 | </a-tab-pane> |
| 203 | </a-tabs> | 189 | </a-tabs> |
| 204 | ) | 190 | ) |
| 205 | - // switch (this.activeMenuKey) { | ||
| 206 | - // case sidebarMenus[0].value: | ||
| 207 | - // return ( | ||
| 208 | - // <a-tabs | ||
| 209 | - // style="height: 100%;" | ||
| 210 | - // tabBarGutter={10} | ||
| 211 | - // > | ||
| 212 | - // <a-tab-pane key="plugin-list" tab={this.$t('editor.sidebar.components')}> | ||
| 213 | - // <RenderShortcutsPanel pluginsList={this.pluginsList} handleClickShortcut={this.clone} /> | ||
| 214 | - // </a-tab-pane> | ||
| 215 | - // <a-tab-pane key='page-manager' tab={this.$t('editor.sidebar.pages')}> | ||
| 216 | - // <RenderPageManager | ||
| 217 | - // pages={this.pages} | ||
| 218 | - // editingPage={this.editingPage} | ||
| 219 | - // onSelectMenuItem={(menuKey) => { | ||
| 220 | - // this.pageManager({ type: menuKey }) | ||
| 221 | - // }} | ||
| 222 | - // onEditTitle={({ pageIndexForEditingTitle, newTitle }) => { | ||
| 223 | - // this.pageManager({ type: 'editTitle', value: { pageIndexForEditingTitle, newTitle } }) | ||
| 224 | - // this.saveWork({ isSaveCover: false }) | ||
| 225 | - // }} | ||
| 226 | - // onSelectPage={(pageIndex) => { this.setEditingPage(pageIndex) }} | ||
| 227 | - // /> | ||
| 228 | - // </a-tab-pane> | ||
| 229 | - // </a-tabs> | ||
| 230 | - // ) | ||
| 231 | - // case sidebarMenus[1].value: | ||
| 232 | - // return ( | ||
| 233 | - // <RenderPageManager | ||
| 234 | - // pages={this.pages} | ||
| 235 | - // editingPage={this.editingPage} | ||
| 236 | - // onSelectMenuItem={(menuKey) => { | ||
| 237 | - // this.pageManager({ type: menuKey }) | ||
| 238 | - // }} | ||
| 239 | - // onSelectPage={(pageIndex) => { this.setEditingPage(pageIndex) }} | ||
| 240 | - // /> | ||
| 241 | - // ) | ||
| 242 | - // default: | ||
| 243 | - // return null | ||
| 244 | - // } | ||
| 245 | } | 191 | } |
| 246 | }, | 192 | }, |
| 247 | mounted () { | 193 | mounted () { |
front-end/h5/src/components/core/editor/shortcuts-panel/index.js
| @@ -11,6 +11,10 @@ export default { | @@ -11,6 +11,10 @@ export default { | ||
| 11 | }, | 11 | }, |
| 12 | handleClickShortcut: { | 12 | handleClickShortcut: { |
| 13 | type: Function | 13 | type: Function |
| 14 | + }, | ||
| 15 | + handleDragStart: { | ||
| 16 | + type: Function, | ||
| 17 | + default: (e) => {} | ||
| 14 | } | 18 | } |
| 15 | }, | 19 | }, |
| 16 | data: () => ({ | 20 | data: () => ({ |
| @@ -97,6 +101,7 @@ export default { | @@ -97,6 +101,7 @@ export default { | ||
| 97 | <a-col span={12} style={{ marginTop: '10px' }}> | 101 | <a-col span={12} style={{ marginTop: '10px' }}> |
| 98 | <ShortcutButton | 102 | <ShortcutButton |
| 99 | clickFn={this.onClickShortcut.bind(this, plugin)} | 103 | clickFn={this.onClickShortcut.bind(this, plugin)} |
| 104 | + mousedownFn={this.handleDragStart.bind(this, plugin)} | ||
| 100 | // title={plugin.title} | 105 | // title={plugin.title} |
| 101 | title={plugin.i18nTitle[this.currentLang] || plugin.title} | 106 | title={plugin.i18nTitle[this.currentLang] || plugin.title} |
| 102 | faIcon={plugin.icon} | 107 | faIcon={plugin.icon} |
front-end/h5/src/components/core/styles/index.scss
| @@ -52,6 +52,28 @@ | @@ -52,6 +52,28 @@ | ||
| 52 | } | 52 | } |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | +.hidden { | ||
| 56 | + display: none !important; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +// 拖拽出来的控件 | ||
| 60 | +.dragging-dom-ele { | ||
| 61 | + position: fixed !important; | ||
| 62 | + z-index: 100; | ||
| 63 | + | ||
| 64 | + display: inline-block; | ||
| 65 | + box-sizing: border-box; | ||
| 66 | + | ||
| 67 | + height: 30px; | ||
| 68 | + width: 130px; | ||
| 69 | + border-radius: 4px; | ||
| 70 | + border: 1px solid #CCC; | ||
| 71 | + | ||
| 72 | + cursor: grabbing; | ||
| 73 | + user-select: none; | ||
| 74 | + opacity: 0.6; | ||
| 75 | +} | ||
| 76 | + | ||
| 55 | .ant-tabs-nav .ant-tabs-tab { | 77 | .ant-tabs-nav .ant-tabs-tab { |
| 56 | padding: 12px 0 !important; | 78 | padding: 12px 0 !important; |
| 57 | } | 79 | } |