Skip to content

Commit b5160c4

Browse files
committed
feat(app-preview): add app preview function & handel dependence in vue-repl
1 parent 7f11e6e commit b5160c4

13 files changed

Lines changed: 255 additions & 70 deletions

File tree

packages/common/component/ToolbarBase.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ export default {
3232
options: {
3333
type: Object,
3434
default: () => ({})
35+
},
36+
trigger: {
37+
type: String,
38+
default: 'hover'
3539
}
3640
},
3741
emits: ['click-api'],
3842
setup(props, { emit }) {
3943
const state = reactive({
4044
icon: computed(() => props.icon),
4145
content: computed(() => props.content),
42-
options: computed(() => props.options)
46+
options: computed(() => props.options),
47+
trigger: computed(() => props.trigger)
4348
})
4449
4550
const click = () => {

packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
<template>
22
<tiny-popover
3-
trigger="hover"
3+
:trigger="trigger"
44
:open-delay="1000"
55
popper-class="toolbar-right-popover"
66
append-to-body
77
:content="content"
88
>
9+
<slot></slot>
910
<template #reference>
1011
<span class="icon">
1112
<span class="icon-hides" v-bind="$attrs">
@@ -36,6 +37,10 @@ export default {
3637
options: {
3738
type: Object,
3839
default: () => ({})
40+
},
41+
trigger: {
42+
type: String,
43+
default: 'hover'
3944
}
4045
}
4146
}

packages/common/js/preview.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ const getQueryParams = (params = {}, isHistory = false) => {
255255
query += `&history=${params.history}`
256256
}
257257

258+
query += `&previewType=${params.previewType}`
259+
258260
return query
259261
}
260262

packages/design-core/src/preview/src/preview/http.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ export const getPageById = async (id) => getMetaApi(META_SERVICE.Http).get(`/app
4444
export const getBlockById = async (id) => getMetaApi(META_SERVICE.Http).get(`/material-center/api/block/detail/${id}`)
4545
export const fetchPageHistory = (pageId) =>
4646
getMetaApi(META_SERVICE.Http).get(`/app-center/api/pages/histories?page=${pageId}`)
47+
48+
export const fetchPageList = (appId) => getMetaApi(META_SERVICE.Http).get(`/app-center/api/pages/list/${appId}`)

packages/design-core/src/preview/src/preview/usePreviewData.ts

Lines changed: 211 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import { reactive } from 'vue'
22
import { constants } from '@opentiny/tiny-engine-utils'
33
import { getImportMap as getInitImportMap } from './importMap'
44
import { 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'
614
import generateMetaFiles, { processAppJsCode } from './generate'
715
import srcFiles from './srcFiles'
816

@@ -332,7 +340,7 @@ interface IUsePreviewData {
332340
setImportMap: (importMap: Record<string, string>) => void
333341
}
334342

335-
export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewData) => {
343+
export const usePreviewData = ({ setFiles, store }: IUsePreviewData) => {
336344
const basicFiles = setFiles(srcFiles, 'src/Main.vue')
337345

338346
const assignFiles = ({ panelName, panelValue, index }: IPanelType, newFiles: Record<string, string>) => {
@@ -350,58 +358,219 @@ 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')
363+
364+
if (previewType === 'page') {
365+
const { appData, metaData, importMapData } = await getBasicData(basicFiles)
366+
previewState.currentPage = params.currentPage
367+
previewState.ancestors = params.ancestors
368+
369+
// importMap 发生变化才更新 importMap
370+
if (JSON.stringify(previewState.importMap) !== JSON.stringify(importMapData)) {
371+
store.setImportMap(importMapData)
372+
previewState.importMap = importMapData
373+
}
374+
const blockSet = new Set()
354375

355-
previewState.currentPage = params.currentPage
356-
previewState.ancestors = params.ancestors
376+
let blocks = []
377+
const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi('engine.service.generateCode')
357378

358-
// importMap 发生变化才更新 importMap
359-
if (JSON.stringify(previewState.importMap) !== JSON.stringify(importMapData)) {
360-
setImportMap(importMapData)
361-
previewState.importMap = importMapData
362-
}
379+
if (params.ancestors?.length) {
380+
const promises = params.ancestors.map((item) =>
381+
getAllNestedBlocksSchema(item.page_content, fetchBlockSchema, blockSet)
382+
)
383+
blocks = (await Promise.all(promises)).flat()
384+
}
363385

364-
const blockSet = new Set()
386+
const currentPageBlocks = await getAllNestedBlocksSchema(
387+
params.currentPage?.page_content || {},
388+
fetchBlockSchema,
389+
blockSet
390+
)
391+
blocks = blocks.concat(currentPageBlocks)
365392

366-
let blocks = []
367-
const { getAllNestedBlocksSchema, generatePageCode } = getMetaApi('engine.service.generateCode')
393+
const pageCode = [
394+
...getPageAncestryFiles(appData, params),
395+
...(blocks || []).map((blockSchema) => {
396+
return {
397+
panelName: `${blockSchema.fileName}.vue`,
398+
panelValue: generatePageCode(blockSchema, appData?.componentsMap || [], { blockRelativePath: './' }) || '',
399+
panelType: 'vue'
400+
}
401+
})
402+
]
403+
404+
const newFiles = store.getFiles()
405+
const appJsCode = processAppJsCode(newFiles['app.js'], JSON.parse(searchParams.get('styles') || '[]'))
406+
407+
newFiles['app.js'] = appJsCode
408+
409+
pageCode.map(fixScriptLang).forEach((item) => assignFiles(item, newFiles))
410+
411+
const metaFiles = generateMetaFiles(metaData)
412+
Object.assign(newFiles, metaFiles)
413+
414+
setFiles(newFiles)
415+
} else if (previewType === 'app') {
416+
const app = searchParams.get('id')
417+
const { getAllNestedBlocksSchema, generateAppCode } = getMetaApi('engine.service.generateCode')
418+
const importMap = await getImportMap(JSON.parse(searchParams.get('scripts') || '{}'))
419+
// store.setImportMap(importMap)
420+
let appSchema
421+
422+
const getPreGenerateInfo = async () => {
423+
const promises = [
424+
getMetaApi(META_SERVICE.Http).get(`/app-center/v1/api/apps/schema/${app}`),
425+
fetchPageList(app)
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+
})
368444

369-
if (params.ancestors?.length) {
370-
const promises = params.ancestors.map((item) =>
371-
getAllNestedBlocksSchema(item.page_content, fetchBlockSchema, blockSet)
372-
)
373-
blocks = (await Promise.all(promises)).flat()
374-
}
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+
}
470+
}
471+
472+
const res = await generateAppCode(appSchema)
473+
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+
}
375484

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'
485+
if (filePath.startsWith('/')) {
486+
filePath = filePath.slice(1)
487+
}
488+
489+
return {
490+
fileContent,
491+
filePath: `${filePath}${fileName}`,
492+
fileType
493+
}
494+
})
495+
496+
return fileRes
497+
}
498+
499+
const getRoutesAndImportSet = (schema) => {
500+
const importSet = new Set()
501+
const pageSchema = (schema.pageSchema || []).sort((a, b) => a.meta?.router?.length - b.meta?.router?.length)
502+
const result = []
503+
const home = {
504+
path: '/'
390505
}
391-
})
392-
]
506+
let isGetHome = false
393507

394-
const newFiles = store.getFiles()
395-
const appJsCode = processAppJsCode(newFiles['app.js'], params.styles)
508+
pageSchema.forEach((item) => {
509+
if ((item.meta?.isHome || item.meta?.isDefault) && !isGetHome) {
510+
home.redirect = { name: `${item.meta.id}` }
511+
isGetHome = true
512+
}
513+
importSet.add(
514+
`import ${item.fileName} from './views${item.path ? `/${item.path}` : ''}/${item.fileName}.vue'`
515+
)
516+
const newNode = {
517+
path: `/${item.meta.router}`,
518+
component: item.fileName,
519+
name: `${item.meta.id}`
520+
}
521+
result.push(newNode)
522+
})
523+
if (!isGetHome) {
524+
isGetHome = true
525+
home.redirect = { name: result[0]?.name }
526+
}
396527

397-
newFiles['app.js'] = appJsCode
528+
return { routes: [home, ...result], importSet }
529+
}
530+
531+
const getRouterFile = (schema) => {
532+
const { routes, importSet } = getRoutesAndImportSet(schema)
533+
534+
const resultStr = JSON.stringify(routes, null, 2).replace(
535+
/("component":\s*)"(.*?)"/g,
536+
(match, p1, p2) => p1 + p2
537+
)
398538

399-
pageCode.forEach((item) => assignFiles(item, newFiles))
539+
// TODO: 支持 hash 模式、history 模式
540+
const importSnippet = `import { createRouter, createMemoryHistory } from 'vue-router'\n${[...importSet].join(
541+
'\n'
542+
)}`
543+
const exportSnippet = `export default createRouter({history: createMemoryHistory(),routes })`
400544

401-
const metaFiles = generateMetaFiles(metaData)
402-
Object.assign(newFiles, metaFiles)
545+
const routeSnippets = `const routes = ${resultStr}`
546+
547+
return `${importSnippet}\n ${routeSnippets} \n ${exportSnippet}`
548+
}
403549

404-
setFiles(newFiles, 'App.vue')
550+
const formatCode = (fileContent, fileName) => {
551+
if (fileName === 'src/router/index.js') {
552+
fileContent = getRouterFile(appSchema)
553+
} else {
554+
fileContent = fileContent.replace(/(from\s*')(@)(\/.*')/g, '$1.$3')
555+
}
556+
return fileContent
557+
}
558+
559+
const fileRes = await getPreGenerateInfo()
560+
const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
561+
const srcFiles = newFileRes.reduce((prev, item) => {
562+
const fileName = item.filePath
563+
prev[fileName] = formatCode(item.fileContent, fileName)
564+
return prev
565+
}, {})
566+
srcFiles['import-map.json'] = JSON.stringify(importMap)
567+
const newFiles = store.getFiles()
568+
const appJsCode = processAppJsCode(newFiles['app.js'], JSON.parse(searchParams.get('styles') || '[]'))
569+
srcFiles['app.js'] = appJsCode
570+
srcFiles['main.js'] = `import app from './app.js' \n ${srcFiles['src/main.js']}`
571+
srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
572+
setFiles(srcFiles, 'src/main.js')
573+
}
405574
}
406575

407576
const loadInitialData = async () => {

0 commit comments

Comments
 (0)