diff --git a/packages/client/src/schemas/action/index.tsx b/packages/client/src/schemas/action/index.tsx index 3590f3bed..ddd2d99f4 100644 --- a/packages/client/src/schemas/action/index.tsx +++ b/packages/client/src/schemas/action/index.tsx @@ -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); @@ -45,7 +51,7 @@ export const Action: any = observer((props: any) => { }} > {schema.title} - + ); return ( @@ -192,20 +198,61 @@ 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 ( - - - - } + { + 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); + }} > - {button} - + {createPortal( + + {dragOverlayContent} + , + document.body, + )} + + + + } + > + {button} + + ); }); diff --git a/packages/client/src/schemas/add-new/index.tsx b/packages/client/src/schemas/add-new/index.tsx index 0c392e63f..7c0cd8633 100644 --- a/packages/client/src/schemas/add-new/index.tsx +++ b/packages/client/src/schemas/add-new/index.tsx @@ -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 }}', }, diff --git a/packages/client/src/schemas/menu/index.tsx b/packages/client/src/schemas/menu/index.tsx index c318e9883..208a5c282 100644 --- a/packages/client/src/schemas/menu/index.tsx +++ b/packages/client/src/schemas/menu/index.tsx @@ -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( @@ -326,8 +326,16 @@ Menu.Action = observer((props: any) => { setVisible(true); }} > - {schema.title} - + + {schema.title} + + {props.children} {/* */} diff --git a/packages/client/src/schemas/table/Sortable.tsx b/packages/client/src/schemas/table/Sortable.tsx index ef3dc589c..edfa660fe 100644 --- a/packages/client/src/schemas/table/Sortable.tsx +++ b/packages/client/src/schemas/table/Sortable.tsx @@ -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 ; } 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]; diff --git a/packages/client/src/schemas/table/index.tsx b/packages/client/src/schemas/table/index.tsx index cc18c32e2..1b0c23819 100644 --- a/packages/client/src/schemas/table/index.tsx +++ b/packages/client/src/schemas/table/index.tsx @@ -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 ( - - - {props.children} - - - ); -} +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: , + dataIndex: 'operation', + ...columnProps, + render: (_: any, record: any) => { + const index = findIndex( + field.value, + (item) => item[rowKey] === record[rowKey], + ); + return ( +
+ + + +
+ ); + }, + }; +}; + 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: ( @@ -308,17 +334,16 @@ const useTableColumns = () => { ); }, }; - }) - .concat( - designable - ? [ - { - title: , - dataIndex: 'addnew', - }, - ] - : [], - ); + }), + ); + + if (designable) { + columns.push({ + title: , + 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 ( @@ -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 ( +
+ 操作 + +
+ ); +}); +Table.Operation.Cell = observer((props: any) => { + const ctx = useContext(TableRowContext); + const schema = props.schema; + return ( +
+ +
+ ); +}); + +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 (
{ {}} + 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); + } + }} /> ))} @@ -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 ; + } return (