@@ -2,7 +2,15 @@ import { reactive } from 'vue'
22import { constants } from '@opentiny/tiny-engine-utils'
33import { getImportMap as getInitImportMap } from './importMap'
44import { getMetaApi , getMergeMeta , META_SERVICE } from '@opentiny/tiny-engine-meta-register'
5- import { fetchMetaData , fetchAppSchema , fetchBlockSchema , getPageById , getBlockById , fetchPageHistory } from './http'
5+ import {
6+ fetchMetaData ,
7+ fetchAppSchema ,
8+ fetchBlockSchema ,
9+ getPageById ,
10+ getBlockById ,
11+ fetchPageHistory ,
12+ fetchPageList
13+ } from './http'
614import generateMetaFiles , { processAppJsCode } from './generate'
715import srcFiles from './srcFiles'
816
@@ -350,59 +358,272 @@ export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewDat
350358 scripts : Record < string , string >
351359 styles : string [ ]
352360 } ) => {
353- const { appData, metaData, importMapData } = await getBasicData ( basicFiles , params . scripts )
361+ const searchParams = new URLSearchParams ( location . search )
362+ const previewType = searchParams . get ( 'previewType' )
354363
355- previewState . currentPage = params . currentPage
356- previewState . ancestors = params . ancestors
364+ if ( previewType === 'page' ) {
365+ const { appData , metaData , importMapData } = await getBasicData ( basicFiles , params . scripts )
357366
358- // importMap 发生变化才更新 importMap
359- if ( JSON . stringify ( previewState . importMap ) !== JSON . stringify ( importMapData ) ) {
360- setImportMap ( importMapData )
361- previewState . importMap = importMapData
362- }
367+ previewState . currentPage = params . currentPage
368+ previewState . ancestors = params . ancestors
369+
370+ // importMap 发生变化才更新 importMap
371+ if ( JSON . stringify ( previewState . importMap ) !== JSON . stringify ( importMapData ) ) {
372+ setImportMap ( importMapData )
373+ previewState . importMap = importMapData
374+ }
363375
364- const blockSet = new Set ( )
376+ const blockSet = new Set ( )
365377
366- let blocks = [ ]
367- const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi ( 'engine.service.generateCode' )
378+ let blocks = [ ]
379+ const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi ( 'engine.service.generateCode' )
368380
369- if ( params . ancestors ?. length ) {
370- const promises = params . ancestors . map ( ( item ) =>
371- getAllNestedBlocksSchema ( item . page_content , fetchBlockSchema , blockSet )
381+ if ( params . ancestors ?. length ) {
382+ const promises = params . ancestors . map ( ( item ) =>
383+ getAllNestedBlocksSchema ( item . page_content , fetchBlockSchema , blockSet )
384+ )
385+ blocks = ( await Promise . all ( promises ) ) . flat ( )
386+ }
387+
388+ const currentPageBlocks = await getAllNestedBlocksSchema (
389+ params . currentPage ?. page_content || { } ,
390+ fetchBlockSchema ,
391+ blockSet
372392 )
373- blocks = ( await Promise . all ( promises ) ) . flat ( )
374- }
393+ blocks = blocks . concat ( currentPageBlocks )
394+ const pageCode = [
395+ ...getPageAncestryFiles ( appData , params ) ,
396+ ...( blocks || [ ] ) . map ( ( blockSchema ) => {
397+ return {
398+ panelName : `${ blockSchema . fileName } .vue` ,
399+ panelValue : generatePageCode ( blockSchema , appData ?. componentsMap || [ ] , { blockRelativePath : './' } ) || '' ,
400+ panelType : 'vue'
401+ }
402+ } )
403+ ]
404+
405+ const newFiles = store . getFiles ( )
406+ const enableTailwindCSS = getMergeMeta ( 'engine.config' ) ?. enableTailwindCSS
407+ const appJsCode = processAppJsCode ( newFiles [ 'app.js' ] || '' , params . styles , enableTailwindCSS )
408+
409+ newFiles [ 'app.js' ] = appJsCode
410+ pageCode . forEach ( ( item ) => assignFiles ( item , newFiles ) )
411+
412+ const metaFiles = generateMetaFiles ( metaData )
413+ Object . assign ( newFiles , metaFiles )
414+ setFiles ( newFiles , 'App.vue' )
415+ } else if ( previewType === 'app' ) {
416+ const appId = searchParams . get ( 'id' )
417+ const { getAllNestedBlocksSchema, generateAppCode } = getMetaApi ( 'engine.service.generateCode' )
418+ const importMap = await getImportMap ( JSON . parse ( searchParams . get ( 'scripts' ) || '{}' ) )
419+
420+ let appSchema
421+
422+ const getPreGenerateInfo = async ( ) => {
423+ const promises = [
424+ getMetaApi ( META_SERVICE . Http ) . get ( `/app-center/v1/api/apps/schema/${ appId } ` ) ,
425+ fetchPageList ( appId )
426+ ]
427+
428+ const [ appData , pageList ] = await Promise . all ( promises )
429+ const pageDetailList = pageList
430+
431+ // 这里需要手动传入 blockSet 的原因是多页面可能会存在重复的区块
432+ const blockSet = new Set ( )
433+ const list = pageDetailList . map ( ( page ) =>
434+ getAllNestedBlocksSchema ( page . page_content , fetchBlockSchema , blockSet )
435+ )
436+ const blocks = await Promise . allSettled ( list )
437+
438+ const blockSchema = [ ]
439+ blocks . forEach ( ( item ) => {
440+ if ( item . status === 'fulfilled' && Array . isArray ( item . value ) ) {
441+ blockSchema . push ( ...item . value )
442+ }
443+ } )
375444
376- const currentPageBlocks = await getAllNestedBlocksSchema (
377- params . currentPage ?. page_content || { } ,
378- fetchBlockSchema ,
379- blockSet
380- )
381- blocks = blocks . concat ( currentPageBlocks )
382-
383- const pageCode = [
384- ...getPageAncestryFiles ( appData , params ) ,
385- ...( blocks || [ ] ) . map ( ( blockSchema ) => {
386- return {
387- panelName : `${ blockSchema . fileName } .vue` ,
388- panelValue : generatePageCode ( blockSchema , appData ?. componentsMap || [ ] , { blockRelativePath : './' } ) || '' ,
389- panelType : 'vue'
445+ appSchema = {
446+ dataSource : appData . dataSource ,
447+ utils : appData . utils ,
448+ i18n : appData . i18n ,
449+ globalState : appData . globalState ,
450+ // 页面 schema
451+ pageSchema : pageDetailList . map ( ( item ) => {
452+ const { page_content, ...meta } = item
453+
454+ return {
455+ ...page_content ,
456+ meta : {
457+ ...meta ,
458+ router : meta . route
459+ }
460+ }
461+ } ) ,
462+ blockSchema,
463+ // 物料数据
464+ componentsMap : [ ...( appData . componentsMap || [ ] ) ] ,
465+ // 物料依赖
466+ packages : [ ...( appData . packages || [ ] ) ] ,
467+ meta : {
468+ ...( appData . meta || { } )
469+ }
390470 }
391- } )
392- ]
393471
394- const newFiles = store . getFiles ( )
395- const enableTailwindCSS = getMergeMeta ( 'engine.config' ) ?. enableTailwindCSS
396- const appJsCode = processAppJsCode ( newFiles [ 'app.js' ] || '' , params . styles , enableTailwindCSS )
472+ const res = await generateAppCode ( appSchema )
397473
398- newFiles [ 'app.js' ] = appJsCode
474+ const { genResult = [ ] } = res || { }
475+ const fileRes = genResult . map ( ( { fileContent, fileName, path, fileType } ) => {
476+ const slash = path . endsWith ( '/' ) || path === '.' ? '' : '/'
477+ let filePath = `${ path } ${ slash } `
478+ if ( filePath . startsWith ( './' ) ) {
479+ filePath = filePath . slice ( 2 )
480+ }
481+ if ( filePath . startsWith ( '.' ) ) {
482+ filePath = filePath . slice ( 1 )
483+ }
399484
400- pageCode . forEach ( ( item ) => assignFiles ( item , newFiles ) )
485+ if ( filePath . startsWith ( '/' ) ) {
486+ filePath = filePath . slice ( 1 )
487+ }
401488
402- const metaFiles = generateMetaFiles ( metaData )
403- Object . assign ( newFiles , metaFiles )
489+ return {
490+ fileContent,
491+ filePath : `${ filePath } ${ fileName } ` ,
492+ fileType
493+ }
494+ } )
404495
405- setFiles ( newFiles , 'App.vue' )
496+ return fileRes
497+ }
498+
499+ const buildTreeRoutes = ( routes ) => {
500+ const tree = [ ]
501+ const routeMap = new Map ( )
502+
503+ // 首先将所有路由存入一个 Map 中,以便快速查找
504+ routes . forEach ( ( route ) => {
505+ routeMap . set ( route . path , route )
506+ } )
507+
508+ // 递归构建树状结构
509+ const buildTree = ( route ) => {
510+ const children = routes . filter ( ( childRoute ) => {
511+ return childRoute . path . startsWith ( route . path ) && childRoute . path !== route . path
512+ } )
513+
514+ if ( children . length > 0 ) {
515+ route . children = children . map ( ( childRoute ) => {
516+ const child = { ...childRoute }
517+ buildTree ( child )
518+ return child
519+ } )
520+ }
521+ }
522+
523+ // 找到所有根路由
524+ routes . forEach ( ( route ) => {
525+ if ( ! routes . some ( ( otherRoute ) => otherRoute . path !== route . path && route . path . startsWith ( otherRoute . path ) ) ) {
526+ const root = { ...route }
527+ buildTree ( root )
528+ tree . push ( root )
529+ }
530+ } )
531+
532+ return tree
533+ }
534+
535+ const getRoutesAndImportSet = ( schema ) => {
536+ const importSet = new Set ( )
537+ const pageSchema = ( schema . pageSchema || [ ] ) . sort ( ( a , b ) => a . meta ?. router ?. length - b . meta ?. router ?. length )
538+ const result = [ ]
539+ const home = {
540+ path : '/'
541+ }
542+ let isGetHome = false
543+ pageSchema . forEach ( ( item ) => {
544+ if ( ( item . meta ?. isHome || item . meta ?. isDefault ) && ! isGetHome ) {
545+ home . redirect = { name : `${ item . meta . id } ` }
546+ isGetHome = true
547+ }
548+ importSet . add (
549+ `import ${ item . fileName } from './views${ item . path ? `/${ item . path } ` : '' } /${ item . fileName } .vue'`
550+ )
551+ const newNode = {
552+ path : `/${ item . meta . router } ` ,
553+ component : item . fileName ,
554+ name : `${ item . meta . id } `
555+ }
556+ result . push ( newNode )
557+ } )
558+ if ( ! isGetHome ) {
559+ isGetHome = true
560+ home . redirect = { name : result [ 0 ] ?. name }
561+ }
562+
563+ return { routes : [ home , ...buildTreeRoutes ( result ) ] , importSet }
564+ }
565+
566+ const getRouterFile = ( schema ) => {
567+ const { routes, importSet } = getRoutesAndImportSet ( schema )
568+ const resultStr = JSON . stringify ( routes , null , 2 ) . replace (
569+ / ( " c o m p o n e n t " : \s * ) " ( .* ?) " / g,
570+ ( match , p1 , p2 ) => p1 + p2
571+ )
572+
573+ // TODO: 支持 hash 模式、history 模式
574+ const importSnippet = `import { createRouter, createMemoryHistory } from 'vue-router'\n${ [ ...importSet ] . join (
575+ '\n'
576+ ) } `
577+ const exportSnippet = `export default createRouter({history: createMemoryHistory(),routes })`
578+
579+ const routeSnippets = `const routes = ${ resultStr } `
580+
581+ return `${ importSnippet } \n ${ routeSnippets } \n ${ exportSnippet } `
582+ }
583+
584+ const formatCode = ( fileContent , fileName ) => {
585+ if ( fileName === 'src/router/index.js' ) {
586+ fileContent = getRouterFile ( appSchema )
587+ } else {
588+ fileContent = fileContent . replace ( / ( f r o m \s * ' ) ( @ ) ( \/ .* ' ) / g, '$1.$3' )
589+ }
590+ if ( fileName === 'src/App.vue' ) {
591+ fileContent = fileContent . replace (
592+ '<router-view></router-view>' ,
593+ `<tiny-select v-model="currentRoute" placeholder="请选择路由" render-type="tree" :tree-op="{ data: $router.options.routes.filter(item => item.path !== '/') }" text-field="path" value-field="path" @change="routeChange"></tiny-select>\n<router-view></router-view>`
594+ )
595+ fileContent = fileContent . replace (
596+ `import { provide } from 'vue'` ,
597+ `import { Select as TinySelect } from '@opentiny/vue'\nimport { ref, provide, watchEffect } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'`
598+ )
599+ fileContent = fileContent . replace (
600+ `provide(I18nInjectionKey, i18n)` ,
601+ `const route = useRoute()\nconst router = useRouter()\nconst currentRoute = ref()\n\nwatchEffect(() => {\n\tcurrentRoute.value = route.path\n})\n\nconst routeChange = () => {\n\trouter.push(currentRoute.value)\n}\nprovide(I18nInjectionKey, i18n)`
602+ )
603+ }
604+ return fileContent
605+ }
606+
607+ const fileRes = await getPreGenerateInfo ( )
608+ const newFileRes = fileRes . filter ( ( item ) => item . filePath . includes ( 'src/' ) )
609+ const srcFiles = newFileRes . reduce ( ( prev , item ) => {
610+ const fileName = item . filePath
611+ prev [ fileName ] = formatCode ( item . fileContent , fileName )
612+ return prev
613+ } , { } )
614+ srcFiles [ 'import-map.json' ] = JSON . stringify ( importMap )
615+ const newFiles = store . getFiles ( )
616+ const enableTailwindCSS = getMergeMeta ( 'engine.config' ) ?. enableTailwindCSS
617+ const appJsCode = processAppJsCode (
618+ newFiles [ 'app.js' ] ,
619+ JSON . parse ( searchParams . get ( 'styles' ) || '[]' ) ,
620+ enableTailwindCSS
621+ )
622+ srcFiles [ 'app.js' ] = appJsCode
623+ srcFiles [ 'main.js' ] = `import app from './app.js' \n ${ srcFiles [ 'src/main.js' ] } `
624+ srcFiles [ 'main.js' ] = srcFiles [ 'main.js' ] . replace ( "import 'element-plus/dist/index.css'" , '' )
625+ setFiles ( srcFiles , 'src/main.js' )
626+ }
406627 }
407628
408629 const loadInitialData = async ( ) => {
0 commit comments