improve action & database field & table

This commit is contained in:
chenos 2021-07-06 23:33:02 +08:00
parent b7a2d5d3e5
commit 14160761d3
7 changed files with 902 additions and 347 deletions

View File

@ -40,7 +40,7 @@ import { Menu } from '../menu';
import { Password } from '../password'; import { Password } from '../password';
import { Radio } from '../radio'; import { Radio } from '../radio';
import { Select } from '../select'; import { Select } from '../select';
import { Table } from '../table'; import { Table, useTableCreateAction, useTableDestroyAction } from '../table';
import { Tabs } from '../tabs'; import { Tabs } from '../tabs';
import { TimePicker } from '../time-picker'; import { TimePicker } from '../time-picker';
import { Upload } from '../upload'; import { Upload } from '../upload';
@ -59,6 +59,8 @@ export const scope = {
useLogin, useLogin,
useRegister, useRegister,
useSubmit, useSubmit,
useTableCreateAction,
useTableDestroyAction,
}; };
export const components = { export const components = {

View File

@ -29,7 +29,7 @@ group:
import React from 'react'; import React from 'react';
import { SchemaRenderer, registerScope } from '../'; import { SchemaRenderer, registerScope } from '../';
function useCustomAction() { function useAction() {
return { return {
run() { run() {
alert('这是自定义的操作逻辑'); alert('这是自定义的操作逻辑');
@ -37,17 +37,13 @@ function useCustomAction() {
} }
} }
registerScope({
useCustomAction,
});
const schema = { const schema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '按钮', title: '按钮',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': { 'x-component-props': {
useAction: '{{ useCustomAction }}' useAction,
}, },
}; };
@ -188,6 +184,17 @@ export default () => {
```tsx ```tsx
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '../';
import { useVisibleContext } from './';
function useAction() {
const { visible, setVisible } = useVisibleContext();
return {
async run() {
console.log({ visible })
setVisible(false);
}
}
}
const schema = { const schema = {
type: 'void', type: 'void',
@ -201,6 +208,40 @@ const schema = {
'x-component': 'Action.Modal', 'x-component': 'Action.Modal',
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
close: {
type: 'void',
name: 'action1',
title: '关闭',
'x-component': 'Action',
'x-component-props': {
useAction,
},
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Modal',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
}
}, },
}, },
}, },
@ -218,11 +259,16 @@ import React, { useRef } from 'react';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '../';
import { ActionContext } from './'; import { ActionContext } from './';
export default () => {
const containerRef = useRef();
const schema = { const schema = {
type: 'void', type: 'void',
name: 'action1', name: 'action1',
title: '按钮', title: '按钮',
'x-component': 'Action', 'x-component': 'Action',
'x-component-props': {
containerRef
},
properties: { properties: {
container1: { container1: {
type: 'void', type: 'void',
@ -240,74 +286,10 @@ const schema = {
}, },
}, },
}; };
export default () => {
const ref = useRef();
console.log('containerRef2222', ref)
return ( return (
<div> <div>
<ActionContext.Provider value={{
containerRef: ref,
}}>
<SchemaRenderer schema={schema} /> <SchemaRenderer schema={schema} />
</ActionContext.Provider>
<div style={{padding: '8px 0'}}>目标容器:</div>
<div ref={ref} id={'container'} style={{ border: '1px dashed #ebedf1', background: '#fafafa', padding: 24 }}/>
</div> </div>
) )
} }
``` ```
## Action.DesignableBar - 按钮配置操作栏
```tsx
import React from 'react';
import { SchemaRenderer } from '../';
const schema = {
type: 'void',
name: 'action1',
title: '按钮',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
action2: {
type: 'void',
title: '打开二级抽屉',
// 'x-decorator': 'FormItem',
'x-component': 'Action',
properties: {
drawer1: {
type: 'void',
title: '二级抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
},
},
},
},
}
},
},
},
};
export default () => {
return <SchemaRenderer schema={schema} />
}
```

View File

@ -10,9 +10,8 @@ import {
Schema, Schema,
useField, useField,
} from '@formily/react'; } from '@formily/react';
import { Button, Dropdown, Menu, Popover, Space } from 'antd'; import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd';
import { Link, useHistory, LinkProps } from 'react-router-dom'; import { Link, useHistory, LinkProps } from 'react-router-dom';
import Drawer from '../../components/Drawer';
import { SchemaRenderer, useDesignable } from '../'; import { SchemaRenderer, useDesignable } from '../';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import get from 'lodash/get'; import get from 'lodash/get';
@ -22,6 +21,7 @@ import { useMount } from 'ahooks';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import './style.less'; import './style.less';
import constate from 'constate';
export function useDefaultAction() { export function useDefaultAction() {
return { return {
@ -95,92 +95,81 @@ function useDesignableBar() {
}; };
} }
export const ActionContext = createContext({ containerRef: null }); const [VisibleProvider, useVisibleContext] = constate(
(props: any = {}) => {
const { initialVisible = false, containerRef = null } = props;
const [visible, setVisible] = useState(initialVisible);
export const Action: ActionType = observer((props) => { return { containerRef, visible, setVisible };
},
);
export { VisibleProvider, useVisibleContext };
const BaseAction = observer((props: any) => {
const { useAction = useDefaultAction, ...others } = props; const { useAction = useDefaultAction, ...others } = props;
const { containerRef } = useContext(ActionContext);
const field = useField(); const field = useField();
const schema = useFieldSchema();
const { run } = useAction(); const { run } = useAction();
const { DesignableBar } = useDesignableBar(); const { DesignableBar } = useDesignableBar();
const { setVisible } = useVisibleContext();
const schema = useFieldSchema();
const renderContainer = () => { const renderButton = () => (
let childSchema = null;
if (schema.properties) {
const key = Object.keys(schema.properties).shift();
const current = schema.properties[key];
childSchema = current;
}
if (childSchema && childSchema['x-component'] === 'Action.Container') {
containerRef &&
ReactDOM.render(
<div>
<SchemaRenderer schema={childSchema} onlyRenderProperties />
</div>,
containerRef.current,
);
}
};
useMount(() => {
renderContainer();
});
let childSchema = null;
if (schema.properties) {
const key = Object.keys(schema.properties).shift();
const current = schema.properties[key];
childSchema = current;
}
console.log({ schema });
if (childSchema && childSchema['x-component'] === 'Action.Popover') {
return (
<Popover
trigger={['click']}
title={childSchema.title}
{...childSchema['x-component-props']}
content={
<div>
<SchemaRenderer schema={childSchema} onlyRenderProperties />
</div>
}
>
<Button {...others}>{field.title}</Button>
</Popover>
);
}
return (
<Button <Button
{...others} {...others}
onClick={async () => { onClick={async () => {
if (!childSchema) { setVisible && setVisible(true);
return await run(); await run();
}
if (childSchema['x-component'] === 'Action.Drawer') {
Drawer.open({
title: childSchema.title,
...(childSchema['x-component-props'] || {}),
content: () => {
return (
<div>
<SchemaRenderer schema={childSchema} onlyRenderProperties />
</div>
);
},
});
}
if (childSchema['x-component'] === 'Action.Container') {
renderContainer();
}
}} }}
> >
{field.title} {field.title}
<DesignableBar /> <DesignableBar />
</Button> </Button>
); );
const popover = schema.reduceProperties((items, current) => {
if (current['x-component'] === 'Action.Popover') {
return current;
}
return null;
}, null);
if (popover) {
return (
<RecursionField
schema={
new Schema({
type: 'object',
properties: {
[popover.name]: {
...popover.toJSON(),
'x-button': renderButton(),
},
},
})
}
/>
);
}
return (
<>
{renderButton()}
{props.children}
</>
);
});
export const Action: ActionType = observer((props: any) => {
const schema = useFieldSchema();
if (schema.properties) {
return (
<VisibleProvider containerRef={props.containerRef}>
<BaseAction {...props} />
</VisibleProvider>
);
}
return <BaseAction {...props} />;
}); });
Action.Link = observer((props) => { Action.Link = observer((props) => {
@ -198,21 +187,51 @@ Action.URL = observer((props) => {
); );
}); });
Action.Container = ({ children }) => { Action.Container = observer((props) => {
return children; const { visible, setVisible } = useVisibleContext();
}; return visible && <div>{props.children}</div>;
});
Action.Popover = ({ children }) => { Action.Popover = observer((props) => {
return children; const schema = useFieldSchema();
}; return (
<Popover {...props} content={props.children}>
{schema['x-button']}
</Popover>
);
});
Action.Drawer = ({ children }) => { Action.Drawer = observer((props) => {
return children; const field = useField();
}; const { visible, setVisible } = useVisibleContext();
return (
<Drawer
title={field.title}
width={'50%'}
{...props}
visible={visible}
onClose={() => setVisible(false)}
>
{props.children}
</Drawer>
);
});
Action.Modal = ({ children }) => { Action.Modal = observer((props) => {
return children; const field = useField();
}; const { visible, setVisible } = useVisibleContext();
return (
<Modal
title={field.title}
width={'50%'}
{...props}
visible={visible}
onCancel={() => setVisible(false)}
>
{props.children}
</Modal>
);
});
Action.DesignableBar = () => { Action.DesignableBar = () => {
const field = useField(); const field = useField();

View File

@ -16,6 +16,7 @@ import React from 'react';
import { createForm } from '@formily/core'; import { createForm } from '@formily/core';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '../';
import { observer, connect, useField } from '@formily/react'; import { observer, connect, useField } from '@formily/react';
import Editor from '@monaco-editor/react';
const schema = { const schema = {
type: 'object', type: 'object',
@ -50,73 +51,13 @@ const form = createForm();
export default observer(() => { export default observer(() => {
return ( return (
<div> <div>
{JSON.stringify(form.values, null, 2)}
<SchemaRenderer form={form} schema={schema} /> <SchemaRenderer form={form} schema={schema} />
<Editor
height="200px"
defaultLanguage="json"
value={JSON.stringify(form.values, null, 2)}
/>
</div> </div>
) )
}) })
``` ```
```tsx
import React from 'react';
import { SchemaRenderer } from '../';
const schema = {
type: 'object',
properties: {
array: {
type: 'array',
// title: '数据表字段',
'x-component': 'ArrayCollapse',
'x-component-props': {
accordion: true,
},
// maxItems: 3,
'x-decorator': 'FormItem',
items: {
type: 'object',
'x-component': 'ArrayCollapse.CollapsePanel',
'x-component-props': {
header: '字段',
},
properties: {
index: {
type: 'void',
'x-component': 'ArrayCollapse.Index',
},
input: {
type: 'string',
'x-decorator': 'FormItem',
title: 'Input',
required: true,
'x-component': 'Input',
},
remove: {
type: 'void',
'x-component': 'ArrayCollapse.Remove',
},
moveUp: {
type: 'void',
'x-component': 'ArrayCollapse.MoveUp',
},
moveDown: {
type: 'void',
'x-component': 'ArrayCollapse.MoveDown',
},
},
},
properties: {
addition: {
type: 'void',
title: '添加字段',
'x-component': 'ArrayCollapse.Addition',
},
},
},
}
};
export default () => {
return <SchemaRenderer schema={schema} />
}
```

View File

@ -11,13 +11,7 @@ import {
import { ArrayCollapse } from '@formily/antd'; import { ArrayCollapse } from '@formily/antd';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import '@formily/antd/lib/form-tab/style'; import '@formily/antd/lib/form-tab/style';
import { Collapse, Button } from 'antd'; import { Collapse, Button, Dropdown, Menu } from 'antd';
const text = `
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
`;
const interfaces = new Map<string, ISchema>(); const interfaces = new Map<string, ISchema>();
@ -33,6 +27,21 @@ interfaces.set('string', {
} as ISchema, } as ISchema,
}, },
properties: { properties: {
'ui.title': {
type: 'string',
title: '字段名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '字段标识',
required: true,
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
type: { type: {
type: 'string', type: 'string',
title: '数据类型', title: '数据类型',
@ -44,20 +53,6 @@ interfaces.set('string', {
{ label: 'Text', value: 'text' }, { label: 'Text', value: 'text' },
], ],
}, },
name: {
type: 'string',
title: '字段标识',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'ui.title': {
type: 'string',
title: '字段名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
'ui.required': { 'ui.required': {
type: 'string', type: 'string',
title: '必填', title: '必填',
@ -79,6 +74,21 @@ interfaces.set('textarea', {
} as ISchema, } as ISchema,
}, },
properties: { properties: {
'ui.title': {
type: 'string',
required: true,
title: '字段名称',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
required: true,
title: '字段标识',
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
type: { type: {
type: 'string', type: 'string',
title: '数据类型', title: '数据类型',
@ -90,13 +100,28 @@ interfaces.set('textarea', {
{ label: 'Text', value: 'text' }, { label: 'Text', value: 'text' },
], ],
}, },
name: { 'ui.required': {
type: 'string', type: 'string',
required: true, title: '必填',
title: '字段标识',
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input.TextArea', 'x-component': 'Checkbox',
}, },
},
});
interfaces.set('subTable', {
type: 'object',
default: {
type: 'string',
// name,
ui: {
type: 'string',
// title,
'x-component': 'Select',
enum: [],
} as ISchema,
},
properties: {
'ui.title': { 'ui.title': {
type: 'string', type: 'string',
required: true, required: true,
@ -104,6 +129,170 @@ interfaces.set('textarea', {
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
}, },
name: {
type: 'string',
required: true,
title: '字段标识',
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
type: {
type: 'string',
title: '数据类型',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: [
{ label: 'String', value: 'string' },
{ label: 'Text', value: 'text' },
{ label: 'HasMany', value: 'hasMany' },
],
},
'children': {
type: 'array',
title: '子表格字段',
'x-decorator': 'FormItem',
'x-component': 'DatabaseField',
},
// 'ui.required': {
// type: 'string',
// title: '必填',
// 'x-decorator': 'FormItem',
// 'x-component': 'Checkbox',
// },
},
});
interfaces.set('select', {
type: 'object',
default: {
type: 'string',
// name,
ui: {
type: 'string',
// title,
'x-component': 'Select',
enum: [],
} as ISchema,
},
properties: {
'ui.title': {
type: 'string',
required: true,
title: '字段名称',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
required: true,
title: '字段标识',
'x-disabled': true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
type: {
type: 'string',
title: '数据类型',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: [
{ label: 'String', value: 'string' },
{ label: 'Text', value: 'text' },
],
},
'ui.enum': {
type: 'array',
title: '可选项',
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-component-props': {
pagination: false,
// scroll: { x: '100%' },
},
items: {
type: 'object',
properties: {
column1: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { width: 50, title: '', align: 'center' },
properties: {
sort: {
type: 'void',
'x-component': 'ArrayTable.SortHandle',
},
},
},
column2: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '选项值' },
properties: {
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column3: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '选项' },
properties: {
label: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
column4: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': { title: '颜色' },
properties: {
color: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'ColorSelect',
},
},
},
column5: {
type: 'void',
'x-component': 'ArrayTable.Column',
'x-component-props': {
title: '',
dataIndex: 'operations',
fixed: 'right',
},
properties: {
item: {
type: 'void',
'x-component': 'FormItem',
properties: {
remove: {
type: 'void',
'x-component': 'ArrayTable.Remove',
},
},
},
},
},
},
},
properties: {
add: {
type: 'void',
'x-component': 'ArrayTable.Addition',
title: '添加可选项',
},
},
},
'ui.required': { 'ui.required': {
type: 'string', type: 'string',
title: '必填', title: '必填',
@ -116,65 +305,101 @@ interfaces.set('textarea', {
export const DatabaseField: any = observer((props) => { export const DatabaseField: any = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>(); const field = useField<Formily.Core.Models.ArrayField>();
console.log('DatabaseField', field.componentProps); console.log('DatabaseField', field.componentProps);
const [activeKey, setActiveKey] = useState(null);
return ( return (
<div> <div>
<Collapse <Collapse activeKey={activeKey} onChange={(key) => {
// activeKey={field.componentProps.activeKey} setActiveKey(key);
{...props} }} accordion>
onChange={(key) => {
field.setComponentProps({
activeKey: key,
});
}}
accordion
>
{field.value?.map((item, index) => { {field.value?.map((item, index) => {
const schema = interfaces.get(item.interface); const schema = interfaces.get(item.interface);
console.log({ schema }); console.log({ schema });
return ( return (
<Collapse.Panel <Collapse.Panel
extra={[ // extra={[
<Button onClick={() => field.moveUp(index)}>Up</Button>, // <Button onClick={() => field.moveUp(index)}>Up</Button>,
<Button // <Button
onClick={() => { // onClick={() => {
field.remove(index); // field.remove(index);
field.insert(index, { // field.insert(index, {
...item, // ...item,
interface: 'textarea', // interface: 'textarea',
}); // });
}} // }}
> // >
Update // Update
</Button>, // </Button>,
]} // ]}
header={item.name} header={
<>
{item.ui && item.ui.title}
{' '}
{item.name}
</>
}
key={item.id} key={item.id}
> >
<RecursionField <RecursionField
key={`${item.id}_${index}`} key={`${item.id}_${index}`}
name={index} name={index}
schema={new Schema(schema)} schema={new Schema({
type: 'object',
properties: {
layout: {
type: 'void',
'x-component': 'FormLayout',
'x-component-props': {
layout: 'vertical',
// labelCol: 4,
// wrapperCol: 20,
},
properties: schema.properties,
},
},
})}
/> />
</Collapse.Panel> </Collapse.Panel>
); );
})} })}
</Collapse> </Collapse>
<Dropdown
placement={'bottomCenter'}
overlayStyle={{
minWidth: 200,
}}
overlay={
<Menu
onClick={info => {
const schema = interfaces.get(info.key);
if (!schema) {
return;
}
const data = {
id: uid(),
name: uid(),
interface: info.key,
...schema.default
};
field.push(data);
setActiveKey(data.id);
console.log('info.key', info.key, schema);
}}
>
<Menu.Item key={'string'}></Menu.Item>
<Menu.Item key={'textarea'}></Menu.Item>
<Menu.Item key={'select'}></Menu.Item>
<Menu.Item key={'subTable'}></Menu.Item>
</Menu>
}
>
<Button <Button
block block
type={'dashed'} type={'dashed'}
style={{ marginTop: 10 }} style={{ marginTop: 10 }}
onClick={() => {
field.push({
id: uid(),
type: 'string',
interface: 'string',
name: uid(),
});
console.log('field.value', field.value);
}}
> >
</Button> </Button>
</Dropdown>
</div> </div>
); );
}); });

View File

@ -9,18 +9,26 @@ group:
path: /client/components path: /client/components
--- ---
# Table - 表格 ### Table.View
只做数据展示,不能用作表单字段
```tsx ```tsx
import React from 'react'; import React from 'react';
import { SchemaRenderer } from '../'; import { SchemaRenderer } from '../';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import Editor from '@monaco-editor/react';
import { createForm } from '@formily/core';
import { observer } from '@formily/react';
const schema = { const schema = {
name: `t_${uid()}`, name: `t_${uid()}`,
type: 'array', type: 'void',
'x-component': 'Table', 'x-component': 'Table.View',
default: [{ field1: 'a1', field2: 'b1' }, { field1: 'a2', field2: 'b2' }], // default: [
// { key: uid(), field1: 'a1', field2: 'b1' },
// { key: uid(), field1: 'a2', field2: 'b2' },
// ],
'x-component-props': {}, 'x-component-props': {},
properties: { properties: {
[`a_${uid()}`]: { [`a_${uid()}`]: {
@ -28,17 +36,64 @@ const schema = {
'x-component': 'Table.ActionBar', 'x-component': 'Table.ActionBar',
properties: { properties: {
action1: { action1: {
type: 'object', type: 'void',
title: '按钮1', name: 'action1',
'x-component': 'Table.BulkAction.Popover', title: '筛选',
'x-component': 'Action',
properties: {
popover1: {
type: 'void',
title: '弹窗标题',
'x-component': 'Action.Popover',
'x-component-props': {}, 'x-component-props': {},
properties: {}, properties: {
input: {
type: 'string',
'x-component': 'Input',
}
},
},
},
}, },
action2: { action2: {
type: 'void', type: 'void',
title: '按钮2', name: 'action1',
'x-component': 'Table.BulkAction', title: '新增',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {}, 'x-component-props': {},
properties: {
input: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
action2: {
type: 'void',
name: 'action1',
title: '提交',
'x-component': 'Action',
'x-component-props': {
'useAction': '{{ useTableCreateAction }}'
},
}
},
},
},
},
action3: {
type: 'void',
name: 'action1',
title: '删除',
'x-component': 'Action',
'x-component-props': {
'useAction': '{{ useTableDestroyAction }}'
},
}, },
}, },
}, },
@ -65,8 +120,6 @@ const schema = {
'x-component': 'Table.Column', 'x-component': 'Table.Column',
'x-component-props': { 'x-component-props': {
title: 'z1', title: 'z1',
width: 100,
// DesignableBar: 'Table.Column.DesignableBar',
}, },
'x-designable-bar': 'Table.Column.DesignableBar', 'x-designable-bar': 'Table.Column.DesignableBar',
properties: { properties: {
@ -101,9 +154,172 @@ const schema = {
}, },
}; };
export default () => { const form = createForm();
export default observer(() => {
return ( return (
<SchemaRenderer schema={schema} /> <div>
); <SchemaRenderer form={form} schema={schema} />
}; <Editor
height="200px"
defaultLanguage="json"
value={JSON.stringify(form.values, null, 2)}
/>
</div>
)
});
```
### Table.Field
可用作表单字段
```tsx
import React from 'react';
import { SchemaRenderer } from '../';
import { uid } from '@formily/shared';
import Editor from '@monaco-editor/react';
import { createForm } from '@formily/core';
import { observer } from '@formily/react';
const schema = {
name: `t_${uid()}`,
type: 'array',
'x-component': 'Table.Field',
// default: [
// { key: uid(), field1: 'a1', field2: 'b1' },
// { key: uid(), field1: 'a2', field2: 'b2' },
// ],
'x-component-props': {},
properties: {
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
properties: {
action1: {
type: 'void',
name: 'action1',
title: '筛选',
'x-component': 'Action',
properties: {
popover1: {
type: 'void',
title: '弹窗标题',
'x-component': 'Action.Popover',
'x-component-props': {},
properties: {
input: {
type: 'string',
'x-component': 'Input',
}
},
},
},
},
action2: {
type: 'void',
name: 'action1',
title: '新增',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '抽屉标题',
'x-component': 'Action.Drawer',
'x-component-props': {},
properties: {
action2: {
type: 'void',
name: 'action1',
title: '提交',
'x-component': 'Action',
'x-component-props': {
},
}
},
},
},
},
action3: {
type: 'void',
name: 'action1',
title: '删除',
'x-component': 'Action',
'x-component-props': {
},
},
},
},
[`a_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-component-props': {
align: 'bottom',
},
properties: {
pagination: {
type: 'void',
'x-component': 'Table.Pagination',
'x-component-props': {
defaultCurrent: 1,
total: 50,
},
},
},
},
[`c_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
'x-component-props': {
title: 'z1',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field1: {
type: 'string',
required: true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`c_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
properties: {
field2: {
type: 'string',
title: '字段2',
required: true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
const form = createForm();
export default observer(() => {
return (
<div>
<SchemaRenderer form={form} schema={schema} />
<Editor
height="200px"
defaultLanguage="json"
value={JSON.stringify(form.values, null, 2)}
/>
</div>
)
});
``` ```

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { createContext, useContext, useState } from 'react';
import { import {
useFieldSchema, useFieldSchema,
Schema, Schema,
@ -6,13 +6,18 @@ import {
RecursionField, RecursionField,
useField, useField,
} from '@formily/react'; } from '@formily/react';
import { Table as AntdTable } from 'antd'; import { Button, Pagination, Space, Table as AntdTable } from 'antd';
import { DesignableBar } from './DesignableBar'; import { DesignableBar } from './DesignableBar';
import { ColumnDesignableBar } from './ColumnDesignableBar'; import { ColumnDesignableBar } from './ColumnDesignableBar';
import { uid } from '@formily/shared';
import useRequest from '@ahooksjs/use-request';
import constate from 'constate';
import { SchemaRenderer } from '../';
import { useVisibleContext } from '../action';
function useTableColumns(props) { function useTableColumns(props?: any) {
const schema = useFieldSchema(); const schema = useFieldSchema();
const { dataSource } = props; const { dataSource } = props || {};
function findColumns(schema: Schema): Schema[] { function findColumns(schema: Schema): Schema[] {
return schema.reduceProperties((columns, current) => { return schema.reduceProperties((columns, current) => {
@ -29,14 +34,14 @@ function useTableColumns(props) {
title: item.title, title: item.title,
dataIndex: item.name, dataIndex: item.name,
...columnProps, ...columnProps,
render(value, record) { render(value, record, index) {
console.log({ item }); // console.log({ item });
const index = dataSource.indexOf(record); // const index = dataSource.indexOf(record);
item.mapProperties((s) => { item.mapProperties((s) => {
s['title'] = undefined; s['title'] = undefined;
s['x-read-pretty'] = true; s['x-read-pretty'] = true;
return s; return s;
}) });
return ( return (
<RecursionField schema={item} name={index} onlyRenderProperties /> <RecursionField schema={item} name={index} onlyRenderProperties />
); );
@ -74,35 +79,200 @@ function useTableActionBars() {
return findActionBars(schema); return findActionBars(schema);
} }
function useTableDetails() { function useTable() {
const schema = useFieldSchema(); const field = useField<Formily.Core.Models.ArrayField>();
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
function findDetails(schema: Schema) { const response = useRequest<any>(
return schema.reduceProperties((items, current) => { () => {
if (current['x-component'] === 'Table.Details') { return new Promise((resolve) => {
return [...items, current]; console.log('useRequest');
} setTimeout(() => {
return [...items, ...findDetails(current)]; resolve([
}, []); { key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
]);
}, 500);
});
},
{
onSuccess(data) {
field.setValue(data);
},
},
);
return {
selectedRowKeys,
setSelectedRowKeys,
mutate: response.mutate,
response,
async create() {
response.refresh()
},
async update() {
response.refresh()
},
async destroy() {
response.refresh()
},
} as any;
} }
return findDetails(schema); const [TableContextProvider, useTableContext] = constate(useTable);
export function useTableCreateAction() {
const { visible, setVisible } = useVisibleContext()
const { create } = useTableContext();
return {
run: async () => {
setVisible(false);
await create();
console.log({ visible })
},
} }
}
export function useTableDestroyAction() {
// const { setVisible } = useVisibleContext()
const { destroy } = useTableContext();
return {
run: async () => {
// setVisible(false);
await destroy();
},
}
}
const ArrayTable = (props) => {
const field = useField<Formily.Core.Models.ArrayField>();
// const dataSource = Array.isArray(field.value) ? field.value.slice() : [];
const columns = useTableColumns();
const actionBars = useTableActionBars();
const {
selectedRowKeys,
setSelectedRowKeys,
response = {},
create,
} = useTableContext();
console.log({ response });
const { data = [], loading, mutate } = response as any;
return (
<div>
{actionBars.top.map((actionBarSchema) => {
return (
<RecursionField
onlyRenderProperties
schema={
new Schema({
type: 'object',
properties: {
[actionBarSchema.name]: actionBarSchema,
},
})
}
/>
);
})}
<Button
onClick={() => {
create();
// field.unshift({ key: uid(), field1: uid(), field2: uid() });
// const dataSource = Array.isArray(field.value)
// ? field.value.slice()
// : [];
// mutate(dataSource);
// response.refresh();
// console.log({ selectedRowKeys })
}}
>
</Button>
<AntdTable
rowKey={'key'}
loading={loading}
pagination={false}
columns={columns}
dataSource={data}
rowSelection={{
type: 'checkbox',
selectedRowKeys,
onChange: (keys) => {
setSelectedRowKeys(keys);
},
}}
/>
{actionBars.bottom.map((actionBarSchema) => {
return (
<RecursionField
onlyRenderProperties
schema={
new Schema({
type: 'object',
properties: {
[actionBarSchema.name]: actionBarSchema,
},
})
}
/>
);
})}
</div>
);
};
export const Table: any = observer((props) => { export const Table: any = observer((props) => {
const field = useField<Formily.Core.Models.ArrayField>();
const dataSource = field.value;
const columns = useTableColumns({ dataSource });
const actionBars = useTableActionBars();
const details = useTableDetails();
console.log({ columns, actionBars, details });
return ( return (
<> <TableContextProvider>
<AntdTable columns={columns} dataSource={dataSource} /> <ArrayTable {...props} />
</> </TableContextProvider>
); );
}); });
Table.Column = () => null; Table.Column = () => null;
Table.Column.DesignableBar = ColumnDesignableBar;
Table.DesignableBar = DesignableBar; Table.ActionBar = observer((props) => {
return (
<div className={'action-bar'}>
<Space>{props.children}</Space>
</div>
);
});
Table.Pagination = observer((props) => {
const { response } = useTableContext();
return (
<Pagination
{...props}
onChange={(page, pageSize) => {
response.refresh();
// console.log('useRequest', { page, pageSize });
}}
/>
);
});
Table.View = observer(() => {
const schema = useFieldSchema();
console.log('Table.View', schema);
return (
<SchemaRenderer
schema={{
type: 'object',
properties: {
[schema.name]: {
...schema.toJSON(),
type: 'array',
'x-component': 'Table.Field',
},
},
}}
/>
);
});
Table.Field = observer((props) => {
return (
<TableContextProvider>
<ArrayTable {...props} />
</TableContextProvider>
);
});