@opentiny/tiny-engine-canvas 1.0.0
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/.eslintrc.js +42 -0
- package/README.md +7 -0
- package/canvas.html +212 -0
- package/dist/index.js +48919 -0
- package/index.html +13 -0
- package/package.json +30 -0
- package/public/favicon.ico +0 -0
- package/src/Design.vue +53 -0
- package/src/assets/logo.png +0 -0
- package/src/canvas.js +34 -0
- package/src/components/builtin/CanvasBox.vue +22 -0
- package/src/components/builtin/CanvasCol.vue +89 -0
- package/src/components/builtin/CanvasCollection.js +278 -0
- package/src/components/builtin/CanvasCollection.vue +106 -0
- package/src/components/builtin/CanvasIcon.vue +30 -0
- package/src/components/builtin/CanvasImg.vue +18 -0
- package/src/components/builtin/CanvasPlaceholder.vue +26 -0
- package/src/components/builtin/CanvasRow.vue +67 -0
- package/src/components/builtin/CanvasRowColContainer.vue +42 -0
- package/src/components/builtin/CanvasSlot.vue +22 -0
- package/src/components/builtin/CanvasText.vue +18 -0
- package/src/components/builtin/builtin.json +955 -0
- package/src/components/builtin/helper.js +46 -0
- package/src/components/builtin/index.js +33 -0
- package/src/components/common/index.js +158 -0
- package/src/components/container/CanvasAction.vue +554 -0
- package/src/components/container/CanvasContainer.vue +244 -0
- package/src/components/container/CanvasDivider.vue +246 -0
- package/src/components/container/CanvasDragItem.vue +38 -0
- package/src/components/container/CanvasFooter.vue +86 -0
- package/src/components/container/CanvasMenu.vue +214 -0
- package/src/components/container/CanvasResize.vue +195 -0
- package/src/components/container/CanvasResizeBorder.vue +219 -0
- package/src/components/container/container.js +791 -0
- package/src/components/container/keyboard.js +147 -0
- package/src/components/container/shortCutPopover.vue +181 -0
- package/src/components/render/CanvasEmpty.vue +14 -0
- package/src/components/render/RenderMain.js +408 -0
- package/src/components/render/context.js +53 -0
- package/src/components/render/render.js +689 -0
- package/src/components/render/runner.js +140 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/zh.json +5 -0
- package/src/i18n.js +21 -0
- package/src/index.js +96 -0
- package/src/locale.js +19 -0
- package/src/lowcode.js +104 -0
- package/src/main.js +17 -0
- package/test/form.json +690 -0
- package/test/group.json +99 -0
- package/test/jsslot.json +427 -0
- package/vite.config.js +73 -0
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2023 - present TinyEngine Authors.
|
|
3
|
+
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license.
|
|
6
|
+
*
|
|
7
|
+
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
|
8
|
+
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
|
9
|
+
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { h, provide, reactive } from 'vue'
|
|
14
|
+
import { isHTMLTag, hyphenate } from '@vue/shared'
|
|
15
|
+
import { useBroadcastChannel } from '@vueuse/core'
|
|
16
|
+
import { constants, utils } from '@opentiny/tiny-engine-utils'
|
|
17
|
+
import babelPluginJSX from '@vue/babel-plugin-jsx'
|
|
18
|
+
import { transformSync } from '@babel/core'
|
|
19
|
+
import i18nHost from '@opentiny/tiny-engine-i18n-host'
|
|
20
|
+
import { context, conditions, setNode } from './context'
|
|
21
|
+
import {
|
|
22
|
+
CanvasBox,
|
|
23
|
+
CanvasCollection,
|
|
24
|
+
CanvasIcon,
|
|
25
|
+
CanvasText,
|
|
26
|
+
CanvasSlot,
|
|
27
|
+
CanvasImg,
|
|
28
|
+
CanvasRow,
|
|
29
|
+
CanvasCol,
|
|
30
|
+
CanvasRowColContainer
|
|
31
|
+
} from '../builtin'
|
|
32
|
+
import { NODE_UID as DESIGN_UIDKEY, NODE_TAG as DESIGN_TAGKEY, NODE_LOOP as DESIGN_LOOPID } from '../common'
|
|
33
|
+
|
|
34
|
+
const { BROADCAST_CHANNEL } = constants
|
|
35
|
+
const { hyphenateRE } = utils
|
|
36
|
+
|
|
37
|
+
const transformJSX = (code) =>
|
|
38
|
+
transformSync(code, {
|
|
39
|
+
plugins: [
|
|
40
|
+
[
|
|
41
|
+
babelPluginJSX,
|
|
42
|
+
{
|
|
43
|
+
pragma: 'h',
|
|
44
|
+
isCustomElement: (name) => custElements[name]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export const blockSlotDataMap = reactive({})
|
|
51
|
+
|
|
52
|
+
const Mapper = {
|
|
53
|
+
Icon: CanvasIcon,
|
|
54
|
+
Text: CanvasText,
|
|
55
|
+
Collection: CanvasCollection,
|
|
56
|
+
div: CanvasBox,
|
|
57
|
+
Slot: CanvasSlot,
|
|
58
|
+
slot: CanvasSlot,
|
|
59
|
+
Template: CanvasBox,
|
|
60
|
+
Img: CanvasImg,
|
|
61
|
+
CanvasRow,
|
|
62
|
+
CanvasCol,
|
|
63
|
+
CanvasRowColContainer
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify })
|
|
67
|
+
|
|
68
|
+
// 此处向外层window传递notify配置参数
|
|
69
|
+
export const globalNotify = (options) => post(options)
|
|
70
|
+
|
|
71
|
+
export const collectionMethodsMap = {}
|
|
72
|
+
|
|
73
|
+
const getNative = (name) => {
|
|
74
|
+
return window.TinyLowcodeComponent?.[name]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const getBlock = (name) => {
|
|
78
|
+
return window.blocks?.[name]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const configure = {}
|
|
82
|
+
const controller = {}
|
|
83
|
+
|
|
84
|
+
export const setConfigure = (configureData) => {
|
|
85
|
+
Object.assign(configure, configureData)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const setController = (controllerData) => {
|
|
89
|
+
Object.assign(controller, controllerData)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const getController = () => controller
|
|
93
|
+
|
|
94
|
+
const isI18nData = (data) => {
|
|
95
|
+
return data && data.type === 'i18n'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const isJSSlot = (data) => {
|
|
99
|
+
return data && data.type === 'JSSlot'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isJSExpression = (data) => {
|
|
103
|
+
return data && data.type === 'JSExpression'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isJSFunction = (data) => {
|
|
107
|
+
return data && data.type === 'JSFunction'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const isJSResource = (data) => {
|
|
111
|
+
return data && data.type === 'JSResource'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isString = (data) => {
|
|
115
|
+
return typeof data === 'string'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const isArray = (data) => {
|
|
119
|
+
return Array.isArray(data)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isFunction = (data) => {
|
|
123
|
+
return typeof data === 'function'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const isObject = (data) => {
|
|
127
|
+
return typeof data === 'object'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 判断是否是状态访问器
|
|
131
|
+
export const isStateAccessor = (stateData) =>
|
|
132
|
+
stateData?.accessor?.getter?.type === 'JSFunction' || stateData?.accessor?.setter?.type === 'JSFunction'
|
|
133
|
+
|
|
134
|
+
const parseI18n = (i18n, scope, ctx) => {
|
|
135
|
+
return parseExpression(
|
|
136
|
+
{
|
|
137
|
+
type: 'JSExpression',
|
|
138
|
+
value: `this.i18n('${i18n.key}')`
|
|
139
|
+
},
|
|
140
|
+
scope,
|
|
141
|
+
{ i18n: i18nHost.global.t, ...ctx }
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const parseExpression = (data, scope, ctx) => {
|
|
146
|
+
try {
|
|
147
|
+
if (data.value.indexOf('this.i18n') > -1) {
|
|
148
|
+
ctx.i18n = i18nHost.global.t
|
|
149
|
+
} else if (data.value.indexOf('t(') > -1) {
|
|
150
|
+
ctx.t = i18nHost.global.t
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return newFn('$scope', `with($scope || {}) { return ${data.value} }`).call(ctx, {
|
|
154
|
+
...ctx,
|
|
155
|
+
...scope,
|
|
156
|
+
slotScope: scope
|
|
157
|
+
})
|
|
158
|
+
} catch (err) {
|
|
159
|
+
return undefined
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const parseJSSlot = (data, scope) => {
|
|
164
|
+
return ($scope) => renderDefault(data.value, { ...scope, ...$scope }, data)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const generateFn = (innerFn, context) => {
|
|
168
|
+
return (...args) => {
|
|
169
|
+
// 如果有数据源标识,则表格的fetchData返回数据源的静态数据
|
|
170
|
+
const sourceId = collectionMethodsMap[innerFn.realName || innerFn.name]
|
|
171
|
+
if (sourceId) {
|
|
172
|
+
return innerFn.call(context, ...args)
|
|
173
|
+
} else {
|
|
174
|
+
let result = null
|
|
175
|
+
|
|
176
|
+
// 这里是为了兼容用户写法报错导致画布异常,但无法捕获promise内部的异常
|
|
177
|
+
try {
|
|
178
|
+
result = innerFn.call(context, ...args)
|
|
179
|
+
} catch (error) {
|
|
180
|
+
globalNotify({
|
|
181
|
+
type: 'warning',
|
|
182
|
+
title: `函数:${innerFn.name}执行报错`,
|
|
183
|
+
message: error?.message || `函数:${innerFn.name}执行报错,请检查语法`
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 这里注意如果innerFn返回的是一个promise则需要捕获异常,重新返回默认一条空数据
|
|
188
|
+
if (result.then) {
|
|
189
|
+
result = new Promise((resolve) => {
|
|
190
|
+
result.then(resolve).catch((error) => {
|
|
191
|
+
globalNotify({
|
|
192
|
+
type: 'warning',
|
|
193
|
+
title: '异步函数执行报错',
|
|
194
|
+
message: error?.message || '异步函数执行报错,请检查语法'
|
|
195
|
+
})
|
|
196
|
+
// 这里需要至少返回一条空数据,方便用户使用表格默认插槽
|
|
197
|
+
resolve({
|
|
198
|
+
result: [{}],
|
|
199
|
+
page: { total: 1 }
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 解析函数字符串结构
|
|
211
|
+
const parseFunctionString = (fnStr) => {
|
|
212
|
+
const fnRegexp = /(async)?.*?(\w+) *\(([\s\S]*?)\) *\{([\s\S]*)\}/
|
|
213
|
+
const result = fnRegexp.exec(fnStr)
|
|
214
|
+
if (result) {
|
|
215
|
+
return {
|
|
216
|
+
type: result[1] || '',
|
|
217
|
+
name: result[2],
|
|
218
|
+
params: result[3]
|
|
219
|
+
.split(',')
|
|
220
|
+
.map((item) => item.trim())
|
|
221
|
+
.filter((item) => Boolean(item)),
|
|
222
|
+
body: result[4]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 规避创建function eslint报错
|
|
229
|
+
export const newFn = (...argv) => {
|
|
230
|
+
const Fn = Function
|
|
231
|
+
return new Fn(...argv)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 解析JSX字符串为可执行函数
|
|
235
|
+
const parseJSXFunction = (data, ctx) => {
|
|
236
|
+
try {
|
|
237
|
+
const newValue = transformJSX(data.value)
|
|
238
|
+
.code.replace(/import \{.+\} from "vue";/, '')
|
|
239
|
+
.replace(/h\(_?resolveComponent\((.*?)\)/g, `h(this.getComponent($1)`)
|
|
240
|
+
.replace(/_?resolveComponent/g, 'h')
|
|
241
|
+
.replace(/_?createTextVNode\((.*?)\)/g, '$1')
|
|
242
|
+
const fnInfo = parseFunctionString(newValue)
|
|
243
|
+
if (!fnInfo) throw Error('函数解析失败,请检查格式。示例:function fnName() { }')
|
|
244
|
+
|
|
245
|
+
return newFn(...fnInfo.params, fnInfo.body).bind({
|
|
246
|
+
...ctx,
|
|
247
|
+
getComponent
|
|
248
|
+
})
|
|
249
|
+
} catch (error) {
|
|
250
|
+
globalNotify({
|
|
251
|
+
type: 'warning',
|
|
252
|
+
title: '函数声明解析报错',
|
|
253
|
+
message: error?.message || '函数声明解析报错,请检查语法'
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return newFn()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const parseJSFunction = (data, scope, ctx = context) => {
|
|
261
|
+
try {
|
|
262
|
+
const innerFn = newFn(`return ${data.value}`).bind(ctx)()
|
|
263
|
+
return generateFn(innerFn, ctx)
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return parseJSXFunction(data, ctx)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const parseList = []
|
|
270
|
+
|
|
271
|
+
export function parseData(data, scope, ctx = context) {
|
|
272
|
+
let res = data
|
|
273
|
+
parseList.some((item) => {
|
|
274
|
+
if (item.type(data)) {
|
|
275
|
+
res = item.parseFunc(data, scope, ctx)
|
|
276
|
+
|
|
277
|
+
return true
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return res
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const parseCondition = (condition, scope, ctx = context) => {
|
|
287
|
+
// eslint-disable-next-line no-eq-null
|
|
288
|
+
return condition == null ? true : parseData(condition, scope, ctx)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const parseLoopArgs = (_loop) => {
|
|
292
|
+
if (_loop) {
|
|
293
|
+
const { item, index, loopArgs = '' } = _loop
|
|
294
|
+
const body = `return {${loopArgs[0] || 'item'}: item, ${loopArgs[1] || 'index'} : index }`
|
|
295
|
+
return newFn('item,index', body)(item, index)
|
|
296
|
+
}
|
|
297
|
+
return undefined
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const parseObjectData = (data, scope, ctx) => {
|
|
301
|
+
if (!data) {
|
|
302
|
+
return data
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 如果是状态访问器,则直接解析默认值
|
|
306
|
+
if (isStateAccessor(data)) {
|
|
307
|
+
return parseData(data.defaultValue)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 解析通过属性传递icon图标组件
|
|
311
|
+
if (data.componentName === 'Icon') {
|
|
312
|
+
return getIcon(data.props.name)
|
|
313
|
+
}
|
|
314
|
+
const res = {}
|
|
315
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
316
|
+
// 如果是插槽则需要进行特殊处理
|
|
317
|
+
if (key === 'slot' && value?.name) {
|
|
318
|
+
res[key] = value.name
|
|
319
|
+
} else {
|
|
320
|
+
res[key] = parseData(value, scope, ctx)
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
return res
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const parseString = (data) => {
|
|
327
|
+
return data.trim()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const parseArray = (data, scope, ctx) => {
|
|
331
|
+
return data.map((item) => parseData(item, scope, ctx))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const parseFunction = (data, scope, ctx) => {
|
|
335
|
+
return data.bind(ctx)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
parseList.push(
|
|
339
|
+
...[
|
|
340
|
+
{
|
|
341
|
+
type: isJSExpression,
|
|
342
|
+
parseFunc: parseExpression
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
type: isI18nData,
|
|
346
|
+
parseFunc: parseI18n
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: isJSFunction,
|
|
350
|
+
parseFunc: parseJSFunction
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
type: isJSResource,
|
|
354
|
+
parseFunc: parseExpression
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
type: isJSSlot,
|
|
358
|
+
parseFunc: parseJSSlot
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
type: isString,
|
|
362
|
+
parseFunc: parseString
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
type: isArray,
|
|
366
|
+
parseFunc: parseArray
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
type: isFunction,
|
|
370
|
+
parseFunc: parseFunction
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: isObject,
|
|
374
|
+
parseFunc: parseObjectData
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
const stopEvent = (event) => {
|
|
380
|
+
event.preventDefault?.()
|
|
381
|
+
event.stopPropagation?.()
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const getPlainProps = (object = {}) => {
|
|
386
|
+
const { slot, ...rest } = object
|
|
387
|
+
const props = {}
|
|
388
|
+
|
|
389
|
+
if (slot) {
|
|
390
|
+
rest.slot = slot.name || slot
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
Object.entries(rest).forEach(([key, value]) => {
|
|
394
|
+
let renderKey = key
|
|
395
|
+
|
|
396
|
+
// html 标签属性会忽略大小写,所以传递包含大写的 props 需要转换为 kebab 形式的 props
|
|
397
|
+
if (!/on[A-Z]/.test(renderKey) && hyphenateRE.test(renderKey)) {
|
|
398
|
+
renderKey = hyphenate(renderKey)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (['boolean', 'string', 'number'].includes(typeof value)) {
|
|
402
|
+
props[renderKey] = value
|
|
403
|
+
} else {
|
|
404
|
+
// 如果传给webcomponent标签的是对象或者数组需要使用.prop修饰符,转化成h函数就是如下写法
|
|
405
|
+
props[`.${renderKey}`] = value
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
return props
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const custElements = {}
|
|
412
|
+
|
|
413
|
+
const generateCollection = (schema) => {
|
|
414
|
+
if (schema.componentName === 'Collection' && schema.props?.dataSource && schema.children) {
|
|
415
|
+
schema.children.forEach((item) => {
|
|
416
|
+
const fetchData = item.props?.fetchData
|
|
417
|
+
const methodMatch = fetchData?.value?.match(/this\.(.+?)}/)
|
|
418
|
+
if (fetchData && methodMatch?.[1]) {
|
|
419
|
+
const methodName = methodMatch[1].trim()
|
|
420
|
+
// 缓存表格fetchData对应的数据源信息
|
|
421
|
+
collectionMethodsMap[methodName] = schema.props.dataSource
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const generateBlockContent = (schema) => {
|
|
428
|
+
if (schema?.componentName === 'Collection') {
|
|
429
|
+
generateCollection(schema)
|
|
430
|
+
}
|
|
431
|
+
if (Array.isArray(schema?.children)) {
|
|
432
|
+
schema.children.forEach((item) => {
|
|
433
|
+
generateBlockContent(item)
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const registerBlock = (componentName) => {
|
|
439
|
+
getController()
|
|
440
|
+
.registerBlock?.(componentName)
|
|
441
|
+
.then((res) => {
|
|
442
|
+
const blockSchema = res.content
|
|
443
|
+
|
|
444
|
+
// 拿到区块数据,建立区块中数据源的映射关系
|
|
445
|
+
generateBlockContent(blockSchema)
|
|
446
|
+
|
|
447
|
+
// 如果区块的根节点有百分比高度,则需要特殊处理,把高度百分比传递下去,适配大屏应用
|
|
448
|
+
if (/height:\s*?[\d|.]+?%/.test(blockSchema?.props?.style)) {
|
|
449
|
+
const blockDoms = document.querySelectorAll(hyphenate(componentName))
|
|
450
|
+
blockDoms.forEach((item) => {
|
|
451
|
+
item.style.height = '100%'
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export const wrapCustomElement = (componentName) => {
|
|
458
|
+
const material = getController().getMaterial(componentName)
|
|
459
|
+
|
|
460
|
+
if (!Object.keys(material).length) {
|
|
461
|
+
registerBlock(componentName)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
custElements[componentName] = {
|
|
465
|
+
name: componentName + '.ce',
|
|
466
|
+
render() {
|
|
467
|
+
return h(
|
|
468
|
+
hyphenate(componentName),
|
|
469
|
+
window.parent.TinyGlobalConfig.dslMode === 'Vue' ? getPlainProps(this.$attrs) : this.$attrs,
|
|
470
|
+
this.$slots.default?.()
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return custElements[componentName]
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const goupSlot = (children, isCustElm) => {
|
|
479
|
+
const slotGrup = {}
|
|
480
|
+
|
|
481
|
+
children.forEach((child) => {
|
|
482
|
+
const { componentName, children, params = [], props } = child
|
|
483
|
+
const slot = child.slot || props?.slot || 'default'
|
|
484
|
+
|
|
485
|
+
isCustElm && (child.props.slot = 'slot') // CE下需要给子节点加上slot标识
|
|
486
|
+
slotGrup[slot] = slotGrup[slot] || {
|
|
487
|
+
value: [],
|
|
488
|
+
params
|
|
489
|
+
}
|
|
490
|
+
slotGrup[slot].value.push(...(componentName === 'Template' ? children : [child])) // template 标签直接过滤掉
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
return slotGrup
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const renderSlot = (children, scope, schema, isCustElm) => {
|
|
497
|
+
if (children.some((a) => a.componentName === 'Template')) {
|
|
498
|
+
const slotGrup = goupSlot(children, isCustElm)
|
|
499
|
+
const slots = {}
|
|
500
|
+
|
|
501
|
+
Object.keys(slotGrup).forEach((slotName) => {
|
|
502
|
+
slots[slotName] = ($scope) => renderDefault(slotGrup[slotName].value, { ...scope, ...$scope }, schema)
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
return slots
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { default: () => renderDefault(children, scope, schema) }
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export const getComponent = (name) => {
|
|
512
|
+
return (
|
|
513
|
+
Mapper[name] ||
|
|
514
|
+
getNative(name) ||
|
|
515
|
+
getBlock(name) ||
|
|
516
|
+
custElements[name] ||
|
|
517
|
+
(isHTMLTag(name) ? name : wrapCustomElement(name))
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export const getIcon = (name) => window.TinyVueIcon?.[name]?.() || ''
|
|
522
|
+
|
|
523
|
+
const checkGroup = (componentName) => configure[componentName]?.nestingRule?.childWhitelist?.length
|
|
524
|
+
|
|
525
|
+
const clickCapture = (componentName) => configure[componentName]?.clickCapture !== false
|
|
526
|
+
|
|
527
|
+
const getBindProps = (schema, scope) => {
|
|
528
|
+
const { id, componentName } = schema
|
|
529
|
+
const invalidity = configure[componentName]?.invalidity || []
|
|
530
|
+
|
|
531
|
+
const bindProps = {
|
|
532
|
+
...parseData(schema.props, scope),
|
|
533
|
+
[DESIGN_UIDKEY]: id,
|
|
534
|
+
[DESIGN_TAGKEY]: componentName,
|
|
535
|
+
onMoseover: stopEvent,
|
|
536
|
+
onFocus: stopEvent
|
|
537
|
+
}
|
|
538
|
+
if (scope) {
|
|
539
|
+
bindProps[DESIGN_LOOPID] = scope.index === undefined ? scope.idx : scope.index
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 在捕获阶段阻止事件的传播
|
|
543
|
+
if (clickCapture(componentName)) {
|
|
544
|
+
bindProps.onClickCapture = stopEvent
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (Mapper[componentName]) {
|
|
548
|
+
bindProps.schema = schema
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// 绑定组件属性时需要将 className 重命名为 class,防止覆盖组件内置 class
|
|
552
|
+
bindProps.class = bindProps.className
|
|
553
|
+
delete bindProps.className
|
|
554
|
+
|
|
555
|
+
// 使画布中元素可拖拽
|
|
556
|
+
bindProps.draggable = true
|
|
557
|
+
|
|
558
|
+
// 过滤在门户网站上配置的画布丢弃的属性
|
|
559
|
+
invalidity.forEach((prop) => delete bindProps[prop])
|
|
560
|
+
|
|
561
|
+
return bindProps
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const renderDefault = (children, scope, parent) =>
|
|
565
|
+
children.map?.((child) =>
|
|
566
|
+
h(renderer, {
|
|
567
|
+
schema: child,
|
|
568
|
+
scope,
|
|
569
|
+
parent
|
|
570
|
+
})
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
const renderGroup = (children, scope, parent) => {
|
|
574
|
+
return children.map?.((schema) => {
|
|
575
|
+
const { componentName, children, loop, loopArgs, condition, id } = schema
|
|
576
|
+
const loopList = parseData(loop, scope)
|
|
577
|
+
|
|
578
|
+
const renderElement = (item, index) => {
|
|
579
|
+
const mergeScope = getLoopScope({
|
|
580
|
+
scope,
|
|
581
|
+
index,
|
|
582
|
+
item,
|
|
583
|
+
loopArgs
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
setNode(schema, parent)
|
|
587
|
+
|
|
588
|
+
if (conditions[id] === false || !parseCondition(condition, mergeScope)) {
|
|
589
|
+
return null
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return h(
|
|
593
|
+
getComponent(componentName),
|
|
594
|
+
getBindProps(schema, mergeScope),
|
|
595
|
+
Array.isArray(children) ? renderSlot(children, mergeScope, schema) : parseData(children, mergeScope)
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return loopList?.length ? loopList.map(renderElement) : renderElement()
|
|
600
|
+
})
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const getLoopScope = ({ scope, index, item, loopArgs }) => {
|
|
604
|
+
return {
|
|
605
|
+
...scope,
|
|
606
|
+
...(parseLoopArgs({
|
|
607
|
+
item,
|
|
608
|
+
index,
|
|
609
|
+
loopArgs
|
|
610
|
+
}) || {})
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const getChildren = (schema, mergeScope) => {
|
|
615
|
+
const { componentName, children } = schema
|
|
616
|
+
|
|
617
|
+
const isNative = typeof component === 'string'
|
|
618
|
+
const isCustElm = custElements[componentName]
|
|
619
|
+
const isGroup = checkGroup(componentName)
|
|
620
|
+
|
|
621
|
+
if (Array.isArray(children)) {
|
|
622
|
+
if (isNative || isCustElm) {
|
|
623
|
+
return renderDefault(children, mergeScope, schema)
|
|
624
|
+
} else {
|
|
625
|
+
return isGroup ? renderGroup(children, mergeScope, schema) : renderSlot(children, mergeScope, schema, isCustElm)
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
return parseData(children, mergeScope)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export const renderer = {
|
|
633
|
+
name: 'renderer',
|
|
634
|
+
props: {
|
|
635
|
+
schema: Object,
|
|
636
|
+
scope: Object,
|
|
637
|
+
parent: Object
|
|
638
|
+
},
|
|
639
|
+
setup(props) {
|
|
640
|
+
provide('schema', props.schema)
|
|
641
|
+
},
|
|
642
|
+
render() {
|
|
643
|
+
const { scope, schema, parent } = this
|
|
644
|
+
const { componentName, loop, loopArgs, condition } = schema
|
|
645
|
+
|
|
646
|
+
// 处理数据源和表格fetchData的映射关系
|
|
647
|
+
generateCollection(schema)
|
|
648
|
+
|
|
649
|
+
if (!componentName) {
|
|
650
|
+
return parseData(schema, scope)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const component = getComponent(componentName)
|
|
654
|
+
|
|
655
|
+
const loopList = parseData(loop, scope)
|
|
656
|
+
|
|
657
|
+
const renderElement = (item, index) => {
|
|
658
|
+
let mergeScope = item
|
|
659
|
+
? getLoopScope({
|
|
660
|
+
item,
|
|
661
|
+
index,
|
|
662
|
+
loopArgs,
|
|
663
|
+
scope
|
|
664
|
+
})
|
|
665
|
+
: scope
|
|
666
|
+
|
|
667
|
+
// 如果是区块,并且使用了区块的作用域插槽,则需要将作用域插槽的数据传递下去
|
|
668
|
+
if (parent?.componentType === 'Block' && componentName === 'Template' && schema.props?.slot?.params?.length) {
|
|
669
|
+
const slotName = schema.props.slot?.name || schema.props.slot
|
|
670
|
+
const blockName = parent.componentName
|
|
671
|
+
const slotData = blockSlotDataMap[blockName]?.[slotName] || {}
|
|
672
|
+
mergeScope = mergeScope ? { ...mergeScope, ...slotData } : slotData
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// 给每个节点设置schema.id,并缓存起来
|
|
676
|
+
setNode(schema, parent)
|
|
677
|
+
|
|
678
|
+
if (conditions[schema.id] === false || !parseCondition(condition, mergeScope)) {
|
|
679
|
+
return null
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return h(component, getBindProps(schema, mergeScope), getChildren(schema, mergeScope))
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return loopList?.length ? loopList.map(renderElement) : renderElement()
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export default renderer
|