diff --git a/__tests__/__snapshots__/factory.test.tsx.snap b/__tests__/__snapshots__/factory.test.tsx.snap index 5876cac9..d3c694ea 100644 --- a/__tests__/__snapshots__/factory.test.tsx.snap +++ b/__tests__/__snapshots__/factory.test.tsx.snap @@ -57,6 +57,457 @@ exports[`factory unregistered Renderer 1`] = ` `; +exports[`factory:definitions 1`] = ` +
+
+
+
+
+

+ + 引用 + +

+
+
+
+
+

+ + 表单 + +

+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+ +
+
+
+
+ +
+
+ +
+ +
+
+
+
+`; + +exports[`factory:definitions override 1`] = ` +
+
+
+
+
+

+ + 引用 + +

+
+
+
+
+

+ + 表单 + +

+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+ + +
+
+ +
+
+ +
+ +
+
+
+
+`; + exports[`factory:registerRenderer 1`] = `
diff --git a/__tests__/factory.test.tsx b/__tests__/factory.test.tsx index e2f16f01..6707db2c 100644 --- a/__tests__/factory.test.tsx +++ b/__tests__/factory.test.tsx @@ -2,8 +2,11 @@ import { registerRenderer, unRegisterRenderer, RendererProps, - render as amisRender } from '../src/factory'; +import '../src/themes/default'; +import { + render as amisRender +} from '../src/index'; import React = require('react'); import {render, fireEvent, cleanup} from 'react-testing-library'; import { wait, makeEnv } from './helper'; @@ -112,4 +115,143 @@ test('factory:registerRenderer', () => { expect(container).toMatchSnapshot(); unRegisterRenderer(renderer); +}); + +test('factory:definitions', () => { + const { + container, + getByText + } = render(amisRender({ + definitions: { + aa: { + type: 'text', + name: 'jack', + value: 'refs value', + remark: '通过\\$refs引入的组件' + }, + bb: { + type: 'combo', + multiple: true, + multiLine: true, + remark: 'combo中的子项引入自身,实现嵌套的效果', + controls: [ + { + label: 'combo 1', + type: 'text', + name: 'key' + }, + { + label: 'combo 2', + name: 'value', + $refs: 'aa' + }, + { + name: 'children', + label: 'children', + $refs: 'bb' + } + ] + } + }, + type: 'page', + title: '引用', + body: [ + { + type: 'form', + api: 'api/xxx', + actions: [], + controls: [ + { + label: 'text2', + $refs: 'aa', + name: 'refs1' + }, + { + label: 'combo', + $refs: 'bb', + name: 'refs2' + } + ] + } + ] + }, { + }, makeEnv({ + }))); + + fireEvent.click(getByText('新增')); + expect(container).toMatchSnapshot(); +}); + +test('factory:definitions override', () => { + const { + container + } = render(amisRender({ + definitions: { + aa: { + type: 'text', + name: 'jack', + remark: '通过\\$refs引入的组件' + }, + bb: { + type: 'combo', + multiple: true, + multiLine: true, + remark: 'combo中的子项引入自身,实现嵌套的效果', + controls: [ + { + label: 'combo 1', + type: 'text', + name: 'key' + }, + { + label: 'combo 2', + name: 'value', + $refs: 'aa' + }, + { + name: 'children', + label: 'children', + $refs: 'bb' + } + ] + } + }, + type: 'page', + title: '引用', + body: [ + { + type: 'form', + api: 'api/xxx', + actions: [], + controls: [ + { + label: 'text2', + $refs: 'aa', + name: 'refs1' + }, + { + label: 'combo', + $refs: 'bb', + name: 'refs2', + type: 'checkboxes', + value: 1 + options: [ + { + label: 'Option A', + value: 1 + }, + { + label: 'Option B', + value: 2 + } + ] + } + ] + } + ] + }, { + }, makeEnv({ + }))); + + expect(container).toMatchSnapshot(); }); \ No newline at end of file diff --git a/docs/renderers.md b/docs/renderers.md index 7e2f574c..2ca94dc5 100644 --- a/docs/renderers.md +++ b/docs/renderers.md @@ -6,6 +6,7 @@ amis 页面是通过 JSON 配置出来的,是由一个一个渲染模型组成 ## 集合 +- [Definitions](./renderers/Definitions.md): 建立当前页面公共的配置项 - [Page](./renderers/Page.md): JSON 配置最外层的 Page 渲染器 - [Form](./renderers/Form/Form.md): 表单渲染器 - [FormItem](./renderers/Form/FormItem.md): Form 中主要是由各种 FormItem 组成 diff --git a/docs/renderers/CRUD.md b/docs/renderers/CRUD.md index 848bc641..d36a3cb5 100644 --- a/docs/renderers/CRUD.md +++ b/docs/renderers/CRUD.md @@ -6,12 +6,14 @@ CRUD 支持三种模式:`table`、`cards`、`list`,默认为 `table`。 | 属性名 | 类型 | 默认值 | 说明 | | ------------------------------ | ------------------------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| type | `string` | | `"Action.md"` 指定为 CRUD 渲染器 | +| type | `string` | | `type` 指定为 CRUD 渲染器 | | mode | `string` | `"table"` | `"table" 、 "cards" 或者 "list"` | | title | `string` | `""` | 可设置成空,当设置成空时,没有标题栏 | | className | `string` | | 表格外层 Dom 的类名 | | api | [Api](./Types.md#Api) | | CRUD 用来获取列表数据的 api。 | -| filter | [Form](./Form/Form.md) | | 设置过滤器,当该表单提交后,会把数据带给当前 [Action](./Action.md) 刷新列表。 | +| loadDataOnce | `boolean` | | 是否一次性加载所有数据(前端分页) | +| source | `string` | | 数据映射接口返回某字段的值,不设置会默认把接口返回的`items`或者`rows`填充进`mode`区域 | +| filter | [Form](./Form/Form.md) | | 设置过滤器,当该表单提交后,会把数据带给当前 `mode` 刷新列表。 | | filterTogglable | `boolean` | `false` | 是否可显隐过滤器 | | filterDefaultVisible | `boolean` | `true` | 设置过滤器默认是否可见。 | | initFetch | `boolean` | `true` | 是否初始化的时候拉取数据, 只针对有 filter 的情况, 没有 filter 初始都会拉取数据 | diff --git a/docs/renderers/Definitions.md b/docs/renderers/Definitions.md new file mode 100644 index 00000000..85c6d348 --- /dev/null +++ b/docs/renderers/Definitions.md @@ -0,0 +1,98 @@ +## Definitions + +`Definitions`建立当前页面公共的配置项,在其他组件中可以通过`$refs`来引用当前配置项中的内容 + +```schema:height="600" +{ + "definitions": { + "aa": { + "type": "text", + "name": "jack", + "value": "refs value", + "remark": "通过\\$refs引入的组件" + }, + "bb": { + "type": "combo", + "multiple": true, + "multiLine": true, + "remark": "combo中的子项引入自身,实现嵌套的效果", + "controls": [ + { + "label": "combo 1", + "type": "text", + "name": "key" + }, + { + "label": "combo 2", + "name": "value", + "$refs": "aa", + "remark": "definitions 中可以引用 definitions 中其他的属性" + }, + { + "name": "children", + "label": "children", + "$refs": "bb" + } + ] + } + }, + "type": "page", + "title": "引用", + "body": [ + { + "type": "form", + "api": "api/xxx", + "actions": [], + "controls": [ + { + "label": "text2", + "$refs": "aa", + "name": "refs1" + }, + { + "label": "combo", + "$refs": "bb", + "name": "refs2" + } + ] + }, + { + "type": "form", + "api": "api/xxx", + "actions": [], + "controls": [ + { + "label": "select", + "$refs": "aa", + "name": "select", + "type": "select", + "value": 1, + "options": [ + { + "label": "Option A", + "value": 1 + }, + { + "label": "Option B", + "value": 2 + } + ], + "remark": "原属性会覆盖引用中的属性" + }, + { + "label": "radios", + "$refs": "bb", + "type": "radios", + "name": "radios", + "value": "Option A", + "options": [ + "Option A", + "Option B" + ], + "remark": "原属性会覆盖引用中的属性" + } + ] + } + ] +} +``` \ No newline at end of file diff --git a/docs/renderers/Table.md b/docs/renderers/Table.md index d2929a41..6f19c1e2 100644 --- a/docs/renderers/Table.md +++ b/docs/renderers/Table.md @@ -4,18 +4,19 @@ | 属性名 | 类型 | 默认值 | 说明 | | ---------------- | ----------------------------- | ------------------------- | ------------------------------------------------------- | -| type | `string` | | `"table"` 指定为 table 渲染器 | +| type | `string` | | `"type"` 指定为 table 渲染器 | | title | `string` | | 标题 | | source | `string` | `${items}` | 数据源, 绑定当前环境变量 | | affixHeader | `boolean` | `true` | 是否固定表头 | | columnsTogglable | `auto` 或者 `boolean` | `auto` | 展示列显示开关, 自动即:列数量大于或等于 5 个时自动开启 | -| placeholder | string | ‘暂无数据’ | 当没数据的时候的文字提示 | +| placeholder | string | `暂无数据` | 当没数据的时候的文字提示 | | className | `string` | `panel-default` | 外层 CSS 类名 | | tableClassName | `string` | `table-db table-striped` | 表格 CSS 类名 | | headerClassName | `string` | `Action.md-table-header` | 顶部外层 CSS 类名 | | footerClassName | `string` | `Action.md-table-footer` | 底部外层 CSS 类名 | | toolbarClassName | `string` | `Action.md-table-toolbar` | 工具栏 CSS 类名 | -| columns | Array of [Column](./Column.md) | | 用来设置列信息 | +| columns | Array of [Column](./Column.md)| | 用来设置列信息 | +| combineNum | `number` | | 自动合并单元格 | ```schema:height="700" scope="body" { diff --git a/examples/components/App.jsx b/examples/components/App.jsx index 5ea4563b..b75843e0 100644 --- a/examples/components/App.jsx +++ b/examples/components/App.jsx @@ -33,6 +33,7 @@ import PickerFormSchema from './Form/Picker'; import FormulaFormSchema from './Form/Formula'; import CustomFormSchema from './Form/Custom'; import FormLayoutTestSchema from './Form/layoutTest'; +import Definitions from './Form/Definitions'; import Docs from './Doc'; import TableCrudSchema from './CRUD/Table'; @@ -238,6 +239,12 @@ const navigations = [ component: makeSchemaRenderer(FormulaFormSchema) }, + { + label: '引用', + path: 'form/definitions', + component: makeSchemaRenderer(Definitions) + } + // { // label: '布局测试', // path: 'form/layout-test', diff --git a/examples/components/Doc.jsx b/examples/components/Doc.jsx index 469e3c27..82c376f3 100644 --- a/examples/components/Doc.jsx +++ b/examples/components/Doc.jsx @@ -39,6 +39,14 @@ export default { }), }, + { + label: 'Definitions', + path: '/docs/renderers/Definitions', + getComponent: (location, cb) => require(['../../docs/renderers/Definitions.md'], (doc) => { + cb(null, makeMarkdownRenderer(doc)); + }), + }, + { label: 'Form', path: '/docs/renderers/Form/Form', diff --git a/examples/components/Form/Definitions.jsx b/examples/components/Form/Definitions.jsx new file mode 100644 index 00000000..fde5e125 --- /dev/null +++ b/examples/components/Form/Definitions.jsx @@ -0,0 +1,93 @@ +export default { + $schema: "https://houtai.baidu.com/v2/schemas/page.json#", + definitions: { + aa: { + type: 'text', + name: 'jack', + value: 'refs value', + remark: '通过\\$refs引入的组件' + }, + bb: { + type: 'combo', + multiple: true, + multiLine: true, + remark: 'combo中的子项引入自身,实现嵌套的效果', + controls: [ + { + label: 'combo 1', + type: 'text', + name: 'key' + }, + { + label: 'combo 2', + name: 'value', + $refs: 'aa', + remark: 'definitions 中可以引用 definitions 中其他的属性' + }, + { + name: 'children', + label: 'children', + $refs: 'bb' + } + ] + } + }, + type: 'page', + title: '引用', + body: [ + { + type: 'form', + api: 'api/xxx', + actions: [], + controls: [ + { + label: 'text2', + $refs: 'aa', + name: 'refs1' + }, + { + label: 'combo', + $refs: 'bb', + name: 'refs2' + } + ] + }, + { + type: 'form', + api: 'api/xxx', + actions: [], + controls: [ + { + label: 'select', + $refs: 'aa', + name: 'select', + type: 'select', + value: 1, + options: [ + { + label: 'Option A', + value: 1 + }, + { + label: 'Option B', + value: 2 + } + ], + remark: '原属性会覆盖引用中的属性' + }, + { + label: 'radios', + $refs: 'bb', + type: 'radios', + name: 'radios', + value: 'Option A', + options: [ + 'Option A', + 'Option B', + ], + remark: '原属性会覆盖引用中的属性' + } + ] + } + ] +}; diff --git a/src/factory.tsx b/src/factory.tsx index 5138c478..810ddeec 100644 --- a/src/factory.tsx +++ b/src/factory.tsx @@ -273,6 +273,18 @@ export interface RootRendererProps { const RootStoreContext = React.createContext(undefined as any); export class RootRenderer extends React.Component { + state = { + error: null, + errorInfo: null + } + + componentDidCatch(error:any, errorInfo:any) { + this.setState({ + error: error, + errorInfo: errorInfo + }); + } + @autobind resolveDefinitions(name:string) { const definitions = (this.props.schema as Schema).definitions; @@ -283,6 +295,10 @@ export class RootRenderer extends React.Component { } render() { + const {error, errorInfo} = this.state; + if (errorInfo) { + return errorRenderer(error, errorInfo); + } const { schema, rootStore, @@ -404,6 +420,14 @@ class SchemaRenderer extends React.Component { delete schema.$refs; path = path.replace(/(?!.*\/).*/, schema.type); } + // value 会提前从 control 中获取到,所有需要把control中的属性也补充完整 + if (schema.control && schema.control.$refs) { + schema.control = { + ...props.resolveDefinitions(schema.control.$refs), + ...schema.control + } + delete schema.control.$refs; + } this.renderer = rendererResolver(path, schema, props); return schema; } @@ -453,7 +477,7 @@ class SchemaRenderer extends React.Component { ...rest } = this.props; - if (schema.$ref) { + if (schema.$refs) { schema = this.resolveRenderer(this.props); } @@ -698,6 +722,15 @@ function loadRenderer(schema:Schema, path:string) { ); } +function errorRenderer(error:any, errorInfo:any) { + return ( + +

{error && error.toString()}

+
{errorInfo.componentStack}
+
+ ) +} + const defaultOptions:RenderOptions = { session: 'global', affixOffsetTop: 50,