This commit is contained in:
chenos 2021-07-23 00:19:08 +08:00
parent 8d298de6fe
commit 07fa09e412
12 changed files with 1072 additions and 598 deletions

View File

@ -20,6 +20,7 @@ import cls from 'classnames';
import { MenuOutlined } from '@ant-design/icons';
import { FormLayout } from '@formily/antd';
import IconPicker from '../../components/icon-picker';
import { useSchemaComponent } from '../../components/schema-renderer';
export const Action: any = observer((props: any) => {
const { useAction = useDefaultAction, icon, ...others } = props;
@ -188,6 +189,7 @@ Action.Dropdown = observer((props: any) => {
const { buttonProps = {}, ...others } = props;
const { schema } = useDesignable();
const componentProps = schema.parent['x-component-props'] || {};
const icon = buttonProps.icon || componentProps['icon'];
return (
<Dropdown
trigger={['click']}
@ -198,7 +200,13 @@ Action.Dropdown = observer((props: any) => {
</Menu>
}
>
<Button {...buttonProps} {...componentProps}>{schema.title || schema.parent.title}</Button>
<Button
{...buttonProps}
{...componentProps}
icon={<IconPicker type={icon} />}
>
{schema.title || schema.parent.title}
</Button>
</Dropdown>
);
});
@ -212,6 +220,10 @@ Action.Popover = observer((props) => {
console.log('Action.Popover', schema, fieldSchema);
const componentProps =
(schema.parent && schema.parent['x-component-props']) || {};
const designableBarComponent = schema.parent
? schema.parent['x-designable-bar']
: null;
const DesignableBar = useSchemaComponent(designableBarComponent);
return (
<Popover
visible={visible}
@ -248,7 +260,10 @@ Action.Popover = observer((props) => {
</div>
}
>
<Button {...componentProps}>{schema.parent.title || schema.title}</Button>
<Button {...componentProps}>
{schema.parent.title || schema.title}
{DesignableBar && <DesignableBar />}
</Button>
</Popover>
);
});

View File

@ -16,6 +16,7 @@ import {
RecursionField,
Schema,
ISchema,
useForm,
} from '@formily/react';
import {
Menu,
@ -58,9 +59,10 @@ import {
import { uid } from '@formily/shared';
import { useRequest } from 'ahooks';
import { SchemaField } from '../../components/schema-renderer';
import { useFormContext } from '../form';
import { useResourceContext } from '../';
import { cloneDeep } from 'lodash';
import { options } from '../database-field/interfaces';
import { useDisplayFieldsContext } from '../form';
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
@ -126,211 +128,111 @@ function generateCardItemSchema(component) {
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
],
'x-component-props': {
rowKey: 'key',
dragSort: true,
showIndex: true,
},
properties: {
[`action_bar_${uid()}`]: {
[uid()]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-designable-bar': 'Table.ActionBar.DesignableBar',
properties: {
action1: {
[uid()]: {
type: 'void',
name: 'action1',
title: '筛选',
'x-component': 'Action',
properties: {
popover1: {
type: 'void',
title: '弹窗标题',
'x-decorator': 'Form',
'x-component': 'Action.Popover',
'x-component-props': {},
properties: {
filter: {
name: 'filter',
type: 'object',
'x-component': 'Filter',
properties: {
column1: {
type: 'void',
title: '字段1',
'x-component': 'Filter.Column',
'x-component': 'Table.Filter',
'x-designable-bar': 'Table.Filter.DesignableBar',
'x-component-props': {
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
},
properties: {
field1: {
type: 'string',
'x-component': 'Input',
fieldNames: [],
},
},
},
column2: {
type: 'void',
title: '字段2',
'x-component': 'Filter.Column',
'x-component-props': {
operations: [
{ label: '大于', value: 'gt' },
{ label: '小于', value: 'lt' },
{
label: '非空',
value: 'notNull',
noValue: true,
},
],
},
properties: {
field1: {
type: 'number',
'x-component': 'InputNumber',
},
},
},
},
},
},
},
},
},
action2: {
[uid()]: {
type: 'void',
name: 'action1',
title: '新增',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
modal: {
type: 'void',
title: '抽屉标题',
title: '新增数据',
'x-decorator': 'Form',
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: {
field1: {
type: 'string',
title: '字段1',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
field2: {
type: 'string',
title: '字段2',
'x-decorator': 'FormItem',
'x-component': 'Input',
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
},
action3: {
},
[uid()]: {
type: 'void',
name: 'action1',
title: '删除',
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
},
},
[`a_${uid()}`]: {
[uid()]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-component-props': {
align: 'bottom',
},
properties: {
pagination: {
[uid()]: {
type: 'void',
'x-component': 'Table.Pagination',
'x-component-props': {},
},
},
},
[`column_${uid()}`]: {
[uid()]: {
type: 'void',
title: '排序',
'x-component': 'Table.Column',
properties: {
sort: {
type: 'void',
'x-component': 'Table.SortHandle',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '序号',
'x-component': 'Table.Column',
properties: {
index: {
type: 'void',
'x-component': 'Table.Index',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
'x-component-props': {
// title: 'z1',
className: 'nb-table-operation',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field1: {
type: 'string',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
[uid()]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-designable-bar': 'Table.Column.DesignableBar',
'x-component': 'Action.Dropdown',
'x-component-props': {
buttonProps: {
icon: 'EllipsisOutlined',
},
},
properties: {
field2: {
type: 'string',
// title: '字段2',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
[uid()]: {
type: 'void',
title: '操作',
'x-component': 'Table.Column',
properties: {
title: '操作 1',
'x-component': 'Menu.Action',
'x-component-props': {
style: {
minWidth: 150,
},
disabled: true,
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Action',
'x-component': 'Menu.Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
@ -385,7 +287,7 @@ function generateCardItemSchema(component) {
type: 'void',
name: 'action1',
title: '修改',
'x-component': 'Action',
'x-component': 'Menu.Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
@ -419,7 +321,7 @@ function generateCardItemSchema(component) {
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Action',
'x-component': 'Menu.Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
@ -428,6 +330,50 @@ function generateCardItemSchema(component) {
},
},
},
[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-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[uid()]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-component-props': {
// title: 'z1',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field2: {
type: 'string',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
Form: {
type: 'void',
name: uid(),
@ -509,6 +455,7 @@ AddNew.CardItem = observer((props: any) => {
console.log({ collections });
return (
<Dropdown
trigger={['click']}
overlayStyle={{
minWidth: 200,
}}
@ -520,7 +467,7 @@ AddNew.CardItem = observer((props: any) => {
<Menu
onClick={async (info) => {
let data: ISchema;
let collectionName = null;
let resourceName = null;
if (['addNewTable', 'addNewForm'].includes(info.key)) {
const values = await FormDialog(`新建数据表`, () => {
return (
@ -539,12 +486,12 @@ AddNew.CardItem = observer((props: any) => {
data = generateCardItemSchema(
info.key === 'addNewTable' ? 'Table' : 'Form',
);
collectionName = values.name;
resourceName = values.name;
} else if (info.key !== 'Markdown') {
const keys = info.key.split('.');
const component = keys.shift();
const tableName = keys.join('.');
collectionName = tableName;
resourceName = tableName;
data = generateCardItemSchema(component);
console.log('info.keyPath', component, tableName);
} else {
@ -553,9 +500,9 @@ AddNew.CardItem = observer((props: any) => {
if (schema['key']) {
data['key'] = uid();
}
if (collectionName) {
if (resourceName) {
data['x-component-props'] = data['x-component-props'] || {};
data['x-component-props']['collectionName'] = collectionName;
data['x-component-props']['resourceName'] = resourceName;
}
if (isGridBlock(schema)) {
path.pop();
@ -595,8 +542,8 @@ AddNew.CardItem = observer((props: any) => {
<Menu.Divider></Menu.Divider>
<Menu.Item key={'addNewForm'}></Menu.Item>
</Menu.SubMenu>
<Menu.Item key={'Markdown'}> Markdown</Menu.Item>
<Menu.SubMenu key={'Ref'} title={'引用模板'}>
<Menu.Item key={'Markdown'}></Menu.Item>
<Menu.SubMenu disabled key={'Ref'} title={'引用模板'}>
<Menu.ItemGroup key={'form-select'} title={'选择模板'}>
<Menu.Item key={'Ref.name1'}>1</Menu.Item>
</Menu.ItemGroup>
@ -684,24 +631,67 @@ AddNew.BlockItem = observer((props: any) => {
);
});
function FieldSwitch({ field, onChange, defaultChecked }) {
const [checked, setChecked] = useState(defaultChecked);
const form = useForm();
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
onClick={(e) => {
setChecked((value) => {
onChange && onChange(!value);
return !value;
});
console.log('FieldSwitch', form);
}}
>
<span>{field?.uiSchema?.title || '未命名'}</span>
<Switch
onChange={(checked, e) => {
e.stopPropagation();
setChecked(checked);
onChange && onChange(checked);
}}
size={'small'}
checked={checked}
/>
</div>
);
}
AddNew.FormItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const { collection = {}, refresh } = useFormContext();
const { resource = {}, refresh } = useResourceContext();
const [visible, setVisible] = useState(false);
const { displayFields = [] } = useDisplayFieldsContext();
console.log({ displayFields });
return (
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
overlayStyle={{
minWidth: 200,
}}
placement={'bottomCenter'}
overlay={
<Menu>
<Menu.ItemGroup title={`选择「${collection.title}」里已有字段`}>
{collection?.fields?.map((field) => (
<Menu.Item
key={field.key}
onClick={async () => {
<Menu.ItemGroup title={`要展示的字段`}>
{resource?.fields?.map((field) => (
<Menu.Item key={field.key}>
<FieldSwitch
field={field}
defaultChecked={displayFields.includes(field.name)}
onChange={async (checked) => {
if (!checked) {
return;
}
let data: ISchema = {
key: uid(),
type: 'void',
@ -731,8 +721,7 @@ AddNew.FormItem = observer((props: any) => {
await createSchema(s);
}
}}
>
{field?.uiSchema?.title || '未命名'}
/>
</Menu.Item>
))}
</Menu.ItemGroup>
@ -745,6 +734,7 @@ AddNew.FormItem = observer((props: any) => {
style={{ minWidth: 150 }}
key={item.name}
onClick={async () => {
setVisible(false);
const values = await FormDialog(`新增字段`, () => {
return (
<FormLayout layout={'vertical'}>
@ -758,7 +748,7 @@ AddNew.FormItem = observer((props: any) => {
name: `f_${uid()}`,
},
});
await createCollectionField(collection.name, values);
await createCollectionField(resource.name, values);
await refresh();
let data: ISchema = cloneDeep(values.uiSchema);
data['name'] = values.name;
@ -790,13 +780,13 @@ AddNew.FormItem = observer((props: any) => {
</Menu.ItemGroup>
))}
</Menu.SubMenu>
<Menu.Divider />
{/* <Menu.Divider /> */}
<Menu.Item
onClick={async () => {
let data: ISchema = {
key: uid(),
type: 'string',
default: '这是一段演示文字',
default: '这是一段演示文字**支持使用 Markdown 语法**',
'x-designable-bar': 'Markdown.FormItemDesignableBar',
'x-decorator': 'FormItem',
'x-read-pretty': true,
@ -823,6 +813,7 @@ AddNew.FormItem = observer((props: any) => {
}
await createSchema(s);
}
setVisible(false);
}}
>

View File

@ -142,4 +142,8 @@ export const select: ISchema = {
'x-component': 'Checkbox',
},
},
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
};

View File

@ -51,4 +51,8 @@ export const string: ISchema = {
'x-component': 'Checkbox',
},
},
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
};

View File

@ -50,4 +50,8 @@ export const textarea: ISchema = {
'x-component': 'Checkbox',
},
},
operations: [
{ label: '等于', value: 'eq' },
{ label: '不等于', value: 'ne' },
],
};

View File

@ -18,6 +18,8 @@ import {
useDesignable,
removeSchema,
useCollectionContext,
useResourceContext,
ResourceContextProvider,
} from '../';
import get from 'lodash/get';
import { Button, Dropdown, Menu, Space } from 'antd';
@ -37,23 +39,25 @@ import { FieldDesignableBar } from './Field.DesignableBar';
import { createContext } from 'react';
import { BlockItem } from '../block-item';
const [FormContextProvider, useFormContext] = constate(({ schema, form }) => {
// const schema = useFieldSchema();
const collectionName = schema['x-component-props']?.['collectionName'];
const { data: collections = [], loading, refresh } = useCollectionContext();
const collection = collectionName
? collections.find((item) => item.name === collectionName)
: {};
console.log({ collection });
return { form, schema, collection, refresh };
const [DisplayFieldsContextProvider, useDisplayFieldsContext] = constate(() => {
const [displayFields, setDisplayFields] = useState([]);
return {
displayFields,
setDisplayFields,
addDisplayField(fieldName) {
setDisplayFields(fields => fields.concat(fieldName));
},
}
});
export { FormContextProvider, useFormContext };
export { DisplayFieldsContextProvider, useDisplayFieldsContext }
export const Form: any = observer((props: any) => {
const {
useValues = () => ({}),
showDefaultButtons = false,
resourceName,
...others
} = props;
const initialValues = useValues();
@ -64,10 +68,8 @@ export const Form: any = observer((props: any) => {
const { schema } = useDesignable();
const path = useSchemaPath();
const scope = useContext(SchemaExpressionScopeContext);
return (
const content = (
<FormProvider form={form}>
{/* @ts-ignore */}
<FormContextProvider schema={schema} form={form}>
{schema['x-decorator'] === 'Form' ? (
<SchemaField
scope={scope}
@ -114,9 +116,18 @@ export const Form: any = observer((props: any) => {
</Button>
</Space>
)}
</FormContextProvider>
</FormProvider>
);
return resourceName ? (
// @ts-ignore
<ResourceContextProvider resourceName={resourceName}>
<DisplayFieldsContextProvider>
{content}
</DisplayFieldsContextProvider>
</ResourceContextProvider>
) : (
content
);
});
export const FormFieldUIDContext = createContext(null);
@ -126,11 +137,17 @@ Form.Field = observer((props: any) => {
const { schema } = useDesignable();
const path = getSchemaPath(schema);
console.log('getSchemaPath', path);
const { collection = {} } = useFormContext();
const { addDisplayField } = useDisplayFieldsContext();
const { resource = {} } = useResourceContext();
useEffect(() => {
if (fieldName) {
addDisplayField(fieldName);
}
}, [fieldName]);
if (!fieldName) {
return null;
}
const field = collection?.fields?.find((item) => item.name === fieldName);
const field = resource?.fields?.find((item) => item.name === fieldName);
if (!field) {
return null;
}

View File

@ -129,3 +129,15 @@ const [SwithDesignableContextProvider, useSwithDesignableContext] = constate(()
});
export { SwithDesignableContextProvider, useSwithDesignableContext };
const [ResourceContextProvider, useResourceContext] = constate(
({ resourceName }) => {
// const schema = useFieldSchema();
const { data: collections = [], loading, refresh } = useCollectionContext();
const resource = collections.find((item) => item.name === resourceName);
console.log({ resource });
return { resource, refresh };
},
);
export { ResourceContextProvider, useResourceContext };

View File

@ -105,9 +105,10 @@ Markdown.DesignableBar = observer((props) => {
key={'update'}
onClick={() => {
field.readPretty = false;
setVisible(false);
}}
>
Markdown
</Menu.Item>
<Menu.Divider />
<Menu.Item

View File

@ -360,6 +360,7 @@ Menu.AddNew = observer((props: any) => {
className={'nb-menu-add-new'}
title={
<Dropdown
// trigger={['click']}
overlay={
<AntdMenu
onClick={async (info) => {

View File

@ -17,34 +17,41 @@ const schema: ISchema = {
name: `table_${uid()}`,
type: 'array',
'x-component': 'Table',
// default: [
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// ],
default: [
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
{ key: uid(), field1: uid(), field2: uid() },
],
'x-component-props': {
rowKey: 'key',
// isRemoteDataSource: true,
dragSort: 'sort',
showIndex: true,
defaultFilter: {},
defaultSort: [],
pagination: {
defaultPageSize: 50,
},
},
properties: {
[`action_bar_${uid()}`]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-designable-bar': 'Table.ActionBar.DesignableBar',
properties: {
action1: {
type: 'void',
name: 'action1',
title: '筛选',
'x-component': 'Action',
'x-designable-bar': 'Table.Filter.DesignableBar',
properties: {
popover1: {
type: 'void',
@ -104,7 +111,7 @@ const schema: ISchema = {
name: 'action1',
title: '新增',
'x-component': 'Action',
'x-designable-bar': 'Action.DesignableBar',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
@ -135,6 +142,7 @@ const schema: ISchema = {
type: 'void',
name: 'action1',
title: '删除',
'x-designable-bar': 'Table.Action.SimpleDesignableBar',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
@ -157,175 +165,43 @@ const schema: ISchema = {
},
},
},
// [`column_${uid()}`]: {
// type: 'void',
// title: '排序',
// 'x-component': 'Table.Column',
// properties: {
// sort: {
// type: 'void',
// 'x-component': 'Table.SortHandle',
// },
// },
// },
// [`column_${uid()}`]: {
// type: 'void',
// title: '序号',
// 'x-component': 'Table.Column',
// properties: {
// index: {
// type: 'void',
// 'x-component': 'Table.Index',
// },
// },
// },
[`column_${uid()}`]: {
type: 'void',
title: '排序',
'x-component': 'Table.Column',
properties: {
sort: {
type: 'void',
'x-component': 'Table.SortHandle',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '序号',
'x-component': 'Table.Column',
properties: {
index: {
type: 'void',
'x-component': 'Table.Index',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段1',
'x-component': 'Table.Column',
'x-component-props': {
// title: 'z1',
className: 'nb-table-operation',
},
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field1: {
type: 'string',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field2: {
type: 'string',
// title: '字段2',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '操作',
'x-component': 'Table.Column',
properties: {
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '查看',
'x-component': 'Action.Modal',
'x-component-props': {},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Tabs',
properties: {
tab1: {
type: 'void',
'x-component': 'Tabs.TabPane',
'x-component-props': {
tab: 'Tab1',
},
properties: {
aaa: {
type: 'string',
title: 'AAA',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
tab2: {
type: 'void',
'x-component': 'Tabs.TabPane',
'x-component-props': {
tab: 'Tab2',
},
properties: {
bbb: {
type: 'string',
title: 'BBB',
'x-decorator': 'FormItem',
required: true,
'x-component': 'Input',
},
},
},
},
},
},
},
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '修改',
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
drawer1: {
type: 'void',
title: '编辑表单',
"x-decorator": 'Form',
'x-decorator-props': {
useValues: '{{ Table.useTableRow }}',
},
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: {
field1: {
type: 'string',
title: '字段1',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
field2: {
type: 'string',
title: '字段2',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
},
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
[uid()]: {
type: 'void',
title: '...',
'x-component': 'Action.Dropdown',
'x-component-props': {
buttonProps: {
icon: 'EllipsisOutlined',
},
},
properties: {
[uid()]: {
type: 'void',
@ -333,6 +209,9 @@ const schema: ISchema = {
'x-component': 'Menu.Action',
'x-component-props': {
useAction,
style: {
minWidth: 150,
},
disabled: true,
},
},
@ -410,7 +289,7 @@ const schema: ISchema = {
drawer1: {
type: 'void',
title: '编辑表单',
"x-decorator": 'Form',
'x-decorator': 'Form',
'x-decorator-props': {
useValues: '{{ Table.useTableRow }}',
},
@ -447,6 +326,46 @@ const schema: ISchema = {
},
},
},
[`column_${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-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
[`column_${uid()}`]: {
type: 'void',
title: '字段2',
'x-component': 'Table.Column',
'x-designable-bar': 'Table.Column.DesignableBar',
properties: {
field2: {
type: 'string',
// title: '字段2',
required: true,
'x-read-pretty': true,
'x-decorator-props': {
feedbackLayout: 'popover',
},
'x-decorator': 'FormItem',
'x-component': 'Input',
},
},
},
},
};
@ -457,7 +376,11 @@ const form = createForm({
export default observer(() => {
return (
<div>
<SchemaRenderer scope={{ Table, useTableCreateAction }} form={form} schema={schema} />
<SchemaRenderer
scope={{ Table, useTableCreateAction }}
form={form}
schema={schema}
/>
</div>
);
});

View File

@ -19,24 +19,40 @@ import {
Table as AntdTable,
Dropdown,
Menu,
Select,
Switch,
} from 'antd';
import { findIndex, get } from 'lodash';
import { findIndex, get, set } from 'lodash';
import constate from 'constate';
import useRequest from '@ahooksjs/use-request';
import { BaseResult } from '@ahooksjs/use-request/lib/types';
import { uid, clone } from '@formily/shared';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import {
EllipsisOutlined,
MenuOutlined,
DragOutlined,
PlusOutlined,
} from '@ant-design/icons';
import {
SortableHandle,
SortableContainer,
SortableElement,
} from 'react-sortable-hoc';
import cls from 'classnames';
import { getSchemaPath, removeSchema, useDesignable, useSchemaPath, VisibleContext } from '../';
import {
getSchemaPath,
removeSchema,
ResourceContextProvider,
useCollectionContext,
useDesignable,
useResourceContext,
useSchemaPath,
VisibleContext,
} from '../';
import './style.less';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import AddNew from '../add-new';
import { isGridRowOrCol } from '../grid';
import { options } from '../database-field/interfaces';
interface TableRowProps {
index: number;
@ -116,7 +132,7 @@ function useTableActionBars() {
}
function useTableColumns(props?: any) {
const { schema } = useDesignable();
const { schema, designable } = useDesignable();
// const schema = useFieldSchema();
const { dataSource } = props || {};
@ -129,8 +145,9 @@ function useTableColumns(props?: any) {
}, []);
}
return findColumns(schema).map((item) => {
const columns = findColumns(schema).map((item) => {
const columnProps = item['x-component-props'] || {};
console.log(item);
return {
title: <RecursionField name={item.name} schema={item} onlyRenderSelf />,
dataIndex: item.name,
@ -150,6 +167,83 @@ function useTableColumns(props?: any) {
},
};
});
// columns.unshift({
// title: '',
// className: 'nb-table-operation',
// dataIndex: 'operation',
// render(value, record, recordIndex) {
// const index = dataSource.indexOf(record);
// return (
// <TableRowContext.Provider
// value={{
// index: index,
// data: record,
// }}
// >
// <Table.Operation />
// </TableRowContext.Provider>
// );
// },
// });
designable &&
columns.push({
title: <AddColumn />,
dataIndex: 'addnew',
});
return columns;
}
function AddColumn() {
const [visible, setVisible] = useState(false);
const { resource = {}, refresh } = useResourceContext();
return (
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
overlay={
<Menu>
<Menu.ItemGroup title={'要展示的字段'}>
{resource?.fields?.map((field) => (
<Menu.Item style={{ minWidth: 150 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span>{field.uiSchema.title}</span>
<Switch size={'small'} defaultChecked />
</div>
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu title={'新增字段'}>
{options.map((option) => (
<Menu.ItemGroup title={option.label}>
{option.children.map((item) => (
<Menu.Item
style={{ minWidth: 150 }}
key={item.name}
onClick={async () => {}}
>
{item.title}
</Menu.Item>
))}
</Menu.ItemGroup>
))}
</Menu.SubMenu>
</Menu>
}
>
<Button size={'small'} type={'dashed'} icon={<PlusOutlined />}></Button>
</Dropdown>
);
}
export function useTableRow() {
@ -245,8 +339,10 @@ const [TableContextProvider, useTableContext] = constate(() => {
const field = useField<Formily.Core.Models.ArrayField>();
const schema = useFieldSchema();
const defaultSelectedRowKeys = useContext(SelectedRowKeysContext);
const [selectedRowKeys, setSelectedRowKeys] = useState(defaultSelectedRowKeys || []);
console.log({ defaultSelectedRowKeys })
const [selectedRowKeys, setSelectedRowKeys] = useState(
defaultSelectedRowKeys || [],
);
console.log({ defaultSelectedRowKeys });
const pagination = usePaginationProps();
const response = useRequest<{
list: any[];
@ -293,8 +389,9 @@ export { TableContextProvider, useTableContext };
export const SelectedRowKeysContext = createContext([]);
const TableContainer = observer((props) => {
const { schema } = useDesignable();
const field = useField<Formily.Core.Models.ArrayField>();
const schema = useFieldSchema();
// const schema = useFieldSchema();
const actionBars = useTableActionBars();
const { loading, data, refresh, selectedRowKeys, setSelectedRowKeys } =
useTableContext();
@ -316,6 +413,8 @@ const TableContainer = observer((props) => {
});
}
};
const dragSort = schema?.['x-component-props']?.['dragSort'];
const showIndex = schema?.['x-component-props']?.['showIndex'];
return (
<div ref={ref} className={'nb-table'}>
{actionBars.top.map((actionBarSchema) => {
@ -340,6 +439,26 @@ const TableContainer = observer((props) => {
rowSelection={{
type: 'checkbox',
selectedRowKeys,
renderCell: (checked, record, _, originNode) => {
const index = dataSource.indexOf(record);
return (
<TableRowContext.Provider
value={{
index: index,
data: record,
}}
>
<div
className={cls('nb-table-selection', { dragSort, showIndex })}
>
{dragSort && <Table.SortHandle />}
{showIndex && <Table.Index />}
{originNode}
{/* <Table.Operation /> */}
</div>
</TableRowContext.Provider>
);
},
onChange: (keys) => {
console.log(keys);
setSelectedRowKeys(keys);
@ -384,13 +503,13 @@ const TableContainer = observer((props) => {
if (!defaultAction) {
return;
}
const el = (e.target as HTMLElement);
if (
!el.classList.contains('ant-table-cell')
) {
const el = e.target as HTMLElement;
if (!el.classList.contains('ant-table-cell')) {
return;
}
const btn = el.parentElement.querySelector<HTMLElement>(`.name-${defaultAction.name}`);
const btn = el.parentElement.querySelector<HTMLElement>(
`.name-${defaultAction.name}`,
);
btn && btn.click();
},
};
@ -415,8 +534,16 @@ const TableContainer = observer((props) => {
);
});
export const Table: any = observer((props) => {
return (
export const Table: any = observer((props: any) => {
const { resourceName } = props;
return resourceName ? (
// @ts-ignore
<ResourceContextProvider resourceName={resourceName}>
<TableContextProvider>
<TableContainer />
</TableContextProvider>
</ResourceContextProvider>
) : (
<TableContextProvider>
<TableContainer />
</TableContextProvider>
@ -477,17 +604,26 @@ Table.Column.DesignableBar = () => {
}}
overlay={
<Menu>
<Menu.Item onClick={(e) => {
<Menu.Item
onClick={(e) => {
const title = uid();
field.title = title;
schema.title = title;
setVisible(false);
}}></Menu.Item>
<Menu.Item onClick={() => {
}}
>
</Menu.Item>
<Menu.Item
onClick={() => {
remove();
console.log('Table.Column.DesignableBar', { schema });
}}></Menu.Item>
<Menu.Item onClick={() => {
}}
>
</Menu.Item>
<Menu.Item
onClick={() => {
const name = uid();
insertAfter({
name: `column_${name}`,
@ -510,8 +646,11 @@ Table.Column.DesignableBar = () => {
'x-component': 'Input',
},
},
})
}}></Menu.Item>
});
}}
>
</Menu.Item>
</Menu>
}
>
@ -522,10 +661,61 @@ Table.Column.DesignableBar = () => {
);
};
Table.ActionBar = observer((props) => {
function AddActionButton() {
const [visible, setVisible] = useState(false);
return (
<div className={'action-bar'}>
<Space>{props.children}</Space>
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
overlay={
<Menu>
<Menu.ItemGroup title={'要展示的操作'}>
{[
{ title: '筛选', key: 'filter' },
{ title: '导出', key: 'export' },
{ title: '新增', key: 'create' },
{ title: '删除', key: 'destroy' },
].map((item) => (
<Menu.Item style={{ minWidth: 150 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span>{item.title}</span>
<Switch size={'small'} defaultChecked />
</div>
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu title={'自定义'}>
<Menu.Item style={{ minWidth: 120 }}></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
<Button type={'dashed'} icon={<PlusOutlined />}></Button>
</Dropdown>
);
}
Table.ActionBar = observer((props: any) => {
const { align = 'top' } = props;
const { schema, designable } = useDesignable();
const designableBar = schema['x-designable-bar'];
console.log('designableBar', designableBar);
return (
<div className={cls('nb-action-bar', `align-${align}`)}>
<Space>
{props.children}
{designable && designableBar && <AddActionButton />}
</Space>
</div>
);
});
@ -533,7 +723,6 @@ Table.ActionBar = observer((props) => {
Table.Pagination = observer((props) => {
const { data, params, run } = useTableContext();
return (
data?.total > params?.pageSize && (
<Pagination
{...props}
defaultCurrent={1}
@ -545,7 +734,6 @@ Table.Pagination = observer((props) => {
run({ page, pageSize });
}}
/>
)
);
});
@ -560,17 +748,16 @@ const SortHandle = SortableHandle((props: any) => {
}) as any;
Table.SortHandle = observer((props) => {
const field = useField<Formily.Core.Models.Field>();
console.log('SortHandle', field.value);
return <SortHandle {...props} />;
});
Table.Operation = observer((props) => {
return <EllipsisOutlined />;
});
Table.Index = observer((props) => {
const index = useTableIndex();
const schema = useFieldSchema();
const field = useField<Formily.Core.Models.Field>();
const path = useSchemaPath();
return <div>{index + 1}</div>;
return <span className={'nb-table-index'}>{index + 1}</span>;
});
Table.Addition = observer((props: any) => {
@ -599,7 +786,7 @@ Table.Addition = observer((props: any) => {
Table.Action = () => null;
Table.Action.DesignableBar = () => {
Table.Action.SimpleDesignableBar = () => {
const field = useField();
const path = useSchemaPath();
const { schema, remove, refresh, insertAfter } = useDesignable();
@ -621,10 +808,125 @@ Table.Action.DesignableBar = () => {
}}
overlay={
<Menu>
<Menu.Item onClick={(e) => {
<Menu.Item
onClick={(e) => {
schema.title = uid();
refresh();
}}></Menu.Item>
}}
>
</Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Divider />
<Menu.Item></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};
Table.Filter = observer((props) => {
return null;
});
Table.Filter.DesignableBar = () => {
const field = useField();
const path = useSchemaPath();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
// console.log('Table.Action.DesignableBar', path, field.address.entire, { schema, field });
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
onClick={(e) => {
schema.title = uid();
refresh();
}}
>
</Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Divider />
<Menu.Item></Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};
Table.Action.DesignableBar = () => {
const field = useField();
const path = useSchemaPath();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
const isPopup = Object.keys(schema.properties || {}).length > 0;
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
// console.log('Table.Action.DesignableBar', path, field.address.entire, { schema, field });
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
onClick={(e) => {
schema.title = uid();
refresh();
}}
>
</Menu.Item>
{isPopup && (
<Menu.Item>
{' '}
<Select
bordered={false}
size={'small'}
defaultValue={'modal'}
>
<Select.Option value={'modal'}></Select.Option>
<Select.Option value={'drawer'}></Select.Option>
<Select.Option value={'window'}></Select.Option>
</Select>{' '}
</Menu.Item>
)}
{inActionBar && <Menu.Item></Menu.Item>}
<Menu.Divider />
<Menu.Item></Menu.Item>
</Menu>
}
>
@ -643,6 +945,8 @@ Table.DesignableBar = observer((props) => {
if (!designable) {
return null;
}
const defaultPageSize =
schema['x-component-props']?.['pagination']?.['defaultPageSize'] || 20;
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -663,12 +967,51 @@ Table.DesignableBar = observer((props) => {
overlay={
<Menu>
<Menu.Item
key={'update'}
key={'showIndex'}
onClick={() => {
field.readPretty = false;
const bool = !field.componentProps.showIndex;
schema['x-component-props']['showIndex'] = bool;
field.componentProps.showIndex = bool;
}}
>
Markdown
{field.componentProps.showIndex ? '隐藏序号' : '显示序号'}
</Menu.Item>
<Menu.Item
key={'dragSort'}
onClick={() => {
const dragSort = field.componentProps.dragSort
? false
: 'sort';
schema['x-component-props']['dragSort'] = dragSort;
field.componentProps.dragSort = dragSort;
}}
>
{field.componentProps.dragSort
? '禁用拖拽排序'
: '启用拖拽排序'}
</Menu.Item>
{!field.componentProps.dragSort && (
<Menu.Item key={'defaultSort'}></Menu.Item>
)}
<Menu.Item key={'defaultFilter'}></Menu.Item>
<Menu.Item key={'defaultPageSize'}>
{' '}
<Select
bordered={false}
size={'small'}
onChange={(value) => {
const componentProps = schema['x-component-props'] || {};
set(componentProps, 'pagination.defaultPageSize', value);
schema['x-component-props'] = componentProps;
refresh();
}}
defaultValue={defaultPageSize}
>
<Select.Option value={20}>20</Select.Option>
<Select.Option value={50}>50</Select.Option>
<Select.Option value={100}>100</Select.Option>
</Select>{' '}
</Menu.Item>
<Menu.Divider />
<Menu.Item

View File

@ -59,3 +59,162 @@
}
}
}
.nb-action-bar {
&.align-top {
margin-bottom: 8px;
}
&.align-bottom {
margin-top: 8px;
}
}
th.nb-table-operation,
td.nb-table-operation {
width: 24px;
text-align: center;
padding: 0 !important;
&::before {
display: none;
}
}
td.nb-table-operation {
.ant-btn {
border: 0;
box-shadow: none;
padding: 6px;
height: auto;
line-height: 16px;
background: none;
&:hover {
background: #f1f1f1;
}
}
}
.nb-table-selection {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
&.dragSort {
.nb-table-index {
padding-left: 20px;
min-width: 38px;
}
}
.nb-table-sort-handle {
margin-right: 8px;
cursor: grab;
}
&.showIndex {
.ant-checkbox-wrapper {
position: absolute;
right: 0;
display: none;
// right: 8px;
}
.nb-table-sort-handle {
display: inline-flex;
align-items: baseline;
position: absolute;
// left: 8px;
cursor: grab;
margin-right: 8px;
z-index: 3;
}
}
.ant-checkbox-wrapper {
// position: absolute;
// right: 0;
// display: none;
// right: 8px;
}
.nb-table-index {
// display: inline-flex;
// align-items: baseline;
position: relative;
z-index: 2;
text-align: center;
// padding-left: 20px;
width: 100%;
// min-width: 38px;
// position: absolute;
// right: 8px;
// height: 16px;
// line-height: 16px;
// text-align: center;
// background-color: #fff;
// min-width: 16px;
// width: 100%;
// z-index: 2;
}
}
.ant-table-selection-column {
text-align: right !important;
&:hover {
.nb-table-index {
opacity: 0;
}
.ant-checkbox-wrapper {
z-index: 4;
display: inline-flex;
}
}
}
.ant-table-row-selected {
.nb-table-index {
opacity: 0;
}
.ant-checkbox-wrapper {
z-index: 4;
display: inline-flex !important;
}
}
.ant-table-cell {
.ant-formily-item-feedback-layout-popover {
margin-bottom: 0;
}
.ant-formily-item-control .ant-formily-item-control-content .ant-formily-item-control-content-component {
min-height: inherit;
line-height: inherit;
}
}
.ant-dropdown-menu-item {
&:hover {
.designable-bar {
display: block;
}
}
.designable-bar {
display: none;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
border-radius: 0;
border: 2px solid #1890ff;
&.active {
display: block;
}
.designable-bar-actions {
position: absolute;
right: 0;
line-height: 1rem;
background-color: #1890ff;
color: #fff;
z-index: 10;
padding: 0 3px;
.anticon {
font-size: 10px;
}
}
}
}