From de2b7cab7585b420a9619ceff4d3f75f13b0f565 Mon Sep 17 00:00:00 2001 From: chenos Date: Sun, 20 Feb 2022 17:58:46 +0800 Subject: [PATCH] feat: improve code --- .../route-switch/antd/admin-layout/index.tsx | 5 +- .../antd/menu/Menu.Designer.tsx | 14 + .../src/schema-component/antd/menu/Menu.tsx | 344 ++++++++++++++---- .../antd/menu/MenuItemInitializer/index.tsx | 204 +++++++++++ .../src/schema-component/antd/menu/index.ts | 1 + .../schema-component/hooks/useDesignable.tsx | 4 + .../src/schema-component/hooks/useDesigner.ts | 6 +- .../BlockInitializer/MarkdownBlock.tsx | 2 +- .../schema-initializer/SchemaInitializer.tsx | 6 +- .../schema-settings/GeneralSchemaDesigner.tsx | 5 +- .../plugin-ui-routes-storage/src/index.ts | 2 + 11 files changed, 516 insertions(+), 77 deletions(-) create mode 100644 packages/client/src/schema-component/antd/menu/Menu.Designer.tsx create mode 100644 packages/client/src/schema-component/antd/menu/MenuItemInitializer/index.tsx diff --git a/packages/client/src/route-switch/antd/admin-layout/index.tsx b/packages/client/src/route-switch/antd/admin-layout/index.tsx index 517706f41..edd992b22 100644 --- a/packages/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/client/src/route-switch/antd/admin-layout/index.tsx @@ -22,6 +22,7 @@ export function AdminLayout(props: any) { const [schema, setSchema] = useState({}); const onSelect = ({ item }) => { const schema = item.props.schema; + console.log('onSelect', schema); setSchema(schema); setTitle(schema.title); history.push(`/admin/${schema['x-uid']}`); @@ -30,7 +31,7 @@ export function AdminLayout(props: any) { const result = useSystemSettings(); return ( - +
{result?.data?.data?.title}
- + diff --git a/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx b/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx new file mode 100644 index 000000000..a51238573 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/Menu.Designer.tsx @@ -0,0 +1,14 @@ +import { useField } from '@formily/react'; +import React from 'react'; +import { GeneralSchemaDesigner, SchemaSettings } from '../../../'; + +export const MenuDesigner = () => { + const field = useField(); + return ( + + 编辑 + + + + ); +}; diff --git a/packages/client/src/schema-component/antd/menu/Menu.tsx b/packages/client/src/schema-component/antd/menu/Menu.tsx index c05a60647..e9290fd54 100644 --- a/packages/client/src/schema-component/antd/menu/Menu.tsx +++ b/packages/client/src/schema-component/antd/menu/Menu.tsx @@ -1,13 +1,121 @@ -import { DesktopOutlined } from '@ant-design/icons'; -import { observer, RecursionField, Schema, SchemaExpressionScopeContext, useFieldSchema } from '@formily/react'; +import { css } from '@emotion/css'; +import { + observer, + RecursionField, + Schema, + SchemaExpressionScopeContext, + useField, + useFieldSchema +} from '@formily/react'; import { Menu as AntdMenu } from 'antd'; import React, { createContext, useContext, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; +import { createDesignable, DndContext, SortableItem, useComponent, useDesignable, useDesigner } from '../..'; +import { Icon, useAPIClient } from '../../../'; +import { MenuDesigner } from './Menu.Designer'; import { findKeysByUid, findMenuItem } from './util'; +const subMenuDesignerCss = css` + position: relative; + display: inline-block; + margin-left: -24px; + margin-right: -34px; + padding: 0 34px 0 24px; + width: calc(100% + 58px); + &:hover { + > .general-schema-designer { + display: block; + } + } + &.nb-action-link { + > .general-schema-designer { + top: -10px; + bottom: -10px; + left: -10px; + right: -10px; + } + } + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: rgba(241, 139, 98, 0.06); + border: 0; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: #f18b62; + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + } + } + } +`; + +const designerCss = css` + position: relative; + display: inline-block; + margin-left: -20px; + margin-right: -20px; + padding: 0 20px; + width: calc(100% + 40px); + &:hover { + > .general-schema-designer { + display: block; + } + } + &.nb-action-link { + > .general-schema-designer { + top: -10px; + bottom: -10px; + left: -10px; + right: -10px; + } + } + > .general-schema-designer { + position: absolute; + z-index: 999; + top: 0; + bottom: 0; + left: 0; + right: 0; + display: none; + background: rgba(241, 139, 98, 0.06); + border: 0; + pointer-events: none; + > .general-schema-designer-icons { + position: absolute; + right: 2px; + top: 2px; + line-height: 16px; + pointer-events: all; + .ant-space-item { + background-color: #f18b62; + color: #fff; + line-height: 16px; + width: 16px; + padding-left: 1px; + } + } + } +`; + type ComposedMenu = React.FC & { Item?: React.FC; + URL?: React.FC; SubMenu?: React.FC; + Designer?: React.FC; }; const MenuModeContext = createContext(null); @@ -22,6 +130,8 @@ const useSideMenuRef = () => { return scope[scopeKey]; }; +const MenuItemDesignerContext = createContext(null); + export const Menu: ComposedMenu = observer((props) => { let { onSelect, @@ -32,7 +142,11 @@ export const Menu: ComposedMenu = observer((props) => { defaultOpenKeys: dOpenKeys, ...others } = props; + const Designer = useDesigner(); const schema = useFieldSchema(); + const { refresh } = useDesignable(); + const api = useAPIClient(); + const Initializer = useComponent(schema['x-initializer']); const sideMenuRef = useSideMenuRef(); const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => { if (dSelectedKeys) { @@ -70,98 +184,190 @@ export const Menu: ComposedMenu = observer((props) => { if (!sideMenuElement) { return; } - sideMenuElement.style.display = sideMenuSchema?.properties ? 'block' : 'none'; - }, [sideMenuSchema?.properties, sideMenuRef]); + sideMenuElement.style.display = sideMenuSchema?.['x-component'] === 'Menu.SubMenu' ? 'block' : 'none'; + }, [sideMenuSchema?.name, sideMenuRef]); return ( - - { - const s = schema.properties[info.key]; - if (mode === 'mix') { - setSideMenuSchema(s); - if (!s?.properties) { - onSelect && onSelect(info); - } else { - const menuItemSchema = findMenuItem(s); - if (!menuItemSchema) { - return; + + + + .ant-menu-title-content > div { + .general-schema-designer { + display: block; + } + } } - // TODO - setLoading(true); - const keys = findKeysByUid(schema, menuItemSchema['x-uid']); - setDefaultSelectedKeys(keys); - setTimeout(() => { - setLoading(false); - }, 100); - onSelect && - onSelect({ - key: menuItemSchema.name, - item: { - props: { - schema: menuItemSchema, - }, - }, - }); - } - } else { - onSelect && onSelect(info); - } - }} - mode={mode === 'mix' ? 'horizontal' : mode} - defaultOpenKeys={defaultOpenKeys} - defaultSelectedKeys={defaultSelectedKeys} - > - - - {loading - ? null - : mode === 'mix' && - sideMenuSchema?.properties && - sideMenuRef?.current?.firstChild && - createPortal( - - { + `} + onSelect={(info: any) => { + const s = schema.properties[info.key]; + if (mode === 'mix') { + setSideMenuSchema(s); + if (s['x-component'] !== 'Menu.SubMenu') { onSelect && onSelect(info); - }} - > - - - , - sideMenuRef.current.firstChild, - )} - + } else { + const menuItemSchema = findMenuItem(s); + if (!menuItemSchema) { + return; + } + // TODO + setLoading(true); + const keys = findKeysByUid(schema, menuItemSchema['x-uid']); + setDefaultSelectedKeys(keys); + setTimeout(() => { + setLoading(false); + }, 100); + onSelect && + onSelect({ + key: menuItemSchema.name, + item: { + props: { + schema: menuItemSchema, + }, + }, + }); + } + } else { + onSelect && onSelect(info); + } + }} + mode={mode === 'mix' ? 'horizontal' : mode} + defaultOpenKeys={defaultOpenKeys} + defaultSelectedKeys={defaultSelectedKeys} + > + + {Initializer && } + + {loading + ? null + : mode === 'mix' && + sideMenuSchema?.['x-component'] === 'Menu.SubMenu' && + sideMenuRef?.current?.firstChild && + createPortal( + + { + onSelect && onSelect(info); + }} + className={css` + .ant-menu-item { + > .ant-menu-title-content { + margin-left: -24px; + margin-right: -16px; + padding: 0 16px 0 24px; + > div { + > .general-schema-designer { + right: 6px !important; + } + } + } + } + .ant-menu-submenu-title { + .ant-menu-title-content { + margin-left: -24px; + margin-right: -34px; + padding: 0 34px 0 24px; + > div { + > .general-schema-designer { + right: 6px !important; + } + } + } + } + `} + > + + {Initializer && ( + { + console.log('createDesignable', s); + const dn = createDesignable({ + api, + refresh, + current: sideMenuSchema, + }); + dn.loadAPIClientEvents(); + dn.insertAdjacent('beforeEnd', s); + }} + /> + )} + + , + sideMenuRef.current.firstChild, + )} + + + ); }); Menu.Item = observer((props) => { + const { icon, ...others } = props; const schema = useFieldSchema(); + const Designer = useContext(MenuItemDesignerContext); return ( - } key={schema.name} eventKey={schema.name} schema={schema}> - {schema.title} + + + + {schema.title} + + + + ); +}); + +Menu.URL = observer((props) => { + const { icon, ...others } = props; + const schema = useFieldSchema(); + const field = useField(); + const Designer = useContext(MenuItemDesignerContext); + return ( + { + window.open(props.href, '_blank'); + }}> + + + {schema.title} + + ); }); Menu.SubMenu = observer((props) => { + const { icon, ...others } = props; const schema = useFieldSchema(); + const field = useField(); const mode = useContext(MenuModeContext); + const Designer = useContext(MenuItemDesignerContext); if (mode === 'mix') { return ; } return ( } - title={schema.title} + {...others} key={schema.name} eventKey={schema.name} + title={ + + + {field.title} + + + } > ); }); + +Menu.Designer = MenuDesigner; diff --git a/packages/client/src/schema-component/antd/menu/MenuItemInitializer/index.tsx b/packages/client/src/schema-component/antd/menu/MenuItemInitializer/index.tsx new file mode 100644 index 000000000..836583177 --- /dev/null +++ b/packages/client/src/schema-component/antd/menu/MenuItemInitializer/index.tsx @@ -0,0 +1,204 @@ +import { FormDialog, FormLayout } from '@formily/antd'; +import { observer, SchemaOptionsContext } from '@formily/react'; +import React, { useContext } from 'react'; +import { useTranslation } from 'react-i18next'; +import { SchemaComponent, SchemaComponentOptions } from '../../..'; +import { SchemaInitializer } from '../../../../schema-initializer'; + +export const MenuItemInitializer = observer((props: any) => { + const { t } = useTranslation(); + return ( + + {t('Add menu item')} + + ); +}); + +const itemWrap = SchemaInitializer.itemWrap; + +export const GroupItem = itemWrap((props) => { + const { insert } = props; + const { t } = useTranslation(); + const options = useContext(SchemaOptionsContext); + return ( + { + const values = await FormDialog('添加分组', () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, + }); + const { title, icon } = values; + insert({ + type: 'void', + title, + 'x-component': 'Menu.SubMenu', + 'x-component-props': { + icon, + }, + }); + }} + /> + ); +}); + +export const PageMenuItem = itemWrap((props) => { + const { insert } = props; + const { t } = useTranslation(); + const options = useContext(SchemaOptionsContext); + return ( + { + const values = await FormDialog('添加页面', () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, + }); + const { title, icon } = values; + insert({ + type: 'void', + title, + 'x-component': 'Menu.Item', + 'x-component-props': { + icon, + }, + properties: { + page: { + type: 'void', + 'x-component': 'Page', + 'x-async': true, + properties: { + grid: { + type: 'void', + 'x-component': 'Grid', + 'x-item-initializer': 'BlockInitializer', + properties: {}, + }, + }, + }, + }, + }); + }} + /> + ); +}); + +export const LinkMenuItem = itemWrap((props) => { + const { insert } = props; + const { t } = useTranslation(); + const options = useContext(SchemaOptionsContext); + return ( + { + const values = await FormDialog('添加链接', () => { + return ( + + + + + + ); + }).open({ + initialValues: {}, + }); + const { title, href, icon } = values; + insert({ + type: 'void', + title, + 'x-component': 'Menu.URL', + 'x-component-props': { + icon, + href, + }, + }); + }} + /> + ); +}); diff --git a/packages/client/src/schema-component/antd/menu/index.ts b/packages/client/src/schema-component/antd/menu/index.ts index 9034dc2e6..2e020af86 100644 --- a/packages/client/src/schema-component/antd/menu/index.ts +++ b/packages/client/src/schema-component/antd/menu/index.ts @@ -1,3 +1,4 @@ export * from './Menu'; +export * from './MenuItemInitializer'; export * from './util'; diff --git a/packages/client/src/schema-component/hooks/useDesignable.tsx b/packages/client/src/schema-component/hooks/useDesignable.tsx index f450363d8..27d9a07ae 100644 --- a/packages/client/src/schema-component/hooks/useDesignable.tsx +++ b/packages/client/src/schema-component/hooks/useDesignable.tsx @@ -1,5 +1,6 @@ import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; +import { message } from 'antd'; import get from 'lodash/get'; import set from 'lodash/set'; import React, { useContext } from 'react'; @@ -92,6 +93,7 @@ export class Designable { method: 'post', }); } + message.success('配置保存成功!', 0.2); }); this.on('patch', async ({ schema }) => { refresh(); @@ -104,6 +106,7 @@ export class Designable { }, }); } + message.success('配置保存成功!', 0.2); }); this.on('remove', async ({ removed }) => { refresh(); @@ -113,6 +116,7 @@ export class Designable { method: 'post', }); } + message.success('配置保存成功!', 0.2); }); } diff --git a/packages/client/src/schema-component/hooks/useDesigner.ts b/packages/client/src/schema-component/hooks/useDesigner.ts index aa70959c3..dc147b771 100644 --- a/packages/client/src/schema-component/hooks/useDesigner.ts +++ b/packages/client/src/schema-component/hooks/useDesigner.ts @@ -1,9 +1,11 @@ import { useFieldSchema } from '@formily/react'; -import { useComponent } from '.'; +import { useComponent, useDesignable } from '.'; const Def = () => null; export const useDesigner = () => { + const { designable } = useDesignable(); const fieldSchema = useFieldSchema(); - return useComponent(fieldSchema['x-designer'], Def); + const component = useComponent(fieldSchema['x-designer'], Def); + return designable ? component : Def; }; diff --git a/packages/client/src/schema-initializer/Initializers/BlockInitializer/MarkdownBlock.tsx b/packages/client/src/schema-initializer/Initializers/BlockInitializer/MarkdownBlock.tsx index 6bfafbc57..035bb5cac 100644 --- a/packages/client/src/schema-initializer/Initializers/BlockInitializer/MarkdownBlock.tsx +++ b/packages/client/src/schema-initializer/Initializers/BlockInitializer/MarkdownBlock.tsx @@ -20,7 +20,7 @@ export const MarkdownBlock = itemWrap((props) => { 'x-component': 'Markdown.Void', 'x-editable': false, 'x-component-props': { - content: '# Markdown content', + content: t('This is a demo text, **supports Markdown syntax**.'), }, }); }} diff --git a/packages/client/src/schema-initializer/SchemaInitializer.tsx b/packages/client/src/schema-initializer/SchemaInitializer.tsx index e0ef46b31..bb16b332a 100644 --- a/packages/client/src/schema-initializer/SchemaInitializer.tsx +++ b/packages/client/src/schema-initializer/SchemaInitializer.tsx @@ -17,7 +17,7 @@ export const SchemaInitializer = () => null; SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { const { insert, wrap = defaultWrap, items = [], insertPosition, dropdown, style, ...others } = props; - let { insertAdjacent, findComponent } = useDesignable(); + let { insertAdjacent, findComponent, designable } = useDesignable(); const [visible, setVisible] = useState(false); const insertSchema = (schema) => { if (props.insert) { @@ -81,7 +81,9 @@ SchemaInitializer.Button = observer((props: SchemaInitializerButtonProps) => { ); - console.log('others', others); + if (!designable) { + return null; + } return ( { - const { dn } = useDesignable(); + const { dn, designable } = useDesignable(); const field = useField(); const fieldSchema = useFieldSchema(); const schemaSettingsProps = { @@ -14,6 +14,9 @@ export const GeneralSchemaDesigner = (props: any) => { field, fieldSchema, }; + if (!designable) { + return null; + } return (
diff --git a/packages/plugin-ui-routes-storage/src/index.ts b/packages/plugin-ui-routes-storage/src/index.ts index b9e9a4dd8..34cf301f0 100644 --- a/packages/plugin-ui-routes-storage/src/index.ts +++ b/packages/plugin-ui-routes-storage/src/index.ts @@ -20,6 +20,8 @@ export class UiRoutesStoragePlugin extends Plugin { uiSchema: { type: 'void', 'x-component': 'Menu', + 'x-designer': 'Menu.Designer', + 'x-initializer': 'MenuItemInitializer', 'x-component-props': { mode: 'mix', theme: 'dark',