优化definitions,增加全局错误异常处理

This commit is contained in:
catchonme 2019-06-21 14:30:37 +08:00
parent cb8dca1b2f
commit 4994d66c6a
10 changed files with 843 additions and 7 deletions

View File

@ -57,6 +57,457 @@ exports[`factory unregistered Renderer 1`] = `
</div>
`;
exports[`factory:definitions 1`] = `
<div>
<div
class="a-Page"
>
<div
class="a-Page-content"
>
<div
class="a-Page-main"
>
<div
class="a-Page-header"
>
<h2
class="a-Page-title"
>
<span
class="a-TplField"
>
引用
</span>
</h2>
</div>
<div
class="a-Page-body"
>
<div
class="a-Panel a-Panel--default a-Panel--form"
style="position: relative;"
>
<div
class="a-Panel-heading"
>
<h3
class="a-Panel-title"
>
<span
class="a-TplField"
>
表单
</span>
</h3>
</div>
<div
class="a-Panel-body"
>
<form
class="a-Form a-Form--normal"
novalidate=""
>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
text2
</span>
</label>
<div
class="a-Form-control a-TextControl"
>
<div
class="a-TextControl-input"
>
<input
autocomplete="off"
name="refs1"
placeholder=""
type="text"
value=""
/>
</div>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
combo
</span>
</label>
<div
class="a-ComboControl a-Form-control"
>
<div
class="a-Combo a-Combo--multi a-Combo--ver"
>
<div
class="a-Combo-items"
>
<div
class="a-Combo-item"
>
<div
class="a-Combo-itemInner"
>
<div
class="a-Form a-Form--normal a-Combo-form"
novalidate=""
>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
combo 1
</span>
</label>
<div
class="a-Form-control a-TextControl"
>
<div
class="a-TextControl-input"
>
<input
autocomplete="off"
name="key"
placeholder=""
type="text"
value=""
/>
</div>
</div>
</div>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
combo 2
</span>
</label>
<div
class="a-Form-control a-TextControl"
>
<div
class="a-TextControl-input"
>
<input
autocomplete="off"
name="value"
placeholder=""
type="text"
value=""
/>
</div>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
children
</span>
</label>
<div
class="a-ComboControl a-Form-control"
>
<div
class="a-Combo a-Combo--multi a-Combo--ver"
>
<div
class="a-Combo-items"
/>
<div
class="a-Combo-toolbar"
>
<button
class="a-Button a-Combo-addBtn"
data-tooltip="新增一条数据"
type="button"
>
<i
class="a-Button-icon fa fa-plus"
/>
<span>
新增
</span>
</button>
</div>
</div>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
</div>
</div>
<div
class="a-Combo-itemToolbar"
>
<a
class="a-Combo-toolbarBtn "
data-position="bottom"
data-tooltip="删除"
>
<i
class="glyphicon glyphicon-remove"
/>
</a>
</div>
</div>
</div>
<div
class="a-Combo-toolbar"
>
<button
class="a-Button a-Combo-addBtn"
data-tooltip="新增一条数据"
type="button"
>
<i
class="a-Button-icon fa fa-plus"
/>
<span>
新增
</span>
</button>
</div>
</div>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
</form>
</div>
<div
class="resize-sensor"
style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
class="resize-sensor-expand"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0px; top: 0px; width: 10px; height: 10px;"
/>
</div>
<div
class="resize-sensor-shrink"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0; top: 0; width: 200%; height: 200%"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`factory:definitions override 1`] = `
<div>
<div
class="a-Page"
>
<div
class="a-Page-content"
>
<div
class="a-Page-main"
>
<div
class="a-Page-header"
>
<h2
class="a-Page-title"
>
<span
class="a-TplField"
>
引用
</span>
</h2>
</div>
<div
class="a-Page-body"
>
<div
class="a-Panel a-Panel--default a-Panel--form"
style="position: relative;"
>
<div
class="a-Panel-heading"
>
<h3
class="a-Panel-title"
>
<span
class="a-TplField"
>
表单
</span>
</h3>
</div>
<div
class="a-Panel-body"
>
<form
class="a-Form a-Form--normal"
novalidate=""
>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
text2
</span>
</label>
<div
class="a-Form-control a-TextControl"
>
<div
class="a-TextControl-input"
>
<input
autocomplete="off"
name="refs1"
placeholder=""
type="text"
value=""
/>
</div>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
<div
class="a-Form-item a-Form-item--normal"
>
<label
class="a-Form-label"
>
<span>
combo
</span>
</label>
<div
class="a-CheckboxesControl a-Form-control"
>
<label
class="a-Checkbox a-Checkbox--checkbox a-Checkbox--full"
>
<input
checked=""
type="checkbox"
/>
<i />
<span>
Option A
</span>
</label>
<label
class="a-Checkbox a-Checkbox--checkbox a-Checkbox--full"
>
<input
type="checkbox"
/>
<i />
<span>
Option B
</span>
</label>
</div>
<div
class="a-Remark a-Form-remark"
>
<i
class="a-Remark-icon fa fa-question-circle"
/>
</div>
</div>
</form>
</div>
<div
class="resize-sensor"
style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
class="resize-sensor-expand"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0px; top: 0px; width: 10px; height: 10px;"
/>
</div>
<div
class="resize-sensor-shrink"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0; top: 0; width: 200%; height: 200%"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`factory:registerRenderer 1`] = `
<div>
<div>

View File

@ -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: '通过<code>\\$refs</code>引入的组件'
},
bb: {
type: 'combo',
multiple: true,
multiLine: true,
remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
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: '通过<code>\\$refs</code>引入的组件'
},
bb: {
type: 'combo',
multiple: true,
multiLine: true,
remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
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();
});

View File

@ -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 组成

View File

@ -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 初始都会拉取数据 |

View File

@ -0,0 +1,98 @@
## Definitions
`Definitions`建立当前页面公共的配置项,在其他组件中可以通过`$refs`来引用当前配置项中的内容
```schema:height="600"
{
"definitions": {
"aa": {
"type": "text",
"name": "jack",
"value": "refs value",
"remark": "通过<code>\\$refs</code>引入的组件"
},
"bb": {
"type": "combo",
"multiple": true,
"multiLine": true,
"remark": "<code>combo</code>中的子项引入自身,实现嵌套的效果",
"controls": [
{
"label": "combo 1",
"type": "text",
"name": "key"
},
{
"label": "combo 2",
"name": "value",
"$refs": "aa",
"remark": "<code>definitions</code> 中可以引用 <code>definitions</code> 中其他的属性"
},
{
"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": "原属性会覆盖引用中的属性"
}
]
}
]
}
```

View File

@ -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"
{

View File

@ -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',

View File

@ -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',

View File

@ -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: '通过<code>\\$refs</code>引入的组件'
},
bb: {
type: 'combo',
multiple: true,
multiLine: true,
remark: '<code>combo</code>中的子项引入自身,实现嵌套的效果',
controls: [
{
label: 'combo 1',
type: 'text',
name: 'key'
},
{
label: 'combo 2',
name: 'value',
$refs: 'aa',
remark: '<code>definitions</code> 中可以引用 <code>definitions</code> 中其他的属性'
},
{
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: '原属性会覆盖引用中的属性'
}
]
}
]
};

View File

@ -273,6 +273,18 @@ export interface RootRendererProps {
const RootStoreContext = React.createContext<IRendererStore>(undefined as any);
export class RootRenderer extends React.Component<RootRendererProps> {
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<RootRendererProps> {
}
render() {
const {error, errorInfo} = this.state;
if (errorInfo) {
return errorRenderer(error, errorInfo);
}
const {
schema,
rootStore,
@ -404,6 +420,14 @@ class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
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<SchemaRendererProps, any> {
...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 (
<Alert level="danger">
<p>{error && error.toString()}</p>
<pre><code>{errorInfo.componentStack}</code></pre>
</Alert>
)
}
const defaultOptions:RenderOptions = {
session: 'global',
affixOffsetTop: 50,