action bar

This commit is contained in:
chenos 2021-07-30 16:10:40 +08:00
parent 7dd9fbf213
commit c3d2c3cd28
5 changed files with 502 additions and 171 deletions

View File

@ -0,0 +1,40 @@
import React, { useEffect, useState } from 'react';
import { Menu, Switch } from 'antd';
export interface SwitchMenuItemProps {
onChange?: any;
title?: any;
checked?: boolean;
}
export function SwitchMenuItem(props: SwitchMenuItemProps) {
const { onChange } = props;
const [checked, setChecked] = useState(props.checked);
useEffect(() => {
setChecked(props.checked);
}, [props.checked]);
return (
<Menu.Item
style={{ minWidth: 150 }}
onClick={async () => {
setChecked((checked) => {
onChange(!checked);
return !checked;
});
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span>{props.title}</span>
<Switch checked={checked} size={'small'} />
</div>
</Menu.Item>
);
}
export default SwitchMenuItem;

View File

@ -69,6 +69,7 @@ import {
useCollectionsContext,
useDisplayedMapContext,
} from '../../constate';
import SwitchMenuItem from '../../components/SwitchMenuItem';
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
@ -149,6 +150,12 @@ function generateCardItemSchema(component) {
properties: {
[uid()]: {
type: 'void',
title: '筛选',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'filter',
},
'x-align': 'left',
'x-component': 'Table.Filter',
'x-designable-bar': 'Table.Filter.DesignableBar',
'x-component-props': {
@ -159,6 +166,11 @@ function generateCardItemSchema(component) {
type: 'void',
name: 'action1',
title: '新增',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'create',
},
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
@ -186,6 +198,11 @@ function generateCardItemSchema(component) {
type: 'void',
name: 'action1',
title: '删除',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'destroy',
},
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
'x-component-props': {
@ -201,6 +218,7 @@ function generateCardItemSchema(component) {
'x-component-props': {
className: 'nb-table-operation',
},
'x-designable-bar': 'Table.OperationDesignableBar',
properties: {
[uid()]: {
type: 'void',
@ -578,32 +596,6 @@ AddNew.CardItem = observer((props: any) => {
);
});
function SwitchField(props) {
const { field, onChange } = props;
const [checked, setChecked] = useState(props.checked);
useEffect(() => {
setChecked(props.checked);
}, [props.checked]);
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
onClick={async () => {
setChecked((checked) => {
onChange(!checked);
return !checked;
});
}}
>
<span>{field.uiSchema.title}</span>
<Switch checked={checked} size={'small'} />
</div>
);
}
AddNew.FormItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild, deepRemove } =
@ -624,9 +616,9 @@ AddNew.FormItem = observer((props: any) => {
<Menu>
<Menu.ItemGroup className={'display-fields'} title={`字段展示`}>
{fields?.map((field) => (
<Menu.Item key={field.key}>
<SwitchField
field={field}
<SwitchMenuItem
key={field.key}
title={field?.uiSchema?.title}
checked={displayed.has(field.name)}
onChange={async (checked) => {
if (!checked) {
@ -674,7 +666,6 @@ AddNew.FormItem = observer((props: any) => {
}
}}
/>
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider />
@ -968,4 +959,16 @@ AddNew.PaneItem = observer((props: any) => {
);
});
AddNew.Displayed = observer((props: any) => {
const { displayName, children } = props;
const displayed = useDisplayedMapContext();
const { schema } = useDesignable();
useEffect(() => {
if (displayName) {
displayed.set(displayName, schema);
}
}, [displayName, schema]);
return children;
});
export default AddNew;

View File

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

View File

@ -1,11 +1,13 @@
import {
FormProvider,
ISchema,
observer,
RecursionField,
Schema,
useField,
useForm,
} from '@formily/react';
import { Pagination, Table as AntdTable } from 'antd';
import { Pagination, Popover, Table as AntdTable } from 'antd';
import { findIndex, forIn, range, set } from 'lodash';
import React, { Fragment, useEffect, useState } from 'react';
import { useContext } from 'react';
@ -22,8 +24,12 @@ import { CSS } from '@dnd-kit/utilities';
import { Select, Dropdown, Menu, Switch, Button, Space } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import './style.less';
import { getSchemaPath } from '../../components/schema-renderer';
import { options } from '../database-field/interfaces';
import {
getSchemaPath,
SchemaField,
SchemaRenderer,
} from '../../components/schema-renderer';
import { interfaces, options } from '../database-field/interfaces';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import AddNew from '../add-new';
import { isGridRowOrCol } from '../grid';
@ -35,6 +41,9 @@ import {
useDisplayedMapContext,
} from '../../constate';
import { useResource as useGeneralResource } from '../../hooks/useResource';
import SwitchMenuItem from '../../components/SwitchMenuItem';
import { useMemo } from 'react';
import { createForm } from '@formily/core';
const SyntheticListenerMapContext = createContext(null);
@ -247,6 +256,7 @@ const useTableColumns = () => {
schema,
props: { rowKey },
} = useTable();
const { designable } = useDesignable();
const { getField } = useCollectionContext();
@ -284,39 +294,17 @@ const useTableColumns = () => {
},
};
})
.concat([
.concat(
designable
? [
{
title: <AddColumn />,
dataIndex: 'addnew',
},
]);
};
function SwitchField(props) {
const { field, onChange } = props;
const [checked, setChecked] = useState(props.checked);
useEffect(() => {
setChecked(props.checked);
}, [props.checked]);
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
onClick={async () => {
setChecked((checked) => {
onChange(!checked);
return !checked;
});
}}
>
<span>{field.uiSchema.title}</span>
<Switch checked={checked} size={'small'} />
</div>
]
: [],
);
}
};
function AddColumn() {
const [visible, setVisible] = useState(false);
@ -333,10 +321,9 @@ function AddColumn() {
<Menu>
<Menu.ItemGroup className={'display-fields'} title={'字段展示'}>
{fields.map((field) => (
<Menu.Item style={{ minWidth: 150 }}>
<SwitchField
<SwitchMenuItem
title={field?.uiSchema?.title}
checked={displayed.has(field.name)}
field={field}
onChange={async (checked) => {
if (checked) {
const data = appendChild({
@ -357,7 +344,6 @@ function AddColumn() {
}
}}
/>
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider />
@ -382,7 +368,9 @@ function AddColumn() {
</Menu>
}
>
<Button size={'small'} type={'dashed'} icon={<PlusOutlined />}></Button>
<Button type={'dashed'} icon={<PlusOutlined />}>
</Button>
</Dropdown>
);
}
@ -582,7 +570,7 @@ const useTotal = () => {
};
Table.Pagination = observer(() => {
const { field, service, pagination, setPagination, props } = useTable();
const { service, pagination, setPagination, props } = useTable();
if (!pagination || Object.keys(pagination).length === 0) {
return null;
}
@ -590,7 +578,7 @@ Table.Pagination = observer(() => {
const total = useTotal();
const { page = 1 } = pagination;
return (
<div>
<div style={{ marginTop: 16 }}>
<Pagination
{...pagination}
showSizeChanger
@ -617,8 +605,90 @@ Table.Pagination = observer(() => {
);
});
function generateActionSchema(type) {
const actions: { [key: string]: ISchema } = {
filter: {
key: uid(),
name: uid(),
type: 'void',
title: '筛选',
'x-align': 'left',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'filter',
},
'x-component': 'Table.Filter',
'x-designable-bar': 'Table.Filter.DesignableBar',
'x-component-props': {
fieldNames: [],
},
},
export: {},
create: {
key: uid(),
type: 'void',
name: uid(),
title: '新增',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'create',
},
'x-component': 'Action',
'x-component-props': {
type: 'primary',
},
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
modal: {
type: 'void',
title: '新增数据',
'x-decorator': 'Form',
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
},
},
destroy: {
key: uid(),
type: 'void',
name: uid(),
title: '删除',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'destroy',
},
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
update: {},
};
return actions[type];
}
function AddActionButton() {
const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext();
const { appendChild, remove } = useDesignable();
const { schema, designable } = useDesignable();
if (!designable) {
return null;
}
return (
<Dropdown
trigger={['click']}
@ -626,25 +696,31 @@ function AddActionButton() {
onVisibleChange={setVisible}
overlay={
<Menu>
<Menu.ItemGroup title={'要展示的操作'}>
<Menu.ItemGroup title={'操作展示'}>
{[
{ title: '筛选', key: 'filter' },
{ title: '导出', key: 'export' },
{ title: '新增', key: 'create' },
{ title: '删除', key: 'destroy' },
{ title: '筛选', name: 'filter' },
// { title: '导出', name: 'export' },
{ title: '新增', name: 'create' },
{ title: '删除', name: 'destroy' },
].map((item) => (
<Menu.Item style={{ minWidth: 150 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
<SwitchMenuItem
key={item.name}
checked={displayed.has(item.name)}
title={item.title}
onChange={async (checked) => {
if (!checked) {
const s = displayed.get(item.name) as Schema;
const path = getSchemaPath(s);
displayed.remove(item.name);
const removed = remove(path);
await removeSchema(removed);
} else {
const s = generateActionSchema(item.name);
const data = appendChild(s);
await createSchema(data);
}
}}
>
<span>{item.title}</span>
<Switch size={'small'} defaultChecked />
</div>
</Menu.Item>
/>
))}
</Menu.ItemGroup>
<Menu.Divider />
@ -656,28 +732,233 @@ function AddActionButton() {
</Menu>
}
>
<Button type={'dashed'} icon={<PlusOutlined />}>
<Button style={{ marginLeft: 8 }} type={'dashed'} icon={<PlusOutlined />}>
</Button>
</Dropdown>
);
}
function Actions(props: any) {
const { align = 'left' } = props;
const { schema, designable } = useDesignable();
return (
<Space>
{schema.mapProperties((s) => {
const currentAlign = s['x-align'] || 'left';
if (currentAlign !== align) {
return null;
}
return <RecursionField name={s.name} schema={s} />;
})}
</Space>
);
}
Table.ActionBar = observer((props: any) => {
const { align = 'top' } = props;
const { schema, designable } = useDesignable();
const designableBar = schema['x-designable-bar'];
console.log('Table.ActionBar', schema);
console.log('Table.ActionBar', { schema });
return (
<DisplayedMapProvider>
<div className={cls('nb-action-bar', `align-${align}`)}>
<Space>
{props.children}
{designable && designableBar && <AddActionButton />}
</Space>
<div>
<Actions align={'left'} />
</div>
<div style={{ marginLeft: 'auto' }}>
<Actions align={'right'} />
</div>
<AddActionButton />
</div>
</DisplayedMapProvider>
);
});
Table.Filter = observer((props: any) => {
const { fieldNames = [] } = props;
const { schema, DesignableBar } = useDesignable();
const form = useMemo(() => createForm(), []);
const { fields = [] } = useCollectionContext();
const fields2properties = (fields: any[]) => {
const properties = {};
fields.forEach((field, index) => {
if (fieldNames?.length && !fieldNames.includes(field.name)) {
return;
}
const fieldOption = interfaces.get(field.interface);
if (!fieldOption.operations) {
return;
}
properties[`column${index}`] = {
type: 'void',
title: field?.uiSchema?.title,
'x-component': 'Filter.Column',
'x-component-props': {
operations: fieldOption.operations,
},
properties: {
[field.name]: {
...field.uiSchema,
title: null,
},
},
};
});
return properties;
};
return (
<Popover
trigger={['click']}
placement={'bottomLeft'}
content={
<div>
<FormProvider form={form}>
<SchemaField
schema={{
type: 'object',
properties: {
filter: {
type: 'object',
'x-component': 'Filter',
properties: fields2properties(fields),
},
},
}}
/>
</FormProvider>
</div>
}
>
<Button>
{schema.title}
<DesignableBar />
</Button>
</Popover>
);
});
Table.Filter.DesignableBar = () => {
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext();
const { fields } = useCollectionContext();
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.ItemGroup title={'筛选字段'}>
{fields
.filter((field) => {
const option = interfaces.get(field.interface);
return option.operations?.length;
})
.map((field) => (
<SwitchMenuItem
title={field?.uiSchema?.title}
checked={true}
onChange={async (checked) => {}}
/>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.Item
onClick={(e) => {
schema.title = uid();
refresh();
}}
>
</Menu.Item>
<Menu.Divider />
<Menu.Item
onClick={async () => {
const displayName =
schema?.['x-decorator-props']?.['displayName'];
const data = remove();
await removeSchema(data);
if (displayName) {
displayed.remove(displayName);
}
setVisible(false);
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};
Table.OperationDesignableBar = () => {
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';
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.ItemGroup title={'操作展示'}>
{[
{ title: '查看', name: 'view' },
{ title: '编辑', name: 'update' },
{ title: '删除', name: 'destroy' },
].map((item) => (
<SwitchMenuItem
key={item.name}
title={item.title}
onChange={async (checked) => {}}
/>
))}
</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>
}
>
<MenuOutlined />
</Dropdown>
</span>
</div>
);
};
Table.Action = () => null;
Table.Action.DesignableBar = () => {
@ -685,7 +966,7 @@ Table.Action.DesignableBar = () => {
const [visible, setVisible] = useState(false);
const isPopup = Object.keys(schema.properties || {}).length > 0;
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
const displayed = useDisplayedMapContext();
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -725,17 +1006,27 @@ Table.Action.DesignableBar = () => {
</Menu.Item>
)}
{inActionBar ? (
<Menu.Item></Menu.Item>
) : (
{!inActionBar && (
<Menu.Item>
&nbsp;&nbsp;
<Switch size={'small'} defaultChecked />
</Menu.Item>
)}
<Menu.Divider />
<Menu.Item></Menu.Item>
<Menu.Item
onClick={async () => {
const displayName =
schema?.['x-decorator-props']?.['displayName'];
const data = remove();
await removeSchema(data);
if (displayName) {
displayed.remove(displayName);
}
setVisible(false);
}}
>
</Menu.Item>
</Menu>
}
>

View File

@ -67,6 +67,7 @@
&.align-bottom {
margin-top: 8px;
}
display: flex;
}
th.nb-table-operation,