@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.
Files changed (52) hide show
  1. package/.eslintrc.js +42 -0
  2. package/README.md +7 -0
  3. package/canvas.html +212 -0
  4. package/dist/index.js +48919 -0
  5. package/index.html +13 -0
  6. package/package.json +30 -0
  7. package/public/favicon.ico +0 -0
  8. package/src/Design.vue +53 -0
  9. package/src/assets/logo.png +0 -0
  10. package/src/canvas.js +34 -0
  11. package/src/components/builtin/CanvasBox.vue +22 -0
  12. package/src/components/builtin/CanvasCol.vue +89 -0
  13. package/src/components/builtin/CanvasCollection.js +278 -0
  14. package/src/components/builtin/CanvasCollection.vue +106 -0
  15. package/src/components/builtin/CanvasIcon.vue +30 -0
  16. package/src/components/builtin/CanvasImg.vue +18 -0
  17. package/src/components/builtin/CanvasPlaceholder.vue +26 -0
  18. package/src/components/builtin/CanvasRow.vue +67 -0
  19. package/src/components/builtin/CanvasRowColContainer.vue +42 -0
  20. package/src/components/builtin/CanvasSlot.vue +22 -0
  21. package/src/components/builtin/CanvasText.vue +18 -0
  22. package/src/components/builtin/builtin.json +955 -0
  23. package/src/components/builtin/helper.js +46 -0
  24. package/src/components/builtin/index.js +33 -0
  25. package/src/components/common/index.js +158 -0
  26. package/src/components/container/CanvasAction.vue +554 -0
  27. package/src/components/container/CanvasContainer.vue +244 -0
  28. package/src/components/container/CanvasDivider.vue +246 -0
  29. package/src/components/container/CanvasDragItem.vue +38 -0
  30. package/src/components/container/CanvasFooter.vue +86 -0
  31. package/src/components/container/CanvasMenu.vue +214 -0
  32. package/src/components/container/CanvasResize.vue +195 -0
  33. package/src/components/container/CanvasResizeBorder.vue +219 -0
  34. package/src/components/container/container.js +791 -0
  35. package/src/components/container/keyboard.js +147 -0
  36. package/src/components/container/shortCutPopover.vue +181 -0
  37. package/src/components/render/CanvasEmpty.vue +14 -0
  38. package/src/components/render/RenderMain.js +408 -0
  39. package/src/components/render/context.js +53 -0
  40. package/src/components/render/render.js +689 -0
  41. package/src/components/render/runner.js +140 -0
  42. package/src/i18n/en.json +5 -0
  43. package/src/i18n/zh.json +5 -0
  44. package/src/i18n.js +21 -0
  45. package/src/index.js +96 -0
  46. package/src/locale.js +19 -0
  47. package/src/lowcode.js +104 -0
  48. package/src/main.js +17 -0
  49. package/test/form.json +690 -0
  50. package/test/group.json +99 -0
  51. package/test/jsslot.json +427 -0
  52. 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