improve Table Operation
This commit is contained in:
parent
6ff216be6b
commit
754be7d1d1
@ -13,15 +13,21 @@ import {
|
||||
} from '@formily/react';
|
||||
import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd';
|
||||
import { Link, useHistory, LinkProps } from 'react-router-dom';
|
||||
import { useDesignable, useDefaultAction } from '..';
|
||||
import { useDesignable, useDefaultAction, updateSchema } from '..';
|
||||
import './style.less';
|
||||
import { uid } from '@formily/shared';
|
||||
import cls from 'classnames';
|
||||
import { MenuOutlined } from '@ant-design/icons';
|
||||
import { FormLayout } from '@formily/antd';
|
||||
import IconPicker from '../../components/icon-picker';
|
||||
import { getSchemaPath, useSchemaComponent } from '../../components/schema-renderer';
|
||||
import {
|
||||
findPropertyByPath,
|
||||
getSchemaPath,
|
||||
useSchemaComponent,
|
||||
} from '../../components/schema-renderer';
|
||||
import { VisibleContext } from '../../context';
|
||||
import { DndContext, DragOverlay } from '@dnd-kit/core';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
export const ButtonComponentContext = createContext(null);
|
||||
|
||||
@ -192,9 +198,49 @@ Action.Drawer = observer((props: any) => {
|
||||
});
|
||||
|
||||
Action.Dropdown = observer((props: any) => {
|
||||
const { schema } = useDesignable();
|
||||
const button = useContext(ButtonComponentContext);
|
||||
const { root, schema, insertAfter, remove } = useDesignable();
|
||||
const moveToAfter = (path1, path2) => {
|
||||
if (!path1 || !path2) {
|
||||
return;
|
||||
}
|
||||
if (path1.join('.') === path2.join('.')) {
|
||||
return;
|
||||
}
|
||||
const data = findPropertyByPath(root, path1);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
remove(path1);
|
||||
return insertAfter(data.toJSON(), path2);
|
||||
};
|
||||
const [dragOverlayContent, setDragOverlayContent] = useState('');
|
||||
return (
|
||||
<DndContext
|
||||
onDragStart={(event) => {
|
||||
console.log({ event });
|
||||
setDragOverlayContent(event.active.data?.current?.title || '');
|
||||
}}
|
||||
onDragEnd={async (event) => {
|
||||
const path1 = event.active?.data?.current?.path;
|
||||
const path2 = event.over?.data?.current?.path;
|
||||
const data = moveToAfter(path1, path2);
|
||||
await updateSchema(data);
|
||||
}}
|
||||
>
|
||||
{createPortal(
|
||||
<DragOverlay
|
||||
zIndex={2222}
|
||||
style={{ pointerEvents: 'none', whiteSpace: 'nowrap' }}
|
||||
dropAnimation={{
|
||||
duration: 10,
|
||||
easing: 'cubic-bezier(0.18, 0.67, 0.6, 1.22)',
|
||||
}}
|
||||
>
|
||||
{dragOverlayContent}
|
||||
</DragOverlay>,
|
||||
document.body,
|
||||
)}
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
{...props}
|
||||
@ -206,6 +252,7 @@ Action.Dropdown = observer((props: any) => {
|
||||
>
|
||||
<span>{button}</span>
|
||||
</Dropdown>
|
||||
</DndContext>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -217,17 +217,16 @@ function generateCardItemSchema(component) {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '操作',
|
||||
'x-component': 'Table.Column',
|
||||
'x-component': 'Table.Operation',
|
||||
'x-component-props': {
|
||||
className: 'nb-table-operation',
|
||||
},
|
||||
'x-designable-bar': 'Table.OperationDesignableBar',
|
||||
'x-designable-bar': 'Table.Operation.DesignableBar',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
name: 'action1',
|
||||
'x-component': 'Action',
|
||||
// 'x-designable-bar': 'Action.DesignableBar',
|
||||
'x-component-props': {
|
||||
icon: 'EllipsisOutlined',
|
||||
},
|
||||
@ -254,6 +253,7 @@ function generateCardItemSchema(component) {
|
||||
title: '查看',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'view',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
@ -274,10 +274,10 @@ function generateCardItemSchema(component) {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '详情',
|
||||
'x-designable-bar': 'Tabs.TabPane.DesignableBar',
|
||||
'x-designable-bar':
|
||||
'Tabs.TabPane.DesignableBar',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-component-props': {
|
||||
},
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
@ -299,6 +299,7 @@ function generateCardItemSchema(component) {
|
||||
title: '编辑',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'update',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
@ -328,6 +329,8 @@ function generateCardItemSchema(component) {
|
||||
type: 'void',
|
||||
title: '删除',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'destroy',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
|
@ -121,7 +121,7 @@ export const Menu: any = observer((props: any) => {
|
||||
}
|
||||
remove(path1);
|
||||
return insertAfter(data.toJSON(), path2);
|
||||
}
|
||||
};
|
||||
const fieldSchema = useFieldSchema();
|
||||
console.log('Menu.schema', schema, fieldSchema);
|
||||
const [selectedKey, setSelectedKey] = useState(
|
||||
@ -325,9 +325,17 @@ Menu.Action = observer((props: any) => {
|
||||
await run();
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
<SortableItem
|
||||
id={schema.name}
|
||||
data={{
|
||||
title: schema.title,
|
||||
path: getSchemaPath(schema),
|
||||
}}
|
||||
>
|
||||
{schema.title}
|
||||
<DesignableBar />
|
||||
</SortableItem>
|
||||
</AntdMenu.Item>
|
||||
{props.children}
|
||||
{/* <RecursionField schema={schema} onlyRenderProperties /> */}
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
} from '../../components/schema-renderer';
|
||||
import { updateSchema } from '..';
|
||||
import { Schema } from '@formily/react';
|
||||
import { isColumn, isColumnComponent } from '.';
|
||||
|
||||
export const RowDraggableContext = createContext({});
|
||||
export const ColDraggableContext = createContext(null);
|
||||
@ -188,12 +189,15 @@ export function SortableHeaderRow(props) {
|
||||
|
||||
export function SortableHeaderCell(props) {
|
||||
const id = useContext(CellContext);
|
||||
if (['RC_TABLE_KEY', 'addnew'].includes(id)) {
|
||||
if (['RC_TABLE_KEY', 'addnew', 'operation'].includes(id)) {
|
||||
return <th {...props} />;
|
||||
}
|
||||
const { schema } = useDesignable();
|
||||
const columns: Schema[] = schema.reduceProperties((columns, current) => {
|
||||
if (current['x-component'] === 'Table.Column' && current.name === id) {
|
||||
if (!isColumn(current)) {
|
||||
return [...columns];
|
||||
}
|
||||
if (current.name === id) {
|
||||
return [...columns, current];
|
||||
}
|
||||
return [...columns];
|
||||
|
@ -59,45 +59,7 @@ import {
|
||||
SortableRowHandle,
|
||||
} from './Sortable';
|
||||
import { DragHandle, Droppable, SortableItem } from '../../components/Sortable';
|
||||
|
||||
const SyntheticListenerMapContext = createContext(null);
|
||||
|
||||
function DragableBodyRow(props: any) {
|
||||
const {
|
||||
className,
|
||||
style: prevStyle,
|
||||
['data-row-key']: dataRowKey,
|
||||
...others
|
||||
} = props;
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
overIndex,
|
||||
transform,
|
||||
transition,
|
||||
} = useSortable({ id: dataRowKey });
|
||||
|
||||
const style = {
|
||||
...prevStyle,
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<SyntheticListenerMapContext.Provider value={listeners}>
|
||||
<tr
|
||||
className={cls(className)}
|
||||
ref={setNodeRef}
|
||||
{...others}
|
||||
{...attributes}
|
||||
style={style}
|
||||
>
|
||||
{props.children}
|
||||
</tr>
|
||||
</SyntheticListenerMapContext.Provider>
|
||||
);
|
||||
}
|
||||
import { VisibleContext } from '../../context';
|
||||
|
||||
export interface ITableContext {
|
||||
props: any;
|
||||
@ -265,6 +227,68 @@ const useTableActionBars = () => {
|
||||
return bars;
|
||||
};
|
||||
|
||||
export function isOperationColumn(schema: Schema) {
|
||||
return ['Table.Operation'].includes(schema['x-component']);
|
||||
}
|
||||
|
||||
export function isColumn(schema: Schema) {
|
||||
return ['Table.Column'].includes(schema['x-component']);
|
||||
}
|
||||
|
||||
export function isColumnComponent(component: string) {
|
||||
return ['Table.Operation', 'Table.Column'].includes(component);
|
||||
}
|
||||
|
||||
const useTableOperation = () => {
|
||||
const {
|
||||
field,
|
||||
schema,
|
||||
props: { rowKey },
|
||||
} = useTable();
|
||||
const findActionDropdown = (schema: Schema) => {
|
||||
return schema.reduceProperties((s, current) => {
|
||||
if (current['x-component'] === 'Action.Dropdown') {
|
||||
return current;
|
||||
}
|
||||
const action = findActionDropdown(current);
|
||||
if (action) {
|
||||
return action;
|
||||
}
|
||||
return s;
|
||||
}, new Schema({}));
|
||||
};
|
||||
const operationSchema = schema.reduceProperties((s, current) => {
|
||||
if (isOperationColumn(current)) {
|
||||
return current;
|
||||
}
|
||||
return s;
|
||||
}, new Schema({}));
|
||||
const dropdownSchema = findActionDropdown(operationSchema);
|
||||
const columnProps = operationSchema?.['x-component-props'] || {};
|
||||
return {
|
||||
title: <Table.Operation path={getSchemaPath(dropdownSchema)} />,
|
||||
dataIndex: 'operation',
|
||||
...columnProps,
|
||||
render: (_: any, record: any) => {
|
||||
const index = findIndex(
|
||||
field.value,
|
||||
(item) => item[rowKey] === record[rowKey],
|
||||
);
|
||||
return (
|
||||
<div className={'nb-table-column'}>
|
||||
<TableRowContext.Provider value={{ index, record }}>
|
||||
<RecursionField
|
||||
schema={operationSchema}
|
||||
name={index}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
</TableRowContext.Provider>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const useTableColumns = () => {
|
||||
const {
|
||||
field,
|
||||
@ -275,19 +299,21 @@ const useTableColumns = () => {
|
||||
|
||||
const { getField } = useCollectionContext();
|
||||
|
||||
const columns = schema.reduceProperties((columns, current) => {
|
||||
if (current['x-component'] === 'Table.Column') {
|
||||
const operation = useTableOperation();
|
||||
|
||||
const columnSchemas = schema.reduceProperties((columns, current) => {
|
||||
if (isColumn(current)) {
|
||||
return [...columns, current];
|
||||
}
|
||||
return [...columns];
|
||||
}, []);
|
||||
|
||||
return columns
|
||||
.map((column: Schema) => {
|
||||
const columns: any[] = [operation].concat(
|
||||
columnSchemas.map((column: Schema) => {
|
||||
const columnProps = column['x-component-props'] || {};
|
||||
const collectionField = getField(columnProps.fieldName);
|
||||
return {
|
||||
title: () => (
|
||||
title: (
|
||||
<CollectionFieldContext.Provider value={collectionField}>
|
||||
<RecursionField name={column.name} schema={column} onlyRenderSelf />
|
||||
</CollectionFieldContext.Provider>
|
||||
@ -308,17 +334,16 @@ const useTableColumns = () => {
|
||||
);
|
||||
},
|
||||
};
|
||||
})
|
||||
.concat(
|
||||
designable
|
||||
? [
|
||||
{
|
||||
}),
|
||||
);
|
||||
|
||||
if (designable) {
|
||||
columns.push({
|
||||
title: <AddColumn />,
|
||||
dataIndex: 'addnew',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
);
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
};
|
||||
|
||||
function AddColumn() {
|
||||
@ -593,6 +618,7 @@ const TableProvider = (props: any) => {
|
||||
};
|
||||
|
||||
export const Table: any = observer((props: any) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
return (
|
||||
<CollectionProvider collectionName={props.collectionName}>
|
||||
<DisplayedMapProvider>
|
||||
@ -712,17 +738,117 @@ function generateActionSchema(type) {
|
||||
'x-decorator-props': {
|
||||
displayName: 'destroy',
|
||||
},
|
||||
'x-action-type': 'destroy',
|
||||
'x-component': 'Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
},
|
||||
view: {},
|
||||
update: {},
|
||||
};
|
||||
return actions[type];
|
||||
}
|
||||
|
||||
function generateMenuActionSchema(type) {
|
||||
const actions: { [key: string]: ISchema } = {
|
||||
view: {
|
||||
key: uid(),
|
||||
name: uid(),
|
||||
type: 'void',
|
||||
title: '查看',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'view',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '查看',
|
||||
'x-component': 'Action.Modal',
|
||||
'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: '详情',
|
||||
'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: '编辑',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'update',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
title: '编辑数据',
|
||||
'x-decorator': 'Form',
|
||||
'x-decorator-props': {
|
||||
useResource: '{{ Table.useResource }}',
|
||||
useValues: '{{ Table.useTableRowRecord }}',
|
||||
},
|
||||
'x-component': 'Action.Modal',
|
||||
'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: '删除',
|
||||
'x-component': 'Menu.Action',
|
||||
'x-designable-bar': 'Table.Action.DesignableBar',
|
||||
'x-action-type': 'destroy',
|
||||
'x-component-props': {
|
||||
useAction: '{{ Table.useTableDestroyAction }}',
|
||||
},
|
||||
},
|
||||
};
|
||||
return actions[type];
|
||||
}
|
||||
|
||||
function AddActionButton() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const displayed = useDisplayedMapContext();
|
||||
@ -1039,12 +1165,36 @@ Table.Filter.DesignableBar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
Table.OperationDesignableBar = () => {
|
||||
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||
Table.Operation = observer((props: any) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const isPopup = Object.keys(schema.properties || {}).length > 0;
|
||||
const inActionBar = schema.parent['x-component'] === 'Table.ActionBar';
|
||||
return (
|
||||
<div className={'nb-table-column'}>
|
||||
操作
|
||||
<Table.Operation.DesignableBar path={props.path} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Table.Operation.Cell = 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>
|
||||
);
|
||||
});
|
||||
|
||||
Table.Operation.DesignableBar = (props) => {
|
||||
const { schema, remove, refresh, appendChild } = useDesignable(props.path);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const map = new Map();
|
||||
schema.mapProperties((s) => {
|
||||
if (!s['x-action-type']) {
|
||||
return;
|
||||
}
|
||||
map.set(s['x-action-type'], s.name);
|
||||
});
|
||||
return (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<span
|
||||
@ -1071,7 +1221,20 @@ Table.OperationDesignableBar = () => {
|
||||
<SwitchMenuItem
|
||||
key={item.name}
|
||||
title={item.title}
|
||||
onChange={async (checked) => {}}
|
||||
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([
|
||||
...props.path,
|
||||
map.get(item.name),
|
||||
]);
|
||||
await removeSchema(removed);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Menu.ItemGroup>
|
||||
@ -1174,6 +1337,9 @@ Table.Cell = 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} />;
|
||||
}
|
||||
return (
|
||||
<RecursionField
|
||||
schema={
|
||||
|
Loading…
Reference in New Issue
Block a user