TinyEngine DSL出码对接codesandbox实现应用预览 #1
|
|
@ -88,9 +88,9 @@ gantt
|
|||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
|
||||
1.0.0-beta.x version :active,2023-09-25, 2024-03-31
|
||||
1.0.0-rc version : 2024-04-01, 2024-06-30
|
||||
1.0.0 version : 2024-07-01, 2024-07-31
|
||||
1.0.0-beta.x version : 2023-09-25, 2024-05-20
|
||||
1.0.0-rc version(refactor version) : 2024-10-01
|
||||
1.0.0 version : 2024-11-01
|
||||
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -84,13 +84,13 @@ pnpm run build:alpha 或 build:prod
|
|||
## 里程碑
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %Y-%m-%d
|
||||
|
||||
1.0.0-beta.x version :active,2023-09-25, 2024-03-31
|
||||
1.0.0-rc version : 2024-04-01, 2024-06-30
|
||||
1.0.0 version : 2024-07-01, 2024-07-31
|
||||
1.0.0-beta.x version : 2023-09-25, 2024-05-20
|
||||
1.0.0-rc version(refactor version) : 2024-10-01
|
||||
1.0.0 version : 2024-11-01
|
||||
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
"@opentiny/tiny-engine-toolbar-logo": ["packages/toolbars/logo/index"],
|
||||
"@opentiny/tiny-engine-toolbar-media": ["packages/toolbars/media/index"],
|
||||
"@opentiny/tiny-engine-toolbar-preview": ["packages/toolbars/preview/index"],
|
||||
"@opentiny/tiny-engine-toolbar-codesandbox": ["packages/toolbars/codesandbox/index"],
|
||||
"@opentiny/tiny-engine-toolbar-generate-vue": ["packages/toolbars/generate-vue/index"],
|
||||
"@opentiny/tiny-engine-toolbar-clean": ["packages/toolbars/clean/index"],
|
||||
"@opentiny/tiny-engine-toolbar-save": ["packages/toolbars/save/index"],
|
||||
|
|
@ -57,6 +58,7 @@
|
|||
"@opentiny/tiny-engine-toolbar-logo/*": ["packages/toolbars/logo/*"],
|
||||
"@opentiny/tiny-engine-toolbar-media/*": ["packages/toolbars/media/*"],
|
||||
"@opentiny/tiny-engine-toolbar-preview/*": ["packages/toolbars/preview/*"],
|
||||
"@opentiny/tiny-engine-toolbar-codesandbox/*": ["packages/toolbars/codesandbox/*"],
|
||||
"@opentiny/tiny-engine-toolbar-clean/*": ["packages/toolbars/clean/*"],
|
||||
"@opentiny/tiny-engine-toolbar-save/*": ["packages/toolbars/save/*"],
|
||||
"@opentiny/tiny-engine-theme-dark/*": ["packages/theme/dark/*"],
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"lint": "eslint --fix ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentiny/tiny-engine-dsl-vue": "^1.0.3",
|
||||
"@opentiny/tiny-engine-dsl-vue": "workspace:*",
|
||||
"@seald-io/nedb": "^4.0.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"glob": "^10.3.4",
|
||||
|
|
|
|||
|
|
@ -1918,6 +1918,13 @@
|
|||
"destructuring": true,
|
||||
"version": "0.1.17"
|
||||
},
|
||||
{
|
||||
"componentName": "TinyCheckbox",
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "Checkbox",
|
||||
"destructuring": true,
|
||||
"version": "3.14.0"
|
||||
},
|
||||
{
|
||||
"componentName": "TinySelect",
|
||||
"package": "@opentiny/vue",
|
||||
|
|
@ -1974,6 +1981,76 @@
|
|||
"destructuring": true,
|
||||
"version": "0.1.16"
|
||||
},
|
||||
{
|
||||
"componentName": "TinyCollapse",
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "Collapse",
|
||||
"destructuring": true,
|
||||
"version": "3.14.0"
|
||||
},
|
||||
{
|
||||
"componentName": "TinyCollapseItem",
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "CollapseItem",
|
||||
"destructuring": true,
|
||||
"version": "3.14.0"
|
||||
},
|
||||
{
|
||||
"componentName": "TinyBreadcrumb",
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "Breadcrumb",
|
||||
"destructuring": true,
|
||||
"version": "3.14.0"
|
||||
},
|
||||
{
|
||||
"componentName": "TinyBreadcrumbItem",
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "BreadcrumbItem",
|
||||
"destructuring": true,
|
||||
"version": "3.14.0"
|
||||
},
|
||||
{
|
||||
"componentName": "ElInput",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElInput",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "ElButton",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElButton",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "ElForm",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElForm",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "ElFormItem",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElFormItem",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "ElTable",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElTable",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "ElTableColumn",
|
||||
"package": "element-plus",
|
||||
"exportName": "ElTableColumn",
|
||||
"destructuring": true,
|
||||
"version": "2.4.2"
|
||||
},
|
||||
{
|
||||
"componentName": "PortalHome",
|
||||
"main": "common/components/home",
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@types/node": "^18.0.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default defineConfig(({ command, mode }) => {
|
|||
}
|
||||
|
||||
const vuePluginConfig = {}
|
||||
const styleLinks = ['https://npm.onmicrosoft.cn/@opentiny/vue-theme@3.14/index.css']
|
||||
const styleLinks = ['https://unpkg.com/@opentiny/vue-theme@3.14/index.css']
|
||||
|
||||
config.publicDir = false
|
||||
|
||||
|
|
|
|||
|
|
@ -229,7 +229,10 @@ export default {
|
|||
}
|
||||
|
||||
const showAction = computed(() => {
|
||||
const { parent } = getCurrent()
|
||||
const { schema, parent } = getCurrent()
|
||||
if (schema?.props?.['data-id'] === 'root-container') {
|
||||
return false
|
||||
}
|
||||
return !props.resize && parent && parent?.type !== 'JSSlot'
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -716,11 +716,7 @@ export const onMouseUp = () => {
|
|||
if (absolute) {
|
||||
targetNode.node = getSchema()
|
||||
data.props = data.props || {}
|
||||
data.props.style = {
|
||||
position: 'absolute',
|
||||
top: dragState.mouse.y + 'px',
|
||||
left: dragState.mouse.x + 'px'
|
||||
}
|
||||
data.props.style = `position: absolute; top: ${dragState.mouse.y}px; left: ${dragState.mouse.x}px`
|
||||
}
|
||||
|
||||
insertNode(targetNode, position)
|
||||
|
|
|
|||
|
|
@ -379,7 +379,8 @@ export default {
|
|||
// 渲染画布增加根节点,与出码和预览保持一致
|
||||
const rootChildrenSchema = {
|
||||
componentName: 'div',
|
||||
props: schema.props,
|
||||
// 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码
|
||||
props: { ...schema.props, 'data-id': 'root-container' },
|
||||
children: schema.children
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ const getBindProps = (schema, scope) => {
|
|||
...parseData(schema.props, scope),
|
||||
[DESIGN_UIDKEY]: id,
|
||||
[DESIGN_TAGKEY]: componentName,
|
||||
onMoseover: stopEvent,
|
||||
onMouseover: stopEvent,
|
||||
onFocus: stopEvent
|
||||
}
|
||||
if (scope) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="head-content">
|
||||
<meta-input v-model="state.text" type="textarea" @change="change"></meta-input>
|
||||
<meta-input v-model="state.text" type="textarea" @update:modelValue="change"></meta-input>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import { useHttp } from '@opentiny/tiny-engine-http'
|
||||
|
||||
const http = useHttp()
|
||||
|
||||
const HEADER_LOWCODE_ORG = 'x-lowcode-org'
|
||||
|
||||
// 获取页面/区块预览的代码
|
||||
export const fetchCode = async ({ platform, app, pageInfo, tenant } = {}) =>
|
||||
http.post(
|
||||
'/app-center/api/schema2code',
|
||||
{ platform, app, pageInfo },
|
||||
{
|
||||
headers: { [HEADER_LOWCODE_ORG]: tenant }
|
||||
}
|
||||
)
|
||||
|
||||
// 获取页面依赖的关联应用数据: i18n/dataSource等
|
||||
export const fetchMetaData = async ({ platform, app, type, id, history, tenant } = {}) =>
|
||||
id
|
||||
? http.get('/app-center/api/preview/metadata', {
|
||||
headers: { [HEADER_LOWCODE_ORG]: tenant },
|
||||
params: { platform, app, type, id, history }
|
||||
})
|
||||
: {}
|
||||
|
||||
// 获取页面列表
|
||||
export const fetchPageList = (appId) => http.get(`/app-center/api/pages/list/${appId}`)
|
||||
|
||||
export const fetchBlockSchema = async (blockName) => http.get(`/material-center/api/block?label=${blockName}`)
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import { useHttp } from '@opentiny/tiny-engine-http'
|
||||
import useCanvas from '../../src/useCanvas'
|
||||
import useBlock from '../../src/useBlock'
|
||||
import useLayout from '../../src/useLayout'
|
||||
import useEditorInfo from '../../src/useEditorInfo'
|
||||
import { parseRequiredBlocks } from '../block'
|
||||
import { getGlobalConfig } from '../../src/globalConfig'
|
||||
import { fetchMetaData, fetchPageList, fetchBlockSchema } from './http'
|
||||
|
||||
const { PREVIEW_SANDBOX } = constants
|
||||
|
||||
const getBlocksSchema = async (pageSchema, blockSet = new Set()) => {
|
||||
let res = []
|
||||
|
||||
const blockNames = parseRequiredBlocks(pageSchema)
|
||||
const promiseList = blockNames
|
||||
.filter((name) => {
|
||||
if (blockSet.has(name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
blockSet.add(name)
|
||||
|
||||
return true
|
||||
})
|
||||
.map((name) => fetchBlockSchema(name))
|
||||
const schemaList = await Promise.allSettled(promiseList)
|
||||
const extraList = []
|
||||
|
||||
schemaList.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
|
||||
res.push(item.value[0].content)
|
||||
extraList.push(getBlocksSchema(item.value[0].content, blockSet))
|
||||
}
|
||||
})
|
||||
;(await Promise.allSettled(extraList)).forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
res.push(...item.value)
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const getAllPageDetails = async (pageList) => {
|
||||
const detailPromise = pageList.map(({ id }) => useLayout().getPluginApi('AppManage').getPageById(id))
|
||||
const detailList = await Promise.allSettled(detailPromise)
|
||||
|
||||
return detailList
|
||||
.map((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
return item.value
|
||||
}
|
||||
})
|
||||
.filter((item) => Boolean(item))
|
||||
}
|
||||
|
||||
const getParams = () => {
|
||||
const { isBlock, getCurrentPage, canvasApi } = useCanvas()
|
||||
const { getCurrentBlock } = useBlock()
|
||||
|
||||
const { getSchema } = canvasApi.value
|
||||
const params = {
|
||||
framework: getGlobalConfig()?.dslMode,
|
||||
platform: getGlobalConfig()?.platformId,
|
||||
pageInfo: {
|
||||
schema: getSchema()
|
||||
}
|
||||
}
|
||||
const paramsMap = new URLSearchParams(location.search)
|
||||
params.app = paramsMap.get('id')
|
||||
params.tenant = paramsMap.get('tenant')
|
||||
|
||||
if (isBlock()) {
|
||||
const block = getCurrentBlock()
|
||||
params.id = block?.id
|
||||
params.pageInfo.name = block?.label
|
||||
params.type = 'Block'
|
||||
} else {
|
||||
const page = getCurrentPage()
|
||||
params.id = page?.id
|
||||
params.pageInfo.name = page?.name
|
||||
params.type = 'Page'
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
export const getPreGenerateInfo = async (instance, sandbox = PREVIEW_SANDBOX.Web) => {
|
||||
const params = getParams()
|
||||
const { id, pageId } = useEditorInfo().useInfo()
|
||||
const currentPage = await useLayout().getPluginApi('AppManage').getPageById(pageId)
|
||||
const promises = [
|
||||
useHttp().get(`/app-center/v1/api/apps/schema/${id}`),
|
||||
fetchMetaData(params),
|
||||
fetchPageList(params.app)
|
||||
]
|
||||
|
||||
const [appData, metaData, pageList] = await Promise.all(promises)
|
||||
const pageDetailList = await getAllPageDetails(pageList)
|
||||
|
||||
const blockSet = new Set()
|
||||
const list = pageDetailList.map((page) => getBlocksSchema(page.page_content, blockSet))
|
||||
const blocks = await Promise.allSettled(list)
|
||||
|
||||
const blockSchema = []
|
||||
blocks.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && Array.isArray(item.value)) {
|
||||
blockSchema.push(...item.value)
|
||||
}
|
||||
})
|
||||
|
||||
const appSchema = {
|
||||
// metaData 包含dataSource、utils、i18n、globalState
|
||||
...metaData,
|
||||
// 页面 schema
|
||||
pageSchema: pageDetailList.map((item) => {
|
||||
const { page_content, ...meta } = item
|
||||
return {
|
||||
...page_content,
|
||||
meta: {
|
||||
...meta,
|
||||
isHome: sandbox === PREVIEW_SANDBOX.Web ? meta.isHome : meta.id === currentPage?.id,
|
||||
router: meta.route
|
||||
}
|
||||
}
|
||||
}),
|
||||
blockSchema,
|
||||
// 物料数据
|
||||
componentsMap: [...(appData.componentsMap || [])],
|
||||
|
||||
meta: {
|
||||
...(appData.meta || {})
|
||||
}
|
||||
}
|
||||
|
||||
const res = await instance.generate(appSchema)
|
||||
|
||||
const { genResult = [] } = res || {}
|
||||
const fileRes = genResult.map(({ fileContent, fileName, path, fileType }) => {
|
||||
const slash = path.endsWith('/') || path === '.' ? '' : '/'
|
||||
let filePath = `${path}${slash}`
|
||||
if (filePath.startsWith('./')) {
|
||||
filePath = filePath.slice(2)
|
||||
}
|
||||
if (filePath.startsWith('.')) {
|
||||
filePath = filePath.slice(1)
|
||||
}
|
||||
|
||||
if (filePath.startsWith('/')) {
|
||||
filePath = filePath.slice(1)
|
||||
}
|
||||
|
||||
return {
|
||||
fileContent,
|
||||
filePath: `${filePath}${fileName}`,
|
||||
fileType
|
||||
}
|
||||
})
|
||||
|
||||
return fileRes
|
||||
}
|
||||
|
||||
export default getPreGenerateInfo
|
||||
|
|
@ -15,13 +15,14 @@ import { isDevelopEnv } from './environments'
|
|||
import useResource from '../src/useResource'
|
||||
// prefer old unicode hacks for backward compatibility
|
||||
|
||||
const { COMPONENT_NAME } = constants
|
||||
const { COMPONENT_NAME, PREVIEW_SANDBOX } = constants
|
||||
|
||||
export const utoa = (string) => btoa(unescape(encodeURIComponent(string)))
|
||||
|
||||
export const atou = (base64) => decodeURIComponent(escape(atob(base64)))
|
||||
|
||||
const open = (params = {}) => {
|
||||
const getParams = (_params, type, sandbox) => {
|
||||
const params = Object.assign({ type, sandbox }, _params)
|
||||
const paramsMap = new URLSearchParams(location.search)
|
||||
params.app = paramsMap.get('id')
|
||||
params.tenant = paramsMap.get('tenant')
|
||||
|
|
@ -30,7 +31,10 @@ const open = (params = {}) => {
|
|||
.filter((item) => item.script)
|
||||
.reduce((pre, cur) => ({ ...pre, [cur.package]: cur.script }), {})
|
||||
params.styles = [...styles]
|
||||
return params
|
||||
}
|
||||
|
||||
const open = (params = {}) => {
|
||||
const href = window.location.href.split('?')[0] || './'
|
||||
const tenant = new URLSearchParams(location.search).get('tenant') || ''
|
||||
let openUrl = ''
|
||||
|
|
@ -46,12 +50,10 @@ const open = (params = {}) => {
|
|||
aTag.click()
|
||||
}
|
||||
|
||||
export const previewPage = (params = {}) => {
|
||||
params.type = COMPONENT_NAME.Page
|
||||
open(params)
|
||||
export const previewPage = (params = {}, sandbox = PREVIEW_SANDBOX.Web) => {
|
||||
open(getParams(params, COMPONENT_NAME.Page, sandbox))
|
||||
}
|
||||
|
||||
export const previewBlock = (params = {}) => {
|
||||
params.type = COMPONENT_NAME.Block
|
||||
open(params)
|
||||
export const previewBlock = (params = {}, sandbox = PREVIEW_SANDBOX.Web) => {
|
||||
open(getParams(params, COMPONENT_NAME.Block, sandbox))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -325,6 +325,13 @@ const initPageOrBlock = async () => {
|
|||
return
|
||||
}
|
||||
|
||||
const updateUrlPageId = (id) => {
|
||||
const url = new URL(window.location)
|
||||
|
||||
url.searchParams.delete('blockid')
|
||||
url.searchParams.set('pageid', id)
|
||||
window.history.replaceState({}, '', url)
|
||||
}
|
||||
// url 没有 pageid 或 blockid,到页面首页或第一页
|
||||
const pageInfo = resState.pageTree.find((page) => page?.meta?.isHome) ||
|
||||
resState.pageTree.find(
|
||||
|
|
@ -332,6 +339,7 @@ const initPageOrBlock = async () => {
|
|||
) || {
|
||||
componentName: COMPONENT_NAME.Page
|
||||
}
|
||||
updateUrlPageId(pageInfo.meta.id)
|
||||
initPage(pageInfo)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# alpha mode, used by the "build:alpha" script
|
||||
|
||||
NODE_ENV=production
|
||||
VITE_CDN_DOMAIN=https://npm.onmicrosoft.cn
|
||||
VITE_CDN_DOMAIN=https://unpkg.com
|
||||
VITE_LOCAL_IMPORT_MAPS=false
|
||||
VITE_LOCAL_BUNDLE_DEPS=false
|
||||
# VITE_ORIGIN=
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# development mode, used by the "vite" command
|
||||
|
||||
NODE_ENV=development
|
||||
VITE_CDN_DOMAIN=https://npm.onmicrosoft.cn
|
||||
VITE_CDN_DOMAIN=https://unpkg.com
|
||||
VITE_LOCAL_IMPORT_MAPS=false
|
||||
VITE_LOCAL_BUNDLE_DEPS=false
|
||||
# request data via alpha service
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# prod mode, used by the "build:prod" script
|
||||
|
||||
NODE_ENV=production
|
||||
VITE_CDN_DOMAIN=https://npm.onmicrosoft.cn
|
||||
VITE_CDN_DOMAIN=https://unpkg.com
|
||||
VITE_LOCAL_IMPORT_MAPS=false
|
||||
VITE_LOCAL_BUNDLE_DEPS=false
|
||||
#VITE_ORIGIN=
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg viewBox="64 64 896 896" focusable="false" data-icon="code-sandbox" width="16" height="16" fill="currentColor" aria-hidden="true" style="display: block;"><path d="M709.6 210l.4-.2h.2L512 96 313.9 209.8h-.2l.7.3L151.5 304v416L512 928l360.5-208V304l-162.9-94zM482.7 843.6L339.6 761V621.4L210 547.8V372.9l272.7 157.3v313.4zM238.2 321.5l134.7-77.8 138.9 79.7 139.1-79.9 135.2 78-273.9 158-274-158zM814 548.3l-128.8 73.1v139.1l-143.9 83V530.4L814 373.1v175.2z"></path></svg>
|
||||
|
After Width: | Height: | Size: 473 B |
|
|
@ -20,6 +20,7 @@ import Redoundo from '@opentiny/tiny-engine-toolbar-redoundo'
|
|||
import Save from '@opentiny/tiny-engine-toolbar-save'
|
||||
import Clean from '@opentiny/tiny-engine-toolbar-clean'
|
||||
import Preview from '@opentiny/tiny-engine-toolbar-preview'
|
||||
import CodeSandbox from '@opentiny/tiny-engine-toolbar-codesandbox'
|
||||
import GenerateVue from '@opentiny/tiny-engine-toolbar-generate-vue'
|
||||
import Refresh from '@opentiny/tiny-engine-toolbar-refresh'
|
||||
import Collaboration from '@opentiny/tiny-engine-toolbar-collaboration'
|
||||
|
|
@ -56,6 +57,7 @@ const addons = {
|
|||
Save,
|
||||
GenerateVue,
|
||||
Preview,
|
||||
CodeSandbox,
|
||||
Redoundo,
|
||||
Fullscreen,
|
||||
Checkinout,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"@opentiny/tiny-engine-toolbar-logout": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-media": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-preview": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-codesandbox": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-redoundo": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-refresh": "workspace:*",
|
||||
"@opentiny/tiny-engine-toolbar-save": "workspace:*",
|
||||
|
|
@ -85,11 +86,11 @@
|
|||
"@vue/babel-plugin-jsx": "1.1.1",
|
||||
"@vue/repl": "^2.9.0",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"codesandbox": "^2.2.3",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"eslint-linter-browserify": "8.57.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jszip": "^3.10.1",
|
||||
"monaco-editor": "0.33.0",
|
||||
"prettier": "2.7.1",
|
||||
"sortablejs": "^1.14.0",
|
||||
|
|
@ -107,6 +108,7 @@
|
|||
"assert": "^2.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"esbuild": "^0.21.5",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-plugin-vue": "^8.0.0",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,110 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Design Core Preview</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.loading .loadingco {
|
||||
width: 200px;
|
||||
height: 80px;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.loading h2 {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.loading span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 0.6em;
|
||||
height: 0.6em;
|
||||
margin: 0.19em;
|
||||
background: #007db6;
|
||||
border-radius: 0.6em;
|
||||
-webkit-animation: loading 1s infinite alternate;
|
||||
animation: loading 1s infinite alternate;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(2) {
|
||||
background: #008fb2;
|
||||
-webkit-animation-delay: 0.2s;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(3) {
|
||||
background: #009b9e;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(4) {
|
||||
background: #00a77d;
|
||||
-webkit-animation-delay: 0.6s;
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(5) {
|
||||
background: #00b247;
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(6) {
|
||||
background: #5ab027;
|
||||
-webkit-animation-delay: 1s;
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.loading span:nth-of-type(7) {
|
||||
background: #a0b61e;
|
||||
-webkit-animation-delay: 1.2s;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes loading {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"exportName": "ElInput"
|
||||
"exportName": "ElInput",
|
||||
"destructuring": true
|
||||
},
|
||||
"group": "表单组件",
|
||||
"category": "element-plus",
|
||||
|
|
@ -300,10 +301,11 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"exportName": "ElButton"
|
||||
"exportName": "ElButton",
|
||||
"destructuring": true
|
||||
},
|
||||
"group": "基础组件",
|
||||
"category": "element-plus",
|
||||
|
|
@ -621,9 +623,10 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"destructuring": true,
|
||||
"exportName": "ElForm"
|
||||
},
|
||||
"group": "表单组件",
|
||||
|
|
@ -1078,9 +1081,10 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"destructuring": true,
|
||||
"exportName": "ElFormItem"
|
||||
},
|
||||
"group": "表单组件",
|
||||
|
|
@ -1428,9 +1432,10 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"destructuring": true,
|
||||
"exportName": "ElTable"
|
||||
},
|
||||
"group": "数据展示",
|
||||
|
|
@ -2666,9 +2671,10 @@
|
|||
"npm": {
|
||||
"package": "element-plus",
|
||||
"version": "2.4.2",
|
||||
"script": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://npm.onmicrosoft.cn/element-plus@2.4.2/dist/index.css",
|
||||
"script": "https://unpkg.com/element-plus@2.4.2/dist/index.full.mjs",
|
||||
"css": "https://unpkg.com/element-plus@2.4.2/dist/index.css",
|
||||
"dependencies": null,
|
||||
"destructuring": true,
|
||||
"exportName": "ElTableColumn"
|
||||
},
|
||||
"group": "表单组件",
|
||||
|
|
@ -8901,7 +8907,7 @@
|
|||
"devMode": "proCode",
|
||||
"npm": {
|
||||
"package": "@opentiny/vue",
|
||||
"exportName": "Select",
|
||||
"exportName": "Breadcrumb",
|
||||
"version": "",
|
||||
"destructuring": true
|
||||
},
|
||||
|
|
@ -14007,20 +14013,10 @@
|
|||
"schema": {
|
||||
"componentName": "TinyBreadcrumb",
|
||||
"props": {
|
||||
"options": [
|
||||
{
|
||||
"to": "{ path: '/' }",
|
||||
"label": "首页"
|
||||
},
|
||||
{
|
||||
"to": "{ path: '/breadcrumb' }",
|
||||
"label": "产品"
|
||||
},
|
||||
{
|
||||
"replace": "true",
|
||||
"label": "软件"
|
||||
}
|
||||
]
|
||||
"options": {
|
||||
"type": "JSExpression",
|
||||
"value": "[{to: { path: '/' },label: '首页'},{to: { path: '/breadcrumb' },label: '产品'},{'replace': true,'label': '软件'}]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -14275,7 +14271,7 @@
|
|||
"schema": {
|
||||
"componentName": "TinyTimeLine",
|
||||
"props": {
|
||||
"active": "2",
|
||||
"active": 2,
|
||||
"data": [
|
||||
{
|
||||
"name": "已下单"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,33 @@
|
|||
<template>
|
||||
<Toolbar v-if="showToolbar" />
|
||||
<Preview />
|
||||
<div v-if="redirect" class="loading">
|
||||
<div class="loadingco">
|
||||
<h2>Loading</h2>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else>
|
||||
<Toolbar v-if="showToolbar" />
|
||||
<Preview />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDebugSwitch } from './preview/debugSwitch'
|
||||
import usePreview from './preview/usePreview'
|
||||
import Preview from './preview/Preview.vue'
|
||||
import { codesandboxFiles } from './preview/codesandboxFiles'
|
||||
import Toolbar from './Toolbar.vue'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import '@opentiny/tiny-engine-theme'
|
||||
import { getParameters } from 'codesandbox/lib/api/define'
|
||||
|
||||
const { PREVIEW_SANDBOX } = constants
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -22,6 +42,105 @@ export default {
|
|||
},
|
||||
setup() {
|
||||
useDebugSwitch()
|
||||
const [sandbox] = usePreview((sandbox, prejectName, srcFiles) => {
|
||||
if (sandbox === PREVIEW_SANDBOX.CodeSandbox) {
|
||||
const files = Object.assign({}, codesandboxFiles)
|
||||
Object.keys(srcFiles).forEach((key) => {
|
||||
if (key === 'tsconfig.json') {
|
||||
return
|
||||
}
|
||||
if (key === 'import-map.json') {
|
||||
const deps = { 'core-js': '^3.8.3' }
|
||||
const dependencies = JSON.parse(srcFiles[key]).imports
|
||||
Object.keys(dependencies).forEach((key) => {
|
||||
if (key === 'vue/server-renderer') {
|
||||
return
|
||||
}
|
||||
const name = key
|
||||
.split('/')
|
||||
.filter((s) => s)
|
||||
.join('/')
|
||||
const version = dependencies[key].match(new RegExp(String.raw`https://unpkg.com/.+@([~^]?[0-9]+.*?)/`))[1]
|
||||
dependencies[key] = version.split('.')
|
||||
if (dependencies[key].length < 3) {
|
||||
for (let index = 0; index < 3; index++) {
|
||||
dependencies[key][index] = dependencies[key][index] ?? '0'
|
||||
}
|
||||
deps[name] = (dependencies[key][0].match(/[~^]/) ? '' : '~') + dependencies[key].join('.')
|
||||
} else {
|
||||
deps[name] = dependencies[key].join('.')
|
||||
}
|
||||
})
|
||||
files['package.json'] = {
|
||||
content: {
|
||||
name: prejectName,
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
scripts: {
|
||||
dev: 'vite',
|
||||
build: 'vite build',
|
||||
lint: 'pnpm run lint:js && pnpm run lint:style',
|
||||
'lint:fix': 'pnpm run lint:js -- --fix && pnpm run lint:style -- --fix',
|
||||
'lint:js': 'eslint --ext .js,.vue --ignore-path .gitignore src',
|
||||
'lint:style': 'stylelint **/*.{vue,css} --ignore-path .gitignore',
|
||||
serve: 'vite preview'
|
||||
},
|
||||
dependencies: deps,
|
||||
devDependencies: {
|
||||
'@vitejs/plugin-vue': '^2.3.2',
|
||||
autoprefixer: '^10.4.7',
|
||||
eslint: '^8.15.0',
|
||||
'eslint-config-airbnb-base': '^15.0.0',
|
||||
'eslint-import-resolver-alias': '^1.1.2',
|
||||
'eslint-plugin-import': '^2.26.0',
|
||||
'eslint-plugin-tailwindcss': '^3.5.0',
|
||||
'eslint-plugin-vue': '^8.7.1',
|
||||
postcss: '^8.4.13',
|
||||
'postcss-html': '^1.4.1',
|
||||
stylelint: '^14.8.2',
|
||||
'stylelint-config-rational-order': '^0.1.2',
|
||||
'stylelint-config-recommended-vue': '^1.4.0',
|
||||
'stylelint-config-standard': '^25.0.0',
|
||||
tailwindcss: '^3.0.24',
|
||||
vite: '^2.9.8',
|
||||
'vite-plugin-eslint': '^1.6.0',
|
||||
'vite-plugin-stylelint': '^2.2.1',
|
||||
'vite-svg-loader': '^3.3.0'
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
files['src/' + key] = {
|
||||
content: srcFiles[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const parameters = getParameters({ files, template: 'vue-cli' })
|
||||
const form = document.createElement('form')
|
||||
form.method = 'POST'
|
||||
form.action = 'https://codesandbox.io/api/v1/sandboxes/define'
|
||||
const parametersInput = document.createElement('input')
|
||||
parametersInput.name = 'parameters'
|
||||
parametersInput.value = parameters
|
||||
const queryInput = document.createElement('input')
|
||||
queryInput.name = 'query'
|
||||
queryInput.value = 'module=/src/App.vue'
|
||||
const environmentInput = document.createElement('input')
|
||||
environmentInput.name = 'environment'
|
||||
environmentInput.value = 'server'
|
||||
form.appendChild(parametersInput)
|
||||
form.appendChild(queryInput)
|
||||
form.appendChild(environmentInput)
|
||||
document.body.append(form)
|
||||
form.submit()
|
||||
document.body.removeChild(form)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
redirect: sandbox !== PREVIEW_SANDBOX.Web
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import { getSearchParams } from './preview/http'
|
|||
import { BROADCAST_CHANNEL } from '../src/preview/srcFiles/constant'
|
||||
import addons from '@opentiny/tiny-engine-app-addons'
|
||||
import { injectDebugSwitch } from './preview/debugSwitch'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
|
||||
const { COMPONENT_NAME } = constants
|
||||
|
||||
const getToolbars = (pluginId) => {
|
||||
return defineAsyncComponent(() =>
|
||||
|
|
@ -37,9 +40,13 @@ export default {
|
|||
const tools = ['breadcrumb', 'lang', 'media']
|
||||
const [Breadcrumb, ChangeLang, ToolbarMedia] = tools.map(getToolbars)
|
||||
|
||||
const { setBreadcrumbPage } = useBreadcrumb()
|
||||
const { pageInfo } = getSearchParams()
|
||||
setBreadcrumbPage([pageInfo?.name])
|
||||
const { setBreadcrumbPage, setBreadcrumbBlock } = useBreadcrumb()
|
||||
const { pageInfo, type } = getSearchParams()
|
||||
if (type === COMPONENT_NAME.Page) {
|
||||
setBreadcrumbPage([pageInfo?.name])
|
||||
} else {
|
||||
setBreadcrumbBlock([pageInfo?.name])
|
||||
}
|
||||
|
||||
const setViewPort = (item) => {
|
||||
const iframe = document.getElementsByClassName('iframe-container')[0]
|
||||
|
|
|
|||
|
|
@ -15,16 +15,9 @@
|
|||
|
||||
<script>
|
||||
import { defineComponent, computed, defineAsyncComponent } from 'vue'
|
||||
import { Repl, ReplStore } from '@vue/repl'
|
||||
import vueJsx from '@vue/babel-plugin-jsx'
|
||||
import { transformSync } from '@babel/core'
|
||||
import { genSFCWithDefaultPlugin, parseRequiredBlocks } from '@opentiny/tiny-engine-dsl-vue'
|
||||
import importMap from './importMap'
|
||||
import srcFiles from './srcFiles'
|
||||
import generateMetaFiles, { processAppJsCode } from './generate'
|
||||
import { getSearchParams, fetchMetaData, fetchImportMap, fetchAppSchema, fetchBlockSchema } from './http'
|
||||
import { PanelType, PreviewTips } from '../constant'
|
||||
import { Repl } from '@vue/repl'
|
||||
import { injectDebugSwitch } from './debugSwitch'
|
||||
import { injectPreview } from './usePreview'
|
||||
import '@vue/repl/style.css'
|
||||
|
||||
const Monaco = defineAsyncComponent(() => import('@vue/repl/monaco-editor')) // 异步组件实现懒加载,打开debug后再加载
|
||||
|
|
@ -42,7 +35,6 @@ export default {
|
|||
setup() {
|
||||
const debugSwitch = injectDebugSwitch()
|
||||
const editorComponent = computed(() => (debugSwitch?.value ? Monaco : EmptyEditor))
|
||||
const store = new ReplStore()
|
||||
|
||||
const sfcOptions = {
|
||||
script: {
|
||||
|
|
@ -51,157 +43,10 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
// 相比store.setFiles,只要少了state.activeFile = state.files[filename],因为改变activeFile会触发多余的文件解析
|
||||
const setFiles = async (newFiles, mainFileName) => {
|
||||
await store.setFiles(newFiles, mainFileName)
|
||||
// 强制更新 codeSandbox
|
||||
store.state.resetFlip = !store.state.resetFlip
|
||||
store['initTsConfig']() // 触发获取组件d.ts方便调试
|
||||
}
|
||||
|
||||
const addUtilsImportMap = (importMap, utils = []) => {
|
||||
const utilsImportMaps = {}
|
||||
utils.forEach(({ type, content: { package: packageName, cdnLink } }) => {
|
||||
if (type === 'npm' && cdnLink) {
|
||||
utilsImportMaps[packageName] = cdnLink
|
||||
}
|
||||
})
|
||||
const newImportMap = { imports: { ...importMap.imports, ...utilsImportMaps } }
|
||||
store.setImportMap(newImportMap)
|
||||
}
|
||||
const getBlocksSchema = async (pageSchema, blockSet = new Set()) => {
|
||||
let res = []
|
||||
|
||||
const blockNames = parseRequiredBlocks(pageSchema)
|
||||
const promiseList = blockNames
|
||||
.filter((name) => {
|
||||
if (blockSet.has(name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
blockSet.add(name)
|
||||
|
||||
return true
|
||||
})
|
||||
.map((name) => fetchBlockSchema(name))
|
||||
|
||||
const schemaList = await Promise.allSettled(promiseList)
|
||||
|
||||
schemaList.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
|
||||
res.push(item.value[0].content)
|
||||
res.push(...getBlocksSchema(item.value[0].content, blockSet))
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const queryParams = getSearchParams()
|
||||
const getImportMap = async () => {
|
||||
if (import.meta.env.VITE_LOCAL_BUNDLE_DEPS === 'true') {
|
||||
const mapJSON = await fetchImportMap()
|
||||
return {
|
||||
imports: {
|
||||
...mapJSON.imports,
|
||||
...getSearchParams().scripts
|
||||
}
|
||||
}
|
||||
}
|
||||
return importMap
|
||||
}
|
||||
|
||||
const promiseList = [
|
||||
fetchAppSchema(queryParams?.app),
|
||||
fetchMetaData(queryParams),
|
||||
setFiles(srcFiles, 'src/Main.vue'),
|
||||
getImportMap()
|
||||
]
|
||||
Promise.all(promiseList).then(async ([appData, metaData, _void, importMapData]) => {
|
||||
addUtilsImportMap(importMapData, metaData.utils || [])
|
||||
|
||||
const blocks = await getBlocksSchema(queryParams.pageInfo?.schema)
|
||||
|
||||
// TODO: 需要验证级联生成 block schema
|
||||
// TODO: 物料内置 block 需要如何处理?
|
||||
const pageCode = [
|
||||
{
|
||||
panelName: 'Main.vue',
|
||||
panelValue:
|
||||
genSFCWithDefaultPlugin(queryParams.pageInfo?.schema, appData?.componentsMap || [], {
|
||||
blockRelativePath: './'
|
||||
}) || '',
|
||||
panelType: 'vue',
|
||||
index: true
|
||||
},
|
||||
...(blocks || []).map((blockSchema) => {
|
||||
return {
|
||||
panelName: blockSchema.fileName,
|
||||
panelValue:
|
||||
genSFCWithDefaultPlugin(blockSchema, appData?.componentsMap || [], { blockRelativePath: './' }) || '',
|
||||
panelType: 'vue',
|
||||
index: true
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
// [@vue/repl] `Only lang="ts" is supported for <script> blocks.`
|
||||
const langReg = /lang="jsx"/
|
||||
const fixScriptLang = (generatedCode) => {
|
||||
const fixedCode = { ...generatedCode }
|
||||
|
||||
if (generatedCode.panelType === PanelType.VUE) {
|
||||
fixedCode.panelValue = generatedCode.panelValue.replace(langReg, '')
|
||||
}
|
||||
|
||||
return fixedCode
|
||||
}
|
||||
|
||||
const newFiles = store.getFiles()
|
||||
|
||||
const assignFiles = ({ panelName, panelValue, index }) => {
|
||||
if (index) {
|
||||
panelName = 'Main.vue'
|
||||
}
|
||||
|
||||
const newPanelValue = panelValue.replace(/<script\s*setup\s*>([\s\S]*)<\/script>/, (match, p1) => {
|
||||
if (!p1) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return '<script setup><\/script>'
|
||||
}
|
||||
|
||||
const transformedScript = transformSync(p1, {
|
||||
babelrc: false,
|
||||
plugins: [[vueJsx, { pragma: 'h' }]],
|
||||
sourceMaps: false,
|
||||
configFile: false
|
||||
})
|
||||
|
||||
const res = `<script setup>${transformedScript.code}`
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const endTag = '<\/script>'
|
||||
|
||||
return `${res}${endTag}`
|
||||
})
|
||||
|
||||
newFiles[panelName] = newPanelValue
|
||||
}
|
||||
|
||||
const appJsCode = processAppJsCode(newFiles['app.js'], queryParams.styles)
|
||||
|
||||
newFiles['app.js'] = appJsCode
|
||||
|
||||
pageCode.map(fixScriptLang).forEach(assignFiles)
|
||||
|
||||
const metaFiles = generateMetaFiles(metaData)
|
||||
Object.assign(newFiles, metaFiles)
|
||||
|
||||
setFiles(newFiles)
|
||||
|
||||
return PreviewTips.READY_FOR_PREVIEW
|
||||
})
|
||||
const [sandbox, store] = injectPreview()
|
||||
|
||||
return {
|
||||
sandbox,
|
||||
store,
|
||||
sfcOptions,
|
||||
editorComponent,
|
||||
|
|
@ -237,16 +82,29 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vue-repl-container {
|
||||
height: calc(100vh - 48px);
|
||||
|
||||
&.preview-debug-mode .vue-repl .split-pane {
|
||||
.left,
|
||||
.right .tab-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.right .output-container {
|
||||
height: calc(100% - 38px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vue-codesandbox {
|
||||
width: 100%;
|
||||
height: 900px;
|
||||
outline: 1px solid #252525;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,236 @@
|
|||
export const codesandboxFiles = {
|
||||
'.codesandbox/tasks.json': {
|
||||
content: String.raw`{
|
||||
// These tasks will run in order when initializing your CodeSandbox project.
|
||||
"setupTasks": [
|
||||
{
|
||||
"name": "pnpm install",
|
||||
"command": "pnpm install"
|
||||
}
|
||||
],
|
||||
|
||||
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
|
||||
"tasks": {
|
||||
"install-dependencies": {
|
||||
"name": "Install Dependencies",
|
||||
"command": "pnpm install"
|
||||
},
|
||||
"start-app": {
|
||||
"name": "Run Dev Server",
|
||||
"command": "pnpm run dev",
|
||||
"runAtStart": true
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
'public/robots.txt': {
|
||||
content: String.raw`User-agent: *
|
||||
Allow: /`
|
||||
},
|
||||
|
||||
'src/main.js': {
|
||||
content: String.raw`import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
const app = window.__app__ = createApp(App);
|
||||
app.mount('#app');
|
||||
`
|
||||
},
|
||||
'.editorconfig': {
|
||||
content: String.raw`# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
`
|
||||
},
|
||||
'.env.sample': {
|
||||
content: String.raw`# To prevent accidentally leaking env variables to the client, only
|
||||
# variables prefixed with VITE_ are exposed to your Vite-processed code
|
||||
|
||||
VITE_BASE_PUBLIC_PATH=
|
||||
`
|
||||
},
|
||||
'.eslintrc.js': {
|
||||
content: String.raw`module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:tailwindcss/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2021,
|
||||
},
|
||||
plugins: ['vue', 'tailwindcss'],
|
||||
rules: {
|
||||
'import/extensions': 'off',
|
||||
'import/no-extraneous-dependencies': [0, { 'packageDir ': './src/' }],
|
||||
'max-len': ['error', {
|
||||
code: 160,
|
||||
ignorePattern: 'class="([\\s\\S]*?)"|d="([\\s\\S]*?)"', // ignore classes or svg draw attributes
|
||||
ignoreUrls: true,
|
||||
}],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', 'svg'],
|
||||
moduleDirectory: ['node_modules', 'src/'],
|
||||
},
|
||||
alias: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', 'svg'],
|
||||
map: [['@', './src']],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
`
|
||||
},
|
||||
'.gitignore': {
|
||||
content: String.raw`# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
report.*.json
|
||||
debug.log
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
# Build
|
||||
dist
|
||||
dist-ssr
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# dotenv
|
||||
.env*
|
||||
*.local
|
||||
!.env.sample
|
||||
`
|
||||
},
|
||||
'index.html': {
|
||||
content: String.raw`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vue.js starter template</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
},
|
||||
'jsconfig.json': {
|
||||
content: String.raw`{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
`
|
||||
},
|
||||
'postcss.config.js': {
|
||||
content: String.raw`module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
`
|
||||
},
|
||||
'stylelint.config.js': {
|
||||
content: String.raw`module.exports = {
|
||||
extends: [
|
||||
'stylelint-config-standard',
|
||||
'stylelint-config-rational-order',
|
||||
'stylelint-config-recommended-vue',
|
||||
],
|
||||
rules: {
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'layer'],
|
||||
},
|
||||
],
|
||||
'declaration-block-trailing-semicolon': null,
|
||||
'no-descending-specificity': null,
|
||||
},
|
||||
};
|
||||
`
|
||||
},
|
||||
'tailwind.config.js': {
|
||||
content: String.raw`const defaultTheme = require('tailwindcss/defaultTheme');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./index.html',
|
||||
'./src/**/*.{vue,js,ts,jsx,tsx}',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
`
|
||||
},
|
||||
'vite.config.js': {
|
||||
content: String.raw`import { defineConfig, loadEnv } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default ({ mode }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
||||
|
||||
return defineConfig({
|
||||
base: process.env.VITE_BASE_PUBLIC_PATH,
|
||||
plugins: [
|
||||
svgLoader(),
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
`
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import { provide, inject } from 'vue'
|
||||
import { ReplStore } from '@vue/repl'
|
||||
import vueJsx from '@vue/babel-plugin-jsx'
|
||||
import { transformSync } from '@babel/core'
|
||||
import { parseRequiredBlocks } from '@opentiny/tiny-engine-controller/js/block'
|
||||
import { genSFCWithDefaultPlugin } from '@opentiny/tiny-engine-dsl-vue'
|
||||
import importMap from './importMap'
|
||||
import srcFiles from './srcFiles'
|
||||
import generateMetaFiles, { processAppJsCode } from './generate'
|
||||
import { getSearchParams, fetchMetaData, fetchImportMap, fetchAppSchema, fetchBlockSchema } from './http'
|
||||
import { PanelType, PreviewTips } from '../constant'
|
||||
|
||||
export const previewTokenKey = Symbol('tiny-engine-preview-preview')
|
||||
|
||||
export default (onReady) => {
|
||||
const params = getSearchParams()
|
||||
|
||||
const store = new ReplStore()
|
||||
|
||||
// 相比store.setFiles,只要少了state.activeFile = state.files[filename],因为改变activeFile会触发多余的文件解析
|
||||
const setFiles = async (newFiles, mainFileName) => {
|
||||
await store.setFiles(newFiles, mainFileName)
|
||||
// 强制更新 codeSandbox
|
||||
store.state.resetFlip = !store.state.resetFlip
|
||||
store['initTsConfig']() // 触发获取组件d.ts方便调试
|
||||
}
|
||||
|
||||
const addUtilsImportMap = (importMap, utils = []) => {
|
||||
const utilsImportMaps = {}
|
||||
utils.forEach(({ type, content: { package: packageName, cdnLink } }) => {
|
||||
if (type === 'npm' && cdnLink) {
|
||||
utilsImportMaps[packageName] = cdnLink
|
||||
}
|
||||
})
|
||||
const newImportMap = { imports: { ...importMap.imports, ...utilsImportMaps } }
|
||||
store.setImportMap(newImportMap)
|
||||
}
|
||||
const getBlocksSchema = async (pageSchema, blockSet = new Set()) => {
|
||||
let res = []
|
||||
|
||||
const blockNames = parseRequiredBlocks(pageSchema)
|
||||
const promiseList = blockNames
|
||||
.filter((name) => {
|
||||
if (blockSet.has(name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
blockSet.add(name)
|
||||
|
||||
return true
|
||||
})
|
||||
.map((name) => fetchBlockSchema(name))
|
||||
|
||||
const schemaList = await Promise.allSettled(promiseList)
|
||||
const extraList = []
|
||||
|
||||
schemaList.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
|
||||
res.push(item.value[0].content)
|
||||
extraList.push(getBlocksSchema(item.value[0].content, blockSet))
|
||||
}
|
||||
})
|
||||
;(await Promise.allSettled(extraList)).forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
res.push(...item.value)
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const getImportMap = async () => {
|
||||
if (import.meta.env.VITE_LOCAL_BUNDLE_DEPS === 'true') {
|
||||
const mapJSON = await fetchImportMap()
|
||||
return {
|
||||
imports: {
|
||||
...mapJSON.imports,
|
||||
...getSearchParams().scripts
|
||||
}
|
||||
}
|
||||
}
|
||||
return importMap
|
||||
}
|
||||
|
||||
const promiseList = [
|
||||
fetchAppSchema(params.app),
|
||||
fetchMetaData(params),
|
||||
setFiles(srcFiles, 'src/Main.vue'),
|
||||
getImportMap()
|
||||
]
|
||||
Promise.all(promiseList).then(async ([appData, metaData, _void, importMapData]) => {
|
||||
addUtilsImportMap(importMapData, metaData.utils || [])
|
||||
|
||||
const blocks = await getBlocksSchema(params.pageInfo?.schema)
|
||||
|
||||
// TODO: 需要验证级联生成 block schema
|
||||
// TODO: 物料内置 block 需要如何处理?
|
||||
const pageCode = [
|
||||
{
|
||||
panelName: 'Main.vue',
|
||||
panelValue:
|
||||
genSFCWithDefaultPlugin(params.pageInfo?.schema, appData?.componentsMap || [], {
|
||||
blockRelativePath: './'
|
||||
}) || '',
|
||||
panelType: 'vue',
|
||||
index: true
|
||||
},
|
||||
...(blocks || []).map((blockSchema) => {
|
||||
return {
|
||||
panelName: `${blockSchema.fileName}.vue`,
|
||||
panelValue:
|
||||
genSFCWithDefaultPlugin(blockSchema, appData?.componentsMap || [], { blockRelativePath: './' }) || '',
|
||||
panelType: 'vue'
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
// [@vue/repl] `Only lang="ts" is supported for <script> blocks.`
|
||||
const langReg = /lang="jsx"/
|
||||
const fixScriptLang = (generatedCode) => {
|
||||
const fixedCode = { ...generatedCode }
|
||||
|
||||
if (generatedCode.panelType === PanelType.VUE) {
|
||||
fixedCode.panelValue = generatedCode.panelValue.replace(langReg, '')
|
||||
}
|
||||
|
||||
return fixedCode
|
||||
}
|
||||
|
||||
const newFiles = store.getFiles()
|
||||
|
||||
const assignFiles = ({ panelName, panelValue, index }) => {
|
||||
if (index) {
|
||||
panelName = 'Main.vue'
|
||||
}
|
||||
|
||||
const newPanelValue = panelValue.replace(/<script\s*setup\s*>([\s\S]*)<\/script>/, (match, p1) => {
|
||||
if (!p1) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return '<script setup></script>'
|
||||
}
|
||||
|
||||
const transformedScript = transformSync(p1, {
|
||||
babelrc: false,
|
||||
plugins: [[vueJsx, { pragma: 'h' }]],
|
||||
sourceMaps: false,
|
||||
configFile: false
|
||||
})
|
||||
|
||||
const res = `<script setup>${transformedScript.code}`
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const endTag = '</script>'
|
||||
|
||||
return `${res}${endTag}`
|
||||
})
|
||||
|
||||
newFiles[panelName] = newPanelValue
|
||||
}
|
||||
|
||||
const appJsCode = processAppJsCode(newFiles['app.js'], params.styles)
|
||||
|
||||
newFiles['app.js'] = appJsCode
|
||||
|
||||
pageCode.map(fixScriptLang).forEach(assignFiles)
|
||||
|
||||
const metaFiles = generateMetaFiles(metaData)
|
||||
Object.assign(newFiles, metaFiles)
|
||||
|
||||
setFiles(newFiles)
|
||||
|
||||
onReady?.(params.sandbox, params.pageInfo?.name ?? 'vue3-tiny-engine-template', newFiles)
|
||||
return PreviewTips.READY_FOR_PREVIEW
|
||||
})
|
||||
|
||||
const preview = [params.sandbox, store]
|
||||
|
||||
provide(previewTokenKey, preview)
|
||||
|
||||
return preview
|
||||
}
|
||||
|
||||
export function injectPreview() {
|
||||
return inject(previewTokenKey)
|
||||
}
|
||||
|
|
@ -167,6 +167,7 @@ const devAlias = {
|
|||
'@opentiny/tiny-engine-toolbar-logout': path.resolve(__dirname, '../toolbars/logout/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-media': path.resolve(__dirname, '../toolbars/media/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-preview': path.resolve(__dirname, '../toolbars/preview/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-codesandbox': path.resolve(__dirname, '../toolbars/codesandbox/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-generate-vue': path.resolve(__dirname, '../toolbars/generate-vue/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-refresh': path.resolve(__dirname, '../toolbars/refresh/index.js'),
|
||||
'@opentiny/tiny-engine-toolbar-redoundo': path.resolve(__dirname, '../toolbars/redoundo/index.js'),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<tiny-dialog-box
|
||||
v-show="dialogVisible"
|
||||
:visible="dialogVisible"
|
||||
title="事件绑定"
|
||||
width="50%"
|
||||
:append-to-body="true"
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ import { ref, watch } from 'vue'
|
|||
import { Collapse, CollapseItem, Input } from '@opentiny/vue'
|
||||
import { useHistory, useCanvas, useProperties } from '@opentiny/tiny-engine-controller'
|
||||
import { MetaCodeEditor, MetaBindVariable } from '@opentiny/tiny-engine-common'
|
||||
import { formatString } from '@opentiny/tiny-engine-controller/js/ast'
|
||||
import {
|
||||
SizeGroup,
|
||||
LayoutGroup,
|
||||
|
|
@ -128,7 +127,7 @@ export default {
|
|||
const { getSchema: getCanvasPageSchema, updateRect } = useCanvas().canvasApi.value
|
||||
const pageSchema = getCanvasPageSchema()
|
||||
const schema = getSchema() || pageSchema
|
||||
const styleString = formatString(styleStrRemoveRoot(content), 'css')
|
||||
const styleString = styleStrRemoveRoot(content)
|
||||
const currentSchema = getCurrentSchema() || pageSchema
|
||||
|
||||
state.styleContent = content
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import component from './src/Main.vue'
|
||||
|
||||
export default {
|
||||
id: 'codesandbox',
|
||||
title: 'codesandbox',
|
||||
icon: '',
|
||||
align: 'right',
|
||||
component
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "@opentiny/tiny-engine-toolbar-codesandbox",
|
||||
"version": "1.0.3",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/opentiny/tiny-engine",
|
||||
"directory": "packages/toolbars/codesandbox"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/opentiny/tiny-engine/issues"
|
||||
},
|
||||
"author": "OpenTiny Team",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opentiny.design/tiny-engine",
|
||||
"dependencies": {
|
||||
"@opentiny/tiny-engine-canvas": "workspace:*",
|
||||
"@opentiny/tiny-engine-common": "workspace:*",
|
||||
"@opentiny/tiny-engine-controller": "workspace:*",
|
||||
"@opentiny/tiny-engine-dsl-vue": "workspace:*",
|
||||
"@opentiny/tiny-engine-utils": "workspace:*",
|
||||
"codesandbox": "^2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"vite": "^4.3.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentiny/vue": "^3.14.0",
|
||||
"vue": "^3.4.15"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<tiny-popover
|
||||
trigger="hover"
|
||||
:open-delay="1000"
|
||||
popper-class="toolbar-right-popover"
|
||||
append-to-body
|
||||
content="CodeSandbox"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="icon" @click="preview">
|
||||
<svg-icon :name="icon"></svg-icon>
|
||||
</span>
|
||||
</template>
|
||||
</tiny-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from 'vue'
|
||||
import { Popover } from '@opentiny/vue'
|
||||
import { previewBlock } from '@opentiny/tiny-engine-controller/js/preview'
|
||||
import getPreGenerateInfo from '@opentiny/tiny-engine-controller/js/generate-files'
|
||||
import { getGlobalConfig, useBlock, useCanvas, useLayout, useNotify } from '@opentiny/tiny-engine-controller'
|
||||
import { constants } from '@opentiny/tiny-engine-utils'
|
||||
import { generateApp } from '@opentiny/tiny-engine-dsl-vue'
|
||||
import { getParameters } from 'codesandbox/lib/api/define'
|
||||
import { codesandboxFiles } from './codesandboxFiles'
|
||||
|
||||
const { PREVIEW_SANDBOX } = constants
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TinyPopover: Popover
|
||||
},
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'code-sandbox'
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
generating: false
|
||||
})
|
||||
|
||||
const { isBlock, canvasApi } = useCanvas()
|
||||
const { getCurrentBlock } = useBlock()
|
||||
|
||||
const preview = async () => {
|
||||
if (useLayout().isEmptyPage()) {
|
||||
useNotify({
|
||||
type: 'warning',
|
||||
message: '请先创建页面'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
framework: getGlobalConfig()?.dslMode,
|
||||
platform: getGlobalConfig()?.platformId,
|
||||
pageInfo: {
|
||||
schema: canvasApi.value?.getSchema?.()
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlock()) {
|
||||
const block = getCurrentBlock()
|
||||
params.id = block?.id
|
||||
params.pageInfo.name = block?.label
|
||||
previewBlock(params, PREVIEW_SANDBOX.CodeSandbox)
|
||||
} else {
|
||||
if (state.generating) {
|
||||
useNotify({ type: 'info', title: '代码生成中, 请稍后...' })
|
||||
return
|
||||
} else {
|
||||
useNotify({ type: 'info', title: '代码生成中...' })
|
||||
state.generating = true
|
||||
}
|
||||
|
||||
try {
|
||||
const instance = generateApp()
|
||||
const fileRes = await getPreGenerateInfo(instance, PREVIEW_SANDBOX.CodeSandbox)
|
||||
|
||||
const files = {}
|
||||
fileRes.forEach((file) => {
|
||||
// 使用 pnpm 包管理
|
||||
if (file.filePath === 'README.md') {
|
||||
files[file.filePath] = { content: file.fileContent.replace(/npm /g, 'pnpm ') }
|
||||
} else {
|
||||
files[file.filePath] = { content: file.fileContent }
|
||||
}
|
||||
})
|
||||
Object.assign(files, codesandboxFiles)
|
||||
|
||||
const parameters = getParameters({ files, template: 'vue-cli' })
|
||||
const form = document.createElement('form')
|
||||
form.method = 'POST'
|
||||
form.action = 'https://codesandbox.io/api/v1/sandboxes/define'
|
||||
form.target = '_blank'
|
||||
const parametersInput = document.createElement('input')
|
||||
parametersInput.name = 'parameters'
|
||||
parametersInput.value = parameters
|
||||
const queryInput = document.createElement('input')
|
||||
queryInput.name = 'query'
|
||||
queryInput.value = 'module=/src/App.vue'
|
||||
const environmentInput = document.createElement('input')
|
||||
environmentInput.name = 'environment'
|
||||
environmentInput.value = 'server'
|
||||
form.appendChild(parametersInput)
|
||||
form.appendChild(queryInput)
|
||||
form.appendChild(environmentInput)
|
||||
document.body.append(form)
|
||||
form.submit()
|
||||
document.body.removeChild(form)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
useNotify({ type: 'error', title: '代码生成失败', message: error?.message || error })
|
||||
state.generating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
preview
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
export const codesandboxFiles = {
|
||||
'.codesandbox/tasks.json': {
|
||||
content: String.raw`{
|
||||
// These tasks will run in order when initializing your CodeSandbox project.
|
||||
"setupTasks": [
|
||||
{
|
||||
"name": "pnpm install",
|
||||
"command": "pnpm install"
|
||||
}
|
||||
],
|
||||
|
||||
// These tasks can be run from CodeSandbox. Running one will open a log in the app.
|
||||
"tasks": {
|
||||
"install-dependencies": {
|
||||
"name": "Install Dependencies",
|
||||
"command": "pnpm install"
|
||||
},
|
||||
"start-app": {
|
||||
"name": "Run Dev Server",
|
||||
"command": "pnpm run dev",
|
||||
"runAtStart": true
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import path from 'path'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
publicDir: false,
|
||||
resolve: {},
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, './index.js'),
|
||||
name: 'toolbar-codesandbox',
|
||||
fileName: () => 'index.js',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['vue', /@opentiny\/tiny-engine.*/, /@opentiny\/vue.*/]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -23,18 +23,10 @@
|
|||
<script>
|
||||
import { reactive } from 'vue'
|
||||
import { Popover } from '@opentiny/vue'
|
||||
import {
|
||||
getGlobalConfig,
|
||||
useBlock,
|
||||
useCanvas,
|
||||
useNotify,
|
||||
useLayout,
|
||||
useEditorInfo
|
||||
} from '@opentiny/tiny-engine-controller'
|
||||
import { useNotify, useLayout } from '@opentiny/tiny-engine-controller'
|
||||
import getPreGenerateInfo from '@opentiny/tiny-engine-controller/js/generate-files'
|
||||
import { fs } from '@opentiny/tiny-engine-utils'
|
||||
import { useHttp } from '@opentiny/tiny-engine-http'
|
||||
import { generateApp, parseRequiredBlocks } from '@opentiny/tiny-engine-dsl-vue'
|
||||
import { fetchMetaData, fetchPageList, fetchBlockSchema } from './http'
|
||||
import { generateApp } from '@opentiny/tiny-engine-dsl-vue'
|
||||
import FileSelector from './FileSelector.vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -49,9 +41,6 @@ export default {
|
|||
}
|
||||
},
|
||||
setup() {
|
||||
const { isBlock, getCurrentPage } = useCanvas()
|
||||
const { getCurrentBlock } = useBlock()
|
||||
|
||||
const state = reactive({
|
||||
dirHandle: null,
|
||||
generating: false,
|
||||
|
|
@ -59,166 +48,12 @@ export default {
|
|||
saveFilesInfo: []
|
||||
})
|
||||
|
||||
const getParams = () => {
|
||||
const { getSchema } = useCanvas().canvasApi.value
|
||||
const params = {
|
||||
framework: getGlobalConfig()?.dslMode,
|
||||
platform: getGlobalConfig()?.platformId,
|
||||
pageInfo: {
|
||||
schema: getSchema()
|
||||
}
|
||||
}
|
||||
const paramsMap = new URLSearchParams(location.search)
|
||||
params.app = paramsMap.get('id')
|
||||
params.tenant = paramsMap.get('tenant')
|
||||
|
||||
if (isBlock()) {
|
||||
const block = getCurrentBlock()
|
||||
params.id = block?.id
|
||||
params.pageInfo.name = block?.label
|
||||
params.type = 'Block'
|
||||
} else {
|
||||
const page = getCurrentPage()
|
||||
params.id = page?.id
|
||||
params.pageInfo.name = page?.name
|
||||
params.type = 'Page'
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
const initDirHandle = (dirHandle) => {
|
||||
if (!state.dirHandle && dirHandle) {
|
||||
state.dirHandle = dirHandle
|
||||
}
|
||||
}
|
||||
|
||||
const getBlocksSchema = async (pageSchema, blockSet = new Set()) => {
|
||||
let res = []
|
||||
|
||||
const blockNames = parseRequiredBlocks(pageSchema)
|
||||
const promiseList = blockNames
|
||||
.filter((name) => {
|
||||
if (blockSet.has(name)) {
|
||||
return false
|
||||
}
|
||||
|
||||
blockSet.add(name)
|
||||
|
||||
return true
|
||||
})
|
||||
.map((name) => fetchBlockSchema(name))
|
||||
const schemaList = await Promise.allSettled(promiseList)
|
||||
const extraList = []
|
||||
|
||||
schemaList.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value?.[0]?.content) {
|
||||
res.push(item.value[0].content)
|
||||
extraList.push(getBlocksSchema(item.value[0].content, blockSet))
|
||||
}
|
||||
})
|
||||
;(await Promise.allSettled(extraList)).forEach((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
res.push(...item.value)
|
||||
}
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const instance = generateApp()
|
||||
|
||||
const getAllPageDetails = async (pageList) => {
|
||||
const detailPromise = pageList.map(({ id }) => useLayout().getPluginApi('AppManage').getPageById(id))
|
||||
const detailList = await Promise.allSettled(detailPromise)
|
||||
|
||||
return detailList
|
||||
.map((item) => {
|
||||
if (item.status === 'fulfilled' && item.value) {
|
||||
return item.value
|
||||
}
|
||||
})
|
||||
.filter((item) => Boolean(item))
|
||||
}
|
||||
|
||||
const getPreGenerateInfo = async () => {
|
||||
const params = getParams()
|
||||
const { id } = useEditorInfo().useInfo()
|
||||
const promises = [
|
||||
useHttp().get(`/app-center/v1/api/apps/schema/${id}`),
|
||||
fetchMetaData(params),
|
||||
fetchPageList(params.app)
|
||||
]
|
||||
|
||||
if (!state.dirHandle) {
|
||||
promises.push(fs.getUserBaseDirHandle())
|
||||
}
|
||||
|
||||
const [appData, metaData, pageList, dirHandle] = await Promise.all(promises)
|
||||
const pageDetailList = await getAllPageDetails(pageList)
|
||||
|
||||
const blockSet = new Set()
|
||||
const list = pageDetailList.map((page) => getBlocksSchema(page.page_content, blockSet))
|
||||
const blocks = await Promise.allSettled(list)
|
||||
|
||||
const blockSchema = []
|
||||
blocks.forEach((item) => {
|
||||
if (item.status === 'fulfilled' && Array.isArray(item.value)) {
|
||||
blockSchema.push(...item.value)
|
||||
}
|
||||
})
|
||||
|
||||
const appSchema = {
|
||||
// metaData 包含dataSource、utils、i18n、globalState
|
||||
...metaData,
|
||||
// 页面 schema
|
||||
pageSchema: pageDetailList.map((item) => {
|
||||
const { page_content, ...meta } = item
|
||||
|
||||
return {
|
||||
...page_content,
|
||||
meta: {
|
||||
...meta,
|
||||
router: meta.route
|
||||
}
|
||||
}
|
||||
}),
|
||||
blockSchema,
|
||||
// 物料数据
|
||||
componentsMap: [...(appData.componentsMap || [])],
|
||||
|
||||
meta: {
|
||||
...(appData.meta || {})
|
||||
}
|
||||
}
|
||||
|
||||
const res = await instance.generate(appSchema)
|
||||
|
||||
const { genResult = [] } = res || {}
|
||||
const fileRes = genResult.map(({ fileContent, fileName, path, fileType }) => {
|
||||
const slash = path.endsWith('/') || path === '.' ? '' : '/'
|
||||
let filePath = `${path}${slash}`
|
||||
if (filePath.startsWith('./')) {
|
||||
filePath = filePath.slice(2)
|
||||
}
|
||||
if (filePath.startsWith('.')) {
|
||||
filePath = filePath.slice(1)
|
||||
}
|
||||
|
||||
if (filePath.startsWith('/')) {
|
||||
filePath = filePath.slice(1)
|
||||
}
|
||||
|
||||
return {
|
||||
fileContent,
|
||||
filePath: `${filePath}${fileName}`,
|
||||
fileType
|
||||
}
|
||||
})
|
||||
|
||||
return [dirHandle, fileRes]
|
||||
}
|
||||
|
||||
const saveCodeToLocal = async (filesInfo) => {
|
||||
if (filesInfo.length && state.dirHandle) {
|
||||
await fs.writeFiles(state.dirHandle, filesInfo)
|
||||
|
|
@ -244,7 +79,11 @@ export default {
|
|||
|
||||
try {
|
||||
// 保存代码前置任务:调用接口生成代码并获取用户本地文件夹授权
|
||||
const [dirHandle, fileRes] = await getPreGenerateInfo()
|
||||
const instance = generateApp()
|
||||
const [dirHandle, fileRes] = await Promise.all([
|
||||
state.dirHandle ? undefined : fs.getUserBaseDirHandle(),
|
||||
getPreGenerateInfo(instance)
|
||||
])
|
||||
|
||||
// 暂存待生成代码文件信息
|
||||
state.saveFilesInfo = fileRes
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@
|
|||
"vite": "^4.3.7",
|
||||
"vitest": "^1.4.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"jszip": "^3.10.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentiny/vue-renderless": "^3.14.0",
|
||||
"vue": "^3.4.15"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ export const COMPONENT_NAME = {
|
|||
Folder: 'Folder'
|
||||
}
|
||||
|
||||
export const PREVIEW_SANDBOX = {
|
||||
Web: 'Web',
|
||||
CodeSandbox: 'CodeSandbox'
|
||||
}
|
||||
|
||||
export const ELEMENT_TAG = {
|
||||
Body: 'body',
|
||||
Div: 'div'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
// browser File System Access API encapsulation
|
||||
import JSZIP from 'jszip'
|
||||
|
||||
/**
|
||||
* 下载文件到本地
|
||||
* @param {Blob} blobData 文件二进制数据
|
||||
* @param {string} fileName 文件名
|
||||
*/
|
||||
export function saveAs(blobData, fileName) {
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.download = fileName
|
||||
downloadLink.style.display = 'none'
|
||||
downloadLink.href = URL.createObjectURL(blobData)
|
||||
document.body.appendChild(downloadLink)
|
||||
downloadLink.click()
|
||||
document.body.removeChild(downloadLink)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个zip
|
||||
*/
|
||||
export const createZip = () => {
|
||||
return new JSZIP()
|
||||
}
|
||||
|
||||
/**
|
||||
* 往zip里面写入文件夹和文件
|
||||
* @param {Array<FileInfo>} filesInfo 文件信息
|
||||
* FileInfo.filePath 文件相对路径,以'/'相连
|
||||
* FileInfo.fileContent 文件内容
|
||||
* @param {ZipExtraInfo} ZipExtraInfo zip额外信息
|
||||
* {string} zipName 打出来的zip名称
|
||||
* {JSZIP} zipHandle 创建好的zip句柄,可以不传,不传就用新的
|
||||
*/
|
||||
export const writeZip = (filesInfo, { zipHandle, zipName } = {}) => {
|
||||
let zip = zipHandle
|
||||
if (!zipHandle) {
|
||||
zip = createZip()
|
||||
}
|
||||
filesInfo.forEach(({ filePath, fileContent }) => {
|
||||
const file = filePath.split('/')
|
||||
const fileName = file.pop()
|
||||
const path = file.join('/')
|
||||
if (path) {
|
||||
zip.folder(path).file(fileName, fileContent)
|
||||
} else {
|
||||
zip.file(fileName, fileContent)
|
||||
}
|
||||
})
|
||||
// 把打包的内容异步转成blob二进制格式
|
||||
return zip.generateAsync({ type: 'blob' }).then((content) => {
|
||||
// content就是blob数据
|
||||
saveAs(content, `${zipName}.zip`)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,17 +1,23 @@
|
|||
/**
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
* Copyright (c) 2023 - present TinyEngine Authors.
|
||||
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license.
|
||||
*
|
||||
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
|
||||
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
|
||||
*
|
||||
*/
|
||||
|
||||
// browser File System Access API encapsulation
|
||||
|
||||
import { createZip, writeZip } from './fszip'
|
||||
|
||||
// 支持file system api的条件:存在这个方法 && 不处于iframe中
|
||||
export const isSupportFileSystemAccess =
|
||||
Object.prototype.hasOwnProperty.call(window, 'showDirectoryPicker') && window.self === window.top
|
||||
|
||||
/**
|
||||
* 获取用户选择并授权的文件夹根路径
|
||||
* @param {*} options
|
||||
|
|
@ -19,8 +25,8 @@
|
|||
* @returns dirHandle 目录句柄
|
||||
*/
|
||||
export const getUserBaseDirHandle = async (options = {}) => {
|
||||
if (!window.showOpenFilePicker) {
|
||||
throw new Error('不支持的浏览器!')
|
||||
if (!isSupportFileSystemAccess) {
|
||||
return createZip()
|
||||
}
|
||||
const dirHandle = await window.showDirectoryPicker({ mode: 'readwrite', ...options })
|
||||
return dirHandle
|
||||
|
|
@ -75,8 +81,8 @@ export async function getFileHandle(baseDirHandle, filePath, { create = false }
|
|||
* @returns fileHandle 文件句柄
|
||||
*/
|
||||
export const getUserFileHandle = async (options = {}) => {
|
||||
if (!window.showOpenFilePicker) {
|
||||
throw new Error('不支持的浏览器!')
|
||||
if (!isSupportFileSystemAccess) {
|
||||
throw new Error('不支持的浏览器或处于iframe中')
|
||||
}
|
||||
const [fileHandle] = await window.showOpenFilePicker({ mode: 'readwrite', ...options })
|
||||
return fileHandle
|
||||
|
|
@ -166,16 +172,29 @@ export const writeFile = async (handle, { filePath, fileContent }) => {
|
|||
* @param {Array<FileInfo>} filesInfo 文件信息
|
||||
* FileInfo.filePath 文件相对路径
|
||||
* FileInfo.fileContent 文件内容
|
||||
* @param {Boolean} supportZipCache 是否支持zip缓存,zip缓存可能会导致文件不能及时更新,默认不缓存
|
||||
*
|
||||
*/
|
||||
export const writeFiles = async (baseDirHandle, filesInfo) => {
|
||||
export const writeFiles = async (
|
||||
baseDirHandle,
|
||||
filesInfo,
|
||||
zipName = 'tiny-engine-generate-code',
|
||||
supportZipCache = false
|
||||
) => {
|
||||
if (!filesInfo?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isSupportFileSystemAccess) {
|
||||
const zipInfo = { zipName, zipHandle: supportZipCache && baseDirHandle }
|
||||
await writeZip(filesInfo, zipInfo)
|
||||
return
|
||||
}
|
||||
|
||||
let directoryHandle = baseDirHandle
|
||||
if (!directoryHandle) {
|
||||
directoryHandle = await window.showDirectoryPicker({ mode: 'readwrite' })
|
||||
}
|
||||
|
||||
await Promise.all(filesInfo.map((fileInfo) => writeFile(baseDirHandle, fileInfo)))
|
||||
await Promise.all(filesInfo.map((fileInfo) => writeFile(directoryHandle, fileInfo)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"fs-extra": "^10.0.1",
|
||||
"prettier": "^2.6.1",
|
||||
"vite": "^4.3.7",
|
||||
"vite-plugin-static-copy": "^1.0.4",
|
||||
"vite-plugin-static-copy": "^0.16.0",
|
||||
"vitest": "^1.4.0",
|
||||
"winston": "^3.10.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { BUILTIN_COMPONENTS_MAP } from '@/constant'
|
||||
import { getImportMap } from './parseImport'
|
||||
import {
|
||||
genTemplateByHook,
|
||||
|
|
@ -259,7 +260,10 @@ export const genSFCWithDefaultPlugin = (schema, componentsMap, config = {}) => {
|
|||
}
|
||||
}
|
||||
|
||||
return generateSFCFile(schema, componentsMap, newConfig)
|
||||
// 兼容单独调用的场景,单独调用时,这里会默认加上 builtInComponents
|
||||
const compsMapWithBuiltIn = [...componentsMap, ...BUILTIN_COMPONENTS_MAP]
|
||||
|
||||
return generateSFCFile(schema, compsMapWithBuiltIn, newConfig)
|
||||
}
|
||||
|
||||
export default generateSFCFile
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ export const handleSlotBindAttrHook = (schemaData) => {
|
|||
let paramsValue = ''
|
||||
|
||||
if (Array.isArray(params)) {
|
||||
paramsValue = `={ ${params.join(',')} }`
|
||||
paramsValue = `="{ ${params.join(',')} }"`
|
||||
} else if (typeof params === 'string') {
|
||||
paramsValue = `="${params}"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,5 +21,3 @@ export {
|
|||
genSFCWithDefaultPlugin,
|
||||
generateSFCFile
|
||||
} from './generator'
|
||||
|
||||
export { parseRequiredBlocks } from './utils/parseRequiredBlocks'
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ function genDependenciesPlugin(options = {}) {
|
|||
.map((item) => {
|
||||
let [key, value] = item
|
||||
|
||||
if (value === '') {
|
||||
value = "''"
|
||||
if (typeof value === 'string') {
|
||||
value = `'${value}'`
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
|
|
@ -57,19 +57,19 @@ function genDependenciesPlugin(options = {}) {
|
|||
.join(',')} })`
|
||||
|
||||
const getterExpression = Object.entries(getters)
|
||||
.filter((item) => item.value?.type === 'JSFunction')
|
||||
.filter((item) => item[1]?.type === 'JSFunction')
|
||||
.map(([key, value]) => `${key}: ${value.value}`)
|
||||
.join(',')
|
||||
|
||||
const actionExpressions = Object.entries(actions)
|
||||
.filter((item) => item.value?.type === 'JSFunction')
|
||||
.filter((item) => item[1]?.type === 'JSFunction')
|
||||
.map(([key, value]) => `${key}: ${value.value}`)
|
||||
.join(',')
|
||||
|
||||
const storeFiles = `
|
||||
${importStatement}
|
||||
export const ${id} = defineStore({
|
||||
id: ${id},
|
||||
id: '${id}',
|
||||
state: ${stateExpression},
|
||||
getters: { ${getterExpression} },
|
||||
actions: { ${actionExpressions} }
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { testState } from './testState'
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { defineStore } from 'pinia'
|
||||
export const testState = defineStore({
|
||||
id: 'testState',
|
||||
state: () => ({
|
||||
name: 'testName',
|
||||
license: '',
|
||||
age: 18,
|
||||
food: ['apple', 'orange', 'banana', 19],
|
||||
desc: { description: 'hello world', money: 100, other: '', rest: ['a', 'b', 'c', 20] }
|
||||
}),
|
||||
getters: {
|
||||
getAge: function getAge() {
|
||||
return this.age
|
||||
},
|
||||
getName: function getName() {
|
||||
return this.name
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setAge: function setAge(age) {
|
||||
this.age = age
|
||||
},
|
||||
setName: function setName(name) {
|
||||
this.name = name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -676,7 +676,43 @@ export const appSchemaDemo01 = {
|
|||
value: 'function dataHanlder(res){\n return res;\n}'
|
||||
}
|
||||
},
|
||||
globalState: [],
|
||||
globalState: [
|
||||
{
|
||||
id: 'testState',
|
||||
state: {
|
||||
name: 'testName',
|
||||
license: '',
|
||||
age: 18,
|
||||
food: ['apple', 'orange', 'banana', 19],
|
||||
desc: {
|
||||
description: 'hello world',
|
||||
money: 100,
|
||||
other: '',
|
||||
rest: ['a', 'b', 'c', 20]
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getAge: {
|
||||
type: 'JSFunction',
|
||||
value: 'function getAge() {\n return this.age \n}'
|
||||
},
|
||||
getName: {
|
||||
type: 'JSFunction',
|
||||
value: 'function getName() {\n return this.name \n}'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setAge: {
|
||||
type: 'JSFunction',
|
||||
value: 'function setAge(age) {\n this.age = age; \n}'
|
||||
},
|
||||
setName: {
|
||||
type: 'JSFunction',
|
||||
value: 'function setName(name) {\n this.name = name; \n}'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
utils: [
|
||||
{
|
||||
name: 'axios',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { genSFCWithDefaultPlugin } from '@/generator/vue/sfc'
|
||||
import schema from './page.schema.json'
|
||||
import componentsMap from './components-map.json'
|
||||
import { formatCode } from '@/utils/formatCode'
|
||||
|
||||
test('should generate slot declaration correctly', async () => {
|
||||
const res = genSFCWithDefaultPlugin(schema, componentsMap)
|
||||
const formattedCode = formatCode(res, 'vue')
|
||||
|
||||
await expect(formattedCode).toMatchFileSnapshot('./expected/slotTest.vue')
|
||||
})
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"componentName": "TinyTree",
|
||||
"exportName": "Tree",
|
||||
"package": "@opentiny/vue",
|
||||
"version": "^3.10.0",
|
||||
"destructuring": true
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div>
|
||||
<tiny-tree
|
||||
:data="[
|
||||
{ label: '一级 1', children: [{ label: '二级 1-1', children: [{ label: '三级 1-1-1' }] }] },
|
||||
{
|
||||
label: '一级 2',
|
||||
children: [
|
||||
{ label: '二级 2-1', children: [{ label: '三级 2-1-1' }] },
|
||||
{ label: '二级 2-2', children: [{ label: '三级 2-2-1' }] }
|
||||
]
|
||||
}
|
||||
]"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<span>{{ data.label }}</span></template
|
||||
></tiny-tree
|
||||
>
|
||||
<tiny-tree
|
||||
:data="[
|
||||
{ label: '一级 1', children: [{ label: '二级 1-1', children: [{ label: '三级 1-1-1' }] }] },
|
||||
{
|
||||
label: '一级 2',
|
||||
children: [
|
||||
{ label: '二级 2-1', children: [{ label: '三级 2-1-1' }] },
|
||||
{ label: '二级 2-2', children: [{ label: '三级 2-2-1' }] }
|
||||
]
|
||||
}
|
||||
]"
|
||||
>
|
||||
<template #default="data">
|
||||
<span>{{ data.label }}</span></template
|
||||
></tiny-tree
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Tree as TinyTree } from '@opentiny/vue'
|
||||
import * as vue from 'vue'
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
import { I18nInjectionKey } from 'vue-i18n'
|
||||
|
||||
const props = defineProps({})
|
||||
|
||||
const emit = defineEmits([])
|
||||
const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode()
|
||||
const wrap = lowcodeWrap(props, { emit })
|
||||
wrap({ stores })
|
||||
|
||||
const state = vue.reactive({})
|
||||
wrap({ state })
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
{
|
||||
"state": {},
|
||||
"methods": {},
|
||||
"componentName": "Page",
|
||||
"fileName": "createVm",
|
||||
"css": "",
|
||||
"props": {},
|
||||
"lifeCycles": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "TinyTree",
|
||||
"props": {
|
||||
"data": [
|
||||
{
|
||||
"label": "一级 1",
|
||||
"children": [
|
||||
{
|
||||
"label": "二级 1-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 1-1-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "一级 2",
|
||||
"children": [
|
||||
{
|
||||
"label": "二级 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "二级 2-2",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 2-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "33f52342",
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Template",
|
||||
"props": {
|
||||
"slot": {
|
||||
"name": "default",
|
||||
"params": ["data"]
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"text": {
|
||||
"type": "JSExpression",
|
||||
"value": "data.label"
|
||||
}
|
||||
},
|
||||
"id": "c225d165"
|
||||
}
|
||||
],
|
||||
"id": "415d5f65"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"componentName": "TinyTree",
|
||||
"props": {
|
||||
"data": [
|
||||
{
|
||||
"label": "一级 1",
|
||||
"children": [
|
||||
{
|
||||
"label": "二级 1-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 1-1-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "一级 2",
|
||||
"children": [
|
||||
{
|
||||
"label": "二级 2-1",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 2-1-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "二级 2-2",
|
||||
"children": [
|
||||
{
|
||||
"label": "三级 2-2-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "33f52342",
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Template",
|
||||
"props": {
|
||||
"slot": {
|
||||
"name": "default",
|
||||
"params": "data"
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"text": {
|
||||
"type": "JSExpression",
|
||||
"value": "data.label"
|
||||
}
|
||||
},
|
||||
"id": "c225d165"
|
||||
}
|
||||
],
|
||||
"id": "415d5f65"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { parseRequiredBlocks } from '@/utils/parseRequiredBlocks'
|
||||
|
||||
test('should return empty array when children is no array', () => {
|
||||
expect(parseRequiredBlocks()).toStrictEqual([])
|
||||
expect(parseRequiredBlocks({})).toStrictEqual([])
|
||||
expect(parseRequiredBlocks({ children: null })).toStrictEqual([])
|
||||
})
|
||||
|
||||
test('should recursive parse children', () => {
|
||||
const mockData = {
|
||||
children: [
|
||||
{
|
||||
componentType: 'Block',
|
||||
componentName: 'Header',
|
||||
children: [
|
||||
{
|
||||
componentType: 'Block',
|
||||
componentName: 'MenuList'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
componentName: 'div',
|
||||
children: [
|
||||
{
|
||||
componentName: 'div',
|
||||
children: [
|
||||
{
|
||||
componentName: 'div',
|
||||
children: [
|
||||
{
|
||||
componentType: 'Block',
|
||||
componentName: 'Container'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
expect(parseRequiredBlocks(mockData)).toStrictEqual(['Header', 'MenuList', 'Container'])
|
||||
})
|
||||
|
|
@ -17,7 +17,7 @@ import { viteStaticCopy } from 'vite-plugin-static-copy'
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
test: {
|
||||
exclude: ['**/result/**'],
|
||||
exclude: ['**/result/**', 'node_modules'],
|
||||
watchExclude: ['**/result/**']
|
||||
},
|
||||
resolve: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ const materialsDir = 'materials'
|
|||
const bundlePath = path.join(process.cwd(), '/packages/design-core/public/mock/bundle.json')
|
||||
// mockServer应用数据
|
||||
const appInfoPath = path.join(process.cwd(), '/mockServer/src/services/appinfo.json')
|
||||
const appSchemaPath = path.join(process.cwd(), 'mockServer/src/mock/get/app-center/v1/apps/schema/918.json')
|
||||
|
||||
const appInfo = fsExtra.readJSONSync(appInfoPath)
|
||||
const appSchema = fsExtra.readJSONSync(appSchemaPath)
|
||||
|
||||
const connection = new MysqlConnection()
|
||||
|
||||
|
|
@ -22,6 +25,7 @@ const connection = new MysqlConnection()
|
|||
const write = (bundle) => {
|
||||
fsExtra.outputJSONSync(bundlePath, bundle, { spaces: 2 })
|
||||
fsExtra.outputJSONSync(appInfoPath, appInfo, { spaces: 2 })
|
||||
fsExtra.outputJSONSync(appSchemaPath, appSchema, { spaces: 2 })
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -36,13 +40,13 @@ const validateComponent = (file, component) => {
|
|||
const requiredList = requiredFields.filter((field) => !fields.includes(field))
|
||||
|
||||
if (requiredList.length) {
|
||||
logger.error(`组件文件 ${file} 缺少必要字段:${requiredList.join('、')}。`)
|
||||
logger.error(`missing required fields: ${requiredList.join(',')} at ${file}.`)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (!component.npm) {
|
||||
logger.warn(`组件文件 ${file} 缺少 npm 字段,出码时将不能通过import语句导入组件。`)
|
||||
logger.warn(`missing the \`npm\` field, and it cannot be imported when coding at ${file}.`)
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -62,7 +66,7 @@ const validateBlock = (file, block) => {
|
|||
const requiredList = requiredFields.filter((field) => !fields.includes(field))
|
||||
|
||||
if (requiredList.length) {
|
||||
logger.error(`区块文件 ${file} 缺少必要字段:${requiredList.join('、')}。`)
|
||||
logger.error(`missing required fields: ${requiredList.join(',')} at ${file}.`)
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -80,7 +84,7 @@ const generateComponents = () => {
|
|||
try {
|
||||
fg([`${materialsDir}/**/*.json`]).then((files) => {
|
||||
if (!files.length) {
|
||||
logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
|
||||
logger.warn('please execute `pnpm splitMaterials` first to split the materials.')
|
||||
}
|
||||
|
||||
const bundle = {
|
||||
|
|
@ -95,6 +99,7 @@ const generateComponents = () => {
|
|||
}
|
||||
const { components = [], snippets = [], blocks = [] } = bundle.data.materials
|
||||
const componentsMap = []
|
||||
const packagesMap = []
|
||||
const appInfoBlocksLabels = appInfo.blockHistories.map((item) => item.label)
|
||||
|
||||
files.forEach((file) => {
|
||||
|
|
@ -103,7 +108,7 @@ const generateComponents = () => {
|
|||
if (!material) {
|
||||
const fileFullPath = path.join(process.cwd(), file)
|
||||
|
||||
logger.error(`文件格式有误 (${fileFullPath})`)
|
||||
logger.error(`incorrect file format at ${fileFullPath}.`)
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -151,13 +156,31 @@ const generateComponents = () => {
|
|||
|
||||
appInfo.materialHistory.components = componentsMap
|
||||
|
||||
write(bundle)
|
||||
const { package: packageName = '', version = '', exportName = '' } = npm || {}
|
||||
|
||||
const mapItem = {
|
||||
componentName: component,
|
||||
package: packageName,
|
||||
version,
|
||||
exportName
|
||||
}
|
||||
|
||||
if (typeof npm.destructuring === 'boolean') {
|
||||
mapItem.destructuring = npm.destructuring
|
||||
}
|
||||
|
||||
if (npm.package) {
|
||||
packagesMap.push(mapItem)
|
||||
}
|
||||
})
|
||||
|
||||
appSchema.data.componentsMap = packagesMap
|
||||
write(bundle)
|
||||
})
|
||||
|
||||
logger.success('物料资产包构建成功')
|
||||
logger.success('materials built.')
|
||||
} catch (error) {
|
||||
logger.error(`物料资产包构建失败:${error}`)
|
||||
logger.error(`failed to build materials: ${error}.`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,13 +189,13 @@ const watcher = chokidar.watch(`${materialsDir}/**/*.json`, { ignoreInitial: tru
|
|||
|
||||
watcher.on('all', (event, file) => {
|
||||
const eventMap = {
|
||||
add: '新增',
|
||||
change: '更新',
|
||||
unlink: '删除'
|
||||
add: 'added',
|
||||
change: 'changed',
|
||||
unlink: 'deleted'
|
||||
}
|
||||
const fileFullPath = path.join(process.cwd(), file)
|
||||
|
||||
logger.info(`${eventMap[event]}组件文件 (${fileFullPath})`)
|
||||
logger.info(`${fileFullPath} ${eventMap[event]}, rebuilding materials...`)
|
||||
|
||||
// 监听物料文件变化,更新物料资产包
|
||||
generateComponents()
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ class MysqlConnection {
|
|||
return new Promise((resolve, reject) => {
|
||||
this.connection.connect((error) => {
|
||||
if (error) {
|
||||
logger.warn('未能连接到数据库,请查看数据库配置是否正确')
|
||||
logger.warn('unable to connect to the database, please check the database configuration is correct.')
|
||||
reject()
|
||||
} else {
|
||||
logger.success('数据库连接成功')
|
||||
logger.success('database connected.')
|
||||
this.connected = true
|
||||
resolve()
|
||||
}
|
||||
|
|
@ -100,11 +100,11 @@ class MysqlConnection {
|
|||
* @returns boolean 校验组件字段是否失败,false-有字段出错
|
||||
*/
|
||||
isValid(component, file) {
|
||||
const longtextFields = ['name', 'npm', 'snippets', 'schema_fragment', 'configure', 'component_metadata']
|
||||
const longTextFields = ['name', 'npm', 'snippets', 'schema_fragment', 'configure', 'component_metadata']
|
||||
|
||||
return Object.entries(component).every(([key, value]) => {
|
||||
if (longtextFields.includes(key) && value !== null && typeof value !== 'object') {
|
||||
logger.error(`"${key}" 的值不是有效的JSON (${file})`)
|
||||
if (longTextFields.includes(key) && value !== null && typeof value !== 'object') {
|
||||
logger.error(`the value of "${key}" is not valid JSON at ${file}.`)
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -193,10 +193,10 @@ class MysqlConnection {
|
|||
|
||||
this.query(sqlContent, component.component)
|
||||
.then(() => {
|
||||
logger.success(`组件 ${component.component} 数据更新成功`)
|
||||
logger.success(`${component.component} updated.`)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`组件 ${component.component} 数据更新失败 ${error}`)
|
||||
logger.error(`failed to update ${component.component}: ${error}.`)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -322,11 +322,11 @@ class MysqlConnection {
|
|||
.then((result) => {
|
||||
const id = result.insertId
|
||||
|
||||
logger.success(`组件 ${component.component} 数据新增成功`)
|
||||
logger.success(`${component.component} added.`)
|
||||
this.relationMaterialHistory(id)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.success(`组件 ${component.component} 数据新增失败:${error}`)
|
||||
logger.error(`add ${component.component} failed:${error}.`)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -344,7 +344,7 @@ class MysqlConnection {
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.success(`查询组件 ${component.component} 失败:${error}`)
|
||||
logger.error(`query ${component.component} failed:${error}.`)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -395,11 +395,11 @@ class MysqlConnection {
|
|||
return new Promise((resolve, reject) => {
|
||||
this.query(sqlContent)
|
||||
.then((result) => {
|
||||
logger.success(`表 ${componentsTableName} 创建成功`)
|
||||
logger.success(`table ${componentsTableName} created.`)
|
||||
resolve(result)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.success(`表 ${componentsTableName} 创建失败:${error}`)
|
||||
logger.error(`create table ${componentsTableName} failed:${error}.`)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ const splitMaterials = () => {
|
|||
fs.outputJsonSync(blockPath, block, { spaces: 2 })
|
||||
})
|
||||
|
||||
logger.success('拆分物料资产包完成')
|
||||
logger.success('materials splitted.')
|
||||
} catch (error) {
|
||||
logger.error(`拆分物料资产包失败: ${error}`)
|
||||
logger.error(`failed to split materials: ${error}.`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue