From 8f746ae319f069ae2f700dbf15c67bb24ce7ce5b Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 5 Aug 2021 18:34:58 +0800 Subject: [PATCH] feat: action bar for the descriptions block --- packages/client/src/context.ts | 2 +- .../client/src/schemas/action/ActionBar.tsx | 256 ++++++++++++++++++ packages/client/src/schemas/action/index.md | 25 ++ packages/client/src/schemas/action/index.tsx | 122 +++++++-- packages/client/src/schemas/add-new/index.tsx | 7 +- packages/client/src/schemas/form/index.tsx | 26 +- packages/client/src/schemas/table/index.tsx | 17 +- packages/client/src/schemas/tabs/style.less | 1 + 8 files changed, 429 insertions(+), 27 deletions(-) create mode 100644 packages/client/src/schemas/action/ActionBar.tsx diff --git a/packages/client/src/context.ts b/packages/client/src/context.ts index 92d8a9d3a..f0b87a83b 100644 --- a/packages/client/src/context.ts +++ b/packages/client/src/context.ts @@ -1,3 +1,3 @@ import { createContext } from "react"; -export const VisibleContext = createContext(null); +export const VisibleContext = createContext([]); diff --git a/packages/client/src/schemas/action/ActionBar.tsx b/packages/client/src/schemas/action/ActionBar.tsx new file mode 100644 index 000000000..5430c7b79 --- /dev/null +++ b/packages/client/src/schemas/action/ActionBar.tsx @@ -0,0 +1,256 @@ +import { DndContext, DragOverlay } from '@dnd-kit/core'; +import { ISchema, observer, RecursionField, Schema } from '@formily/react'; +import React, { useState } from 'react'; +import { createSchema, removeSchema, updateSchema } from '..'; +import { + findPropertyByPath, + getSchemaPath, + useDesignable, +} from '../../components/schema-renderer'; +import { DisplayedMapProvider, useDisplayedMapContext } from '../../constate'; +import cls from 'classnames'; +import { Button, Dropdown, Menu, Space } from 'antd'; +import SwitchMenuItem from '../../components/SwitchMenuItem'; +import { uid } from '@formily/shared'; +import { PlusOutlined } from '@ant-design/icons'; +import { Droppable, SortableItem } from '../../components/Sortable'; + +export const ActionBar = 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 [dragOverlayContent, setDragOverlayContent] = useState(''); + return ( + { + 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); + } + }} + > + + {dragOverlayContent} + {/*
*/} +
+ +
+
+ +
+
+ +
+ +
+
+
+ ); +}); + +function generateActionSchema(type) { + const actions: { [key: string]: ISchema } = { + update: { + type: 'void', + title: '编辑', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'update', + }, + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + }, + 'x-action-type': 'update', + 'x-designable-bar': 'Action.DesignableBar', + properties: { + modal: { + 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: { + type: 'void', + title: '删除', + 'x-align': 'right', + 'x-decorator': 'AddNew.Displayed', + 'x-decorator-props': { + displayName: 'destroy', + }, + 'x-action-type': 'destroy', + 'x-component': 'Action', + 'x-designable-bar': 'Action.DesignableBar', + 'x-component-props': { + useAction: '{{ Table.useTableDestroyAction }}', + }, + }, + }; + return actions[type]; +} + +function AddActionButton() { + const [visible, setVisible] = useState(false); + const displayed = useDisplayedMapContext(); + const { appendChild, remove } = useDesignable(); + const { schema, designable } = useDesignable(); + if (!designable) { + return null; + } + return ( + + + {[ + { title: '编辑', name: 'update' }, + { title: '删除', name: 'destroy' }, + ].map((item) => ( + { + 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); + } + }} + /> + ))} + + + + 函数操作 + 弹窗表单 + 复杂弹窗 + + + } + > + + + ); +} + +function Actions(props: any) { + const { align = 'left' } = props; + const { schema, designable } = useDesignable(); + return ( + + + {schema.mapProperties((s) => { + const currentAlign = s['x-align'] || 'left'; + if (currentAlign !== align) { + return null; + } + return ( + + + + ); + })} + + + ); +} diff --git a/packages/client/src/schemas/action/index.md b/packages/client/src/schemas/action/index.md index 1df7c9dca..5db32402d 100644 --- a/packages/client/src/schemas/action/index.md +++ b/packages/client/src/schemas/action/index.md @@ -420,3 +420,28 @@ export default () => { ); }; ``` + + +### Action.Bar + +```tsx +import React from 'react'; +import { SchemaRenderer } from '@nocobase/client'; + +const schema = { + type: 'void', + name: 'actionbar1', + 'x-component': 'Action.Bar', + 'x-designable-bar': 'Action.Bar.DesignableBar', + 'x-component-props': { + }, +}; + +export default () => { + return ( + + ); +}; +``` diff --git a/packages/client/src/schemas/action/index.tsx b/packages/client/src/schemas/action/index.tsx index ddd2d99f4..da66d8ce9 100644 --- a/packages/client/src/schemas/action/index.tsx +++ b/packages/client/src/schemas/action/index.tsx @@ -11,23 +11,41 @@ import { useField, SchemaExpressionScopeContext, } from '@formily/react'; -import { Button, Dropdown, Menu, Popover, Space, Drawer, Modal } from 'antd'; -import { Link, useHistory, LinkProps } from 'react-router-dom'; -import { useDesignable, useDefaultAction, updateSchema } from '..'; +import { + Button, + Dropdown, + Menu, + Popover, + Space, + Drawer, + Modal, + Select, +} from 'antd'; +import { Link, useHistory, LinkProps, Switch } from 'react-router-dom'; +import { + useDesignable, + useDefaultAction, + updateSchema, + removeSchema, +} 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 { FormDialog, FormLayout } from '@formily/antd'; import IconPicker from '../../components/icon-picker'; import { findPropertyByPath, getSchemaPath, + SchemaField, useSchemaComponent, } from '../../components/schema-renderer'; import { VisibleContext } from '../../context'; import { DndContext, DragOverlay } from '@dnd-kit/core'; import { createPortal } from 'react-dom'; +import { ActionBar } from './ActionBar'; +import { DragHandle } from '../../components/Sortable'; +import { useDisplayedMapContext } from '../../constate'; export const ButtonComponentContext = createContext(null); @@ -90,6 +108,8 @@ Action.Modal = observer((props: any) => { const { run: runOk } = useOkAction(); const { run: runCancel } = useCancelAction(); const isFormDecorator = schema['x-decorator'] === 'Form'; + const field = useField(); + console.log('Action.Modal.field', field); return ( { }); Action.DesignableBar = (props: any) => { - const field = useField(); - // const schema = useFieldSchema(); - const { schema, insertAfter, refresh } = useDesignable(props.path); + const { schema, remove, refresh, insertAfter } = useDesignable(); const [visible, setVisible] = useState(false); + const isPopup = Object.keys(schema.properties || {}).length > 0; + const displayed = useDisplayedMapContext(); + const field = useField(); return (
{ }} className={cls('designable-bar-actions', { active: visible })} > + { overlay={ { - console.log('按钮文案被修改了', { schema }); - schema.title = '按钮文案被修改了'; - // field.setTitle('按钮文案被修改了'); - // setVisible(false); + onClick={async (e) => { + const values = await FormDialog('修改名称和图标', () => { + return ( + + + + ); + }).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(); }} > - 点击修改按钮文案 + 修改名称和图标 + {isPopup && ( + + 在{' '} + {' '} + 内打开 + + )} + { - insertAfter({ - name: uid(), - 'x-component': 'Input', - }); + onClick={async () => { + const displayName = + schema?.['x-decorator-props']?.['displayName']; + const data = remove(); + await removeSchema(data); + if (displayName) { + displayed.remove(displayName); + } + setVisible(false); }} > - insertAfter + 移除 } @@ -354,3 +436,5 @@ Action.DesignableBar = (props: any) => {
); }; + +Action.Bar = ActionBar; diff --git a/packages/client/src/schemas/add-new/index.tsx b/packages/client/src/schemas/add-new/index.tsx index 18359db76..b9bfd8a5d 100644 --- a/packages/client/src/schemas/add-new/index.tsx +++ b/packages/client/src/schemas/add-new/index.tsx @@ -225,7 +225,6 @@ function generateCardItemSchema(component) { properties: { [uid()]: { type: 'void', - name: 'action1', 'x-component': 'Action', 'x-component-props': { icon: 'EllipsisOutlined', @@ -844,6 +843,12 @@ AddNew.PaneItem = observer((props: any) => { }, 'x-designable-bar': 'Form.DesignableBar', properties: { + [uid()]: { + type: 'void', + 'x-component': 'Action.Bar', + 'x-designable-bar': 'Action.Bar.DesignableBar', + 'x-component-props': {}, + }, [uid()]: { type: 'void', 'x-component': 'Grid', diff --git a/packages/client/src/schemas/form/index.tsx b/packages/client/src/schemas/form/index.tsx index 6eca14ae0..8ff45dfd1 100644 --- a/packages/client/src/schemas/form/index.tsx +++ b/packages/client/src/schemas/form/index.tsx @@ -37,6 +37,18 @@ import { useDisplayedMapContext, } from '../../constate'; import { useResource as useGeneralResource } from '../../hooks/useResource'; +import { Resource } from '../../resource'; +import { BaseResult } from '@ahooksjs/use-request/lib/types'; + +export interface FormReadPrettyContextProps { + resource?: Resource; + service?: BaseResult; +} + +export const FormContext = createContext({}); +export const FormReadPrettyContext = createContext( + {}, +); const FormMain = (props: any) => { const { @@ -50,9 +62,11 @@ const FormMain = (props: any) => { readPretty: schema['x-read-pretty'], }); }, []); - const { resource, run } = useResource({ + const { resource, run, service } = useResource({ onSuccess: (initialValues: any) => { + console.log('onSuccess', { initialValues }); form.setInitialValues(initialValues); + form.setValues(initialValues); }, }); const path = useSchemaPath(); @@ -66,7 +80,7 @@ const FormMain = (props: any) => { console.log(displayed.map, 'displayed.map', collection?.name); } }, [displayed.map]); - return ( + const content = ( {schema['x-decorator'] === 'Form' ? ( { )} ); + + return schema['x-read-pretty'] ? ( + + {content} + + ) : ( + <>{content} + ); }; export const Form: any = observer((props: any) => { diff --git a/packages/client/src/schemas/table/index.tsx b/packages/client/src/schemas/table/index.tsx index 7259609c0..b6de27463 100644 --- a/packages/client/src/schemas/table/index.tsx +++ b/packages/client/src/schemas/table/index.tsx @@ -70,6 +70,8 @@ import { isValid } from '@formily/shared'; import { FormButtonGroup, FormDialog, FormLayout, Submit } from '@formily/antd'; import flatten from 'flat'; import IconPicker from '../../components/icon-picker'; +import { FormReadPrettyContext } from '../form'; +import { VisibleContext } from '../../context'; export interface ITableContext { props: any; @@ -153,6 +155,7 @@ const useTableUpdateAction = () => { } = useTable(); const ctx = useContext(TableRowContext); const form = useForm(); + const { service: formService } = useContext(FormReadPrettyContext); return { async run() { @@ -160,7 +163,11 @@ const useTableUpdateAction = () => { await resource.save(form.values, { resourceKey: ctx.record[rowKey], }); - return service.refresh(); + if (formService) { + await formService.refresh(); + } + await service.refresh(); + return; } field.value[ctx.index] = form.values; // refresh(); @@ -179,6 +186,7 @@ const useTableDestroyAction = () => { props: { refreshRequestOnChange, rowKey }, } = useTable(); const ctx = useContext(TableRowContext); + const [, setVisible] = useContext(VisibleContext); return { async run() { if (refreshRequestOnChange) { @@ -190,6 +198,7 @@ const useTableDestroyAction = () => { [`${rowKey}.in`]: rowKeys, }); setSelectedRowKeys([]); + setVisible && setVisible(false); return service.refresh(); } if (ctx) { @@ -873,7 +882,7 @@ function generateMenuActionSchema(type) { properties: { [uid()]: { type: 'void', - title: '查看', + title: '查看数据', 'x-component': 'Action.Modal', 'x-component-props': { bodyStyle: { @@ -2011,7 +2020,7 @@ Table.useResource = ({ onSuccess }) => { resourceName: collection.name, resourceKey: ctx.record[props.rowKey], }); - const { data, loading, run } = useRequest( + const service = useRequest( (params?: any) => { console.log('Table.useResource', params); return resource.get(params); @@ -2022,7 +2031,7 @@ Table.useResource = ({ onSuccess }) => { manual: true, }, ); - return { initialValues: data, loading, run, resource }; + return { resource, service, initialValues: service.data, ...service }; }; Table.useTableFilterAction = useTableFilterAction; diff --git a/packages/client/src/schemas/tabs/style.less b/packages/client/src/schemas/tabs/style.less index 23bcf96ad..ecc9fb419 100644 --- a/packages/client/src/schemas/tabs/style.less +++ b/packages/client/src/schemas/tabs/style.less @@ -69,6 +69,7 @@ } } +.ant-drawer-body, .ant-card-body, .ant-modal-body { .nb-tabs {