@epxing/designer 5.3.0-beta.2 → 5.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +1 -1
- package/README.md +46 -46
- package/dist/src/page-designer/PageDesigner.js +11 -0
- package/dist/src/page-designer/designer.js +21 -0
- package/dist/src/page-designer/previews/add-form/Preview.js +1 -1
- package/dist/src/page-designer/previews/add-form/Preview.vue.js +19 -8
- package/dist/src/page-designer/setting-panel/Setting.js +46 -10
- package/dist/src/visual/config-page/ComponentLibraryDialog.js +8 -2
- package/dist/src/visual/config-page/DashboardMobile.js +1 -1
- package/dist/style.css +116 -112
- package/package.json +8 -8
- package/src/index.js +27 -27
- package/src/page-designer/PageDesigner.vue +11 -0
- package/src/page-designer/designer.js +1322 -1299
- package/src/page-designer/previews/add-form/Preview.vue +43 -44
- package/src/page-designer/setting-panel/Setting.vue +24 -2
- package/src/page-designer/widget-preview/Mixin.js +285 -285
- package/src/page-designer/widget-preview/Preview.vue +322 -322
- package/src/page-designer/widget-preview/WidgetWrapper.vue +197 -197
- package/src/page-designer/widget-preview/directive.js +258 -258
- package/src/visual/CategoryList.vue +563 -563
- package/src/visual/ComponentList.vue +280 -280
- package/src/visual/CreatePage.vue +389 -389
- package/src/visual/EntityCardList.vue +449 -449
- package/src/visual/EntityList.vue +838 -838
- package/src/visual/config-page/ComponentLibraryDialog.vue +7 -1
- package/src/visual/config-page/DashboardMobile.vue +934 -934
- package/src/visual/widgets/ProcessFormItemVisual.vue +58 -58
- package/types/index.d.ts +22 -22
- package/types/page-designer.d.ts +3 -3
- package/types/visual-component.d.ts +1 -1
- package/types/visual-entity-list.d.ts +1 -1
- package/types/visual-entity-sub-list.d.ts +1 -1
|
@@ -1,285 +1,285 @@
|
|
|
1
|
-
import PreviewItem from './PreviewItem.vue'
|
|
2
|
-
import util from '@epxing/core/src/runtime-utils/util'
|
|
3
|
-
import { createDropCache, clearDropCache, insertIndex, insertLineRect } from './dropPosition'
|
|
4
|
-
function appendCss (id, css) {
|
|
5
|
-
const formatCss = css.replace(/:current/g, '.css_' + id).replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
6
|
-
return `url(${util.getFileUrl($1)})`
|
|
7
|
-
})
|
|
8
|
-
const element = document.getElementById('style_' + id)
|
|
9
|
-
if (element) {
|
|
10
|
-
element.textContent = formatCss
|
|
11
|
-
} else {
|
|
12
|
-
const style = document.createElement('style')
|
|
13
|
-
style.id = 'style_' + id
|
|
14
|
-
style.textContent = formatCss
|
|
15
|
-
|
|
16
|
-
style.setAttribute('type', 'text/css')
|
|
17
|
-
document.head.appendChild(style)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function removeCss (id) {
|
|
21
|
-
const element = document.getElementById('style_' + id)
|
|
22
|
-
if (element) document.head.removeChild(element)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default {
|
|
26
|
-
props: {
|
|
27
|
-
designer: {
|
|
28
|
-
type: Object
|
|
29
|
-
},
|
|
30
|
-
widget: {
|
|
31
|
-
type: Object
|
|
32
|
-
},
|
|
33
|
-
pageRelationList: {
|
|
34
|
-
type: Array
|
|
35
|
-
},
|
|
36
|
-
templateInfo: {
|
|
37
|
-
type: Object
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
data () {
|
|
41
|
-
return {
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
components: {
|
|
45
|
-
'widget-preview': PreviewItem
|
|
46
|
-
},
|
|
47
|
-
computed: {
|
|
48
|
-
$isMobile () {
|
|
49
|
-
return this.designer.terminal === 'mobile' ? true : this.$store.state.config.isMobile
|
|
50
|
-
},
|
|
51
|
-
selectedWidget () {
|
|
52
|
-
return this.designer.selectedWidget
|
|
53
|
-
},
|
|
54
|
-
widgetInfo () {
|
|
55
|
-
return this.designer.getWidgetByType(this.widget.contentType) || {}
|
|
56
|
-
},
|
|
57
|
-
childWidgetList: {
|
|
58
|
-
get () {
|
|
59
|
-
return this.widget.items || []
|
|
60
|
-
},
|
|
61
|
-
set (v) {
|
|
62
|
-
this.designer.updateWidget(this.widget.id, { items: v })
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
style () {
|
|
66
|
-
const { style = {} } = this.widget
|
|
67
|
-
const _style = { ...style }
|
|
68
|
-
if (this.$configs.height && !style.height) {
|
|
69
|
-
const height = this.$configs.height.includes('%') ? this.$configs.height : this.$configs.height + 'px'
|
|
70
|
-
_style.height = height
|
|
71
|
-
}
|
|
72
|
-
if (_style.backgroundImage) {
|
|
73
|
-
_style.backgroundImage = _style.backgroundImage.replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
74
|
-
return `url(${util.getFileUrl($1)})`
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
return _style
|
|
78
|
-
},
|
|
79
|
-
widgetStyle () {
|
|
80
|
-
const { style = {} } = this.widget
|
|
81
|
-
return style
|
|
82
|
-
},
|
|
83
|
-
$configs () {
|
|
84
|
-
const { id, configs = {} } = this.widget
|
|
85
|
-
if (this.templateInfo) {
|
|
86
|
-
// 获取当前组件的配置 获取模板的配置
|
|
87
|
-
const templateConfig = this.designer.widgetConfig[this.templateInfo.id] || {}
|
|
88
|
-
const { itemConfigs } = this.templateInfo.content
|
|
89
|
-
const _configs = { ...configs, ...(itemConfigs[id] || {}) }
|
|
90
|
-
for (const key in _configs) {
|
|
91
|
-
const item = _configs[key]
|
|
92
|
-
if (typeof item === 'string') {
|
|
93
|
-
_configs[key] = item.replace(/@\[([\w-.|$#]+)\]/g, (macth, key) => templateConfig[key] || '')
|
|
94
|
-
.replace(/\{\{([\w-.|$#]+)\}\}/g, (macth, key) => `⌊${key}⌉`)
|
|
95
|
-
.replace(/\$\[([\w-.|$#]+)\]/g, (macth, key) => `⌊${key}⌉`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return _configs
|
|
99
|
-
} else {
|
|
100
|
-
const widgetConfig = this.designer.widgetConfig[id] || {}
|
|
101
|
-
// 将变量转为⌊⌉『』
|
|
102
|
-
const allConfigs = { ...widgetConfig, ...configs }
|
|
103
|
-
const _configs = {}
|
|
104
|
-
for (const key in allConfigs) {
|
|
105
|
-
const item = allConfigs[key]
|
|
106
|
-
if (typeof item === 'string') {
|
|
107
|
-
_configs[key] = item.replace(/\{\{([\w-.|$#]+)\}\}/g, (macth, key) => `⌊${key}⌉`).replace(/\$\[([\w-.|$#]+)\]/g, (macth, key) => `⌊${key}⌉`)
|
|
108
|
-
} else {
|
|
109
|
-
_configs[key] = item
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return _configs
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
$metaId () {
|
|
116
|
-
const { metaId } = this.$configs
|
|
117
|
-
return metaId || this.designer.page.metaId
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
watch: {
|
|
121
|
-
'widget.css' (v) {
|
|
122
|
-
appendCss(this.widget.id, this.widget.css)
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
mounted () {
|
|
126
|
-
if (this.widget.css) {
|
|
127
|
-
appendCss(this.widget.id, this.widget.css)
|
|
128
|
-
}
|
|
129
|
-
if (this.$el) {
|
|
130
|
-
if (this.$el.classList) {
|
|
131
|
-
this.$el.classList.add('css_' + this.widget.id)
|
|
132
|
-
} else {
|
|
133
|
-
this.$el.className += ' css_' + this.widget.id
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 自动为容器组件绑定拖拽事件
|
|
138
|
-
const isContainer = this.widgetInfo.CONTAINER === '1' || (this.widget.items !== undefined)
|
|
139
|
-
if (isContainer && this.$el) {
|
|
140
|
-
// 保存事件处理器引用,用于销毁时移除
|
|
141
|
-
this._dragEnterHandler = this.onContainerDragEnter.bind(this)
|
|
142
|
-
this._dragOverHandler = (e) => {
|
|
143
|
-
e.preventDefault()
|
|
144
|
-
this.onContainerDragOver(e)
|
|
145
|
-
}
|
|
146
|
-
this._dragLeaveHandler = this.onContainerDragLeave.bind(this)
|
|
147
|
-
this._dropHandler = (e) => {
|
|
148
|
-
e.preventDefault()
|
|
149
|
-
e.stopPropagation() // 阻止事件继续冒泡到父容器
|
|
150
|
-
this.onContainerDrop(e)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 使用 capture 阶段捕获事件,优先于子元素处理
|
|
154
|
-
this.$el.addEventListener('dragenter', this._dragEnterHandler, false)
|
|
155
|
-
this.$el.addEventListener('dragover', this._dragOverHandler, false)
|
|
156
|
-
this.$el.addEventListener('dragleave', this._dragLeaveHandler, false)
|
|
157
|
-
this.$el.addEventListener('drop', this._dropHandler, false)
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
unmounted () {
|
|
161
|
-
removeCss(this.widget.id)
|
|
162
|
-
|
|
163
|
-
// 清除可能存在的离开定时器
|
|
164
|
-
if (this._dragLeaveTimer) {
|
|
165
|
-
clearTimeout(this._dragLeaveTimer)
|
|
166
|
-
this._dragLeaveTimer = null
|
|
167
|
-
}
|
|
168
|
-
if (this._dragFrame) {
|
|
169
|
-
cancelAnimationFrame(this._dragFrame)
|
|
170
|
-
this._dragFrame = null
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 移除拖拽事件监听
|
|
174
|
-
if (this._dragEnterHandler && this.$el) {
|
|
175
|
-
this.$el.removeEventListener('dragenter', this._dragEnterHandler)
|
|
176
|
-
this.$el.removeEventListener('dragover', this._dragOverHandler)
|
|
177
|
-
this.$el.removeEventListener('dragleave', this._dragLeaveHandler)
|
|
178
|
-
this.$el.removeEventListener('drop', this._dropHandler)
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
methods: {
|
|
182
|
-
// ========== 容器拖拽通用方法 ==========
|
|
183
|
-
// 判断是否是从组件库或模板库拖拽
|
|
184
|
-
isDraggingFromLibrary (e) {
|
|
185
|
-
const hasData = e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.includes('text/plain')
|
|
186
|
-
if (!hasData) return false
|
|
187
|
-
|
|
188
|
-
// 尝试获取数据判断类型(注意:在 dragover 中无法获取数据,只能在 drop 中获取)
|
|
189
|
-
// 所以这里只能做基本判断
|
|
190
|
-
return true
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
// 是否在拖画布上已有的组件(否则是组件库 / 模板库):读拖拽状态,不再每次 querySelector
|
|
194
|
-
isDraggingWidget () {
|
|
195
|
-
return this.designer.dragState.dragSource === 'preview'
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
// 是否容器组件:有 CONTAINER 标记或有 items
|
|
199
|
-
isContainerWidget () {
|
|
200
|
-
return this.widgetInfo.CONTAINER === '1' || (this.widget.items !== undefined)
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
// 容器拖拽进入:重新测量一次 children,之后的 dragover 复用缓存
|
|
204
|
-
onContainerDragEnter (e) {
|
|
205
|
-
if (!this.isContainerWidget() || !this.isDraggingFromLibrary(e)) return
|
|
206
|
-
e.preventDefault()
|
|
207
|
-
e.stopPropagation()
|
|
208
|
-
if (this._dragLeaveTimer) {
|
|
209
|
-
clearTimeout(this._dragLeaveTimer)
|
|
210
|
-
this._dragLeaveTimer = null
|
|
211
|
-
}
|
|
212
|
-
if (!this._dropCache) this._dropCache = createDropCache()
|
|
213
|
-
clearDropCache(this._dropCache)
|
|
214
|
-
this.designer.setDragOverTarget({ type: 'container', widgetId: this.widget.id })
|
|
215
|
-
this.designer.setDragOverTargetElement(this.$el)
|
|
216
|
-
this.designer.setPreviewDragTarget({ widgetId: this.widget.id, position: 'inside' })
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// 容器拖拽悬停:只记坐标 + 允许放置;插入位置的计算与状态更新合并到下一帧
|
|
220
|
-
onContainerDragOver (e) {
|
|
221
|
-
if (!this.isContainerWidget() || !this.isDraggingFromLibrary(e)) return
|
|
222
|
-
e.preventDefault()
|
|
223
|
-
e.stopPropagation()
|
|
224
|
-
e.dataTransfer.dropEffect = this.isDraggingWidget() ? 'move' : 'copy'
|
|
225
|
-
this._dragClientX = e.clientX
|
|
226
|
-
this._dragClientY = e.clientY
|
|
227
|
-
this.scheduleDragFrame()
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
scheduleDragFrame () {
|
|
231
|
-
if (this._dragFrame) return
|
|
232
|
-
this._dragFrame = requestAnimationFrame(() => {
|
|
233
|
-
this._dragFrame = null
|
|
234
|
-
if (this._dragClientY === undefined || !this.designer.dragState.isDragging) return
|
|
235
|
-
// 事件被 stopPropagation 后到不了 document,悬浮窗位置在这里更新
|
|
236
|
-
this.designer.updateDragGhostPosition(this._dragClientX, this._dragClientY)
|
|
237
|
-
// 列表元素(子类可以覆盖 getDropListElement 方法自定义)
|
|
238
|
-
const listEl = this.getDropListElement ? this.getDropListElement() : this.$el
|
|
239
|
-
if (!this._dropCache) this._dropCache = createDropCache()
|
|
240
|
-
const lineRect = insertLineRect(this._dropCache, listEl, this._dragClientY)
|
|
241
|
-
if (lineRect) this.designer.setDragInsertLine(lineRect)
|
|
242
|
-
this.designer.setPreviewDragTarget({ widgetId: this.widget.id, index: insertIndex(this._dropCache, listEl, this._dragClientY), position: 'inside' })
|
|
243
|
-
})
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
// 容器拖拽离开:relatedTarget 为空(离开窗口)或不在当前元素内才算真正离开
|
|
247
|
-
onContainerDragLeave (e) {
|
|
248
|
-
if (!this.isContainerWidget()) return
|
|
249
|
-
const relatedTarget = e.relatedTarget
|
|
250
|
-
if (!relatedTarget || !this.$el.contains(relatedTarget)) {
|
|
251
|
-
// 小延迟避免在子元素之间快速移动时误判
|
|
252
|
-
if (this._dragLeaveTimer) clearTimeout(this._dragLeaveTimer)
|
|
253
|
-
this._dragLeaveTimer = setTimeout(() => {
|
|
254
|
-
if (this.designer.dragOverTarget?.widgetId === this.widget.id) {
|
|
255
|
-
this.designer.clearDragOverTarget()
|
|
256
|
-
this.designer.clearDragInsertLine()
|
|
257
|
-
this.designer.clearPreviewDragTarget()
|
|
258
|
-
}
|
|
259
|
-
if (this._dropCache) clearDropCache(this._dropCache)
|
|
260
|
-
this._dragLeaveTimer = null
|
|
261
|
-
}, 30)
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
// 容器放置
|
|
266
|
-
onContainerDrop (e) {
|
|
267
|
-
if (!this.isContainerWidget()) return
|
|
268
|
-
e.preventDefault()
|
|
269
|
-
e.stopPropagation()
|
|
270
|
-
if (this._dragLeaveTimer) {
|
|
271
|
-
clearTimeout(this._dragLeaveTimer)
|
|
272
|
-
this._dragLeaveTimer = null
|
|
273
|
-
}
|
|
274
|
-
this.designer.clearDragOverTarget()
|
|
275
|
-
this.designer.clearDragInsertLine()
|
|
276
|
-
this.designer.clearPreviewDragTarget()
|
|
277
|
-
const listEl = this.getDropListElement ? this.getDropListElement() : this.$el
|
|
278
|
-
if (!this._dropCache) this._dropCache = createDropCache()
|
|
279
|
-
const index = insertIndex(this._dropCache, listEl, e.clientY)
|
|
280
|
-
clearDropCache(this._dropCache)
|
|
281
|
-
// 使用 designer 的统一拖拽处理方法;不在这里发 drag-end,让 dragend 事件自然触发
|
|
282
|
-
this.designer.handleDrop({ dataTransfer: e.dataTransfer, target: { type: 'container', widgetId: this.widget.id, index } })
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
1
|
+
import PreviewItem from './PreviewItem.vue'
|
|
2
|
+
import util from '@epxing/core/src/runtime-utils/util'
|
|
3
|
+
import { createDropCache, clearDropCache, insertIndex, insertLineRect } from './dropPosition'
|
|
4
|
+
function appendCss (id, css) {
|
|
5
|
+
const formatCss = css.replace(/:current/g, '.css_' + id).replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
6
|
+
return `url(${util.getFileUrl($1)})`
|
|
7
|
+
})
|
|
8
|
+
const element = document.getElementById('style_' + id)
|
|
9
|
+
if (element) {
|
|
10
|
+
element.textContent = formatCss
|
|
11
|
+
} else {
|
|
12
|
+
const style = document.createElement('style')
|
|
13
|
+
style.id = 'style_' + id
|
|
14
|
+
style.textContent = formatCss
|
|
15
|
+
|
|
16
|
+
style.setAttribute('type', 'text/css')
|
|
17
|
+
document.head.appendChild(style)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function removeCss (id) {
|
|
21
|
+
const element = document.getElementById('style_' + id)
|
|
22
|
+
if (element) document.head.removeChild(element)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
props: {
|
|
27
|
+
designer: {
|
|
28
|
+
type: Object
|
|
29
|
+
},
|
|
30
|
+
widget: {
|
|
31
|
+
type: Object
|
|
32
|
+
},
|
|
33
|
+
pageRelationList: {
|
|
34
|
+
type: Array
|
|
35
|
+
},
|
|
36
|
+
templateInfo: {
|
|
37
|
+
type: Object
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
data () {
|
|
41
|
+
return {
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
components: {
|
|
45
|
+
'widget-preview': PreviewItem
|
|
46
|
+
},
|
|
47
|
+
computed: {
|
|
48
|
+
$isMobile () {
|
|
49
|
+
return this.designer.terminal === 'mobile' ? true : this.$store.state.config.isMobile
|
|
50
|
+
},
|
|
51
|
+
selectedWidget () {
|
|
52
|
+
return this.designer.selectedWidget
|
|
53
|
+
},
|
|
54
|
+
widgetInfo () {
|
|
55
|
+
return this.designer.getWidgetByType(this.widget.contentType) || {}
|
|
56
|
+
},
|
|
57
|
+
childWidgetList: {
|
|
58
|
+
get () {
|
|
59
|
+
return this.widget.items || []
|
|
60
|
+
},
|
|
61
|
+
set (v) {
|
|
62
|
+
this.designer.updateWidget(this.widget.id, { items: v })
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
style () {
|
|
66
|
+
const { style = {} } = this.widget
|
|
67
|
+
const _style = { ...style }
|
|
68
|
+
if (this.$configs.height && !style.height) {
|
|
69
|
+
const height = this.$configs.height.includes('%') ? this.$configs.height : this.$configs.height + 'px'
|
|
70
|
+
_style.height = height
|
|
71
|
+
}
|
|
72
|
+
if (_style.backgroundImage) {
|
|
73
|
+
_style.backgroundImage = _style.backgroundImage.replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
74
|
+
return `url(${util.getFileUrl($1)})`
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
return _style
|
|
78
|
+
},
|
|
79
|
+
widgetStyle () {
|
|
80
|
+
const { style = {} } = this.widget
|
|
81
|
+
return style
|
|
82
|
+
},
|
|
83
|
+
$configs () {
|
|
84
|
+
const { id, configs = {} } = this.widget
|
|
85
|
+
if (this.templateInfo) {
|
|
86
|
+
// 获取当前组件的配置 获取模板的配置
|
|
87
|
+
const templateConfig = this.designer.widgetConfig[this.templateInfo.id] || {}
|
|
88
|
+
const { itemConfigs } = this.templateInfo.content
|
|
89
|
+
const _configs = { ...configs, ...(itemConfigs[id] || {}) }
|
|
90
|
+
for (const key in _configs) {
|
|
91
|
+
const item = _configs[key]
|
|
92
|
+
if (typeof item === 'string') {
|
|
93
|
+
_configs[key] = item.replace(/@\[([\w-.|$#]+)\]/g, (macth, key) => templateConfig[key] || '')
|
|
94
|
+
.replace(/\{\{([\w-.|$#]+)\}\}/g, (macth, key) => `⌊${key}⌉`)
|
|
95
|
+
.replace(/\$\[([\w-.|$#]+)\]/g, (macth, key) => `⌊${key}⌉`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return _configs
|
|
99
|
+
} else {
|
|
100
|
+
const widgetConfig = this.designer.widgetConfig[id] || {}
|
|
101
|
+
// 将变量转为⌊⌉『』
|
|
102
|
+
const allConfigs = { ...widgetConfig, ...configs }
|
|
103
|
+
const _configs = {}
|
|
104
|
+
for (const key in allConfigs) {
|
|
105
|
+
const item = allConfigs[key]
|
|
106
|
+
if (typeof item === 'string') {
|
|
107
|
+
_configs[key] = item.replace(/\{\{([\w-.|$#]+)\}\}/g, (macth, key) => `⌊${key}⌉`).replace(/\$\[([\w-.|$#]+)\]/g, (macth, key) => `⌊${key}⌉`)
|
|
108
|
+
} else {
|
|
109
|
+
_configs[key] = item
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return _configs
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
$metaId () {
|
|
116
|
+
const { metaId } = this.$configs
|
|
117
|
+
return metaId || this.designer.page.metaId
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
watch: {
|
|
121
|
+
'widget.css' (v) {
|
|
122
|
+
appendCss(this.widget.id, this.widget.css)
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
mounted () {
|
|
126
|
+
if (this.widget.css) {
|
|
127
|
+
appendCss(this.widget.id, this.widget.css)
|
|
128
|
+
}
|
|
129
|
+
if (this.$el) {
|
|
130
|
+
if (this.$el.classList) {
|
|
131
|
+
this.$el.classList.add('css_' + this.widget.id)
|
|
132
|
+
} else {
|
|
133
|
+
this.$el.className += ' css_' + this.widget.id
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 自动为容器组件绑定拖拽事件
|
|
138
|
+
const isContainer = this.widgetInfo.CONTAINER === '1' || (this.widget.items !== undefined)
|
|
139
|
+
if (isContainer && this.$el) {
|
|
140
|
+
// 保存事件处理器引用,用于销毁时移除
|
|
141
|
+
this._dragEnterHandler = this.onContainerDragEnter.bind(this)
|
|
142
|
+
this._dragOverHandler = (e) => {
|
|
143
|
+
e.preventDefault()
|
|
144
|
+
this.onContainerDragOver(e)
|
|
145
|
+
}
|
|
146
|
+
this._dragLeaveHandler = this.onContainerDragLeave.bind(this)
|
|
147
|
+
this._dropHandler = (e) => {
|
|
148
|
+
e.preventDefault()
|
|
149
|
+
e.stopPropagation() // 阻止事件继续冒泡到父容器
|
|
150
|
+
this.onContainerDrop(e)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 使用 capture 阶段捕获事件,优先于子元素处理
|
|
154
|
+
this.$el.addEventListener('dragenter', this._dragEnterHandler, false)
|
|
155
|
+
this.$el.addEventListener('dragover', this._dragOverHandler, false)
|
|
156
|
+
this.$el.addEventListener('dragleave', this._dragLeaveHandler, false)
|
|
157
|
+
this.$el.addEventListener('drop', this._dropHandler, false)
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
unmounted () {
|
|
161
|
+
removeCss(this.widget.id)
|
|
162
|
+
|
|
163
|
+
// 清除可能存在的离开定时器
|
|
164
|
+
if (this._dragLeaveTimer) {
|
|
165
|
+
clearTimeout(this._dragLeaveTimer)
|
|
166
|
+
this._dragLeaveTimer = null
|
|
167
|
+
}
|
|
168
|
+
if (this._dragFrame) {
|
|
169
|
+
cancelAnimationFrame(this._dragFrame)
|
|
170
|
+
this._dragFrame = null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 移除拖拽事件监听
|
|
174
|
+
if (this._dragEnterHandler && this.$el) {
|
|
175
|
+
this.$el.removeEventListener('dragenter', this._dragEnterHandler)
|
|
176
|
+
this.$el.removeEventListener('dragover', this._dragOverHandler)
|
|
177
|
+
this.$el.removeEventListener('dragleave', this._dragLeaveHandler)
|
|
178
|
+
this.$el.removeEventListener('drop', this._dropHandler)
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
methods: {
|
|
182
|
+
// ========== 容器拖拽通用方法 ==========
|
|
183
|
+
// 判断是否是从组件库或模板库拖拽
|
|
184
|
+
isDraggingFromLibrary (e) {
|
|
185
|
+
const hasData = e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.includes('text/plain')
|
|
186
|
+
if (!hasData) return false
|
|
187
|
+
|
|
188
|
+
// 尝试获取数据判断类型(注意:在 dragover 中无法获取数据,只能在 drop 中获取)
|
|
189
|
+
// 所以这里只能做基本判断
|
|
190
|
+
return true
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// 是否在拖画布上已有的组件(否则是组件库 / 模板库):读拖拽状态,不再每次 querySelector
|
|
194
|
+
isDraggingWidget () {
|
|
195
|
+
return this.designer.dragState.dragSource === 'preview'
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// 是否容器组件:有 CONTAINER 标记或有 items
|
|
199
|
+
isContainerWidget () {
|
|
200
|
+
return this.widgetInfo.CONTAINER === '1' || (this.widget.items !== undefined)
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// 容器拖拽进入:重新测量一次 children,之后的 dragover 复用缓存
|
|
204
|
+
onContainerDragEnter (e) {
|
|
205
|
+
if (!this.isContainerWidget() || !this.isDraggingFromLibrary(e)) return
|
|
206
|
+
e.preventDefault()
|
|
207
|
+
e.stopPropagation()
|
|
208
|
+
if (this._dragLeaveTimer) {
|
|
209
|
+
clearTimeout(this._dragLeaveTimer)
|
|
210
|
+
this._dragLeaveTimer = null
|
|
211
|
+
}
|
|
212
|
+
if (!this._dropCache) this._dropCache = createDropCache()
|
|
213
|
+
clearDropCache(this._dropCache)
|
|
214
|
+
this.designer.setDragOverTarget({ type: 'container', widgetId: this.widget.id })
|
|
215
|
+
this.designer.setDragOverTargetElement(this.$el)
|
|
216
|
+
this.designer.setPreviewDragTarget({ widgetId: this.widget.id, position: 'inside' })
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// 容器拖拽悬停:只记坐标 + 允许放置;插入位置的计算与状态更新合并到下一帧
|
|
220
|
+
onContainerDragOver (e) {
|
|
221
|
+
if (!this.isContainerWidget() || !this.isDraggingFromLibrary(e)) return
|
|
222
|
+
e.preventDefault()
|
|
223
|
+
e.stopPropagation()
|
|
224
|
+
e.dataTransfer.dropEffect = this.isDraggingWidget() ? 'move' : 'copy'
|
|
225
|
+
this._dragClientX = e.clientX
|
|
226
|
+
this._dragClientY = e.clientY
|
|
227
|
+
this.scheduleDragFrame()
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
scheduleDragFrame () {
|
|
231
|
+
if (this._dragFrame) return
|
|
232
|
+
this._dragFrame = requestAnimationFrame(() => {
|
|
233
|
+
this._dragFrame = null
|
|
234
|
+
if (this._dragClientY === undefined || !this.designer.dragState.isDragging) return
|
|
235
|
+
// 事件被 stopPropagation 后到不了 document,悬浮窗位置在这里更新
|
|
236
|
+
this.designer.updateDragGhostPosition(this._dragClientX, this._dragClientY)
|
|
237
|
+
// 列表元素(子类可以覆盖 getDropListElement 方法自定义)
|
|
238
|
+
const listEl = this.getDropListElement ? this.getDropListElement() : this.$el
|
|
239
|
+
if (!this._dropCache) this._dropCache = createDropCache()
|
|
240
|
+
const lineRect = insertLineRect(this._dropCache, listEl, this._dragClientY)
|
|
241
|
+
if (lineRect) this.designer.setDragInsertLine(lineRect)
|
|
242
|
+
this.designer.setPreviewDragTarget({ widgetId: this.widget.id, index: insertIndex(this._dropCache, listEl, this._dragClientY), position: 'inside' })
|
|
243
|
+
})
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// 容器拖拽离开:relatedTarget 为空(离开窗口)或不在当前元素内才算真正离开
|
|
247
|
+
onContainerDragLeave (e) {
|
|
248
|
+
if (!this.isContainerWidget()) return
|
|
249
|
+
const relatedTarget = e.relatedTarget
|
|
250
|
+
if (!relatedTarget || !this.$el.contains(relatedTarget)) {
|
|
251
|
+
// 小延迟避免在子元素之间快速移动时误判
|
|
252
|
+
if (this._dragLeaveTimer) clearTimeout(this._dragLeaveTimer)
|
|
253
|
+
this._dragLeaveTimer = setTimeout(() => {
|
|
254
|
+
if (this.designer.dragOverTarget?.widgetId === this.widget.id) {
|
|
255
|
+
this.designer.clearDragOverTarget()
|
|
256
|
+
this.designer.clearDragInsertLine()
|
|
257
|
+
this.designer.clearPreviewDragTarget()
|
|
258
|
+
}
|
|
259
|
+
if (this._dropCache) clearDropCache(this._dropCache)
|
|
260
|
+
this._dragLeaveTimer = null
|
|
261
|
+
}, 30)
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
// 容器放置
|
|
266
|
+
onContainerDrop (e) {
|
|
267
|
+
if (!this.isContainerWidget()) return
|
|
268
|
+
e.preventDefault()
|
|
269
|
+
e.stopPropagation()
|
|
270
|
+
if (this._dragLeaveTimer) {
|
|
271
|
+
clearTimeout(this._dragLeaveTimer)
|
|
272
|
+
this._dragLeaveTimer = null
|
|
273
|
+
}
|
|
274
|
+
this.designer.clearDragOverTarget()
|
|
275
|
+
this.designer.clearDragInsertLine()
|
|
276
|
+
this.designer.clearPreviewDragTarget()
|
|
277
|
+
const listEl = this.getDropListElement ? this.getDropListElement() : this.$el
|
|
278
|
+
if (!this._dropCache) this._dropCache = createDropCache()
|
|
279
|
+
const index = insertIndex(this._dropCache, listEl, e.clientY)
|
|
280
|
+
clearDropCache(this._dropCache)
|
|
281
|
+
// 使用 designer 的统一拖拽处理方法;不在这里发 drag-end,让 dragend 事件自然触发
|
|
282
|
+
this.designer.handleDrop({ dataTransfer: e.dataTransfer, target: { type: 'container', widgetId: this.widget.id, index } })
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|