refactor: code splitting of the table component (#121)
* refactor: 表格拆分模块化 * refactor: 表格拆分模块化 * refactor: code splitting of the table component (#120) * missing TableIndex * refactor: 表格拆分模块化 * code format Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
1072e03c5a
commit
12f58effad
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 80,
|
"printWidth": 120,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ".prettierrc",
|
"files": ".prettierrc",
|
||||||
|
@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons';
|
|||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { Resource } from '../../../resource';
|
import { Resource } from '../../../resource';
|
||||||
import { TableRowContext, useTable } from '../../../schemas/table';
|
import { TableRowContext } from '../../../schemas/table';
|
||||||
|
import { useTable } from '../../../schemas/table';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { VisibleContext } from '../../../context';
|
import { VisibleContext } from '../../../context';
|
||||||
import { connect, ISchema, observer, useField } from '@formily/react';
|
import { connect, ISchema, observer, useField } from '@formily/react';
|
||||||
|
@ -8,7 +8,8 @@ import { LockOutlined } from '@ant-design/icons';
|
|||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { uid, isValid } from '@formily/shared';
|
import { uid, isValid } from '@formily/shared';
|
||||||
import { Resource } from '../../../resource';
|
import { Resource } from '../../../resource';
|
||||||
import { TableRowContext, useTable } from '../../../schemas/table';
|
import { TableRowContext } from '../../../schemas/table';
|
||||||
|
import { useTable } from '../../../schemas/table';
|
||||||
import { useRequest } from 'ahooks';
|
import { useRequest } from 'ahooks';
|
||||||
import { VisibleContext } from '../../../context';
|
import { VisibleContext } from '../../../context';
|
||||||
import { connect, observer, useForm } from '@formily/react';
|
import { connect, observer, useForm } from '@formily/react';
|
||||||
|
@ -52,11 +52,8 @@ import { Menu } from '../../schemas/menu';
|
|||||||
import { Password } from '../../schemas/password';
|
import { Password } from '../../schemas/password';
|
||||||
import { Radio } from '../../schemas/radio';
|
import { Radio } from '../../schemas/radio';
|
||||||
import { Select } from '../../schemas/select';
|
import { Select } from '../../schemas/select';
|
||||||
import {
|
import { Table } from '../../schemas/table';
|
||||||
CollectionFieldContext,
|
import { CollectionFieldContext, TableRowContext } from '../../schemas/table';
|
||||||
Table,
|
|
||||||
TableRowContext,
|
|
||||||
} from '../../schemas/table';
|
|
||||||
import { Tabs } from '../../schemas/tabs';
|
import { Tabs } from '../../schemas/tabs';
|
||||||
import { TimePicker } from '../../schemas/time-picker';
|
import { TimePicker } from '../../schemas/time-picker';
|
||||||
import { Upload } from '../../schemas/upload';
|
import { Upload } from '../../schemas/upload';
|
||||||
|
40
packages/client/src/schemas/table/Actions.tsx
Normal file
40
packages/client/src/schemas/table/Actions.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Space } from 'antd';
|
||||||
|
import { RecursionField } from '@formily/react';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { Droppable, SortableItem } from '../../components/Sortable';
|
||||||
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export const Actions = (props: any) => {
|
||||||
|
const { align = 'left' } = props;
|
||||||
|
const { schema, designable } = useDesignable();
|
||||||
|
return (
|
||||||
|
<Droppable
|
||||||
|
id={`${schema.name}-${align}`}
|
||||||
|
className={`action-bar-align-${align}`}
|
||||||
|
data={{ align, path: getSchemaPath(schema) }}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
{schema.mapProperties((s) => {
|
||||||
|
const currentAlign = s['x-align'] || 'left';
|
||||||
|
if (currentAlign !== align) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SortableItem
|
||||||
|
id={s.name}
|
||||||
|
data={{
|
||||||
|
align,
|
||||||
|
draggable: true,
|
||||||
|
title: s.title,
|
||||||
|
path: getSchemaPath(s),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RecursionField name={s.name} schema={s} />
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</Droppable>
|
||||||
|
);
|
||||||
|
};
|
76
packages/client/src/schemas/table/AddActionButton.tsx
Normal file
76
packages/client/src/schemas/table/AddActionButton.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Dropdown, Menu, Button } from 'antd';
|
||||||
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Schema } from '@formily/react';
|
||||||
|
import { useDesignable, ISchema } from '..';
|
||||||
|
import { useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
|
import { generateActionSchema } from './utils';
|
||||||
|
|
||||||
|
export const AddActionButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const { appendChild, remove } = useDesignable();
|
||||||
|
const { schema, designable } = useDesignable();
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
|
||||||
|
if (!designable || !schema['x-designable-bar']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={setVisible}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup title={t('Enable actions')}>
|
||||||
|
{[
|
||||||
|
{ title: t('Filter'), name: 'filter' },
|
||||||
|
{ title: t('Export'), name: 'export' },
|
||||||
|
{ title: t('Add new'), name: 'create' },
|
||||||
|
{ title: t('Delete'), name: 'destroy' },
|
||||||
|
].map((item) => (
|
||||||
|
<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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.SubMenu disabled title={t('Customize')}>
|
||||||
|
<Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
|
||||||
|
<Menu.Item>{t('Popup form')}</Menu.Item>
|
||||||
|
<Menu.Item>{t('Flexible popup')}</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={'designable-btn designable-btn-dash'}
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
type={'dashed'}
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
>
|
||||||
|
{t('Configure actions')}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
232
packages/client/src/schemas/table/AddColumn.tsx
Normal file
232
packages/client/src/schemas/table/AddColumn.tsx
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Dropdown, Menu, Button } from 'antd';
|
||||||
|
import { SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { useDesignable, createCollectionField, ISchema } from '..';
|
||||||
|
import { useCollectionContext, useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import { useTable } from './hooks/useTable';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
import { isAssociation, options } from '../database-field/interfaces';
|
||||||
|
import { getSchemaPath, SchemaField } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export function AddColumn() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { appendChild, remove } = useDesignable();
|
||||||
|
const { loadCollections } = useCollectionsContext();
|
||||||
|
const { collection, fields, refresh } = useCollectionContext();
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const { service } = useTable();
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={setVisible}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup className={'display-fields'} title={t('Display fields')}>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<SwitchMenuItem
|
||||||
|
title={field?.uiSchema?.title}
|
||||||
|
checked={displayed.has(field.name)}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
console.log('SwitchMenuItem.field.name', field.dataType, service.params[0]);
|
||||||
|
const columnSchema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldName: field.name,
|
||||||
|
},
|
||||||
|
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||||
|
};
|
||||||
|
if (field.interface === 'linkTo') {
|
||||||
|
columnSchema.properties = {
|
||||||
|
options: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-component': 'Select.Options.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
useOkAction: '{{ Select.useOkAction }}',
|
||||||
|
},
|
||||||
|
title: "{{t('Select record')}}",
|
||||||
|
properties: {
|
||||||
|
table: {
|
||||||
|
type: 'array',
|
||||||
|
'x-designable-bar': 'Table.DesignableBar',
|
||||||
|
'x-decorator': 'BlockItem',
|
||||||
|
'x-decorator-props': {
|
||||||
|
draggable: false,
|
||||||
|
},
|
||||||
|
'x-component': 'Table',
|
||||||
|
default: [],
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
useRowSelection: '{{ Select.useRowSelection }}',
|
||||||
|
useSelectedRowKeys: '{{ Select.useSelectedRowKeys }}',
|
||||||
|
onSelect: '{{ Select.useSelect() }}',
|
||||||
|
collectionName: field.target,
|
||||||
|
// dragSort: true,
|
||||||
|
// showIndex: true,
|
||||||
|
refreshRequestOnChange: true,
|
||||||
|
pagination: {
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Table.ActionBar',
|
||||||
|
'x-designable-bar': 'Table.ActionBar.DesignableBar',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{t('Filter')}}",
|
||||||
|
'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': {
|
||||||
|
fieldNames: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Select.OptionTag',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{t('View record')}}",
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
bodyStyle: {
|
||||||
|
background: '#f0f2f5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
'x-designable-bar': 'Tabs.DesignableBar',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{t('Details')}}",
|
||||||
|
'x-designable-bar': 'Tabs.TabPane.DesignableBar',
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-component-props': {
|
||||||
|
addNewComponent: 'AddNew.PaneItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const data = appendChild(columnSchema);
|
||||||
|
await createSchema(data);
|
||||||
|
if (isAssociation(field)) {
|
||||||
|
const defaultAppends = service.params[0]?.defaultAppends || [];
|
||||||
|
defaultAppends.push(field.name);
|
||||||
|
await service.run({
|
||||||
|
...service.params[0],
|
||||||
|
defaultAppends,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const s: any = displayed.get(field.name);
|
||||||
|
const p = getSchemaPath(s);
|
||||||
|
const removed = remove(p);
|
||||||
|
await removeSchema(removed);
|
||||||
|
displayed.remove(field.name);
|
||||||
|
if (isAssociation(field)) {
|
||||||
|
const defaultAppends = service.params[0]?.defaultAppends || [];
|
||||||
|
const index = defaultAppends.indexOf(field.name);
|
||||||
|
if (index > -1) {
|
||||||
|
defaultAppends.splice(index, 1);
|
||||||
|
}
|
||||||
|
await service.run({
|
||||||
|
...service.params[0],
|
||||||
|
defaultAppends,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// service.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.SubMenu disabled popupClassName={'add-new-fields-popup'} title={t('Add field')}>
|
||||||
|
{options.map((option) => (
|
||||||
|
<Menu.ItemGroup title={option.label}>
|
||||||
|
{option.children.map((item) => (
|
||||||
|
<Menu.Item
|
||||||
|
style={{ minWidth: 150 }}
|
||||||
|
key={item.name}
|
||||||
|
onClick={async () => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog(t('Add field'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField scope={{ loadCollections }} schema={item} />
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
interface: item.name,
|
||||||
|
...item.default,
|
||||||
|
key: uid(),
|
||||||
|
name: `f_${uid()}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await createCollectionField(collection?.name, values);
|
||||||
|
const data = appendChild({
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldName: values.name,
|
||||||
|
},
|
||||||
|
'x-designable-bar': 'Table.Column.DesignableBar',
|
||||||
|
});
|
||||||
|
await createSchema(data);
|
||||||
|
await refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Menu.Item>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
))}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button type={'dashed'} className={'designable-btn designable-btn-dash'} icon={<SettingOutlined />}>
|
||||||
|
{t('Configure fields')}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
@ -1,24 +1,7 @@
|
|||||||
import React, { useContext, useMemo, useRef, useState } from 'react';
|
import React, { useContext, useMemo, useRef, useState } from 'react';
|
||||||
import { createForm } from '@formily/core';
|
import { createForm } from '@formily/core';
|
||||||
import {
|
import { observer, useField } from '@formily/react';
|
||||||
SchemaOptionsContext,
|
import { useSchemaPath, SchemaField, useDesignable, removeSchema, updateSchema } from '../';
|
||||||
Schema,
|
|
||||||
useFieldSchema,
|
|
||||||
observer,
|
|
||||||
SchemaExpressionScopeContext,
|
|
||||||
FormProvider,
|
|
||||||
ISchema,
|
|
||||||
useField,
|
|
||||||
useForm,
|
|
||||||
RecursionField,
|
|
||||||
} from '@formily/react';
|
|
||||||
import {
|
|
||||||
useSchemaPath,
|
|
||||||
SchemaField,
|
|
||||||
useDesignable,
|
|
||||||
removeSchema,
|
|
||||||
updateSchema,
|
|
||||||
} from '../';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { Button, Dropdown, Menu, Modal, Select, Space } from 'antd';
|
import { Button, Dropdown, Menu, Modal, Select, Space } from 'antd';
|
||||||
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
|
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
|
||||||
@ -45,8 +28,7 @@ export const SimpleDesignableBar = observer((props) => {
|
|||||||
if (!designable) {
|
if (!designable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const defaultPageSize =
|
const defaultPageSize = field?.componentProps?.pagination?.defaultPageSize || 10;
|
||||||
field?.componentProps?.pagination?.defaultPageSize || 10;
|
|
||||||
return (
|
return (
|
||||||
<div className={cls('designable-bar', { active: visible })}>
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
<span
|
<span
|
||||||
@ -69,109 +51,104 @@ export const SimpleDesignableBar = observer((props) => {
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={'defaultFilter'}
|
key={'defaultFilter'}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { defaultFilter } = await FormDialog(
|
const { defaultFilter } = await FormDialog(t('Set the data scope'), () => {
|
||||||
t('Set the data scope'),
|
return (
|
||||||
() => {
|
<FormLayout layout={'vertical'}>
|
||||||
return (
|
<SchemaField
|
||||||
<FormLayout layout={'vertical'}>
|
schema={{
|
||||||
<SchemaField
|
type: 'object',
|
||||||
schema={{
|
properties: {
|
||||||
type: 'object',
|
defaultFilter: {
|
||||||
properties: {
|
type: 'object',
|
||||||
defaultFilter: {
|
'x-component': 'Filter',
|
||||||
type: 'object',
|
properties: {
|
||||||
'x-component': 'Filter',
|
column1: {
|
||||||
properties: {
|
type: 'void',
|
||||||
column1: {
|
title: t('Action type'),
|
||||||
type: 'void',
|
'x-component': 'Filter.Column',
|
||||||
title: t('Action type'),
|
'x-component-props': {
|
||||||
'x-component': 'Filter.Column',
|
operations: [
|
||||||
'x-component-props': {
|
{
|
||||||
operations: [
|
label: '{{t("is")}}',
|
||||||
{
|
value: 'eq',
|
||||||
label: '{{t("is")}}',
|
selected: true,
|
||||||
value: 'eq',
|
schema: {
|
||||||
selected: true,
|
'x-component': 'Select',
|
||||||
schema: {
|
},
|
||||||
'x-component': 'Select',
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("is not")}}',
|
||||||
|
value: 'ne',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("contains")}}',
|
||||||
|
value: 'in',
|
||||||
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'tags',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
label: '{{t("is not")}}',
|
{
|
||||||
value: 'ne',
|
label: '{{t("does not contain")}}',
|
||||||
schema: {
|
value: 'notIn',
|
||||||
'x-component': 'Select',
|
schema: {
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
mode: 'tags',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("is empty")}}',
|
||||||
|
value: '$null',
|
||||||
|
noValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{t("is not empty")}}',
|
||||||
|
value: '$notNull',
|
||||||
|
noValue: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: [
|
||||||
{
|
{
|
||||||
label: '{{t("contains")}}',
|
label: '{{t("Insert")}}',
|
||||||
value: 'in',
|
value: 'create',
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': {
|
|
||||||
mode: 'tags',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("does not contain")}}',
|
label: '{{t("Update")}}',
|
||||||
value: 'notIn',
|
value: 'update',
|
||||||
schema: {
|
|
||||||
'x-component': 'Select',
|
|
||||||
'x-component-props': {
|
|
||||||
mode: 'tags',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '{{t("is empty")}}',
|
label: '{{t("Delete")}}',
|
||||||
value: '$null',
|
value: 'destroy',
|
||||||
noValue: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("is not empty")}}',
|
|
||||||
value: '$notNull',
|
|
||||||
noValue: true,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
properties: {
|
|
||||||
type: {
|
|
||||||
type: 'string',
|
|
||||||
'x-component': 'Select',
|
|
||||||
enum: [
|
|
||||||
{
|
|
||||||
label: '{{t("Insert")}}',
|
|
||||||
value: 'create',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Update")}}',
|
|
||||||
value: 'update',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{t("Delete")}}',
|
|
||||||
value: 'destroy',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
/>
|
}}
|
||||||
</FormLayout>
|
/>
|
||||||
);
|
</FormLayout>
|
||||||
},
|
);
|
||||||
).open({
|
}).open({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
defaultFilter:
|
defaultFilter: field?.componentProps?.defaultFilter || {},
|
||||||
field?.componentProps?.defaultFilter || {},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
schema['x-component-props']['defaultFilter'] =
|
schema['x-component-props']['defaultFilter'] = defaultFilter;
|
||||||
defaultFilter;
|
|
||||||
field.componentProps.defaultFilter = defaultFilter;
|
field.componentProps.defaultFilter = defaultFilter;
|
||||||
await updateSchema(schema);
|
await updateSchema(schema);
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
@ -1,42 +1,15 @@
|
|||||||
import React, { forwardRef, useState } from 'react';
|
import React, { createContext, useContext, forwardRef, useState } from 'react';
|
||||||
import {
|
import { SortableContext, useSortable, horizontalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
SortableContext,
|
|
||||||
useSortable,
|
|
||||||
horizontalListSortingStrategy,
|
|
||||||
verticalListSortingStrategy,
|
|
||||||
sortableKeyboardCoordinates,
|
|
||||||
} from '@dnd-kit/sortable';
|
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { Table } from 'antd';
|
|
||||||
import { createContext } from 'react';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
import { range } from 'lodash';
|
|
||||||
import parse from 'html-react-parser';
|
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
|
import { DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
|
||||||
import { MenuOutlined } from '@ant-design/icons';
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
import {
|
|
||||||
DndContext,
|
|
||||||
DragOverlay,
|
|
||||||
closestCenter,
|
|
||||||
KeyboardSensor,
|
|
||||||
PointerSensor,
|
|
||||||
useSensor,
|
|
||||||
useSensors,
|
|
||||||
useDroppable,
|
|
||||||
useDraggable,
|
|
||||||
} from '@dnd-kit/core';
|
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useRef } from 'react';
|
|
||||||
import { SortableItem } from '../../components/Sortable';
|
import { SortableItem } from '../../components/Sortable';
|
||||||
import {
|
import { findPropertyByPath, getSchemaPath, useDesignable } from '../../components/schema-renderer';
|
||||||
findPropertyByPath,
|
|
||||||
getSchemaPath,
|
|
||||||
useDesignable,
|
|
||||||
} from '../../components/schema-renderer';
|
|
||||||
import { updateSchema } from '..';
|
import { updateSchema } from '..';
|
||||||
import { Schema } from '@formily/react';
|
import { Schema } from '@formily/react';
|
||||||
import { isColumn, isColumnComponent } from '.';
|
import { isColumn } from './utils';
|
||||||
|
|
||||||
export const RowDraggableContext = createContext({});
|
export const RowDraggableContext = createContext({});
|
||||||
export const ColDraggableContext = createContext(null);
|
export const ColDraggableContext = createContext(null);
|
||||||
@ -68,9 +41,7 @@ export function SortableColumn(props) {
|
|||||||
ref={setDroppableNodeRef}
|
ref={setDroppableNodeRef}
|
||||||
// {...attributes}
|
// {...attributes}
|
||||||
>
|
>
|
||||||
<ColDraggableContext.Provider
|
<ColDraggableContext.Provider value={{ setDraggableNodeRef, attributes, listeners }}>
|
||||||
value={{ setDraggableNodeRef, attributes, listeners }}
|
|
||||||
>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</ColDraggableContext.Provider>
|
</ColDraggableContext.Provider>
|
||||||
{/* <span ref={setDraggableNodeRef} {...attributes} {...listeners}>
|
{/* <span ref={setDraggableNodeRef} {...attributes} {...listeners}>
|
||||||
@ -81,22 +52,9 @@ export function SortableColumn(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SortableBodyRow(props: any) {
|
export function SortableBodyRow(props: any) {
|
||||||
const {
|
const { className, style: prevStyle, ['data-row-key']: dataRowKey, ...others } = props;
|
||||||
className,
|
const { isDragging, attributes, listeners, setNodeRef, setDraggableNodeRef, overIndex, transform, transition } =
|
||||||
style: prevStyle,
|
useSortable({ id: dataRowKey });
|
||||||
['data-row-key']: dataRowKey,
|
|
||||||
...others
|
|
||||||
} = props;
|
|
||||||
const {
|
|
||||||
isDragging,
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef,
|
|
||||||
setDraggableNodeRef,
|
|
||||||
overIndex,
|
|
||||||
transform,
|
|
||||||
transition,
|
|
||||||
} = useSortable({ id: dataRowKey });
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
...prevStyle,
|
...prevStyle,
|
||||||
@ -105,15 +63,9 @@ export function SortableBodyRow(props: any) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowDraggableContext.Provider
|
<RowDraggableContext.Provider value={{ listeners, setDraggableNodeRef, attributes }}>
|
||||||
value={{ listeners, setDraggableNodeRef, attributes }}
|
|
||||||
>
|
|
||||||
<tr
|
<tr
|
||||||
className={cls(
|
className={cls({ isDragging }, props.className, `droppable-${props['data-row-key']}`)}
|
||||||
{ isDragging },
|
|
||||||
props.className,
|
|
||||||
`droppable-${props['data-row-key']}`,
|
|
||||||
)}
|
|
||||||
// className={cls(className)}
|
// className={cls(className)}
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
{...others}
|
{...others}
|
||||||
@ -127,9 +79,7 @@ export function SortableBodyRow(props: any) {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{React.Children.map(props.children, (child, index) => (
|
{React.Children.map(props.children, (child, index) => (
|
||||||
<CellContext.Provider value={`td${child.key}`}>
|
<CellContext.Provider value={`td${child.key}`}>{child}</CellContext.Provider>
|
||||||
{child}
|
|
||||||
</CellContext.Provider>
|
|
||||||
))}
|
))}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</tr>
|
</tr>
|
||||||
@ -264,8 +214,7 @@ export function SortableBodyCell(props) {
|
|||||||
|
|
||||||
export function SortableRowHandle(props) {
|
export function SortableRowHandle(props) {
|
||||||
const { className, ...others } = props;
|
const { className, ...others } = props;
|
||||||
const { setDraggableNodeRef, attributes, listeners } =
|
const { setDraggableNodeRef, attributes, listeners } = useContext<any>(RowDraggableContext);
|
||||||
useContext<any>(RowDraggableContext);
|
|
||||||
return setDraggableNodeRef ? (
|
return setDraggableNodeRef ? (
|
||||||
<MenuOutlined
|
<MenuOutlined
|
||||||
{...others}
|
{...others}
|
||||||
|
68
packages/client/src/schemas/table/Table.tsx
Normal file
68
packages/client/src/schemas/table/Table.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { CollectionProvider, DisplayedMapProvider } from '../../constate';
|
||||||
|
import { SimpleDesignableBar } from './SimpleDesignableBar';
|
||||||
|
import { useActionLogsResource } from './hooks/useActionLogsResource';
|
||||||
|
import { useTableDestroyAction } from './hooks/useTableDestroyAction';
|
||||||
|
import { useActionLogDetailsResource } from './hooks/useActionLogDetailsResource';
|
||||||
|
import { TableActionBar } from './TableActionBar';
|
||||||
|
import { TablePagination } from './TablePagination';
|
||||||
|
import { TableFilter } from './TableFilter';
|
||||||
|
import { TableFilterDesignableBar } from './TableFilterDesignableBar';
|
||||||
|
import { TableExportActionDesignableBar } from './TableExportActionDesignableBar';
|
||||||
|
import { useResource } from './hooks/useResource';
|
||||||
|
import { TableDesignableBar } from './TableDesignableBar';
|
||||||
|
import { useTableFilterAction } from './hooks/useTableFilterAction';
|
||||||
|
import { useTableCreateAction } from './hooks/useTableCreateAction';
|
||||||
|
import { useTableUpdateAction } from './hooks/useTableUpdateAction';
|
||||||
|
import { useTableExportAction } from './hooks/useTableExportAction';
|
||||||
|
import { useTableIndex } from './hooks/useTableIndex';
|
||||||
|
import { useTableRowRecord } from './hooks/useTableRowRecord';
|
||||||
|
import { TableSortHandle } from './TableSortHandle';
|
||||||
|
import { TableOperation } from './TableOperation';
|
||||||
|
import { TableOperationCell } from './TableOperationCell';
|
||||||
|
import { TableCell } from './TableCell';
|
||||||
|
import { TableColumn } from './TableColumn';
|
||||||
|
import { TableActionDesignableBar } from './TableActionDesignableBar';
|
||||||
|
import { TableOperationDesignableBar } from './TableOperationDesignableBar';
|
||||||
|
import { TableProvider } from './TableProvider';
|
||||||
|
import { TableIndex } from './TableIndex';
|
||||||
|
|
||||||
|
export const Table: any = observer((props: any) => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
return (
|
||||||
|
<CollectionProvider collectionName={props.collectionName}>
|
||||||
|
<DisplayedMapProvider>
|
||||||
|
<TableProvider {...props} />
|
||||||
|
</DisplayedMapProvider>
|
||||||
|
</CollectionProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Table.Pagination = TablePagination;
|
||||||
|
Table.ActionBar = TableActionBar;
|
||||||
|
Table.Filter = TableFilter;
|
||||||
|
Table.Filter.DesignableBar = TableFilterDesignableBar;
|
||||||
|
Table.ExportActionDesignableBar = TableExportActionDesignableBar;
|
||||||
|
Table.Operation = TableOperation;
|
||||||
|
Table.Operation.Cell = TableOperationCell;
|
||||||
|
Table.Operation.DesignableBar = TableOperationDesignableBar;
|
||||||
|
Table.Action = () => null;
|
||||||
|
Table.Action.DesignableBar = TableActionDesignableBar;
|
||||||
|
Table.Cell = TableCell;
|
||||||
|
Table.Column = TableColumn;
|
||||||
|
Table.SortHandle = TableSortHandle;
|
||||||
|
Table.DesignableBar = TableDesignableBar;
|
||||||
|
Table.SimpleDesignableBar = SimpleDesignableBar;
|
||||||
|
Table.Index = TableIndex;
|
||||||
|
|
||||||
|
Table.useResource = useResource;
|
||||||
|
Table.useActionLogDetailsResource = useActionLogDetailsResource;
|
||||||
|
Table.useActionLogsResource = useActionLogsResource;
|
||||||
|
Table.useTableFilterAction = useTableFilterAction;
|
||||||
|
Table.useTableCreateAction = useTableCreateAction;
|
||||||
|
Table.useTableUpdateAction = useTableUpdateAction;
|
||||||
|
Table.useTableDestroyAction = useTableDestroyAction;
|
||||||
|
Table.useTableExportAction = useTableExportAction;
|
||||||
|
Table.useTableIndex = useTableIndex;
|
||||||
|
Table.useTableRowRecord = useTableRowRecord;
|
106
packages/client/src/schemas/table/TableActionBar.tsx
Normal file
106
packages/client/src/schemas/table/TableActionBar.tsx
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { DndContext, DragOverlay } from '@dnd-kit/core';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { DisplayedMapProvider, useClient } from '../../constate';
|
||||||
|
import { AddActionButton } from './AddActionButton';
|
||||||
|
import { Actions } from './Actions';
|
||||||
|
import { findPropertyByPath } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export const TableActionBar = observer((props: any) => {
|
||||||
|
const { align = 'top' } = props;
|
||||||
|
// const { schema, designable } = useDesignable();
|
||||||
|
const { root, schema, insertAfter, remove, appendChild } = useDesignable();
|
||||||
|
const moveToAfter = (path1, path2, extra = {}) => {
|
||||||
|
if (!path1 || !path2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (path1.join('.') === path2.join('.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = findPropertyByPath(root, path1);
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remove(path1);
|
||||||
|
return insertAfter(
|
||||||
|
{
|
||||||
|
...data.toJSON(),
|
||||||
|
...extra,
|
||||||
|
},
|
||||||
|
path2,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
|
||||||
|
const [dragOverlayContent, setDragOverlayContent] = useState('');
|
||||||
|
return (
|
||||||
|
<DndContext
|
||||||
|
onDragStart={(event) => {
|
||||||
|
setDragOverlayContent(event.active.data?.current?.title || '');
|
||||||
|
// const previewRef = event.active.data?.current?.previewRef;
|
||||||
|
// if (previewRef) {
|
||||||
|
// setDragOverlayContent(previewRef?.current?.innerHTML);
|
||||||
|
// } else {
|
||||||
|
// setDragOverlayContent('');
|
||||||
|
// }
|
||||||
|
}}
|
||||||
|
onDragEnd={async (event) => {
|
||||||
|
const path1 = event.active?.data?.current?.path;
|
||||||
|
const path2 = event.over?.data?.current?.path;
|
||||||
|
const align = event.over?.data?.current?.align;
|
||||||
|
const draggable = event.over?.data?.current?.draggable;
|
||||||
|
if (!path1 || !path2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (path1.join('.') === path2.join('.')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!draggable) {
|
||||||
|
console.log('alignalignalignalign', align);
|
||||||
|
const p = findPropertyByPath(root, path1);
|
||||||
|
if (!p) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remove(path1);
|
||||||
|
const data = appendChild(
|
||||||
|
{
|
||||||
|
...p.toJSON(),
|
||||||
|
'x-align': align,
|
||||||
|
},
|
||||||
|
path2,
|
||||||
|
);
|
||||||
|
await updateSchema(data);
|
||||||
|
} else {
|
||||||
|
const data = moveToAfter(path1, path2, {
|
||||||
|
'x-align': align,
|
||||||
|
});
|
||||||
|
await updateSchema(data);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DragOverlay
|
||||||
|
dropAnimation={{
|
||||||
|
duration: 10,
|
||||||
|
easing: 'cubic-bezier(0.18, 0.67, 0.6, 1.22)',
|
||||||
|
}}
|
||||||
|
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
{dragOverlayContent}
|
||||||
|
{/* <div style={{ transform: 'translateX(-100%)' }} dangerouslySetInnerHTML={{__html: dragOverlayContent}}></div> */}
|
||||||
|
</DragOverlay>
|
||||||
|
<DisplayedMapProvider>
|
||||||
|
<div className={cls('nb-action-bar', `align-${align}`)}>
|
||||||
|
<div style={{ width: '50%' }}>
|
||||||
|
<Actions align={'left'} />
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: 'auto', width: '50%', textAlign: 'right' }}>
|
||||||
|
<Actions align={'right'} />
|
||||||
|
</div>
|
||||||
|
<AddActionButton />
|
||||||
|
</div>
|
||||||
|
</DisplayedMapProvider>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
});
|
143
packages/client/src/schemas/table/TableActionDesignableBar.tsx
Normal file
143
packages/client/src/schemas/table/TableActionDesignableBar.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Select, Dropdown, Menu, Switch, Space } from 'antd';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export const TableActionDesignableBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const isPopup = Object.keys(schema.properties || {}).length > 0;
|
||||||
|
const popupSchema = Object.values(schema.properties || {}).shift();
|
||||||
|
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const field = useField();
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
const popupComponent = popupSchema?.['x-component'] || 'Action.Drawer';
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async (e) => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog(t('Edit button'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Display name'),
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Icon'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'IconPicker',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
title: schema['title'],
|
||||||
|
icon: schema['x-component-props']?.['icon'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schema['title'] = values.title;
|
||||||
|
schema['x-component-props']['icon'] = values.icon;
|
||||||
|
field.componentProps.icon = values.icon;
|
||||||
|
field.title = values.title;
|
||||||
|
updateSchema(schema);
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Edit button')}
|
||||||
|
</Menu.Item>
|
||||||
|
{isPopup && (
|
||||||
|
<Menu.Item>
|
||||||
|
<Trans t={t}>
|
||||||
|
Open in
|
||||||
|
<Select
|
||||||
|
bordered={false}
|
||||||
|
size={'small'}
|
||||||
|
defaultValue={popupComponent}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
onChange={async (value) => {
|
||||||
|
const s = Object.values(schema.properties).shift();
|
||||||
|
s['x-component'] = value;
|
||||||
|
refresh();
|
||||||
|
await updateSchema(s);
|
||||||
|
window.location.reload();
|
||||||
|
// const f = field.query(getSchemaPath(s)).take()
|
||||||
|
// console.log('fffffff', { schema, f });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value={'Action.Modal'}>Modal</Select.Option>
|
||||||
|
<Select.Option value={'Action.Drawer'}>Drawer</Select.Option>
|
||||||
|
<Select.Option disabled value={'Action.Window'}>
|
||||||
|
Window
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Trans>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
{!inActionBar && (
|
||||||
|
<Menu.Item>
|
||||||
|
{t('Triggered when the row is clicked')}
|
||||||
|
<Switch size={'small'} defaultChecked />
|
||||||
|
</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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Hide')}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
54
packages/client/src/schemas/table/TableCell.tsx
Normal file
54
packages/client/src/schemas/table/TableCell.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { observer, RecursionField, Schema } from '@formily/react';
|
||||||
|
import { cloneDeepWith, set } from 'lodash';
|
||||||
|
import { uid, merge } from '@formily/shared';
|
||||||
|
import { CollectionFieldContext, TableRowContext } from './context';
|
||||||
|
import { Table } from './Table';
|
||||||
|
|
||||||
|
export const TableCell = observer((props: any) => {
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const schema = props.schema;
|
||||||
|
const collectionField = useContext(CollectionFieldContext);
|
||||||
|
if (schema['x-component'] === 'Table.Operation') {
|
||||||
|
return <Table.Operation.Cell {...props} />;
|
||||||
|
}
|
||||||
|
let uiSchema = collectionField?.uiSchema as Schema;
|
||||||
|
if (uiSchema?.['x-component'] === 'Upload.Attachment') {
|
||||||
|
uiSchema = cloneDeepWith(uiSchema);
|
||||||
|
set(uiSchema, 'x-component-props.size', 'small');
|
||||||
|
}
|
||||||
|
const componentProps = merge(uiSchema?.['x-component-props'] || {}, schema?.['x-component-props'] || {}, {
|
||||||
|
arrayMerge: (t, s) => s,
|
||||||
|
});
|
||||||
|
console.log('Table.Cell', collectionField?.interface, componentProps);
|
||||||
|
return (
|
||||||
|
<div className={`field-interface-${collectionField?.interface}`}>
|
||||||
|
<RecursionField
|
||||||
|
schema={
|
||||||
|
!collectionField
|
||||||
|
? schema
|
||||||
|
: new Schema({
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
[collectionField.name]: {
|
||||||
|
...uiSchema,
|
||||||
|
title: undefined,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator-props': {
|
||||||
|
feedbackLayout: 'popover',
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormilyFormItem',
|
||||||
|
'x-component-props': componentProps,
|
||||||
|
properties: {
|
||||||
|
...schema?.properties,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
name={ctx.index}
|
||||||
|
onlyRenderProperties
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
24
packages/client/src/schemas/table/TableColumn.tsx
Normal file
24
packages/client/src/schemas/table/TableColumn.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React, { useContext, useEffect } from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useDisplayedMapContext } from '../../constate';
|
||||||
|
import { useCompile } from '../../hooks/useCompile';
|
||||||
|
import { CollectionFieldContext } from './context';
|
||||||
|
|
||||||
|
export const TableColumn = observer((props: any) => {
|
||||||
|
const collectionField = useContext(CollectionFieldContext);
|
||||||
|
const { schema, DesignableBar } = useDesignable();
|
||||||
|
const compile = useCompile();
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
useEffect(() => {
|
||||||
|
if (collectionField?.name) {
|
||||||
|
displayed.set(collectionField.name, schema);
|
||||||
|
}
|
||||||
|
}, [collectionField, schema]);
|
||||||
|
return (
|
||||||
|
<div className={'nb-table-column'}>
|
||||||
|
{compile(schema.title || collectionField?.uiSchema?.title)}
|
||||||
|
<DesignableBar />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
158
packages/client/src/schemas/table/TableColumnDesignableBar.tsx
Normal file
158
packages/client/src/schemas/table/TableColumnDesignableBar.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { Select, Dropdown, Menu, Space } from 'antd';
|
||||||
|
import { set } from 'lodash';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import { FormDialog, FormLayout, Submit } from '@formily/antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
import { useCollectionsContext, useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useCompile } from '../../hooks/useCompile';
|
||||||
|
import { useTable } from './hooks/useTable';
|
||||||
|
import { CollectionFieldContext } from './context';
|
||||||
|
|
||||||
|
export const TableColumnDesignableBar = () => {
|
||||||
|
const field = useField();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { service, refresh: refreshTable } = useTable();
|
||||||
|
// const fieldSchema = useFieldSchema();
|
||||||
|
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const { getFieldsByCollection } = useCollectionsContext();
|
||||||
|
const collectionField = useContext(CollectionFieldContext);
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
console.log('displayed.map', displayed.map);
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async (e) => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog(t('Custom name'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
fieldName: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Original name'),
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Custom name'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
fieldName: collectionField?.uiSchema?.title,
|
||||||
|
title: schema['title'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const title = values.title || null;
|
||||||
|
field.title = title;
|
||||||
|
schema.title = title;
|
||||||
|
refresh();
|
||||||
|
await updateSchema({
|
||||||
|
key: schema['key'],
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Custom column name')}
|
||||||
|
</Menu.Item>
|
||||||
|
{collectionField?.interface === 'linkTo' && (
|
||||||
|
<Menu.Item>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Label field')}{' '}
|
||||||
|
<Select
|
||||||
|
value={schema?.['x-component-props']?.['fieldNames']?.['label']}
|
||||||
|
placeholder={t('Default is the ID field')}
|
||||||
|
onChange={async (value) => {
|
||||||
|
set(schema['x-component-props'], 'fieldNames.label', value);
|
||||||
|
await updateSchema({
|
||||||
|
key: schema['key'],
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: {
|
||||||
|
label: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
refreshTable();
|
||||||
|
// await service.refresh();
|
||||||
|
}}
|
||||||
|
bordered={false}
|
||||||
|
size={'small'}
|
||||||
|
style={{ marginLeft: 16, minWidth: 120 }}
|
||||||
|
options={getFieldsByCollection(collectionField.target)
|
||||||
|
.filter((f) => f?.uiSchema?.title)
|
||||||
|
.map((field) => ({
|
||||||
|
label: compile(field?.uiSchema?.title || field.name),
|
||||||
|
value: field.name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async () => {
|
||||||
|
const fieldName = schema['x-component-props']?.['fieldName'];
|
||||||
|
displayed.remove(fieldName);
|
||||||
|
schema['x-hidden'] = true;
|
||||||
|
refresh();
|
||||||
|
await updateSchema({
|
||||||
|
key: schema['key'],
|
||||||
|
['x-hidden']: true,
|
||||||
|
});
|
||||||
|
// const s = remove();
|
||||||
|
// await removeSchema(s);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Hide')}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
275
packages/client/src/schemas/table/TableDesignableBar.tsx
Normal file
275
packages/client/src/schemas/table/TableDesignableBar.tsx
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import { Modal, Select, Dropdown, Menu, Switch, Space } from 'antd';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import { set } from 'lodash';
|
||||||
|
import { observer, useField } from '@formily/react';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { useCompile } from '../../hooks/useCompile';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { DraggableBlockContext } from '../../components/drag-and-drop';
|
||||||
|
import { useCollection, useClient } from '../../constate';
|
||||||
|
import AddNew from '../add-new';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
import { fieldsToSortColumns, fieldsToFilterColumns } from './utils';
|
||||||
|
|
||||||
|
export const TableDesignableBar = observer((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const compile = useCompile();
|
||||||
|
const field = useField();
|
||||||
|
const { schema, refresh, deepRemove } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { dragRef } = useContext(DraggableBlockContext);
|
||||||
|
const defaultPageSize = field?.componentProps?.pagination?.defaultPageSize || 10;
|
||||||
|
const collectionName = field?.componentProps?.collectionName;
|
||||||
|
const { collection, fields } = useCollection({ collectionName });
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
console.log({ collectionName });
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<div className={'designable-info'}>{compile(collection?.title || collection?.name)}</div>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space size={2}>
|
||||||
|
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item
|
||||||
|
key={'showIndex'}
|
||||||
|
onClick={() => {
|
||||||
|
const bool = !field.componentProps.showIndex;
|
||||||
|
schema['x-component-props']['showIndex'] = bool;
|
||||||
|
field.componentProps.showIndex = bool;
|
||||||
|
updateSchema(schema);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={'nb-space-between'}>
|
||||||
|
{t('Display order number')} <Switch size={'small'} checked={field.componentProps.showIndex} />
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item
|
||||||
|
key={'dragSort'}
|
||||||
|
onClick={() => {
|
||||||
|
const dragSort = field.componentProps.dragSort ? false : 'sort';
|
||||||
|
schema['x-component-props']['dragSort'] = dragSort;
|
||||||
|
field.componentProps.dragSort = dragSort;
|
||||||
|
updateSchema(schema);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={'nb-space-between'}>
|
||||||
|
{t('Enable drag and drop sorting')}
|
||||||
|
|
||||||
|
<Switch size={'small'} checked={field.componentProps.dragSort} />
|
||||||
|
</div>
|
||||||
|
</Menu.Item>
|
||||||
|
{!field.componentProps.dragSort && (
|
||||||
|
<Menu.Item
|
||||||
|
key={'defaultSort'}
|
||||||
|
onClick={async () => {
|
||||||
|
const defaultSort = field.componentProps?.defaultSort?.map((item: string) => {
|
||||||
|
return item.startsWith('-')
|
||||||
|
? {
|
||||||
|
field: item.substring(1),
|
||||||
|
direction: 'desc',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
field: item,
|
||||||
|
direction: 'asc',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const values = await FormDialog(t('Set default sorting rules'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
defaultSort: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'ArrayItems',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
space: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
properties: {
|
||||||
|
sort: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.SortHandle',
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: fieldsToSortColumns(fields),
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
width: 260,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: 'string',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Radio.Group',
|
||||||
|
'x-component-props': {
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
enum: [
|
||||||
|
{
|
||||||
|
label: t('ASC'),
|
||||||
|
value: 'asc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('DESC'),
|
||||||
|
value: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ArrayItems.Remove',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Add sort field'),
|
||||||
|
'x-component': 'ArrayItems.Addition',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
defaultSort,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sort = values.defaultSort.map((item) => {
|
||||||
|
return item.direction === 'desc' ? `-${item.field}` : item.field;
|
||||||
|
});
|
||||||
|
schema['x-component-props']['defaultSort'] = sort;
|
||||||
|
field.componentProps.defaultSort = sort;
|
||||||
|
await updateSchema(schema);
|
||||||
|
setVisible(false);
|
||||||
|
console.log('defaultSort', sort);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Set default sorting rules')}
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
|
<Menu.Item
|
||||||
|
key={'defaultFilter'}
|
||||||
|
onClick={async () => {
|
||||||
|
const { defaultFilter } = await FormDialog(t('Set the data scope'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
defaultFilter: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'Filter',
|
||||||
|
properties: fieldsToFilterColumns(fields),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
defaultFilter: field?.componentProps?.defaultFilter || {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schema['x-component-props']['defaultFilter'] = defaultFilter;
|
||||||
|
field.componentProps.defaultFilter = defaultFilter;
|
||||||
|
await updateSchema(schema);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Set the data scope')}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key={'defaultPageSize'}>
|
||||||
|
<Trans>
|
||||||
|
{'Display '}
|
||||||
|
<Select
|
||||||
|
bordered={false}
|
||||||
|
size={'small'}
|
||||||
|
onChange={(value) => {
|
||||||
|
const componentProps = schema['x-component-props'] || {};
|
||||||
|
set(componentProps, 'pagination.defaultPageSize', value);
|
||||||
|
set(componentProps, 'pagination.pageSize', value);
|
||||||
|
schema['x-component-props'] = componentProps;
|
||||||
|
field.componentProps.pagination.pageSize = value;
|
||||||
|
field.componentProps.pagination.defaultPageSize = value;
|
||||||
|
refresh();
|
||||||
|
updateSchema(schema);
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
defaultValue={defaultPageSize}
|
||||||
|
>
|
||||||
|
<Select.Option value={10}>10</Select.Option>
|
||||||
|
<Select.Option value={20}>20</Select.Option>
|
||||||
|
<Select.Option value={50}>50</Select.Option>
|
||||||
|
<Select.Option value={100}>100</Select.Option>
|
||||||
|
</Select>
|
||||||
|
{' items per page'}
|
||||||
|
</Trans>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
key={'delete'}
|
||||||
|
onClick={async () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('Delete block'),
|
||||||
|
content: t('Are you sure you want to delete it?'),
|
||||||
|
onOk: async () => {
|
||||||
|
const removed = deepRemove();
|
||||||
|
// console.log({ removed })
|
||||||
|
const last = removed.pop();
|
||||||
|
await removeSchema(last);
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Delete')}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,134 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Dropdown, Menu, Space } from 'antd';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export const TableExportActionDesignableBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const { fields } = useCollectionContext();
|
||||||
|
const field = useField();
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
let fieldNames = field.componentProps.fieldNames || [];
|
||||||
|
if (fieldNames.length === 0) {
|
||||||
|
fieldNames = fields.map((field) => field.name);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup title={t('Export fields')}>
|
||||||
|
{fields.map((collectionField) => (
|
||||||
|
<SwitchMenuItem
|
||||||
|
title={collectionField?.uiSchema?.title}
|
||||||
|
checked={fieldNames.includes(collectionField.name)}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
fieldNames.push(collectionField.name);
|
||||||
|
} else {
|
||||||
|
const index = fieldNames.indexOf(collectionField.name);
|
||||||
|
if (index > -1) {
|
||||||
|
fieldNames.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log({ fieldNames, field });
|
||||||
|
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
field.componentProps.fieldNames = fieldNames;
|
||||||
|
updateSchema(schema);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async (e) => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog(t('Edit button'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Display name'),
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Icon'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'IconPicker',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
title: schema['title'],
|
||||||
|
icon: schema['x-component-props']?.['icon'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schema['title'] = values.title;
|
||||||
|
schema['x-component-props']['icon'] = values.icon;
|
||||||
|
field.componentProps.icon = values.icon;
|
||||||
|
field.title = values.title;
|
||||||
|
updateSchema(schema);
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Edit button')}
|
||||||
|
</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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Hide')}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
93
packages/client/src/schemas/table/TableFilter.tsx
Normal file
93
packages/client/src/schemas/table/TableFilter.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { Popover, Button } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import flatten from 'flat';
|
||||||
|
import { createForm } from '@formily/core';
|
||||||
|
import { FormProvider, observer } from '@formily/react';
|
||||||
|
import { FormButtonGroup, Submit } from '@formily/antd';
|
||||||
|
import { interfaces } from '../database-field/interfaces';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useCollectionContext } from '../../constate';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
import { useTable } from './hooks/useTable';
|
||||||
|
import { useCompile } from '../../hooks/useCompile';
|
||||||
|
import IconPicker from '../../components/icon-picker';
|
||||||
|
import { fieldsToFilterColumns } from './utils';
|
||||||
|
|
||||||
|
export const TableFilter = observer((props: any) => {
|
||||||
|
const { service } = useTable();
|
||||||
|
const { fieldNames = [] } = props;
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { schema, DesignableBar } = useDesignable();
|
||||||
|
const form = useMemo(() => createForm(), []);
|
||||||
|
const { fields = [] } = useCollectionContext();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const obj = flatten(form.values.filter || {});
|
||||||
|
console.log('flatten', obj, Object.values(obj));
|
||||||
|
const count = Object.values(obj).filter((i) => (Array.isArray(i) ? i.length : i)).length;
|
||||||
|
const icon = props.icon || 'FilterOutlined';
|
||||||
|
const properties = fieldsToFilterColumns(fields, { fieldNames });
|
||||||
|
schema.mapProperties((p) => {
|
||||||
|
properties[p.name] = p;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
trigger={['click']}
|
||||||
|
placement={'bottomLeft'}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={setVisible}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<FormProvider form={form}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
filter: {
|
||||||
|
type: 'object',
|
||||||
|
'x-component': 'Filter',
|
||||||
|
properties,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormButtonGroup align={'right'}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setVisible(false);
|
||||||
|
form.reset();
|
||||||
|
const { filter } = form.values;
|
||||||
|
return service.run({
|
||||||
|
...service.params[0],
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Reset')}
|
||||||
|
</Button>
|
||||||
|
<Submit
|
||||||
|
onSubmit={() => {
|
||||||
|
const { filter } = form.values;
|
||||||
|
console.log('Table.Filter', form.values);
|
||||||
|
setVisible(false);
|
||||||
|
return service.run({
|
||||||
|
...service.params[0],
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Submit')}
|
||||||
|
</Submit>
|
||||||
|
</FormButtonGroup>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button icon={<IconPicker type={icon} />}>
|
||||||
|
{count > 0 ? t('{{count}} filter items', { count }) : compile(schema.title)}
|
||||||
|
<DesignableBar />
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
});
|
140
packages/client/src/schemas/table/TableFilterDesignableBar.tsx
Normal file
140
packages/client/src/schemas/table/TableFilterDesignableBar.tsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Dropdown, Menu, Space } from 'antd';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
import { interfaces } from '../database-field/interfaces';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useCollectionContext, useDisplayedMapContext, useClient } from '../../constate';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
import { SchemaField } from '../../components/schema-renderer';
|
||||||
|
|
||||||
|
export const TableFilterDesignableBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const displayed = useDisplayedMapContext();
|
||||||
|
const { fields } = useCollectionContext();
|
||||||
|
const field = useField();
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
let fieldNames = field.componentProps.fieldNames || [];
|
||||||
|
if (fieldNames.length === 0) {
|
||||||
|
fieldNames = fields.map((field) => field.name);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup title={t('Filterable fields')}>
|
||||||
|
{fields
|
||||||
|
.filter((collectionField) => {
|
||||||
|
const option = interfaces.get(collectionField.interface);
|
||||||
|
return option?.operations?.length;
|
||||||
|
})
|
||||||
|
.map((collectionField) => (
|
||||||
|
<SwitchMenuItem
|
||||||
|
title={collectionField?.uiSchema?.title}
|
||||||
|
checked={fieldNames.includes(collectionField.name)}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
fieldNames.push(collectionField.name);
|
||||||
|
} else {
|
||||||
|
const index = fieldNames.indexOf(collectionField.name);
|
||||||
|
if (index > -1) {
|
||||||
|
fieldNames.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log({ fieldNames, field });
|
||||||
|
schema['x-component-props']['fieldNames'] = fieldNames;
|
||||||
|
field.componentProps.fieldNames = fieldNames;
|
||||||
|
updateSchema(schema);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.Item
|
||||||
|
onClick={async (e) => {
|
||||||
|
setVisible(false);
|
||||||
|
const values = await FormDialog(t('Edit button'), () => {
|
||||||
|
return (
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaField
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Display name'),
|
||||||
|
required: true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: 'string',
|
||||||
|
title: t('Icon'),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'IconPicker',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
);
|
||||||
|
}).open({
|
||||||
|
initialValues: {
|
||||||
|
title: schema['title'],
|
||||||
|
icon: schema['x-component-props']?.['icon'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
schema['title'] = values.title;
|
||||||
|
schema['x-component-props']['icon'] = values.icon;
|
||||||
|
field.componentProps.icon = values.icon;
|
||||||
|
field.title = values.title;
|
||||||
|
updateSchema(schema);
|
||||||
|
refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Edit button')}
|
||||||
|
</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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Hide')}
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
8
packages/client/src/schemas/table/TableIndex.tsx
Normal file
8
packages/client/src/schemas/table/TableIndex.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { useTableIndex } from './hooks/useTableIndex';
|
||||||
|
|
||||||
|
export const TableIndex = observer(() => {
|
||||||
|
const index = useTableIndex();
|
||||||
|
return <span className={'nb-table-index'}>{index + 1}</span>;
|
||||||
|
});
|
139
packages/client/src/schemas/table/TableMain.tsx
Normal file
139
packages/client/src/schemas/table/TableMain.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import React, { DndContext } from '@dnd-kit/core';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Table as AntdTable } from 'antd';
|
||||||
|
import { findIndex } from 'lodash';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { RecursionField, Schema } from '@formily/react';
|
||||||
|
import { isValid } from '@formily/shared';
|
||||||
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
|
import { SortableBodyRow, SortableHeaderCell, SortableHeaderRow } from './Sortable';
|
||||||
|
import { TableRowContext } from './context';
|
||||||
|
import { useDataSource } from './hooks/useDataSource';
|
||||||
|
import { useDefaultRowSelection } from './hooks/useDefaultRowSelection';
|
||||||
|
import { useTable } from './hooks/useTable';
|
||||||
|
import { useTableActionBars } from './hooks/useTableActionBars';
|
||||||
|
import { useTableColumns } from './hooks/useTableColumns';
|
||||||
|
import { Table } from './Table';
|
||||||
|
|
||||||
|
export const TableMain = () => {
|
||||||
|
const {
|
||||||
|
resource,
|
||||||
|
selectedRowKeys,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
service,
|
||||||
|
field,
|
||||||
|
props: { rowKey, dragSort, showIndex, onSelect, useRowSelection = useDefaultRowSelection },
|
||||||
|
refresh,
|
||||||
|
} = useTable();
|
||||||
|
const columns = useTableColumns();
|
||||||
|
const dataSource = useDataSource();
|
||||||
|
const actionBars = useTableActionBars();
|
||||||
|
const [html, setHtml] = useState('');
|
||||||
|
const { type } = useRowSelection();
|
||||||
|
return (
|
||||||
|
<div className={'nb-table'}>
|
||||||
|
<DndContext
|
||||||
|
onDragEnd={async (event) => {
|
||||||
|
const fromId = event.active?.id as any;
|
||||||
|
const toId = event.over?.id as any;
|
||||||
|
if (isValid(fromId) && isValid(toId)) {
|
||||||
|
const fromIndex = findIndex(field.value, (item) => item[rowKey] === fromId);
|
||||||
|
const toIndex = findIndex(field.value, (item) => item[rowKey] === toId);
|
||||||
|
console.log({ fromId, toId, fromIndex, toIndex });
|
||||||
|
field.move(fromIndex, toIndex);
|
||||||
|
refresh();
|
||||||
|
await resource.sort({
|
||||||
|
resourceKey: fromId,
|
||||||
|
target: {
|
||||||
|
[rowKey]: toId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await service.refresh();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actionBars.map((actionBar) => (
|
||||||
|
<RecursionField
|
||||||
|
schema={
|
||||||
|
new Schema({
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
[actionBar.name]: actionBar,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<SortableContext items={dataSource || []} strategy={verticalListSortingStrategy}>
|
||||||
|
<AntdTable
|
||||||
|
pagination={false}
|
||||||
|
onChange={(pagination) => {}}
|
||||||
|
loading={service?.loading}
|
||||||
|
rowKey={rowKey}
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
// components={{
|
||||||
|
// body: {
|
||||||
|
// row: DragableBodyRow,
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
components={{
|
||||||
|
header: {
|
||||||
|
row: SortableHeaderRow,
|
||||||
|
cell: SortableHeaderCell,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
// wrapper: (props) => {
|
||||||
|
// return (
|
||||||
|
// <tbody {...props}>
|
||||||
|
// <DragOverlay
|
||||||
|
// className={'ant-table-row'}
|
||||||
|
// wrapperElement={'tr'}
|
||||||
|
// >
|
||||||
|
// <div />
|
||||||
|
// </DragOverlay>
|
||||||
|
// {props.children}
|
||||||
|
// </tbody>
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
row: SortableBodyRow,
|
||||||
|
// cell: SortableBodyCell,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rowSelection={{
|
||||||
|
type: type || 'checkbox',
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: (rowKeys, rows) => {
|
||||||
|
setSelectedRowKeys(rowKeys);
|
||||||
|
onSelect && onSelect(rowKeys, rows);
|
||||||
|
},
|
||||||
|
renderCell: (checked, record, _, originNode) => {
|
||||||
|
const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]);
|
||||||
|
return (
|
||||||
|
<TableRowContext.Provider
|
||||||
|
value={{
|
||||||
|
index,
|
||||||
|
record,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cls('nb-table-selection', {
|
||||||
|
dragSort,
|
||||||
|
showIndex,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{dragSort && <Table.SortHandle />}
|
||||||
|
{showIndex && <Table.Index />}
|
||||||
|
{originNode}
|
||||||
|
</div>
|
||||||
|
</TableRowContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
<Table.Pagination />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
16
packages/client/src/schemas/table/TableOperation.tsx
Normal file
16
packages/client/src/schemas/table/TableOperation.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { Table } from './Table';
|
||||||
|
|
||||||
|
export const TableOperation = observer((props: any) => {
|
||||||
|
const { designable, schema } = useDesignable();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className={'nb-table-column'}>
|
||||||
|
{t('Operations')}
|
||||||
|
<Table.Operation.DesignableBar path={props.path} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
13
packages/client/src/schemas/table/TableOperationCell.tsx
Normal file
13
packages/client/src/schemas/table/TableOperationCell.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { observer, RecursionField } from '@formily/react';
|
||||||
|
import { TableRowContext } from './context';
|
||||||
|
|
||||||
|
export const TableOperationCell = observer((props: any) => {
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const schema = props.schema;
|
||||||
|
return (
|
||||||
|
<div className={'nb-table-column'}>
|
||||||
|
<RecursionField schema={schema} name={ctx.index} onlyRenderProperties />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -0,0 +1,194 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Dropdown, Menu, Space } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { uid, merge } from '@formily/shared';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
import { useDesignable, ISchema } from '..';
|
||||||
|
import { getSchemaPath } from '../../components/schema-renderer';
|
||||||
|
import { useClient } from '../../constate';
|
||||||
|
import SwitchMenuItem from '../../components/SwitchMenuItem';
|
||||||
|
import { DragHandle } from '../../components/Sortable';
|
||||||
|
|
||||||
|
export const TableOperationDesignableBar = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { schema: columnSchema } = useDesignable();
|
||||||
|
const groupSchema = Object.values(columnSchema.properties || {}).shift();
|
||||||
|
const groupPath = getSchemaPath(groupSchema);
|
||||||
|
const { schema, remove, refresh, appendChild } = useDesignable(groupPath);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { createSchema, removeSchema, updateSchema } = useClient();
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
schema.mapProperties((s) => {
|
||||||
|
if (!s['x-action-type']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
map.set(s['x-action-type'], s.name);
|
||||||
|
});
|
||||||
|
const path = getSchemaPath(schema);
|
||||||
|
return (
|
||||||
|
<div className={cls('designable-bar', { active: visible })}>
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
className={cls('designable-bar-actions', { active: visible })}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<DragHandle />
|
||||||
|
<Dropdown
|
||||||
|
trigger={['hover']}
|
||||||
|
visible={visible}
|
||||||
|
onVisibleChange={(visible) => {
|
||||||
|
setVisible(visible);
|
||||||
|
}}
|
||||||
|
overlay={
|
||||||
|
<Menu>
|
||||||
|
<Menu.ItemGroup title={t('Enable actions')}>
|
||||||
|
{[
|
||||||
|
{ title: t('View'), name: 'view' },
|
||||||
|
{ title: t('Edit'), name: 'update' },
|
||||||
|
{ title: t('Delete'), name: 'destroy' },
|
||||||
|
].map((item) => (
|
||||||
|
<SwitchMenuItem
|
||||||
|
key={item.name}
|
||||||
|
title={item.title}
|
||||||
|
checked={map.has(item.name)}
|
||||||
|
onChange={async (checked) => {
|
||||||
|
if (checked) {
|
||||||
|
const s = generateMenuActionSchema(item.name);
|
||||||
|
const data = appendChild(s);
|
||||||
|
await createSchema(data);
|
||||||
|
} else if (map.get(item.name)) {
|
||||||
|
const removed = remove([...path, map.get(item.name)]);
|
||||||
|
await removeSchema(removed);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Menu.ItemGroup>
|
||||||
|
<Menu.Divider />
|
||||||
|
<Menu.SubMenu disabled title={t('Customize')}>
|
||||||
|
<Menu.Item style={{ minWidth: 120 }}>{t('Function')}</Menu.Item>
|
||||||
|
<Menu.Item>{t('Popup form')}</Menu.Item>
|
||||||
|
<Menu.Item>{t('Flexible popup')}</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuOutlined />
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
function generateMenuActionSchema(type) {
|
||||||
|
const actions: { [key: string]: ISchema } = {
|
||||||
|
view: {
|
||||||
|
key: uid(),
|
||||||
|
name: uid(),
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('View') }}",
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'link',
|
||||||
|
},
|
||||||
|
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||||
|
'x-action-type': 'view',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('View record') }}",
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
bodyStyle: {
|
||||||
|
background: '#f0f2f5',
|
||||||
|
// paddingTop: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Tabs',
|
||||||
|
'x-designable-bar': 'Tabs.DesignableBar',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Details') }}",
|
||||||
|
'x-designable-bar': 'Tabs.TabPane.DesignableBar',
|
||||||
|
'x-component': 'Tabs.TabPane',
|
||||||
|
'x-component-props': {},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-component-props': {
|
||||||
|
addNewComponent: 'AddNew.PaneItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
key: uid(),
|
||||||
|
name: uid(),
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Edit') }}",
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'link',
|
||||||
|
},
|
||||||
|
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||||
|
'x-action-type': 'update',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Edit record') }}",
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useResource: '{{ Table.useResource }}',
|
||||||
|
useValues: '{{ Table.useTableRowRecord }}',
|
||||||
|
},
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-component-props': {
|
||||||
|
useOkAction: '{{ Table.useTableUpdateAction }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-component-props': {
|
||||||
|
addNewComponent: 'AddNew.FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
destroy: {
|
||||||
|
key: uid(),
|
||||||
|
name: uid(),
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Delete') }}",
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||||
|
'x-action-type': 'destroy',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ Table.useTableDestroyAction }}',
|
||||||
|
type: 'link',
|
||||||
|
confirm: {
|
||||||
|
title: "{{ t('Delete record') }}",
|
||||||
|
content: "{{ t('Are you sure you want to delete it?') }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return actions[type];
|
||||||
|
}
|
40
packages/client/src/schemas/table/TablePagination.tsx
Normal file
40
packages/client/src/schemas/table/TablePagination.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Pagination } from 'antd';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { useTable } from './hooks/useTable';
|
||||||
|
import { useTotal } from './hooks/useTotal';
|
||||||
|
|
||||||
|
export const TablePagination = observer(() => {
|
||||||
|
const { service, pagination, setPagination, props } = useTable();
|
||||||
|
if (!pagination || Object.keys(pagination).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { clientSidePagination } = props;
|
||||||
|
const total = useTotal();
|
||||||
|
const { page = 1 } = pagination;
|
||||||
|
return (
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Pagination
|
||||||
|
{...pagination}
|
||||||
|
showSizeChanger
|
||||||
|
current={page}
|
||||||
|
total={total}
|
||||||
|
onChange={(current, pageSize) => {
|
||||||
|
const page = pagination.pageSize !== pageSize ? 1 : current;
|
||||||
|
setPagination({
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
});
|
||||||
|
// if (clientSidePagination) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// service.run({
|
||||||
|
// ...service.params[0],
|
||||||
|
// page,
|
||||||
|
// pageSize,
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
103
packages/client/src/schemas/table/TableProvider.tsx
Normal file
103
packages/client/src/schemas/table/TableProvider.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import useRequest from '@ahooksjs/use-request';
|
||||||
|
import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect';
|
||||||
|
import { useResource as useGeneralResource } from '../../hooks/useResource';
|
||||||
|
import { useDesignable } from '..';
|
||||||
|
import { useCollectionContext } from '../../constate';
|
||||||
|
import { useDefaultSelectedRowKeys } from './hooks/useDefaultSelectedRowKeys';
|
||||||
|
import { usePagination } from './hooks/usePagination';
|
||||||
|
import { useCollectionFields } from './hooks/useCollectionFields';
|
||||||
|
import { TableContext } from './context';
|
||||||
|
import { TableMain } from './TableMain';
|
||||||
|
|
||||||
|
export const TableProvider = (props: any) => {
|
||||||
|
const {
|
||||||
|
rowKey = 'id',
|
||||||
|
dataRequest,
|
||||||
|
useResource = useGeneralResource,
|
||||||
|
defaultSelectedRowKeys,
|
||||||
|
useSelectedRowKeys = useDefaultSelectedRowKeys,
|
||||||
|
...others
|
||||||
|
} = props;
|
||||||
|
const { schema } = useDesignable();
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const [pagination, setPagination] = usePagination();
|
||||||
|
const { selectedRowKeys, setSelectedRowKeys } = useSelectedRowKeys();
|
||||||
|
console.log('props.useSelectedRowKeys', selectedRowKeys);
|
||||||
|
const [, refresh] = useState(uid());
|
||||||
|
const { resource } = useResource();
|
||||||
|
const { sortableField } = useCollectionContext();
|
||||||
|
const dragSort = props.dragSort;
|
||||||
|
const collectionFields = useCollectionFields(schema);
|
||||||
|
// console.log({ collectionFields, pagination });
|
||||||
|
const getDefaultParams = () => {
|
||||||
|
const defaultParams = { ...pagination };
|
||||||
|
if (dragSort) {
|
||||||
|
defaultParams['sort'] = [sortableField || 'sort'];
|
||||||
|
} else {
|
||||||
|
defaultParams['sort'] = (props.defaultSort || []).join(',');
|
||||||
|
}
|
||||||
|
defaultParams['defaultAppends'] = [...(props.defaultAppends || []), ...collectionFields];
|
||||||
|
if (props.defaultFilter) {
|
||||||
|
defaultParams['defaultFilter'] = props.defaultFilter;
|
||||||
|
}
|
||||||
|
console.log({ defaultParams });
|
||||||
|
return defaultParams;
|
||||||
|
};
|
||||||
|
const service = useRequest(
|
||||||
|
(params?: any) => {
|
||||||
|
if (!resource) {
|
||||||
|
return Promise.resolve({
|
||||||
|
list: field.value,
|
||||||
|
total: field?.value?.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return resource.list(params).then((res) => {
|
||||||
|
return {
|
||||||
|
list: res?.data || [],
|
||||||
|
total: res?.meta?.count || res?.data?.length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess(data: any) {
|
||||||
|
field.setValue(data?.list || []);
|
||||||
|
},
|
||||||
|
manual: true,
|
||||||
|
// defaultParams: [getDefaultParams()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
useDeepCompareEffectNoCheck(() => {
|
||||||
|
service.run(getDefaultParams());
|
||||||
|
}, [pagination?.pageSize, pagination?.page, props.dragSort, props.defaultSort, props.defaultFilter]);
|
||||||
|
return (
|
||||||
|
<TableContext.Provider
|
||||||
|
value={{
|
||||||
|
resource,
|
||||||
|
refresh: () => {
|
||||||
|
const { page = 1, pageSize } = pagination;
|
||||||
|
const total = props.clientSidePagination ? field?.value?.length : service?.data?.total;
|
||||||
|
const maxPage = Math.ceil(total / pageSize);
|
||||||
|
if (page > maxPage) {
|
||||||
|
setPagination({ page: maxPage });
|
||||||
|
} else {
|
||||||
|
refresh(uid());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedRowKeys,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
pagination,
|
||||||
|
setPagination,
|
||||||
|
service,
|
||||||
|
field,
|
||||||
|
schema,
|
||||||
|
props: { ...others, rowKey, dataRequest },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableMain />
|
||||||
|
</TableContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
7
packages/client/src/schemas/table/TableSortHandle.tsx
Normal file
7
packages/client/src/schemas/table/TableSortHandle.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { observer } from '@formily/react';
|
||||||
|
import { SortableRowHandle } from './Sortable';
|
||||||
|
|
||||||
|
export const TableSortHandle = observer((props: any) => {
|
||||||
|
return <SortableRowHandle {...props} />;
|
||||||
|
});
|
6
packages/client/src/schemas/table/context.ts
Normal file
6
packages/client/src/schemas/table/context.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
import { ITableContext, ITableRowContext } from './types';
|
||||||
|
|
||||||
|
export const TableContext = createContext<ITableContext>({} as any);
|
||||||
|
export const CollectionFieldContext = createContext(null);
|
||||||
|
export const TableRowContext = createContext<ITableRowContext>(null);
|
@ -3,10 +3,7 @@ import { range } from 'lodash';
|
|||||||
import { ISchema } from '@formily/react';
|
import { ISchema } from '@formily/react';
|
||||||
import { SchemaRenderer } from '../..';
|
import { SchemaRenderer } from '../..';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import {
|
import { CollectionsProvider, DesignableSwitchProvider } from '@nocobase/client/src/constate';
|
||||||
CollectionsProvider,
|
|
||||||
DesignableSwitchProvider,
|
|
||||||
} from '@nocobase/client/src/constate';
|
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
name: 'table1',
|
name: 'table1',
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import useRequest from '@ahooksjs/use-request';
|
||||||
|
import { useCollectionContext, useResourceRequest } from '../../../constate';
|
||||||
|
import { VisibleContext } from '../../../context';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
|
||||||
|
export const useActionLogDetailsResource = ({ onSuccess }) => {
|
||||||
|
const { props } = useTable();
|
||||||
|
const { collection } = useCollectionContext();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const resource = useResourceRequest({
|
||||||
|
resourceName: 'action_logs',
|
||||||
|
resourceKey: ctx.record[props.rowKey],
|
||||||
|
});
|
||||||
|
const service = useRequest(
|
||||||
|
(params?: any) => {
|
||||||
|
return resource.get({
|
||||||
|
...params,
|
||||||
|
appends: ['changes', 'user', 'collection'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formatResult: (result) => result?.data,
|
||||||
|
onSuccess,
|
||||||
|
manual: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const [visible] = useContext(VisibleContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
service.run({});
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
|
return { resource, service, initialValues: service.data, ...service };
|
||||||
|
};
|
@ -0,0 +1,35 @@
|
|||||||
|
import { useContext, createContext, useEffect, useState } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { ListOptions, Resource } from '../../../resource';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import { useResourceRequest } from '../../../constate';
|
||||||
|
|
||||||
|
export const useActionLogsResource = (options: any = {}) => {
|
||||||
|
const { props } = useTable();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
|
||||||
|
class ActionLogoResource extends Resource {
|
||||||
|
list(options?: ListOptions) {
|
||||||
|
console.log({ options });
|
||||||
|
let defaultFilter = options?.defaultFilter;
|
||||||
|
if (ctx?.record) {
|
||||||
|
const extra = {
|
||||||
|
index: ctx?.record?.id,
|
||||||
|
collection_name: props.collectionName,
|
||||||
|
};
|
||||||
|
if (defaultFilter) {
|
||||||
|
defaultFilter = { and: [defaultFilter, extra] };
|
||||||
|
} else {
|
||||||
|
defaultFilter = extra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.list({ ...options, defaultFilter });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = useResourceRequest('action_logs', ActionLogoResource);
|
||||||
|
|
||||||
|
return {
|
||||||
|
resource,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Schema } from '@formily/react';
|
||||||
|
import { isColumn } from '../utils';
|
||||||
|
|
||||||
|
export const useCollectionFields = (schema: Schema) => {
|
||||||
|
const columns = schema.reduceProperties((columns, current) => {
|
||||||
|
if (isColumn(current)) {
|
||||||
|
if (current['x-hidden']) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
if (current['x-display'] && current['x-display'] !== 'visible') {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
return [...columns, current];
|
||||||
|
}
|
||||||
|
return [...columns];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return columns
|
||||||
|
.map((column) => {
|
||||||
|
const columnProps = column['x-component-props'] || {};
|
||||||
|
return columnProps.fieldName;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
17
packages/client/src/schemas/table/hooks/useDataSource.ts
Normal file
17
packages/client/src/schemas/table/hooks/useDataSource.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useTable } from './useTable';
|
||||||
|
|
||||||
|
export const useDataSource = () => {
|
||||||
|
const {
|
||||||
|
pagination,
|
||||||
|
field,
|
||||||
|
props: { clientSidePagination, dataRequest },
|
||||||
|
} = useTable();
|
||||||
|
let dataSource = field.value;
|
||||||
|
// if (pagination && (clientSidePagination || !dataRequest)) {
|
||||||
|
// const { page = 1, pageSize } = pagination;
|
||||||
|
// const startIndex = (page - 1) * pageSize;
|
||||||
|
// const endIndex = startIndex + pageSize - 1;
|
||||||
|
// dataSource = field.value?.slice(startIndex, endIndex + 1);
|
||||||
|
// }
|
||||||
|
return dataSource;
|
||||||
|
};
|
@ -0,0 +1,5 @@
|
|||||||
|
export const useDefaultRowSelection = () => {
|
||||||
|
return {
|
||||||
|
type: 'checkbox',
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,6 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const useDefaultSelectedRowKeys = () => {
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<any>([]);
|
||||||
|
return { selectedRowKeys, setSelectedRowKeys };
|
||||||
|
};
|
33
packages/client/src/schemas/table/hooks/usePagination.ts
Normal file
33
packages/client/src/schemas/table/hooks/usePagination.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
|
||||||
|
export const usePagination = () => {
|
||||||
|
const field = useField<ArrayField>();
|
||||||
|
const paginationProps = field.componentProps.pagination;
|
||||||
|
|
||||||
|
let pagination = paginationProps;
|
||||||
|
|
||||||
|
// const [pagination, setPagination] = useState(() => {
|
||||||
|
// if (!paginationProps) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// const { defaultPageSize = 10, ...others } = paginationProps;
|
||||||
|
// return { page: 1, pageSize: defaultPageSize, ...others };
|
||||||
|
// });
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (!paginationProps) {
|
||||||
|
// return setPagination(false);
|
||||||
|
// }
|
||||||
|
// const { defaultPageSize = 10, ...others } = paginationProps;
|
||||||
|
// setPagination({ page: 1, pageSize: defaultPageSize, ...others });
|
||||||
|
// }, [paginationProps]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
pagination,
|
||||||
|
(params) => {
|
||||||
|
const defaults = field.componentProps.pagination;
|
||||||
|
field.componentProps.pagination = { ...defaults, ...params };
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
51
packages/client/src/schemas/table/hooks/useResource.ts
Normal file
51
packages/client/src/schemas/table/hooks/useResource.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { Schema } from '@formily/react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import useRequest from '@ahooksjs/use-request';
|
||||||
|
import { useCollectionContext, useResourceRequest } from '../../../constate';
|
||||||
|
import { useDesignable } from '../..';
|
||||||
|
|
||||||
|
export const useResource = ({ onSuccess, manual = true }) => {
|
||||||
|
const { props } = useTable();
|
||||||
|
const { collection } = useCollectionContext();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const resource = useResourceRequest({
|
||||||
|
resourceName: collection?.name || props.collectionName,
|
||||||
|
resourceKey: ctx.record[props.rowKey],
|
||||||
|
});
|
||||||
|
const { schema } = useDesignable();
|
||||||
|
const fieldFields = (schema: Schema) => {
|
||||||
|
const names = [];
|
||||||
|
schema.reduceProperties((buf, current) => {
|
||||||
|
if (current['x-component'] === 'Form.Field') {
|
||||||
|
const fieldName = current['x-component-props']?.['fieldName'];
|
||||||
|
if (fieldName) {
|
||||||
|
buf.push(fieldName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const fieldNames = fieldFields(current);
|
||||||
|
buf.push(...fieldNames);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}, names);
|
||||||
|
return names;
|
||||||
|
};
|
||||||
|
console.log(
|
||||||
|
'collection?.name || props.collectionName',
|
||||||
|
collection?.name || props.collectionName,
|
||||||
|
// fieldFields(schema),
|
||||||
|
);
|
||||||
|
const service = useRequest(
|
||||||
|
(params?: any) => {
|
||||||
|
console.log('Table.useResource', params);
|
||||||
|
return resource.get({ ...params, appends: fieldFields(schema) });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formatResult: (result) => result?.data,
|
||||||
|
onSuccess,
|
||||||
|
manual,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return { resource, service, initialValues: service.data, ...service };
|
||||||
|
};
|
6
packages/client/src/schemas/table/hooks/useTable.ts
Normal file
6
packages/client/src/schemas/table/hooks/useTable.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { TableContext } from '../context';
|
||||||
|
|
||||||
|
export const useTable = () => {
|
||||||
|
return useContext(TableContext);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { useTable } from './useTable';
|
||||||
|
|
||||||
|
export const useTableActionBars = () => {
|
||||||
|
const {
|
||||||
|
field,
|
||||||
|
schema,
|
||||||
|
props: { rowKey },
|
||||||
|
} = useTable();
|
||||||
|
|
||||||
|
const bars = schema.reduceProperties((bars, current) => {
|
||||||
|
if (current['x-component'] === 'Table.ActionBar') {
|
||||||
|
return [...bars, current];
|
||||||
|
}
|
||||||
|
return [...bars];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return bars;
|
||||||
|
};
|
69
packages/client/src/schemas/table/hooks/useTableColumns.tsx
Normal file
69
packages/client/src/schemas/table/hooks/useTableColumns.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React, { createContext } from 'react';
|
||||||
|
import { findIndex } from 'lodash';
|
||||||
|
import { RecursionField, Schema } from '@formily/react';
|
||||||
|
import { useDesignable } from '../..';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import { useCollectionContext } from '../../../constate';
|
||||||
|
import { Table } from '../Table';
|
||||||
|
import { isColumn } from '../utils';
|
||||||
|
import { AddColumn } from '../AddColumn';
|
||||||
|
import { CollectionFieldContext } from '../context';
|
||||||
|
|
||||||
|
export const useTableColumns = () => {
|
||||||
|
const {
|
||||||
|
field,
|
||||||
|
schema,
|
||||||
|
props: { rowKey },
|
||||||
|
} = useTable();
|
||||||
|
const { designable } = useDesignable();
|
||||||
|
|
||||||
|
const { getField } = useCollectionContext();
|
||||||
|
|
||||||
|
const columnSchemas = schema.reduceProperties((columns, current) => {
|
||||||
|
if (isColumn(current)) {
|
||||||
|
if (current['x-hidden']) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
if (current['x-display'] && current['x-display'] !== 'visible') {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
return [...columns, current];
|
||||||
|
}
|
||||||
|
return [...columns];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columns: any[] = [].concat(
|
||||||
|
columnSchemas.map((column: Schema) => {
|
||||||
|
const columnProps = column['x-component-props'] || {};
|
||||||
|
const collectionField = getField(columnProps.fieldName);
|
||||||
|
return {
|
||||||
|
title: (
|
||||||
|
<CollectionFieldContext.Provider value={collectionField}>
|
||||||
|
<RecursionField name={column.name} schema={column} onlyRenderSelf />
|
||||||
|
</CollectionFieldContext.Provider>
|
||||||
|
),
|
||||||
|
dataIndex: column.name,
|
||||||
|
...columnProps,
|
||||||
|
render: (_: any, record: any) => {
|
||||||
|
const index = findIndex(field.value, (item) => item[rowKey] === record[rowKey]);
|
||||||
|
return (
|
||||||
|
<CollectionFieldContext.Provider value={collectionField}>
|
||||||
|
<TableRowContext.Provider value={{ index, record }}>
|
||||||
|
<Table.Cell schema={column} />
|
||||||
|
</TableRowContext.Provider>
|
||||||
|
</CollectionFieldContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (designable && schema['x-designable-bar'] && schema['x-designable-bar'] !== 'Table.SimpleDesignableBar') {
|
||||||
|
columns.push({
|
||||||
|
title: <AddColumn />,
|
||||||
|
dataIndex: 'addnew',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
|
||||||
|
export function useTableCreateAction() {
|
||||||
|
const {
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
resource,
|
||||||
|
refresh,
|
||||||
|
props: { refreshRequestOnChange },
|
||||||
|
} = useTable();
|
||||||
|
const form = useForm();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
console.log('refreshRequestOnChange', refreshRequestOnChange);
|
||||||
|
if (refreshRequestOnChange) {
|
||||||
|
await resource.create(form.values);
|
||||||
|
await form.reset();
|
||||||
|
return service.refresh();
|
||||||
|
}
|
||||||
|
field.unshift(form.values);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import { VisibleContext } from '../../../context';
|
||||||
|
import { findIndex } from 'lodash';
|
||||||
|
|
||||||
|
export const useTableDestroyAction = () => {
|
||||||
|
const {
|
||||||
|
resource,
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
selectedRowKeys,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
refresh,
|
||||||
|
props: { refreshRequestOnChange, rowKey },
|
||||||
|
} = useTable();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const [, setVisible] = useContext(VisibleContext);
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
if (refreshRequestOnChange) {
|
||||||
|
const rowKeys = selectedRowKeys || [];
|
||||||
|
if (ctx) {
|
||||||
|
rowKeys.push(ctx.record[rowKey]);
|
||||||
|
}
|
||||||
|
await resource.destroy({
|
||||||
|
[`${rowKey}.in`]: rowKeys,
|
||||||
|
});
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
setVisible && setVisible(false);
|
||||||
|
return service.refresh();
|
||||||
|
}
|
||||||
|
if (ctx) {
|
||||||
|
console.log('ctx.index', ctx.index);
|
||||||
|
field.remove(ctx.index);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
const rowKeys = [...selectedRowKeys];
|
||||||
|
while (rowKeys.length) {
|
||||||
|
const key = rowKeys.shift();
|
||||||
|
const index = findIndex(field.value, (item) => item[rowKey] === key);
|
||||||
|
field.remove(index);
|
||||||
|
}
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
refresh();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,51 @@
|
|||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import { useCollectionContext } from '../../../constate';
|
||||||
|
|
||||||
|
export const useTableExportAction = () => {
|
||||||
|
const {
|
||||||
|
resource,
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
selectedRowKeys,
|
||||||
|
setSelectedRowKeys,
|
||||||
|
refresh,
|
||||||
|
schema,
|
||||||
|
props: { refreshRequestOnChange, rowKey },
|
||||||
|
} = useTable();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
|
||||||
|
const actionField = useField();
|
||||||
|
const fieldNames = actionField.componentProps.fieldNames || [];
|
||||||
|
const { getField } = useCollectionContext();
|
||||||
|
|
||||||
|
const columns = fieldNames
|
||||||
|
.map((name) => {
|
||||||
|
const f = getField(name);
|
||||||
|
return {
|
||||||
|
title: f?.uiSchema.title,
|
||||||
|
name,
|
||||||
|
sort: f?.sort,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.sort - b.sort);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
const rowKeys = selectedRowKeys || [];
|
||||||
|
const { filter = {}, ...others } = service.params[0];
|
||||||
|
if (rowKeys.length) {
|
||||||
|
filter[`${rowKey}.in`] = rowKeys;
|
||||||
|
}
|
||||||
|
await resource.export({
|
||||||
|
...others,
|
||||||
|
columns,
|
||||||
|
perPage: -1,
|
||||||
|
page: 1,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
import { useTable } from './useTable';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
|
||||||
|
export function useTableFilterAction() {
|
||||||
|
const {
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
refresh,
|
||||||
|
props: { refreshRequestOnChange },
|
||||||
|
} = useTable();
|
||||||
|
const form = useForm();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
console.log('useTableFilterAction', form.values);
|
||||||
|
if (refreshRequestOnChange) {
|
||||||
|
return service.run({
|
||||||
|
...service.params[0],
|
||||||
|
// filter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
16
packages/client/src/schemas/table/hooks/useTableIndex.ts
Normal file
16
packages/client/src/schemas/table/hooks/useTableIndex.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
|
||||||
|
export const useTableIndex = () => {
|
||||||
|
const { pagination, props } = useTable();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const { pageSize, page = 1 } = pagination;
|
||||||
|
console.log({ pageSize, page }, ctx.index);
|
||||||
|
return ctx.index + (page - 1) * pageSize;
|
||||||
|
if (pagination && !props.clientSidePagination) {
|
||||||
|
const { pageSize, page = 1 } = pagination;
|
||||||
|
return ctx.index + (page - 1) * pageSize;
|
||||||
|
}
|
||||||
|
return ctx.index;
|
||||||
|
};
|
6
packages/client/src/schemas/table/hooks/useTableRow.ts
Normal file
6
packages/client/src/schemas/table/hooks/useTableRow.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
|
||||||
|
export const useTableRow = () => {
|
||||||
|
return useContext(TableRowContext);
|
||||||
|
};
|
@ -0,0 +1,7 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
|
||||||
|
export const useTableRowRecord = () => {
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
return ctx.record;
|
||||||
|
};
|
@ -0,0 +1,35 @@
|
|||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useTable } from './useTable';
|
||||||
|
import { TableRowContext } from '../context';
|
||||||
|
import { DescriptionsContext } from '../../form';
|
||||||
|
|
||||||
|
export const useTableUpdateAction = () => {
|
||||||
|
const {
|
||||||
|
resource,
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
refresh,
|
||||||
|
props: { refreshRequestOnChange, rowKey },
|
||||||
|
} = useTable();
|
||||||
|
const ctx = useContext(TableRowContext);
|
||||||
|
const form = useForm();
|
||||||
|
const { service: formService } = useContext(DescriptionsContext);
|
||||||
|
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
if (refreshRequestOnChange) {
|
||||||
|
await resource.save(form.values, {
|
||||||
|
resourceKey: ctx.record[rowKey],
|
||||||
|
});
|
||||||
|
if (formService) {
|
||||||
|
await formService.refresh();
|
||||||
|
}
|
||||||
|
await service.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.value[ctx.index] = form.values;
|
||||||
|
// refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
10
packages/client/src/schemas/table/hooks/useTotal.ts
Normal file
10
packages/client/src/schemas/table/hooks/useTotal.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { useTable } from './useTable';
|
||||||
|
|
||||||
|
export const useTotal = () => {
|
||||||
|
const {
|
||||||
|
field,
|
||||||
|
service,
|
||||||
|
props: { clientSidePagination },
|
||||||
|
} = useTable();
|
||||||
|
return clientSidePagination ? field?.value?.length : service?.data?.total;
|
||||||
|
};
|
5
packages/client/src/schemas/table/index.ts
Normal file
5
packages/client/src/schemas/table/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './Table';
|
||||||
|
export * from './context';
|
||||||
|
|
||||||
|
//hooks export
|
||||||
|
export * from './hooks/useTable';
|
File diff suppressed because it is too large
Load Diff
@ -186,9 +186,7 @@ td.nb-table-operation {
|
|||||||
.ant-formily-item-feedback-layout-popover {
|
.ant-formily-item-feedback-layout-popover {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.ant-formily-item-control
|
.ant-formily-item-control .ant-formily-item-control-content .ant-formily-item-control-content-component {
|
||||||
.ant-formily-item-control-content
|
|
||||||
.ant-formily-item-control-content-component {
|
|
||||||
min-height: inherit;
|
min-height: inherit;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
}
|
||||||
|
22
packages/client/src/schemas/table/types.ts
Normal file
22
packages/client/src/schemas/table/types.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ArrayField } from '@formily/core';
|
||||||
|
import { Schema } from '@formily/react';
|
||||||
|
import { BaseResult } from '@ahooksjs/use-request/lib/types';
|
||||||
|
import { Resource } from '../../resource';
|
||||||
|
|
||||||
|
export interface ITableContext {
|
||||||
|
props: any;
|
||||||
|
field: ArrayField;
|
||||||
|
schema: Schema;
|
||||||
|
service: BaseResult<any, any>;
|
||||||
|
selectedRowKeys?: any;
|
||||||
|
setSelectedRowKeys?: any;
|
||||||
|
pagination?: any;
|
||||||
|
setPagination?: any;
|
||||||
|
refresh?: any;
|
||||||
|
resource?: Resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITableRowContext {
|
||||||
|
index: number;
|
||||||
|
record: any;
|
||||||
|
}
|
164
packages/client/src/schemas/table/utils.ts
Normal file
164
packages/client/src/schemas/table/utils.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { Schema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import { interfaces } from '../database-field/interfaces';
|
||||||
|
import { ISchema } from '..';
|
||||||
|
|
||||||
|
export const fieldsToSortColumns = (fields: any[]) => {
|
||||||
|
const dataSource = [];
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const fieldOption = interfaces.get(field.interface);
|
||||||
|
if (!fieldOption?.sortable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataSource.push({
|
||||||
|
value: field.name,
|
||||||
|
label: field?.uiSchema?.title,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return dataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fieldsToFilterColumns = (fields: any[], options: any = {}) => {
|
||||||
|
const { fieldNames = [] } = options;
|
||||||
|
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,
|
||||||
|
'x-decorator': 'FormilyFormItem',
|
||||||
|
title: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return properties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateActionSchema = (type) => {
|
||||||
|
const actions: { [key: string]: ISchema } = {
|
||||||
|
filter: {
|
||||||
|
key: uid(),
|
||||||
|
name: uid(),
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Filter') }}",
|
||||||
|
'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: {
|
||||||
|
key: uid(),
|
||||||
|
type: 'void',
|
||||||
|
name: uid(),
|
||||||
|
title: "{{ t('Export') }}",
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'AddNew.Displayed',
|
||||||
|
'x-decorator-props': {
|
||||||
|
displayName: 'export',
|
||||||
|
},
|
||||||
|
'x-action-type': 'export',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-designable-bar': 'Table.ExportActionDesignableBar',
|
||||||
|
'x-component-props': {
|
||||||
|
fieldNames: [],
|
||||||
|
icon: 'ExportOutlined',
|
||||||
|
useAction: '{{ Table.useTableExportAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: uid(),
|
||||||
|
type: 'void',
|
||||||
|
name: uid(),
|
||||||
|
title: "{{ t('Add new') }}",
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'AddNew.Displayed',
|
||||||
|
'x-decorator-props': {
|
||||||
|
displayName: 'create',
|
||||||
|
},
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
icon: 'PlusOutlined',
|
||||||
|
},
|
||||||
|
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||||
|
properties: {
|
||||||
|
modal: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{ t('Add record') }}",
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'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: "{{ t('Delete') }}",
|
||||||
|
'x-align': 'right',
|
||||||
|
'x-decorator': 'AddNew.Displayed',
|
||||||
|
'x-decorator-props': {
|
||||||
|
displayName: 'destroy',
|
||||||
|
},
|
||||||
|
'x-action-type': 'destroy',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||||
|
'x-component-props': {
|
||||||
|
confirm: {
|
||||||
|
title: "{{ t('Delete record') }}",
|
||||||
|
content: "{{ t('Are you sure you want to delete it?') }}",
|
||||||
|
},
|
||||||
|
useAction: '{{ Table.useTableDestroyAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
view: {},
|
||||||
|
update: {},
|
||||||
|
};
|
||||||
|
return actions[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isColumn(schema: Schema) {
|
||||||
|
return ['Table.Column'].includes(schema['x-component']);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isColumnComponent(component: string) {
|
||||||
|
return ['Table.Operation', 'Table.Column'].includes(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isOperationColumn(schema: Schema) {
|
||||||
|
return ['Table.Operation'].includes(schema['x-component']);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user