@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,258 +1,258 @@
|
|
|
1
|
-
// import util from '@epxing/core/src/runtime-utils/util'
|
|
2
|
-
|
|
3
|
-
// function appendCss (id, css) {
|
|
4
|
-
// const formatCss = css.replace(/:current/g, '.css_' + id).replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
5
|
-
// return `url(${util.getFileUrl($1)})`
|
|
6
|
-
// })
|
|
7
|
-
// const element = document.getElementById('style_' + id)
|
|
8
|
-
// if (element) {
|
|
9
|
-
// element.textContent = formatCss
|
|
10
|
-
// } else {
|
|
11
|
-
// const style = document.createElement('style')
|
|
12
|
-
// style.id = 'style_' + id
|
|
13
|
-
// style.textContent = formatCss
|
|
14
|
-
// style.setAttribute('type', 'text/css')
|
|
15
|
-
// document.head.appendChild(style)
|
|
16
|
-
// }
|
|
17
|
-
// }
|
|
18
|
-
|
|
19
|
-
// function removeCss (id) {
|
|
20
|
-
// const element = document.getElementById('style_' + id)
|
|
21
|
-
// if (element) document.head.removeChild(element)
|
|
22
|
-
// }
|
|
23
|
-
|
|
24
|
-
// 卸掉 el 上一次 apply 绑的全部监听(beforeMount 与 mounted 会各 apply 一次,不先卸就会双份触发)
|
|
25
|
-
function detach (el) {
|
|
26
|
-
const targetEl = el._widgetTargetEl || el
|
|
27
|
-
const pairs = [['mouseenter', '_widgetMouseEnter'], ['mouseleave', '_widgetMouseLeave'], ['click', '_widgetClick'], ['dragstart', '_widgetDragStart'], ['dragend', '_widgetDragEnd']]
|
|
28
|
-
for (const [type, key] of pairs) {
|
|
29
|
-
if (el[key]) {
|
|
30
|
-
targetEl.removeEventListener(type, el[key])
|
|
31
|
-
delete el[key]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
delete el._widgetTargetEl
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 全局指令:在组件上添加设计器相关属性和事件
|
|
38
|
-
export default {
|
|
39
|
-
beforeMount (el, binding) {
|
|
40
|
-
const { widget, designer, parentWidget, templateInfo } = binding.value || {}
|
|
41
|
-
if (!widget || !designer) return
|
|
42
|
-
|
|
43
|
-
// 等待组件挂载后再操作(Vue 组件可能需要等待)
|
|
44
|
-
const applyDirective = () => {
|
|
45
|
-
detach(el)
|
|
46
|
-
// Vue3 里指令挂在组件上时 el 就是组件根元素(等价于 Vue2 的 el.__vue__.$el),直接用。
|
|
47
|
-
// 迁移前遗留的「el.__vue__ 不存在就取唯一子元素」启发式在 Vue3 下恒命中,会把 data-widget-id
|
|
48
|
-
// 与点击 / 悬停 / 拖拽监听挂到内部子元素上(表单项挂在头部 <p>、占位组件挂在 <span>),
|
|
49
|
-
// 导致点组件身体选不中、选中浮层只框住头部。
|
|
50
|
-
if (el.nodeType !== 1) return
|
|
51
|
-
const targetEl = el
|
|
52
|
-
|
|
53
|
-
// 添加 data-widget-id 属性
|
|
54
|
-
targetEl.setAttribute('data-widget-id', widget.id)
|
|
55
|
-
|
|
56
|
-
// 使组件可拖拽(但不改变鼠标样式)
|
|
57
|
-
targetEl.setAttribute('draggable', 'true')
|
|
58
|
-
|
|
59
|
-
// 获取组件名称并添加到 data 属性
|
|
60
|
-
const widgetInfo = designer.getWidgetByType(widget.contentType, widget.type) || {}
|
|
61
|
-
const widgetTitle = widgetInfo.TITLE || widgetInfo.NAME || widget.title || ''
|
|
62
|
-
if (widgetTitle) {
|
|
63
|
-
targetEl.setAttribute('data-widget-title', widgetTitle)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 添加 css_ 类名
|
|
67
|
-
// if (targetEl.classList) {
|
|
68
|
-
// targetEl.classList.add('css_' + widget.id)
|
|
69
|
-
// } else {
|
|
70
|
-
// targetEl.className += ' css_' + widget.id
|
|
71
|
-
// }
|
|
72
|
-
|
|
73
|
-
// 如果不是容器组件,添加布局类(el-col)
|
|
74
|
-
if (!parentWidget) {
|
|
75
|
-
if (targetEl.classList) {
|
|
76
|
-
targetEl.classList.add('el-col')
|
|
77
|
-
targetEl.classList.add(`el-col-${widget.span || 24}`)
|
|
78
|
-
} else {
|
|
79
|
-
targetEl.className += ` el-col el-col-${widget.span || 24}`
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 应用组件的 CSS 样式
|
|
84
|
-
// if (widget.css) {
|
|
85
|
-
// appendCss(widget.id, widget.css)
|
|
86
|
-
// }
|
|
87
|
-
|
|
88
|
-
// 添加事件监听器
|
|
89
|
-
const handleMouseEnter = (event) => {
|
|
90
|
-
if (!templateInfo) {
|
|
91
|
-
// 阻止事件冒泡,防止触发外层组件的悬停
|
|
92
|
-
event.stopPropagation()
|
|
93
|
-
// 通过 designer 统一管理悬浮组件状态
|
|
94
|
-
if (typeof designer.setHoverById === 'function') {
|
|
95
|
-
designer.setHoverById(widget.id)
|
|
96
|
-
} else if (designer.emitEvent) {
|
|
97
|
-
// 兼容旧逻辑
|
|
98
|
-
designer.emitEvent('widget-hover', widget.id)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const handleMouseLeave = (event) => {
|
|
104
|
-
if (!templateInfo) {
|
|
105
|
-
const relatedTarget = event.relatedTarget
|
|
106
|
-
|
|
107
|
-
// 1. 鼠标仍在当前组件内部(含子元素),不处理
|
|
108
|
-
if (relatedTarget && targetEl.contains(relatedTarget)) {
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
event.stopPropagation()
|
|
113
|
-
|
|
114
|
-
// 2. 鼠标移动到了另一个挂了 data-widget-id 的组件上,直接把 hover 切换到该组件
|
|
115
|
-
if (relatedTarget && relatedTarget.closest) {
|
|
116
|
-
const nextWidgetEl = relatedTarget.closest('[data-widget-id]')
|
|
117
|
-
if (nextWidgetEl) {
|
|
118
|
-
const nextId = nextWidgetEl.getAttribute('data-widget-id')
|
|
119
|
-
if (nextId && typeof designer.setHoverById === 'function') {
|
|
120
|
-
designer.setHoverById(nextId)
|
|
121
|
-
return
|
|
122
|
-
} else if (nextId && designer.emitEvent) {
|
|
123
|
-
// 兼容旧逻辑
|
|
124
|
-
designer.emitEvent('widget-hover', nextId)
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 3. 否则认为鼠标离开了所有组件区域,清理悬浮状态
|
|
131
|
-
if (typeof designer.clearHover === 'function') {
|
|
132
|
-
designer.clearHover()
|
|
133
|
-
} else if (designer.emitEvent) {
|
|
134
|
-
designer.emitEvent('widget-hover', null)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const handleClick = (event) => {
|
|
140
|
-
if (!templateInfo) {
|
|
141
|
-
const widgetInfo = designer.getWidgetByType(widget.contentType, widget.type) || {}
|
|
142
|
-
if (widgetInfo.ID) {
|
|
143
|
-
designer.setSelected(widget)
|
|
144
|
-
event.stopPropagation()
|
|
145
|
-
// 触发全局事件
|
|
146
|
-
if (designer.emitEvent) {
|
|
147
|
-
designer.emitEvent('widget-click', {
|
|
148
|
-
widget: widget,
|
|
149
|
-
element: targetEl,
|
|
150
|
-
parentWidget: parentWidget
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// 组件拖拽开始
|
|
158
|
-
const handleDragStart = (event) => {
|
|
159
|
-
event.stopPropagation()
|
|
160
|
-
|
|
161
|
-
// 设置拖拽数据
|
|
162
|
-
const dragData = {
|
|
163
|
-
type: 'widget-reorder',
|
|
164
|
-
widgetId: widget.id,
|
|
165
|
-
parentWidgetId: parentWidget?.id || null
|
|
166
|
-
}
|
|
167
|
-
const dragDataStr = JSON.stringify(dragData)
|
|
168
|
-
|
|
169
|
-
event.dataTransfer.setData('text/plain', dragDataStr)
|
|
170
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
171
|
-
|
|
172
|
-
// 设置自定义拖拽图像(空元素),避免浏览器默认拖拽行为干扰
|
|
173
|
-
const emptyEl = document.createElement('div')
|
|
174
|
-
emptyEl.style.cssText = 'position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none'
|
|
175
|
-
document.body.appendChild(emptyEl)
|
|
176
|
-
event.dataTransfer.setDragImage(emptyEl, 0, 0)
|
|
177
|
-
setTimeout(() => { if (emptyEl.parentNode) emptyEl.parentNode.removeChild(emptyEl) }, 0)
|
|
178
|
-
|
|
179
|
-
// 添加拖拽样式和标记类
|
|
180
|
-
targetEl.style.opacity = '0.5'
|
|
181
|
-
targetEl.classList.add('epx-widget-dragging')
|
|
182
|
-
|
|
183
|
-
// 注意:不要设置 pointer-events: none,否则会阻止 drop 事件
|
|
184
|
-
// 使用 CSS 类来控制样式即可
|
|
185
|
-
|
|
186
|
-
// 通知 designer 开始拖拽(使用统一的拖拽状态管理)
|
|
187
|
-
if (designer.startDrag) {
|
|
188
|
-
designer.startDrag({
|
|
189
|
-
source: 'preview',
|
|
190
|
-
data: dragData,
|
|
191
|
-
title: widgetInfo.TITLE || widget.title,
|
|
192
|
-
icon: widgetInfo.ICON,
|
|
193
|
-
x: event.clientX,
|
|
194
|
-
y: event.clientY,
|
|
195
|
-
isNative: true
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 组件拖拽结束
|
|
201
|
-
const handleDragEnd = (event) => {
|
|
202
|
-
event.stopPropagation()
|
|
203
|
-
|
|
204
|
-
// 恢复样式
|
|
205
|
-
targetEl.style.opacity = '1'
|
|
206
|
-
targetEl.classList.remove('epx-widget-dragging')
|
|
207
|
-
|
|
208
|
-
// 预览区域组件拖拽结束,需要关闭悬浮窗
|
|
209
|
-
// 与组件库不同,预览区域的组件不会在拖拽开始时关闭/销毁,
|
|
210
|
-
// 所以可以安全地发送 'drag-end' 事件来关闭悬浮窗
|
|
211
|
-
if (designer.emitEvent) {
|
|
212
|
-
designer.emitEvent('drag-end')
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 保存事件处理函数和元素引用,以便后续移除
|
|
217
|
-
el._widgetTargetEl = targetEl
|
|
218
|
-
el._widgetMouseEnter = handleMouseEnter
|
|
219
|
-
el._widgetMouseLeave = handleMouseLeave
|
|
220
|
-
el._widgetClick = handleClick
|
|
221
|
-
el._widgetDragStart = handleDragStart
|
|
222
|
-
el._widgetDragEnd = handleDragEnd
|
|
223
|
-
|
|
224
|
-
targetEl.addEventListener('mouseenter', handleMouseEnter)
|
|
225
|
-
targetEl.addEventListener('mouseleave', handleMouseLeave)
|
|
226
|
-
targetEl.addEventListener('click', handleClick)
|
|
227
|
-
targetEl.addEventListener('dragstart', handleDragStart)
|
|
228
|
-
targetEl.addEventListener('dragend', handleDragEnd)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 立即执行,如果组件还没挂载,在 inserted 钩子中再执行一次
|
|
232
|
-
applyDirective()
|
|
233
|
-
el._applyDirective = applyDirective
|
|
234
|
-
},
|
|
235
|
-
mounted (el, binding) {
|
|
236
|
-
// 组件插入后再次应用,确保找到正确的根元素;
|
|
237
|
-
// 组件在同一 tick 内挂载又卸载时 unmounted 已删掉该引用,回调里要再判一次
|
|
238
|
-
if (el._applyDirective) {
|
|
239
|
-
setTimeout(() => {
|
|
240
|
-
if (typeof el._applyDirective === 'function') el._applyDirective()
|
|
241
|
-
}, 0)
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
updated (el, binding) {
|
|
245
|
-
// console.log('update', binding.value)
|
|
246
|
-
// const { widget } = binding.value || {}
|
|
247
|
-
// if (!widget) return
|
|
248
|
-
|
|
249
|
-
// // 更新 CSS 样式
|
|
250
|
-
// if (widget.css) {
|
|
251
|
-
// appendCss(widget.id, widget.css)
|
|
252
|
-
// }
|
|
253
|
-
},
|
|
254
|
-
unmounted (el) {
|
|
255
|
-
detach(el)
|
|
256
|
-
delete el._applyDirective
|
|
257
|
-
}
|
|
258
|
-
}
|
|
1
|
+
// import util from '@epxing/core/src/runtime-utils/util'
|
|
2
|
+
|
|
3
|
+
// function appendCss (id, css) {
|
|
4
|
+
// const formatCss = css.replace(/:current/g, '.css_' + id).replace(/url\s*\(([\w+-/.\s]+)\)/g, (str, $1) => {
|
|
5
|
+
// return `url(${util.getFileUrl($1)})`
|
|
6
|
+
// })
|
|
7
|
+
// const element = document.getElementById('style_' + id)
|
|
8
|
+
// if (element) {
|
|
9
|
+
// element.textContent = formatCss
|
|
10
|
+
// } else {
|
|
11
|
+
// const style = document.createElement('style')
|
|
12
|
+
// style.id = 'style_' + id
|
|
13
|
+
// style.textContent = formatCss
|
|
14
|
+
// style.setAttribute('type', 'text/css')
|
|
15
|
+
// document.head.appendChild(style)
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
// function removeCss (id) {
|
|
20
|
+
// const element = document.getElementById('style_' + id)
|
|
21
|
+
// if (element) document.head.removeChild(element)
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
// 卸掉 el 上一次 apply 绑的全部监听(beforeMount 与 mounted 会各 apply 一次,不先卸就会双份触发)
|
|
25
|
+
function detach (el) {
|
|
26
|
+
const targetEl = el._widgetTargetEl || el
|
|
27
|
+
const pairs = [['mouseenter', '_widgetMouseEnter'], ['mouseleave', '_widgetMouseLeave'], ['click', '_widgetClick'], ['dragstart', '_widgetDragStart'], ['dragend', '_widgetDragEnd']]
|
|
28
|
+
for (const [type, key] of pairs) {
|
|
29
|
+
if (el[key]) {
|
|
30
|
+
targetEl.removeEventListener(type, el[key])
|
|
31
|
+
delete el[key]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
delete el._widgetTargetEl
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 全局指令:在组件上添加设计器相关属性和事件
|
|
38
|
+
export default {
|
|
39
|
+
beforeMount (el, binding) {
|
|
40
|
+
const { widget, designer, parentWidget, templateInfo } = binding.value || {}
|
|
41
|
+
if (!widget || !designer) return
|
|
42
|
+
|
|
43
|
+
// 等待组件挂载后再操作(Vue 组件可能需要等待)
|
|
44
|
+
const applyDirective = () => {
|
|
45
|
+
detach(el)
|
|
46
|
+
// Vue3 里指令挂在组件上时 el 就是组件根元素(等价于 Vue2 的 el.__vue__.$el),直接用。
|
|
47
|
+
// 迁移前遗留的「el.__vue__ 不存在就取唯一子元素」启发式在 Vue3 下恒命中,会把 data-widget-id
|
|
48
|
+
// 与点击 / 悬停 / 拖拽监听挂到内部子元素上(表单项挂在头部 <p>、占位组件挂在 <span>),
|
|
49
|
+
// 导致点组件身体选不中、选中浮层只框住头部。
|
|
50
|
+
if (el.nodeType !== 1) return
|
|
51
|
+
const targetEl = el
|
|
52
|
+
|
|
53
|
+
// 添加 data-widget-id 属性
|
|
54
|
+
targetEl.setAttribute('data-widget-id', widget.id)
|
|
55
|
+
|
|
56
|
+
// 使组件可拖拽(但不改变鼠标样式)
|
|
57
|
+
targetEl.setAttribute('draggable', 'true')
|
|
58
|
+
|
|
59
|
+
// 获取组件名称并添加到 data 属性
|
|
60
|
+
const widgetInfo = designer.getWidgetByType(widget.contentType, widget.type) || {}
|
|
61
|
+
const widgetTitle = widgetInfo.TITLE || widgetInfo.NAME || widget.title || ''
|
|
62
|
+
if (widgetTitle) {
|
|
63
|
+
targetEl.setAttribute('data-widget-title', widgetTitle)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 添加 css_ 类名
|
|
67
|
+
// if (targetEl.classList) {
|
|
68
|
+
// targetEl.classList.add('css_' + widget.id)
|
|
69
|
+
// } else {
|
|
70
|
+
// targetEl.className += ' css_' + widget.id
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// 如果不是容器组件,添加布局类(el-col)
|
|
74
|
+
if (!parentWidget) {
|
|
75
|
+
if (targetEl.classList) {
|
|
76
|
+
targetEl.classList.add('el-col')
|
|
77
|
+
targetEl.classList.add(`el-col-${widget.span || 24}`)
|
|
78
|
+
} else {
|
|
79
|
+
targetEl.className += ` el-col el-col-${widget.span || 24}`
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 应用组件的 CSS 样式
|
|
84
|
+
// if (widget.css) {
|
|
85
|
+
// appendCss(widget.id, widget.css)
|
|
86
|
+
// }
|
|
87
|
+
|
|
88
|
+
// 添加事件监听器
|
|
89
|
+
const handleMouseEnter = (event) => {
|
|
90
|
+
if (!templateInfo) {
|
|
91
|
+
// 阻止事件冒泡,防止触发外层组件的悬停
|
|
92
|
+
event.stopPropagation()
|
|
93
|
+
// 通过 designer 统一管理悬浮组件状态
|
|
94
|
+
if (typeof designer.setHoverById === 'function') {
|
|
95
|
+
designer.setHoverById(widget.id)
|
|
96
|
+
} else if (designer.emitEvent) {
|
|
97
|
+
// 兼容旧逻辑
|
|
98
|
+
designer.emitEvent('widget-hover', widget.id)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleMouseLeave = (event) => {
|
|
104
|
+
if (!templateInfo) {
|
|
105
|
+
const relatedTarget = event.relatedTarget
|
|
106
|
+
|
|
107
|
+
// 1. 鼠标仍在当前组件内部(含子元素),不处理
|
|
108
|
+
if (relatedTarget && targetEl.contains(relatedTarget)) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
event.stopPropagation()
|
|
113
|
+
|
|
114
|
+
// 2. 鼠标移动到了另一个挂了 data-widget-id 的组件上,直接把 hover 切换到该组件
|
|
115
|
+
if (relatedTarget && relatedTarget.closest) {
|
|
116
|
+
const nextWidgetEl = relatedTarget.closest('[data-widget-id]')
|
|
117
|
+
if (nextWidgetEl) {
|
|
118
|
+
const nextId = nextWidgetEl.getAttribute('data-widget-id')
|
|
119
|
+
if (nextId && typeof designer.setHoverById === 'function') {
|
|
120
|
+
designer.setHoverById(nextId)
|
|
121
|
+
return
|
|
122
|
+
} else if (nextId && designer.emitEvent) {
|
|
123
|
+
// 兼容旧逻辑
|
|
124
|
+
designer.emitEvent('widget-hover', nextId)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 3. 否则认为鼠标离开了所有组件区域,清理悬浮状态
|
|
131
|
+
if (typeof designer.clearHover === 'function') {
|
|
132
|
+
designer.clearHover()
|
|
133
|
+
} else if (designer.emitEvent) {
|
|
134
|
+
designer.emitEvent('widget-hover', null)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const handleClick = (event) => {
|
|
140
|
+
if (!templateInfo) {
|
|
141
|
+
const widgetInfo = designer.getWidgetByType(widget.contentType, widget.type) || {}
|
|
142
|
+
if (widgetInfo.ID) {
|
|
143
|
+
designer.setSelected(widget)
|
|
144
|
+
event.stopPropagation()
|
|
145
|
+
// 触发全局事件
|
|
146
|
+
if (designer.emitEvent) {
|
|
147
|
+
designer.emitEvent('widget-click', {
|
|
148
|
+
widget: widget,
|
|
149
|
+
element: targetEl,
|
|
150
|
+
parentWidget: parentWidget
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 组件拖拽开始
|
|
158
|
+
const handleDragStart = (event) => {
|
|
159
|
+
event.stopPropagation()
|
|
160
|
+
|
|
161
|
+
// 设置拖拽数据
|
|
162
|
+
const dragData = {
|
|
163
|
+
type: 'widget-reorder',
|
|
164
|
+
widgetId: widget.id,
|
|
165
|
+
parentWidgetId: parentWidget?.id || null
|
|
166
|
+
}
|
|
167
|
+
const dragDataStr = JSON.stringify(dragData)
|
|
168
|
+
|
|
169
|
+
event.dataTransfer.setData('text/plain', dragDataStr)
|
|
170
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
171
|
+
|
|
172
|
+
// 设置自定义拖拽图像(空元素),避免浏览器默认拖拽行为干扰
|
|
173
|
+
const emptyEl = document.createElement('div')
|
|
174
|
+
emptyEl.style.cssText = 'position:absolute;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none'
|
|
175
|
+
document.body.appendChild(emptyEl)
|
|
176
|
+
event.dataTransfer.setDragImage(emptyEl, 0, 0)
|
|
177
|
+
setTimeout(() => { if (emptyEl.parentNode) emptyEl.parentNode.removeChild(emptyEl) }, 0)
|
|
178
|
+
|
|
179
|
+
// 添加拖拽样式和标记类
|
|
180
|
+
targetEl.style.opacity = '0.5'
|
|
181
|
+
targetEl.classList.add('epx-widget-dragging')
|
|
182
|
+
|
|
183
|
+
// 注意:不要设置 pointer-events: none,否则会阻止 drop 事件
|
|
184
|
+
// 使用 CSS 类来控制样式即可
|
|
185
|
+
|
|
186
|
+
// 通知 designer 开始拖拽(使用统一的拖拽状态管理)
|
|
187
|
+
if (designer.startDrag) {
|
|
188
|
+
designer.startDrag({
|
|
189
|
+
source: 'preview',
|
|
190
|
+
data: dragData,
|
|
191
|
+
title: widgetInfo.TITLE || widget.title,
|
|
192
|
+
icon: widgetInfo.ICON,
|
|
193
|
+
x: event.clientX,
|
|
194
|
+
y: event.clientY,
|
|
195
|
+
isNative: true
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 组件拖拽结束
|
|
201
|
+
const handleDragEnd = (event) => {
|
|
202
|
+
event.stopPropagation()
|
|
203
|
+
|
|
204
|
+
// 恢复样式
|
|
205
|
+
targetEl.style.opacity = '1'
|
|
206
|
+
targetEl.classList.remove('epx-widget-dragging')
|
|
207
|
+
|
|
208
|
+
// 预览区域组件拖拽结束,需要关闭悬浮窗
|
|
209
|
+
// 与组件库不同,预览区域的组件不会在拖拽开始时关闭/销毁,
|
|
210
|
+
// 所以可以安全地发送 'drag-end' 事件来关闭悬浮窗
|
|
211
|
+
if (designer.emitEvent) {
|
|
212
|
+
designer.emitEvent('drag-end')
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 保存事件处理函数和元素引用,以便后续移除
|
|
217
|
+
el._widgetTargetEl = targetEl
|
|
218
|
+
el._widgetMouseEnter = handleMouseEnter
|
|
219
|
+
el._widgetMouseLeave = handleMouseLeave
|
|
220
|
+
el._widgetClick = handleClick
|
|
221
|
+
el._widgetDragStart = handleDragStart
|
|
222
|
+
el._widgetDragEnd = handleDragEnd
|
|
223
|
+
|
|
224
|
+
targetEl.addEventListener('mouseenter', handleMouseEnter)
|
|
225
|
+
targetEl.addEventListener('mouseleave', handleMouseLeave)
|
|
226
|
+
targetEl.addEventListener('click', handleClick)
|
|
227
|
+
targetEl.addEventListener('dragstart', handleDragStart)
|
|
228
|
+
targetEl.addEventListener('dragend', handleDragEnd)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 立即执行,如果组件还没挂载,在 inserted 钩子中再执行一次
|
|
232
|
+
applyDirective()
|
|
233
|
+
el._applyDirective = applyDirective
|
|
234
|
+
},
|
|
235
|
+
mounted (el, binding) {
|
|
236
|
+
// 组件插入后再次应用,确保找到正确的根元素;
|
|
237
|
+
// 组件在同一 tick 内挂载又卸载时 unmounted 已删掉该引用,回调里要再判一次
|
|
238
|
+
if (el._applyDirective) {
|
|
239
|
+
setTimeout(() => {
|
|
240
|
+
if (typeof el._applyDirective === 'function') el._applyDirective()
|
|
241
|
+
}, 0)
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
updated (el, binding) {
|
|
245
|
+
// console.log('update', binding.value)
|
|
246
|
+
// const { widget } = binding.value || {}
|
|
247
|
+
// if (!widget) return
|
|
248
|
+
|
|
249
|
+
// // 更新 CSS 样式
|
|
250
|
+
// if (widget.css) {
|
|
251
|
+
// appendCss(widget.id, widget.css)
|
|
252
|
+
// }
|
|
253
|
+
},
|
|
254
|
+
unmounted (el) {
|
|
255
|
+
detach(el)
|
|
256
|
+
delete el._applyDirective
|
|
257
|
+
}
|
|
258
|
+
}
|