Skip to content

Commit 3cc9e44

Browse files
committed
feat: add blockSchema
1 parent fc16b7c commit 3cc9e44

5 files changed

Lines changed: 416 additions & 11 deletions

File tree

packages/toolbars/upload/src/Main.vue

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

packages/toolbars/upload/src/http.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,15 @@ import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
1616

1717
// 获取页面列表
1818
export const fetchPageList = (appId: string) => getMetaApi(META_SERVICE.Http).get(`/app-center/api/pages/list/${appId}`)
19+
20+
// 获取区块分组列表
21+
export const fetchBlockGroups = (params?: any) =>
22+
getMetaApi(META_SERVICE.Http).get('/material-center/api/block-groups', { params: { ...params, from: 'block' } })
23+
24+
// 创建区块分组
25+
export const createBlockGroup = (params: any) =>
26+
getMetaApi(META_SERVICE.Http).post('/material-center/api/block-groups/create', params)
27+
28+
// 创建区块
29+
export const createBlock = (params: any) =>
30+
getMetaApi(META_SERVICE.Http).post('/material-center/api/block/create', params)

0 commit comments

Comments
 (0)