diff --git a/docs/zh-cn/SUMMARY.md b/docs/zh-cn/SUMMARY.md
index bf2137ac0d2..00602827a11 100644
--- a/docs/zh-cn/SUMMARY.md
+++ b/docs/zh-cn/SUMMARY.md
@@ -23,6 +23,7 @@
- [玩转查询列表](./schema-develop/form-query.md)
- [玩转自增列表组件](./schema-develop/complext-self-inc-component.md)
- [实现超复杂自定义组件](./schema-develop/create-complex-field-component.md)
+ - [页面完全表达](./schema-develop/full-express.md)
- [FAQ](./schema-develop/faq.md)
- Form
- [介绍](./jsx-develop/introduction.md)
diff --git a/docs/zh-cn/schema-develop/full-express.md b/docs/zh-cn/schema-develop/full-express.md
new file mode 100644
index 00000000000..a02f1292c47
--- /dev/null
+++ b/docs/zh-cn/schema-develop/full-express.md
@@ -0,0 +1,1230 @@
+# 页面完全表达
+
+得益于 `Formily` 完善的注册组件机制以及状态管理机制,使得开发者能够轻松描述表单场景。
+实际上,基于这些的能力的延伸,甚至还能够覆盖到页面级别,下面以几个例子来说明。
+
+## 描述页面
+
+在表单场景下,描述非表单字段使用 `VirtualField` 我们已经非常熟悉了,常用的场景有 **布局容器** 或用来 **控制显示或隐藏** 的表单字段的容器。基于这样的实践,我们同样可以放大到页面:
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaForm,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const Header = createVirtualBox('header', (props) => {
+ const { color = '#333' } = props
+ return
{props.children}
+})
+
+const Description = createVirtualBox('description', (props) => {
+ return {props.children}
+})
+
+const Body = createVirtualBox('body', (props) => {
+ return {props.children}
+})
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+const actions = createFormActions()
+
+const App = () => {
+ return (
+
+ 页面标题 返回,
+ renderDesc: '页面描述',
+ toggleHeaderColor: () => {
+ actions.setFieldState('header', state => {
+ state.props['x-component-props'].color = state.props['x-component-props'].color === 'red' ? '#333' : 'red'
+ })
+ },
+ toggleShowHeader: () => {
+ actions.setFieldState('header', state => state.visible = !state.visible)
+ },
+ toggleShowBody: () => {
+ actions.setFieldState('body', state => state.visible = !state.visible)
+ },
+ setDesc: () => {
+ actions.setFieldState('desc', state => {
+ state.props['x-component-props'].children = 随机数:{`${Math.random()}`.slice(2)}
+ })
+ }
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+
+
+ 改变Header字体颜色
+ 显示或隐藏Header
+ 显示或隐藏Body
+ 动态修改页面描述
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+### 完全基于Schema描述页面
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaForm,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const Header = createVirtualBox('header', (props) => {
+ const { color = '#333' } = props
+ return {props.children}
+})
+
+const Description = createVirtualBox('description', (props) => {
+ return {props.children}
+})
+
+const Body = createVirtualBox('body', (props) => {
+ return {props.children}
+})
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+const actions = createFormActions()
+
+const App = () => {
+ return (
+
+ 页面标题 返回,
+ renderDesc: '页面描述',
+ toggleHeaderColor: () => {
+ actions.setFieldState('header', state => {
+ state.props['x-component-props'].color = state.props['x-component-props'].color === 'red' ? '#333' : 'red'
+ })
+ },
+ toggleShowHeader: () => {
+ actions.setFieldState('header', state => state.visible = !state.visible)
+ },
+ toggleShowBody: () => {
+ actions.setFieldState('body', state => state.visible = !state.visible)
+ },
+ setDesc: () => {
+ actions.setFieldState('desc', state => {
+ state.props['x-component-props'].children = 随机数:{`${Math.random()}`.slice(2)}
+ })
+ }
+ }}
+ schema={{
+ "type": "object",
+ "properties": {
+ "header": {
+ "key": "header",
+ "type": "object",
+ "name": "header",
+ "x-component": "header",
+ "x-component-props": {
+ "children": "{{renderHeader}}"
+ }
+ },
+ "desc": {
+ "key": "desc",
+ "type": "object",
+ "name": "desc",
+ "x-component": "description",
+ "x-component-props": {
+ "children": "{{renderDesc}}"
+ }
+ },
+ "body": {
+ "key": "body",
+ "type": "object",
+ "name": "body",
+ "x-component": "body",
+ "x-component-props": {},
+ "properties": {
+ "NO_NAME_FIELD_$0": {
+ "key": "NO_NAME_FIELD_$0",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$0",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "labelCol": 4
+ },
+ "properties": {
+ "username": {
+ "key": "username",
+ "name": "username",
+ "title": "姓名",
+ "x-component": "input"
+ },
+ "age": {
+ "key": "age",
+ "name": "age",
+ "title": "年龄",
+ "x-component": "input"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$1": {
+ "key": "NO_NAME_FIELD_$1",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$1",
+ "x-component": "buttongroup",
+ "x-component-props": {
+ "align": "center"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$2": {
+ "key": "NO_NAME_FIELD_$2",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$2",
+ "x-component": "submit",
+ "x-component-props": {
+ "children": "提交"
+ }
+ },
+ "NO_NAME_FIELD_$3": {
+ "key": "NO_NAME_FIELD_$3",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$3",
+ "x-component": "reset",
+ "x-component-props": {
+ "children": "重置"
+ }
+ }
+ }
+ }
+ }
+ },
+ "NO_NAME_FIELD_$4": {
+ "key": "NO_NAME_FIELD_$4",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$4",
+ "x-component": "buttongroup",
+ "x-component-props": {},
+ "properties": {
+ "NO_NAME_FIELD_$5": {
+ "key": "NO_NAME_FIELD_$5",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$5",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{toggleHeaderColor}}",
+ "children": "改变Header字体颜色"
+ }
+ },
+ "NO_NAME_FIELD_$6": {
+ "key": "NO_NAME_FIELD_$6",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$6",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{toggleShowHeader}}",
+ "children": "显示或隐藏Header"
+ }
+ },
+ "NO_NAME_FIELD_$7": {
+ "key": "NO_NAME_FIELD_$7",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$7",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{toggleShowBody}}",
+ "children": "显示或隐藏Body"
+ }
+ },
+ "NO_NAME_FIELD_$8": {
+ "key": "NO_NAME_FIELD_$8",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$8",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{setDesc}}",
+ "children": "动态修改页面描述"
+ }
+ }
+ }
+ }
+ }
+ }}
+ />
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+**案例解析**
+
+- 页面元素如 `Header`, `Body`, `Button` 等都可以作为 `VirtualField` 存在,并且可以通过 **actions** 来进行状态管理
+- `VirtualField` 所有属性都可以托管在 **expressionScope**,甚至还可以动态设置 `children`
+- 页面完全表达的方式可以将页面的所有内容通过 `schema` 来描述
+
+## 弹窗场景
+
+基于上面的实践,我们可以拓展到常见的弹窗表单场景。弹窗场景稍微会复杂一些:
+* 首先 `schema` 描述上可以一眼看清楚页面上一共有几个弹窗,触发这些弹窗的是哪些元素
+* 其次弹窗的子表单应该和主表单是隔离的
+
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaField,
+ SchemaForm,
+ Schema,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ createControllerBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import { message, Modal } from 'antd'
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const SchemaDialog = createControllerBox("Modal", props => {
+ const { schema } = props;
+ const componentProps = schema.getExtendsComponentProps();
+ const { properties } = schema.toJSON();
+ const nestedSchema = new Schema({
+ type: "object",
+ properties
+ });
+
+ const { visible, title, onCancel, footer, ...others } = componentProps;
+ return (
+
+
+
+
+
+ );
+});
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+
+const actions = createFormActions()
+const actions2 = createFormActions()
+const actions3 = createFormActions()
+
+const toggleDialog1Show = () => {
+ const dialogVisible = actions.getFieldState('dialog1', state => state.props['x-component-props'].visible)
+ if (dialogVisible) {
+ actions2.reset()
+ }
+
+ actions.setFieldState('dialog1', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ })
+}
+
+const toggleDialog2Show = () => {
+ const dialogVisible = actions.getFieldState('dialog2', state => state.props['x-component-props'].visible)
+ if (dialogVisible) {
+ actions3.reset()
+ }
+
+ actions.setFieldState('dialog2', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ })
+}
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+const components = { Input }
+
+const App = () => {
+ return (
+
+ {
+ console.log('====弹窗表单1 values===', values)
+ await sleep(500)
+ message.success('弹窗1提交成功')
+ toggleDialog1Show()
+ },
+ onDialog2Submit: async (values) => {
+ console.log('====弹窗表单2 values===', values)
+ await sleep(500)
+ message.success('弹窗2提交成功')
+ toggleDialog2Show()
+ }
+ }}
+ >
+
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+ {/* 弹窗表单1 */}
+
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+
+ {/* 弹窗表单2 */}
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+
+
+ 显示或隐藏弹窗1
+ 显示或隐藏弹窗2
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+### 完全基于Schema实现弹窗场景
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaField,
+ SchemaForm,
+ Schema,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ createControllerBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import { message, Modal } from 'antd'
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const SchemaDialog = createControllerBox("Modal", props => {
+ const { schema } = props;
+ const componentProps = schema.getExtendsComponentProps();
+ const { properties } = schema.toJSON();
+ const nestedSchema = new Schema({
+ type: "object",
+ properties
+ });
+
+ const { visible, title, onCancel, footer, ...others } = componentProps;
+ return (
+
+
+
+
+
+ );
+});
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+
+const actions = createFormActions()
+const actions2 = createFormActions()
+const actions3 = createFormActions()
+
+const toggleDialog1Show = () => {
+ const dialogVisible = actions.getFieldState('dialog1', state => state.props['x-component-props'].visible)
+ if (dialogVisible) {
+ actions2.reset()
+ }
+
+ actions.setFieldState('dialog1', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ })
+}
+
+const toggleDialog2Show = () => {
+ const dialogVisible = actions.getFieldState('dialog2', state => state.props['x-component-props'].visible)
+ if (dialogVisible) {
+ actions3.reset()
+ }
+
+ actions.setFieldState('dialog2', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ })
+}
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+const components = { Input }
+
+const App = () => {
+ return (
+
+ {
+ console.log('====弹窗表单1 values===', values)
+ await sleep(500)
+ message.success('弹窗1提交成功')
+ toggleDialog1Show()
+ },
+ onDialog2Submit: async (values) => {
+ console.log('====弹窗表单2 values===', values)
+ await sleep(500)
+ message.success('弹窗2提交成功')
+ toggleDialog2Show()
+ }
+ }}
+ schema={{
+ "type": "object",
+ "properties": {
+ "NO_NAME_FIELD_$0": {
+ "key": "NO_NAME_FIELD_$0",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$0",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "labelCol": 4
+ },
+ "properties": {
+ "username": {
+ "key": "username",
+ "name": "username",
+ "title": "姓名",
+ "x-component": "input"
+ },
+ "age": {
+ "key": "age",
+ "name": "age",
+ "title": "年龄",
+ "x-component": "input"
+ },
+ "inputBox": {
+ "key": "inputBox",
+ "name": "inputBox",
+ "title": "输入框",
+ "x-component": "input"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$1": {
+ "key": "NO_NAME_FIELD_$1",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$1",
+ "x-component": "buttongroup",
+ "x-component-props": {
+ "align": "center"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$2": {
+ "key": "NO_NAME_FIELD_$2",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$2",
+ "x-component": "submit",
+ "x-component-props": {
+ "children": "提交"
+ }
+ },
+ "NO_NAME_FIELD_$3": {
+ "key": "NO_NAME_FIELD_$3",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$3",
+ "x-component": "reset",
+ "x-component-props": {
+ "children": "重置"
+ }
+ }
+ }
+ },
+ "dialog1": {
+ "key": "dialog1",
+ "type": "object",
+ "name": "dialog1",
+ "x-component": "modal",
+ "x-component-props": {
+ "title": "弹窗1",
+ "onCancel": "{{toggleDialog1Show}}",
+ "footer": null,
+ "components": "{{components}}",
+ "onSubmit": "{{onDialog1Submit}}",
+ "actions": "{{actions2}}"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$4": {
+ "key": "NO_NAME_FIELD_$4",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$4",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "labelCol": 4
+ },
+ "properties": {
+ "username": {
+ "key": "username",
+ "name": "username",
+ "title": "姓名",
+ "x-component": "input"
+ },
+ "age": {
+ "key": "age",
+ "name": "age",
+ "title": "年龄",
+ "x-component": "input"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$5": {
+ "key": "NO_NAME_FIELD_$5",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$5",
+ "x-component": "buttongroup",
+ "x-component-props": {
+ "align": "center"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$6": {
+ "key": "NO_NAME_FIELD_$6",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$6",
+ "x-component": "submit",
+ "x-component-props": {
+ "children": "提交"
+ }
+ },
+ "NO_NAME_FIELD_$7": {
+ "key": "NO_NAME_FIELD_$7",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$7",
+ "x-component": "reset",
+ "x-component-props": {
+ "children": "重置"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dialog2": {
+ "key": "dialog2",
+ "type": "object",
+ "name": "dialog2",
+ "x-component": "modal",
+ "x-component-props": {
+ "title": "弹窗2",
+ "onCancel": "{{toggleDialog2Show}}",
+ "footer": null,
+ "components": "{{components}}",
+ "onSubmit": "{{onDialog2Submit}}",
+ "actions": "{{actions3}}"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$8": {
+ "key": "NO_NAME_FIELD_$8",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$8",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "labelCol": 4
+ },
+ "properties": {
+ "inputBox": {
+ "key": "inputBox",
+ "name": "inputBox",
+ "title": "输入框",
+ "x-component": "input"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$9": {
+ "key": "NO_NAME_FIELD_$9",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$9",
+ "x-component": "buttongroup",
+ "x-component-props": {
+ "align": "center"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$10": {
+ "key": "NO_NAME_FIELD_$10",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$10",
+ "x-component": "submit",
+ "x-component-props": {
+ "children": "提交"
+ }
+ },
+ "NO_NAME_FIELD_$11": {
+ "key": "NO_NAME_FIELD_$11",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$11",
+ "x-component": "reset",
+ "x-component-props": {
+ "children": "重置"
+ }
+ }
+ }
+ }
+ }
+ },
+ "NO_NAME_FIELD_$12": {
+ "key": "NO_NAME_FIELD_$12",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$12",
+ "x-component": "buttongroup",
+ "x-component-props": {},
+ "properties": {
+ "NO_NAME_FIELD_$13": {
+ "key": "NO_NAME_FIELD_$13",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$13",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{toggleDialog1Show}}",
+ "children": "显示或隐藏弹窗1"
+ }
+ },
+ "NO_NAME_FIELD_$14": {
+ "key": "NO_NAME_FIELD_$14",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$14",
+ "x-component": "button",
+ "x-component-props": {
+ "onClick": "{{toggleDialog2Show}}",
+ "children": "显示或隐藏弹窗2"
+ }
+ }
+ }
+ }
+ }
+ }}
+ />
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+**案例解析**
+
+- 当描述页面的内容涉及嵌套表单时,需要使用 `SchemaField` 来调整渲染内容,保证JSX与Schema之间的互转
+- 弹窗也是 `VirtualField`,可以通过 `actions` 管理显示或隐藏
+
+
+## CRUD 场景
+
+接下里把完全描述页面的能力应用在更加通用的日常中后台开发场景(CRUD),以 **Table** + **弹窗** 举例
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaField,
+ SchemaForm,
+ Schema,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ createControllerBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import { message, Modal, Table } from 'antd'
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const SchemaDialog = createControllerBox("Modal", props => {
+ const { schema } = props;
+ const componentProps = schema.getExtendsComponentProps();
+ const { properties } = schema.toJSON();
+ const nestedSchema = new Schema({
+ type: "object",
+ properties
+ });
+
+ const { visible, title, onCancel, footer, ...others } = componentProps;
+ return (
+
+
+
+
+
+ );
+});
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+const SchemaTable = createVirtualBox('table', Table)
+
+const actions = createFormActions()
+const actions2 = createFormActions()
+
+const toggleDialogShow = (values) => {
+ actions.setFieldState('dialog', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ state.props['x-component-props'].value = 'tatget' in values ? {} : values
+ })
+}
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+const components = { Input }
+
+const App = () => {
+ return (
+
+ {
+ return
+ },
+ onDialogSubmit: async (values) => {
+ console.log('====弹窗表单 values===', values)
+ await sleep(500)
+ message.success('弹窗提交成功')
+ toggleDialogShow()
+ },
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+
+
+
+ {/* 弹窗表单 */}
+
+
+
+
+
+
+
+ 提交
+ 重置
+
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+### 完全基于Schema描述CRUD场景
+
+```jsx
+import React, { useEffect } from 'react'
+import ReactDOM from 'react-dom'
+import { Button } from 'antd'
+import {
+ SchemaField,
+ SchemaForm,
+ Schema,
+ FormSlot,
+ SchemaMarkupField as Field,
+ FormButtonGroup,
+ createFormActions,
+ createVirtualBox,
+ createControllerBox,
+ Submit,
+ Reset
+} from '@formily/antd' // 或者 @formily/next
+import { message, Modal, Table } from 'antd'
+import styled, { css } from 'styled-components'
+import { FormMegaLayout, Input, DatePicker } from '@formily/antd-components'
+import Printer from '@formily/printer'
+import 'antd/dist/antd.css'
+
+const SchemaDialog = createControllerBox("Modal", props => {
+ const { schema } = props;
+ const componentProps = schema.getExtendsComponentProps();
+ const { properties } = schema.toJSON();
+ const nestedSchema = new Schema({
+ type: "object",
+ properties
+ });
+
+ const { visible, title, onCancel, footer, ...others } = componentProps;
+ return (
+
+
+
+
+
+ );
+});
+const SchemaFormButtonGroup = createVirtualBox('buttonGroup', FormButtonGroup)
+const SchemaButton = createVirtualBox('button', Button)
+const SchemaSubmit = createVirtualBox('submit', Submit)
+const SchemaReset = createVirtualBox('reset', Reset)
+const SchemaTable = createVirtualBox('table', Table)
+
+const actions = createFormActions()
+const actions2 = createFormActions()
+
+const toggleDialogShow = (values) => {
+ actions.setFieldState('dialog', state => {
+ state.props['x-component-props'].visible = !state.props['x-component-props'].visible
+ state.props['x-component-props'].value = 'tatget' in values ? {} : values
+ })
+}
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
+const components = { Input }
+
+const App = () => {
+ return (
+
+ {
+ return
+ },
+ onDialogSubmit: async (values) => {
+ console.log('====弹窗表单 values===', values)
+ await sleep(500)
+ message.success('弹窗提交成功')
+ toggleDialogShow()
+ },
+ }}
+ schema={{
+ "type": "object",
+ "properties": {
+ "NO_NAME_FIELD_$0": {
+ "key": "NO_NAME_FIELD_$0",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$0",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "grid": true,
+ "columns": 4,
+ "autoRow": true
+ },
+ "properties": {
+ "username": {
+ "key": "username",
+ "name": "username",
+ "title": "姓名",
+ "x-component": "input"
+ },
+ "age": {
+ "key": "age",
+ "name": "age",
+ "title": "年龄",
+ "x-component": "input"
+ },
+ "inputBox": {
+ "key": "inputBox",
+ "name": "inputBox",
+ "title": "输入框",
+ "x-component": "input"
+ },
+ "NO_NAME_FIELD_$1": {
+ "key": "NO_NAME_FIELD_$1",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$1"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$2": {
+ "key": "NO_NAME_FIELD_$2",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$2",
+ "x-component": "button",
+ "x-component-props": {
+ "type": "primary",
+ "onClick": "{{toggleDialogShow}}",
+ "style": {
+ "marginBottom": "12px"
+ },
+ "children": "新建"
+ }
+ },
+ "NO_NAME_FIELD_$3": {
+ "key": "NO_NAME_FIELD_$3",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$3",
+ "x-component": "table",
+ "x-component-props": {
+ "dataSource": [
+ {
+ "key": "1",
+ "username": "billy",
+ "age": 23
+ },
+ {
+ "key": "2",
+ "username": "joe",
+ "age": 21
+ }
+ ],
+ "columns": [
+ {
+ "title": "姓名",
+ "dataIndex": "username",
+ "key": "name"
+ },
+ {
+ "title": "年龄",
+ "dataIndex": "age",
+ "key": "age"
+ },
+ {
+ "title": "操作",
+ "render": "{{renderOperation}}"
+ }
+ ]
+ }
+ },
+ "dialog": {
+ "key": "dialog",
+ "type": "object",
+ "name": "dialog",
+ "x-component": "modal",
+ "x-component-props": {
+ "title": "弹窗",
+ "onCancel": "{{toggleDialogShow}}",
+ "footer": null,
+ "components": "{{components}}",
+ "onSubmit": "{{onDialogSubmit}}",
+ "actions": "{{actions2}}"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$4": {
+ "key": "NO_NAME_FIELD_$4",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$4",
+ "x-component": "mega-layout",
+ "x-component-props": {
+ "labelCol": 4
+ },
+ "properties": {
+ "username": {
+ "key": "username",
+ "name": "username",
+ "title": "姓名",
+ "x-component": "input"
+ },
+ "age": {
+ "key": "age",
+ "name": "age",
+ "title": "年龄",
+ "x-component": "input"
+ }
+ }
+ },
+ "NO_NAME_FIELD_$5": {
+ "key": "NO_NAME_FIELD_$5",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$5",
+ "x-component": "buttongroup",
+ "x-component-props": {
+ "align": "center"
+ },
+ "properties": {
+ "NO_NAME_FIELD_$6": {
+ "key": "NO_NAME_FIELD_$6",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$6",
+ "x-component": "submit",
+ "x-component-props": {
+ "children": "提交"
+ }
+ },
+ "NO_NAME_FIELD_$7": {
+ "key": "NO_NAME_FIELD_$7",
+ "type": "object",
+ "name": "NO_NAME_FIELD_$7",
+ "x-component": "reset",
+ "x-component-props": {
+ "children": "重置"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }}
+ />
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+**案例解析**
+
+- 弹窗和基于 `VirtualField` 的 **Table** 实现列表 + 弹窗表单的 CRUD 场景
\ No newline at end of file
diff --git a/docs/zh-cn/schema-develop/mega-layout-antd.md b/docs/zh-cn/schema-develop/mega-layout-antd.md
index 5736e7ab879..6ade64a3503 100644
--- a/docs/zh-cn/schema-develop/mega-layout-antd.md
+++ b/docs/zh-cn/schema-develop/mega-layout-antd.md
@@ -1667,4 +1667,4 @@ const App = () => {
}
ReactDOM.render(, document.getElementById('root'))
-```
+```
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/components/SchemaMarkup.tsx b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx
index 0e8f41e8aa6..441d7bad1cb 100644
--- a/packages/react-schema-renderer/src/components/SchemaMarkup.tsx
+++ b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx
@@ -29,6 +29,9 @@ export const SchemaMarkupField: React.FC = ({
if (parentSchema.isObject()) {
props.name = props.name || getRandomName()
const schema = parentSchema.setProperty(props.name, props)
+ if (typeof children === 'string') {
+ schema['x-component-props'].children = children
+ }
return (
{children}
)
@@ -145,4 +148,4 @@ export const FormSlot: React.FC<{
}}
/>
)
-}
+}
\ No newline at end of file