@@ -45,10 +45,11 @@ import {
4545 usePage ,
4646 useTranslate ,
4747 getMetaApi ,
48- META_SERVICE
48+ META_SERVICE ,
49+ META_APP
4950} from ' @opentiny/tiny-engine-meta-register'
5051import { ToolbarBase } from ' @opentiny/tiny-engine-common'
51- import { fetchPageList } from ' ./http'
52+ import { fetchPageList , fetchBlockGroups , createBlockGroup , createBlock } from ' ./http'
5253import { VueToDslConverter } from ' @opentiny/tiny-engine-vue-to-dsl'
5354import OverwriteDialog from ' ./OverwriteDialog.vue'
5455import { TinyPopover } from ' @opentiny/vue'
@@ -78,6 +79,8 @@ export default {
7879 appId: ' ' as any
7980 })
8081
82+ const { publishBlock } = getMetaApi (META_APP .BlockManage )
83+
8184 const poperVisible = ref (false )
8285 const clickPopover = () => {
8386 poperVisible .value = ! poperVisible .value
@@ -246,17 +249,240 @@ export default {
246249 }
247250 }
248251
252+ // 3) 创建区块(批量)—— 将页面中引用的子组件创建为区块
253+ const blocks = Array .isArray (appSchema ?.blockSchemas ) ? appSchema .blockSchemas : []
254+ if (blocks .length ) {
255+ try {
256+ // 查询或创建区块分组
257+ let groupId: string | null = null
258+ try {
259+ const groupsRes: any = await fetchBlockGroups ({ app: appId })
260+ const groups = Array .isArray (groupsRes ) ? groupsRes : groupsRes ?.data || []
261+
262+ if (groups .length > 0 ) {
263+ // 使用第一个分组
264+ groupId = groups [0 ].id
265+ } else {
266+ // 创建默认分组 "我的分组"
267+ const createGroupRes: any = await createBlockGroup ({
268+ name: ' 我的分组' ,
269+ app: appId
270+ })
271+ groupId = createGroupRes ?.id
272+ }
273+ } catch (e ) {
274+ // 继续创建区块,即使分组创建失败
275+ }
276+
277+ // 创建区块
278+ // 先从页面 schema 中收集父组件传给各区块的 props
279+ const pages = Array .isArray (appSchema ?.pageSchema ) ? appSchema .pageSchema : []
280+ const parentPropsMap: Record <string , Record <string , any >> = {}
281+ const collectParentProps = (node : any ) => {
282+ if (! node || typeof node !== ' object' ) return
283+ if (node .componentType === ' Block' && node .componentName && node .props ) {
284+ const name = node .componentName
285+ if (! parentPropsMap [name ]) parentPropsMap [name ] = {}
286+ Object .keys (node .props ).forEach ((key ) => {
287+ if (key !== ' className' && key !== ' style' && key !== ' class' ) {
288+ parentPropsMap [name ][key ] = node .props [key ]
289+ }
290+ })
291+ }
292+ if (Array .isArray (node .children )) {
293+ node .children .forEach (collectParentProps )
294+ }
295+ }
296+ pages .forEach ((ps : any ) => {
297+ if (ps ?.children ) ps .children .forEach (collectParentProps )
298+ })
299+
300+ await Promise .allSettled (
301+ blocks .map ((bs : any ) => {
302+ const blockLabel = bs .fileName || bs .meta ?.name || ' Block'
303+
304+ // 类型对应的默认编辑器组件
305+ const META_COMPONENTS: Record <string , string > = {
306+ array: ' CodeConfigurator' ,
307+ string: ' InputConfigurator' ,
308+ number: ' NumberConfigurator' ,
309+ object: ' CodeConfigurator' ,
310+ boolean: ' SwitchConfigurator' ,
311+ function: ' CodeConfigurator'
312+ }
313+
314+ // 推断值的类型
315+ const inferType = (val : any ): string => {
316+ if (val === null || val === undefined ) return ' string'
317+ if (typeof val === ' object' && val .type === ' JSExpression' ) return ' string'
318+ if (Array .isArray (val )) return ' array'
319+ return typeof val
320+ }
321+
322+ // 获取默认值(JSExpression 取其原始值,其他直接用)
323+ const getDefaultValue = (val : any ): any => {
324+ if (val === null || val === undefined ) return ' '
325+ if (typeof val === ' object' && val .type === ' JSExpression' ) return ' '
326+ return val
327+ }
328+
329+ // 合并两个来源的 props:
330+ // 1. 子组件自身声明的 props(bs.props)
331+ // 2. 父组件传递的 props(parentPropsMap)
332+ const declaredProps: Record <string , any > = {}
333+ if (Array .isArray (bs .props )) {
334+ bs .props .forEach ((p : any ) => {
335+ declaredProps [p .name ] = p
336+ })
337+ }
338+ const parentProps = parentPropsMap [blockLabel ] || {}
339+
340+ // 以父组件传递的 props 为主,合并子组件声明的 props
341+ const mergedPropNames = new Set ([... Object .keys (declaredProps ), ... Object .keys (parentProps )])
342+
343+ const blockProperties: any [] = []
344+ mergedPropNames .forEach ((propName ) => {
345+ const declared = declaredProps [propName ]
346+ const parentVal = parentProps [propName ]
347+
348+ const propType = declared ?.type || inferType (parentVal )
349+ const defaultValue = declared ?.default !== undefined ? declared .default : getDefaultValue (parentVal )
350+
351+ blockProperties .push ({
352+ property: propName ,
353+ type: propType ,
354+ defaultValue: defaultValue ,
355+ label: {
356+ text: {
357+ zh_CN: propName
358+ }
359+ },
360+ cols: 12 ,
361+ rules: [],
362+ accessor: {},
363+ hidden: false ,
364+ required: declared ?.required || false ,
365+ readOnly: false ,
366+ disabled: false ,
367+ widget: {
368+ component: META_COMPONENTS [propType ] || ' InputConfigurator' ,
369+ props: {}
370+ },
371+ properties: [
372+ {
373+ label: {
374+ zh_CN: ' 默认分组'
375+ },
376+ content: []
377+ }
378+ ]
379+ })
380+ })
381+
382+ // 将子节点中引用 state 变量的地方转换为 this.props.xxx
383+ const propNames = new Set (blockProperties .map ((p : any ) => p .property ))
384+ const rewriteChildrenProps = (node : any ) => {
385+ if (! node || typeof node !== ' object' ) return
386+ if (node .props && typeof node .props === ' object' ) {
387+ Object .keys (node .props ).forEach ((key ) => {
388+ const val = node .props [key ]
389+ // 将 state 引用转为 props 引用
390+ if (typeof val === ' object' && val ?.type === ' JSExpression' && typeof val .value === ' string' ) {
391+ propNames .forEach ((pn ) => {
392+ if (val .value .includes (` this.state.${pn } ` )) {
393+ val .value = val .value .replace (new RegExp (` this\\ .state\\ .${pn } ` , ' g' ), ` this.props.${pn } ` )
394+ }
395+ })
396+ }
397+ })
398+ }
399+ if (Array .isArray (node .children )) {
400+ node .children .forEach (rewriteChildrenProps )
401+ }
402+ }
403+ const children = JSON .parse (JSON .stringify (bs .children || []))
404+ children .forEach (rewriteChildrenProps )
405+
406+ const blockParams: any = {
407+ label: blockLabel ,
408+ name_cn: blockLabel ,
409+ public: 1 ,
410+ framework: ' Vue' ,
411+ created_app: appId ,
412+ content: {
413+ componentName: ' Block' ,
414+ fileName: blockLabel ,
415+ css: bs .css || ' ' ,
416+ props: {},
417+ children: children ,
418+ schema: {
419+ properties: [
420+ {
421+ label: {
422+ zh_CN: ' 基础信息'
423+ },
424+ description: {
425+ zh_CN: ' 基础信息'
426+ },
427+ collapse: {
428+ number: 6 ,
429+ text: {
430+ zh_CN: ' 显示更多'
431+ }
432+ },
433+ content: blockProperties
434+ }
435+ ],
436+ events: {},
437+ slots: {}
438+ },
439+ state: {},
440+ methods: bs .methods || {},
441+ dataSource: bs .dataSource || {},
442+ dependencies: bs .dependencies || { scripts: [], styles: [] },
443+ id: ' body'
444+ }
445+ }
446+
447+ // 添加分组ID(如果成功获取或创建)
448+ if (groupId ) {
449+ blockParams .groups = [groupId ]
450+ } else {
451+ // 备选方案:使用空分类数组
452+ blockParams .categories = []
453+ }
454+
455+ return createBlock (blockParams ).then ((data : any ) => {
456+ if (data ?.id ) {
457+ const params = {
458+ block: data ,
459+ is_compile: true ,
460+ deploy_info: ' 导入项目自动发布' ,
461+ version: ' 1.0.1' ,
462+ needToSave: true
463+ }
464+ publishBlock (params )
465+ }
466+ })
467+ })
468+ )
469+ } catch (e ) {
470+ // 区块创建失败不阻塞页面导入流程
471+ }
472+ }
473+
249474 // 若弹出覆盖选择对话框,则先不立即渲染,等用户选择后再渲染
250475 if (! state .showOverwriteDialog ) {
251476 const chosen = pages .find ((p : any ) => p ?.meta ?.isHome ) || pages [0 ]
477+ const blockMsg = blocks .length ? ` ,已创建 ${blocks .length } 个区块 ` : ' '
252478 if (! chosen ) {
253- useNotify ({ type: ' success' , title: ' 导入成功' , message: ` 已更新全局配置(未检测到页面) ` })
479+ useNotify ({ type: ' success' , title: ' 导入成功' , message: ` 已更新全局配置(未检测到页面)${ blockMsg } ` })
254480 } else {
255481 await switchToPageByName (chosen ?.meta ?.name || chosen ?.fileName )
256482 useNotify ({
257483 type: ' success' ,
258484 title: ' 导入成功' ,
259- message: ` 已创建页面并加载:${chosen ?.meta ?.name || ' 页面' } `
485+ message: ` 已创建页面并加载:${chosen ?.meta ?.name || ' 页面' }${ blockMsg } `
260486 })
261487 }
262488 }
0 commit comments