improved designable styling

This commit is contained in:
chenos 2021-08-17 14:40:38 +08:00
parent da418720aa
commit 2ba5e6f76f
13 changed files with 967 additions and 842 deletions

View File

@ -349,7 +349,6 @@ const schema: ISchema = {
'x-component-props': { 'x-component-props': {
className: 'nb-table-operation', className: 'nb-table-operation',
}, },
'x-designable-bar': 'Table.Operation.DesignableBar',
properties: { properties: {
[uid()]: { [uid()]: {
type: 'void', type: 'void',

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect } from 'react'; import React, { useContext, useEffect } from 'react';
import { Button, Dropdown, Menu } from 'antd'; import { Button, Dropdown, Menu, Spin } from 'antd';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { request } from '../../schemas'; import { request } from '../../schemas';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
@ -25,7 +25,7 @@ export function SystemSettingsProvider(props) {
); );
return ( return (
<SystemSettingsContext.Provider value={{ service, resource }}> <SystemSettingsContext.Provider value={{ service, resource }}>
{props.children} {service.loading ? <Spin /> : props.children}
</SystemSettingsContext.Provider> </SystemSettingsContext.Provider>
); );
} }

View File

@ -89,7 +89,7 @@ function LayoutWithMenu(props: LayoutWithMenuProps) {
}; };
return ( return (
<Layout> <Layout>
<Layout.Header style={{ display: 'flex' }}> <Layout.Header className={'site-header'} style={{ display: 'flex' }}>
<SiteTitle /> <SiteTitle />
<SchemaRenderer <SchemaRenderer
schema={schema} schema={schema}

View File

@ -50,8 +50,9 @@
.nb-designable-toggle { .nb-designable-toggle {
background: none !important; background: none !important;
border-radius: 0 !important;
&.active { &.active {
background: #1890ff !important; // background: #faad14 !important;
} }
} }
@ -76,3 +77,23 @@
padding-right: 8px; padding-right: 8px;
} }
} }
.site-header {
.ant-menu.ant-menu-dark .ant-menu-item-selected, .ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected {
background-color: rgba(255, 255, 255, 0.1);
}
.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.ant-btn {
color: rgba(255, 255, 255, 0.65) !important;
&.nb-designable-toggle:hover {
color: #fff !important;
background-color: #F18B62 !important
}
}
.ant-btn:hover {
color: #fff !important;
background-color: rgba(255, 255, 255, 0.1) !important;
}
}

View File

@ -214,7 +214,7 @@ function AddActionButton() {
</Menu> </Menu>
} }
> >
<Button style={{ marginLeft: 8 }} type={'dashed'} icon={<PlusOutlined />}> <Button className={'designable-btn designable-btn-dash'} style={{ marginLeft: 8 }} type={'dashed'} icon={<PlusOutlined />}>
</Button> </Button>
</Dropdown> </Dropdown>

View File

@ -339,101 +339,107 @@ Action.DesignableBar = (props: any) => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space size={2}>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.Item <Menu>
onClick={async (e) => { <Menu.Item
const values = await FormDialog('修改名称和图标', () => { onClick={async (e) => {
return ( const values = await FormDialog('修改名称和图标', () => {
<FormLayout layout={'vertical'}> return (
<SchemaField <FormLayout layout={'vertical'}>
schema={{ <SchemaField
type: 'object', schema={{
properties: { type: 'object',
title: { properties: {
type: 'string', title: {
title: '按钮名称', type: 'string',
required: true, title: '按钮名称',
'x-decorator': 'FormItem', required: true,
'x-component': 'Input', 'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
}, },
icon: { }}
type: 'string', />
title: '按钮图标', </FormLayout>
'x-decorator': 'FormItem', );
'x-component': 'IconPicker', }).open({
}, initialValues: {
}, title: schema['title'],
}} icon: schema['x-component-props']?.['icon'],
/> },
</FormLayout> });
); schema['title'] = values.title;
}).open({ schema['x-component-props']['icon'] = values.icon;
initialValues: { field.componentProps.icon = values.icon;
title: schema['title'], field.title = values.title;
icon: schema['x-component-props']?.['icon'], updateSchema(schema);
}, refresh();
}); }}
schema['title'] = values.title; >
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon;
field.title = values.title;
updateSchema(schema);
refresh();
}}
>
</Menu.Item>
{isPopup && (
<Menu.Item>
{' '}
<Select
bordered={false}
size={'small'}
defaultValue={'Action.Modal'}
onChange={(value) => {
const s = Object.values(schema.properties).shift();
s['x-component'] = value;
refresh();
updateSchema(s);
}}
>
<Select.Option value={'Action.Modal'}></Select.Option>
<Select.Option value={'Action.Drawer'}></Select.Option>
<Select.Option value={'Action.Window'}>
</Select.Option>
</Select>{' '}
</Menu.Item> </Menu.Item>
)} {isPopup && (
<Menu.Divider /> <Menu.Item>
<Menu.Item {' '}
onClick={async () => { <Select
const displayName = bordered={false}
schema?.['x-decorator-props']?.['displayName']; size={'small'}
const data = remove(); defaultValue={'Action.Modal'}
await removeSchema(data); onChange={(value) => {
if (displayName) { const s = Object.values(schema.properties).shift();
displayed.remove(displayName); s['x-component'] = value;
} refresh();
setVisible(false); updateSchema(s);
}} }}
> >
<Select.Option value={'Action.Modal'}>
</Menu.Item>
</Menu> </Select.Option>
} <Select.Option value={'Action.Drawer'}>
>
<MenuOutlined /> </Select.Option>
</Dropdown> <Select.Option value={'Action.Window'}>
</Select.Option>
</Select>{' '}
</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);
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span> </span>
</div> </div>
); );

View File

@ -935,7 +935,7 @@ AddNew.CardItem = observer((props: any) => {
{ghost ? ( {ghost ? (
<PlusOutlined /> <PlusOutlined />
) : ( ) : (
<Button type={'dashed'} icon={<PlusOutlined />}> <Button type={'dashed'} className={'designable-btn designable-btn-dash'} icon={<PlusOutlined />}>
</Button> </Button>
)} )}
@ -1122,7 +1122,7 @@ AddNew.FormItem = observer((props: any) => {
{ghost ? ( {ghost ? (
<PlusOutlined /> <PlusOutlined />
) : ( ) : (
<Button type={'dashed'} icon={<SettingOutlined />}> <Button type={'dashed'} className={'designable-btn designable-btn-dash'} icon={<SettingOutlined />}>
</Button> </Button>
)} )}
@ -1326,7 +1326,7 @@ AddNew.PaneItem = observer((props: any) => {
{ghost ? ( {ghost ? (
<PlusOutlined /> <PlusOutlined />
) : ( ) : (
<Button block type={'dashed'} icon={<PlusOutlined />}> <Button block className={'designable-btn designable-btn-dash'} type={'dashed'} icon={<PlusOutlined />}>
</Button> </Button>
)} )}

View File

@ -27,9 +27,6 @@
color: #fff; color: #fff;
z-index: 10; z-index: 10;
padding: 0 3px; padding: 0 3px;
.ant-space {
gap: 1px !important;
}
.anticon { .anticon {
width: 16px; width: 16px;
height: 16px; height: 16px;

View File

@ -1,8 +1,71 @@
@bgcolor: #F18B62;
.designable-info { .designable-info {
position: absolute; position: absolute;
font-size: 12px; font-size: 12px;
background: #1890ff; background: @bgcolor;
color: #fff; color: #fff;
padding: 0 5px; padding: 0 5px;
line-height: 16px;
height: 16px;
border-bottom-right-radius: 2px; border-bottom-right-radius: 2px;
}
.designable-bar {
border-radius: 1px !important;
border: 2px solid @bgcolor !important;
.designable-bar-actions {
padding: 0 !important;
background: none !important;
display: flex !important;
right: 2px !important;
// .ant-space-item {
// margin: 0 !important;
// }
.ant-space {
gap: 2px !important;
}
.anticon {
width: 16px !important;
height: 16px !important;
vertical-align: top;
line-height: 16px !important;
// font-size: 10px;
// display: inline-flex;
background-color: @bgcolor !important;
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
// margin-left: 0 !important;
// margin-right: 2px !important;
}
}
}
.designable-btn-dash {
border-color: @bgcolor !important;
color: @bgcolor !important;
}
.nb-add-new-menu-item.menu-mode-horizontal.designable {
color: @bgcolor !important;
border: 1px dashed !important;
height: 32px !important;
// &:hover {
// color: #fff !important;
// background: @bgcolor !important;
// }
// &:active {
// color: #fff !important;
// background: @bgcolor !important;
// }
}
.ant-menu-item-group.nb-menu-add-new {
.menu-mode-horizontal {
margin-left: 8px;
}
}
.nb-designable-toggle.active {
background: @bgcolor !important;
} }

View File

@ -62,7 +62,7 @@ export const FieldDesignableBar = observer((props) => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<Space size={'small'}> <Space size={2}>
<AddNew.FormItem defaultAction={'insertAfter'} ghost /> <AddNew.FormItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />} {dragRef && <DragOutlined ref={dragRef} />}
<Dropdown <Dropdown

View File

@ -29,6 +29,7 @@ import {
Dropdown, Dropdown,
Modal, Modal,
Button, Button,
Space,
} from 'antd'; } from 'antd';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import cls from 'classnames'; import cls from 'classnames';
@ -97,7 +98,7 @@ const SideMenu = (props: any) => {
<Button <Button
block block
type={'dashed'} type={'dashed'}
className={`nb-add-new-menu-item menu-mode-inline`} className={`nb-add-new-menu-item menu-mode-inline designable-btn designable-btn-dash`}
> >
<PlusOutlined /> <PlusOutlined />
</Button> </Button>
@ -207,7 +208,7 @@ export const Menu: any = observer((props: any) => {
<Menu.AddNew key={uid()} path={path}> <Menu.AddNew key={uid()} path={path}>
{/* <PlusOutlined className={'nb-add-new-icon'} /> */} {/* <PlusOutlined className={'nb-add-new-icon'} /> */}
<Button <Button
className={`nb-add-new-menu-item menu-mode-${ className={`designable-btn designable-btn-dash nb-add-new-menu-item designable menu-mode-${
mode === 'mix' ? 'horizontal' : mode mode === 'mix' ? 'horizontal' : mode
}`} }`}
block block
@ -660,350 +661,357 @@ Menu.DesignableBar = (props) => {
}} }}
className={'designable-bar-actions'} className={'designable-bar-actions'}
> >
<DragHandle /> <Space size={2}>
<Dropdown <DragHandle />
overlayStyle={{ <Dropdown
minWidth: 150, overlayStyle={{
}} minWidth: 150,
trigger={['click']} }}
overlay={ trigger={['click']}
<AntdMenu overlay={
onClick={async (info) => { <AntdMenu
if (['update', 'move', 'delete'].includes(info.key)) { onClick={async (info) => {
return; if (['update', 'move', 'delete'].includes(info.key)) {
} return;
const methodLabels = { }
insertBefore: '之前', const methodLabels = {
insertAfter: '之后', insertBefore: '之前',
appendChild: '里', insertAfter: '之后',
}; appendChild: '里',
const keys = info.key.split('.'); };
const method = keys.shift(); const keys = info.key.split('.');
const type = keys.join('.'); const method = keys.shift();
const config = schemas[type]; const type = keys.join('.');
if (!config) { const config = schemas[type];
return; if (!config) {
} return;
const values = await FormDialog( }
`在「${schema.title}${methodLabels[method]}新建${config.title}`,
() => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={config.schema} />
</FormLayout>
);
},
).open();
const methods = {
insertAfter,
insertBefore,
appendChild,
};
const defaults = generateDefaultSchema(type);
const data = methods[method](deepmerge(defaults, values));
await createSchema(data);
}}
>
<AntdMenu.Item
key={'update'}
onClick={async () => {
const initialValues = {};
Object.keys(formConfig.schema.properties).forEach((name) => {
_.set(initialValues, name, get(schema, name));
});
const values = await FormDialog( const values = await FormDialog(
`修改${formConfig.title}`, `在「${schema.title}${methodLabels[method]}新建${config.title}`,
() => { () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField schema={formConfig.schema} /> <SchemaField schema={config.schema} />
</FormLayout> </FormLayout>
); );
}, },
).open({ ).open();
initialValues, const methods = {
}); insertAfter,
if (values.title) { insertBefore,
schema.title = values.title; appendChild,
} };
const icon = _.get(values, 'x-component-props.icon') || null; const defaults = generateDefaultSchema(type);
schema['x-component-props'] = const data = methods[method](deepmerge(defaults, values));
schema['x-component-props'] || {}; await createSchema(data);
schema['x-component-props']['icon'] = icon;
field.componentProps['icon'] = icon;
refresh();
await updateSchema(schema);
}} }}
> >
<EditOutlined /> {formConfig.title} <AntdMenu.Item
</AntdMenu.Item> key={'update'}
<AntdMenu.Item onClick={async () => {
key={'move'} const initialValues = {};
onClick={async () => { Object.keys(formConfig.schema.properties).forEach(
let menuSchema: Schema; (name) => {
let parent = schema.parent; _.set(initialValues, name, get(schema, name));
while (parent) { },
if (parent['x-component'] === 'Menu') { );
menuSchema = parent; const values = await FormDialog(
break; `修改${formConfig.title}`,
} () => {
parent = parent.parent; return (
} <FormLayout layout={'vertical'}>
<SchemaField schema={formConfig.schema} />
console.log({ menuSchema }); </FormLayout>
);
const toTreeData = (s: Schema) => { },
const items = []; ).open({
Object.keys(s.properties || {}).forEach((name) => { initialValues,
const current = s.properties[name];
if (
!(current['x-component'] as string).startsWith('Menu.')
) {
return;
}
// if (current.name === schema.name) {
// return;
// }
items.push({
key: current['key'] || current.name,
label: current.title,
title: current.title,
value: getSchemaPath(current).join('.'),
children: toTreeData(current),
});
}); });
return items; if (values.title) {
}; schema.title = values.title;
}
const icon =
_.get(values, 'x-component-props.icon') || null;
schema['x-component-props'] =
schema['x-component-props'] || {};
schema['x-component-props']['icon'] = icon;
field.componentProps['icon'] = icon;
refresh();
await updateSchema(schema);
}}
>
<EditOutlined /> {formConfig.title}
</AntdMenu.Item>
<AntdMenu.Item
key={'move'}
onClick={async () => {
let menuSchema: Schema;
let parent = schema.parent;
while (parent) {
if (parent['x-component'] === 'Menu') {
menuSchema = parent;
break;
}
parent = parent.parent;
}
const dataSource = toTreeData(menuSchema); console.log({ menuSchema });
const values = await FormDialog( const toTreeData = (s: Schema) => {
`将「${schema.title}」移动到`, const items = [];
() => { Object.keys(s.properties || {}).forEach((name) => {
const current = s.properties[name];
if (
!(current['x-component'] as string).startsWith(
'Menu.',
)
) {
return;
}
// if (current.name === schema.name) {
// return;
// }
items.push({
key: current['key'] || current.name,
label: current.title,
title: current.title,
value: getSchemaPath(current).join('.'),
children: toTreeData(current),
});
});
return items;
};
const dataSource = toTreeData(menuSchema);
const values = await FormDialog(
`将「${schema.title}」移动到`,
() => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
schema={{
type: 'object',
properties: {
path: {
type: 'string',
title: '目标位置',
enum: dataSource,
'x-decorator': 'FormItem',
'x-component': 'TreeSelect',
},
method: {
type: 'string',
default: 'insertAfter',
enum: [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
],
'x-decorator': 'FormItem',
'x-component': 'Radio.Group',
},
},
}}
/>
</FormLayout>
);
},
).open({
effects(form) {
onFieldChange('path', (field) => {
const target = findPropertyByPath(
schema.root,
// @ts-ignore
field.value,
);
console.log({ field });
field.query('method').take((f) => {
// @ts-ignore
// f.value = 'insertAfter';
// @ts-ignore
f.dataSource =
target['x-component'] === 'Menu.SubMenu'
? [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
{ label: '组里', value: 'appendChild' },
]
: [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
];
});
});
},
});
const methods = {
insertAfter,
insertBefore,
appendChild,
};
const data = schema.toJSON();
remove();
const source = methods[values.method](data, values.path);
await updateSchema(source);
}}
>
<DragOutlined />
</AntdMenu.Item>
<AntdMenu.Divider />
<AntdMenu.SubMenu
key={'insertBefore'}
icon={<ArrowUpOutlined />}
title={'当前菜单项之前'}
>
<AntdMenu.Item
key={'insertBefore.Menu.Link'}
icon={<MenuOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertBefore.Menu.SubMenu'}
icon={<GroupOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertBefore.Menu.URL'}
icon={<LinkOutlined />}
>
</AntdMenu.Item>
</AntdMenu.SubMenu>
<AntdMenu.SubMenu
key={'insertAfter'}
icon={<ArrowDownOutlined />}
title={'当前菜单项之后'}
>
<AntdMenu.Item
key={'insertAfter.Menu.Link'}
icon={<MenuOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertAfter.Menu.SubMenu'}
icon={<GroupOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertAfter.Menu.URL'}
icon={<LinkOutlined />}
>
</AntdMenu.Item>
</AntdMenu.SubMenu>
{isSubMenu && (
<AntdMenu.SubMenu
key={'appendChild'}
icon={<ArrowRightOutlined />}
title={'当前菜单分组里'}
>
<AntdMenu.Item
key={'appendChild.Menu.Link'}
icon={<MenuOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'appendChild.Menu.SubMenu'}
icon={<GroupOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'appendChild.Menu.URL'}
icon={<LinkOutlined />}
>
</AntdMenu.Item>
</AntdMenu.SubMenu>
)}
<AntdMenu.Item
icon={<LockOutlined />}
onClick={async () => {
const loadRoles = async () => {
const resource = Resource.make('roles');
const data = await resource.list();
console.log('loadRoles', data);
return data?.data.map((item) => ({
label: item.title,
value: item.name,
}));
};
const resource = Resource.make({
associatedName: 'ui_schemas',
associatedKey: schema['key'],
resourceName: 'roles',
});
const uiSchemasRoles = await resource.list();
console.log({ uiSchemasRoles });
const values = await FormDialog(`设置权限`, () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
scope={{ loadRoles }}
schema={{ schema={{
type: 'object', type: 'object',
properties: { properties: {
path: { roles: {
type: 'string', type: 'array',
title: '目标位置', title: '权限组',
enum: dataSource, 'x-reactions': [
'x-decorator': 'FormItem', '{{useAsyncDataSource(loadRoles)}}',
'x-component': 'TreeSelect',
},
method: {
type: 'string',
default: 'insertAfter',
enum: [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
], ],
'x-decorator': 'FormItem', 'x-decorator': 'FormilyFormItem',
'x-component': 'Radio.Group', 'x-component': 'Select',
'x-component-props': {
mode: 'tags',
},
}, },
}, },
}} }}
/> />
</FormLayout> </FormLayout>
); );
}, }).open({
).open({ initialValues: {
effects(form) { roles: uiSchemasRoles?.data?.map((role) => role.name),
onFieldChange('path', (field) => { },
const target = findPropertyByPath( });
schema.root, await Resource.make({
// @ts-ignore resourceName: 'ui_schemas',
field.value, resourceKey: schema['key'],
); }).save(values);
console.log({ field }); }}
field.query('method').take((f) => {
// @ts-ignore
// f.value = 'insertAfter';
// @ts-ignore
f.dataSource =
target['x-component'] === 'Menu.SubMenu'
? [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
{ label: '组里', value: 'appendChild' },
]
: [
{ label: '之后', value: 'insertAfter' },
{ label: '之前', value: 'insertBefore' },
];
});
});
},
});
const methods = {
insertAfter,
insertBefore,
appendChild,
};
const data = schema.toJSON();
remove();
const source = methods[values.method](data, values.path);
await updateSchema(source);
}}
>
<DragOutlined />
</AntdMenu.Item>
<AntdMenu.Divider />
<AntdMenu.SubMenu
key={'insertBefore'}
icon={<ArrowUpOutlined />}
title={'当前菜单项之前'}
>
<AntdMenu.Item
key={'insertBefore.Menu.Link'}
icon={<MenuOutlined />}
> >
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Divider />
<AntdMenu.Item <AntdMenu.Item
key={'insertBefore.Menu.SubMenu'} key={'delete'}
icon={<GroupOutlined />} onClick={() => {
Modal.confirm({
title: '删除菜单',
content: '确认删除此菜单项吗?',
onOk: async () => {
const target = remove();
await removeSchema(target);
ctx.onRemove && ctx.onRemove(target);
},
});
}}
> >
<DeleteOutlined />
</AntdMenu.Item> </AntdMenu.Item>
<AntdMenu.Item </AntdMenu>
key={'insertBefore.Menu.URL'} }
icon={<LinkOutlined />} >
> <MenuOutlined />
</Dropdown>
</AntdMenu.Item> </Space>
</AntdMenu.SubMenu>
<AntdMenu.SubMenu
key={'insertAfter'}
icon={<ArrowDownOutlined />}
title={'当前菜单项之后'}
>
<AntdMenu.Item
key={'insertAfter.Menu.Link'}
icon={<MenuOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertAfter.Menu.SubMenu'}
icon={<GroupOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'insertAfter.Menu.URL'}
icon={<LinkOutlined />}
>
</AntdMenu.Item>
</AntdMenu.SubMenu>
{isSubMenu && (
<AntdMenu.SubMenu
key={'appendChild'}
icon={<ArrowRightOutlined />}
title={'当前菜单分组里'}
>
<AntdMenu.Item
key={'appendChild.Menu.Link'}
icon={<MenuOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'appendChild.Menu.SubMenu'}
icon={<GroupOutlined />}
>
</AntdMenu.Item>
<AntdMenu.Item
key={'appendChild.Menu.URL'}
icon={<LinkOutlined />}
>
</AntdMenu.Item>
</AntdMenu.SubMenu>
)}
<AntdMenu.Item
icon={<LockOutlined />}
onClick={async () => {
const loadRoles = async () => {
const resource = Resource.make('roles');
const data = await resource.list();
console.log('loadRoles', data);
return data?.data.map((item) => ({
label: item.title,
value: item.name,
}));
};
const resource = Resource.make({
associatedName: 'ui_schemas',
associatedKey: schema['key'],
resourceName: 'roles',
});
const uiSchemasRoles = await resource.list();
console.log({ uiSchemasRoles });
const values = await FormDialog(`设置权限`, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField
scope={{ loadRoles }}
schema={{
type: 'object',
properties: {
roles: {
type: 'array',
title: '权限组',
'x-reactions': [
'{{useAsyncDataSource(loadRoles)}}',
],
'x-decorator': 'FormilyFormItem',
'x-component': 'Select',
'x-component-props': {
mode: 'tags',
},
},
},
}}
/>
</FormLayout>
);
}).open({
initialValues: {
roles: uiSchemasRoles?.data?.map((role) => role.name),
},
});
await Resource.make({
resourceName: 'ui_schemas',
resourceKey: schema['key'],
}).save(values);
}}
>
</AntdMenu.Item>
<AntdMenu.Divider />
<AntdMenu.Item
key={'delete'}
onClick={() => {
Modal.confirm({
title: '删除菜单',
content: '确认删除此菜单项吗?',
onOk: async () => {
const target = remove();
await removeSchema(target);
ctx.onRemove && ctx.onRemove(target);
},
});
}}
>
<DeleteOutlined />
</AntdMenu.Item>
</AntdMenu>
}
>
<MenuOutlined />
</Dropdown>
</div> </div>
</div> </div>
); );

View File

@ -110,4 +110,4 @@
.nb-add-new-menu-item.menu-mode-inline { .nb-add-new-menu-item.menu-mode-inline {
margin: 0 14px; margin: 0 14px;
width: calc(100% - 28px); width: calc(100% - 28px);
} }

View File

@ -510,7 +510,11 @@ function AddColumn() {
</Menu> </Menu>
} }
> >
<Button type={'dashed'} icon={<SettingOutlined />}> <Button
type={'dashed'}
className={'designable-btn designable-btn-dash'}
icon={<SettingOutlined />}
>
</Button> </Button>
</Dropdown> </Dropdown>
@ -1089,7 +1093,12 @@ function AddActionButton() {
</Menu> </Menu>
} }
> >
<Button style={{ marginLeft: 8 }} type={'dashed'} icon={<PlusOutlined />}> <Button
className={'designable-btn designable-btn-dash'}
style={{ marginLeft: 8 }}
type={'dashed'}
icon={<PlusOutlined />}
>
</Button> </Button>
</Dropdown> </Dropdown>
@ -1350,108 +1359,111 @@ Table.Filter.DesignableBar = () => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.ItemGroup title={'筛选字段'}> <Menu>
{fields <Menu.ItemGroup title={'筛选字段'}>
.filter((collectionField) => { {fields
const option = interfaces.get(collectionField.interface); .filter((collectionField) => {
return option.operations?.length; const option = interfaces.get(collectionField.interface);
}) return option.operations?.length;
.map((collectionField) => ( })
<SwitchMenuItem .map((collectionField) => (
title={collectionField?.uiSchema?.title} <SwitchMenuItem
checked={fieldNames.includes(collectionField.name)} title={collectionField?.uiSchema?.title}
onChange={async (checked) => { checked={fieldNames.includes(collectionField.name)}
if (checked) { onChange={async (checked) => {
fieldNames.push(collectionField.name); if (checked) {
} else { fieldNames.push(collectionField.name);
const index = fieldNames.indexOf( } else {
collectionField.name, const index = fieldNames.indexOf(
); collectionField.name,
if (index > -1) { );
fieldNames.splice(index, 1); if (index > -1) {
fieldNames.splice(index, 1);
}
} }
} console.log({ fieldNames, field });
console.log({ fieldNames, field }); schema['x-component-props']['fieldNames'] =
schema['x-component-props']['fieldNames'] = fieldNames; fieldNames;
field.componentProps.fieldNames = fieldNames; field.componentProps.fieldNames = fieldNames;
updateSchema(schema); updateSchema(schema);
}} }}
/> />
))} ))}
</Menu.ItemGroup> </Menu.ItemGroup>
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
onClick={async (e) => { onClick={async (e) => {
const values = await FormDialog('修改名称和图标', () => { const values = await FormDialog('修改名称和图标', () => {
return ( return (
<FormLayout layout={'vertical'}> <FormLayout layout={'vertical'}>
<SchemaField <SchemaField
schema={{ schema={{
type: 'object', type: 'object',
properties: { properties: {
title: { title: {
type: 'string', type: 'string',
title: '按钮名称', title: '按钮名称',
required: true, required: true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Input', 'x-component': 'Input',
},
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
}, },
icon: { }}
type: 'string', />
title: '按钮图标', </FormLayout>
'x-decorator': 'FormItem', );
'x-component': 'IconPicker', }).open({
}, initialValues: {
}, title: schema['title'],
}} icon: schema['x-component-props']?.['icon'],
/> },
</FormLayout> });
); schema['title'] = values.title;
}).open({ schema['x-component-props']['icon'] = values.icon;
initialValues: { field.componentProps.icon = values.icon;
title: schema['title'], field.title = values.title;
icon: schema['x-component-props']?.['icon'], updateSchema(schema);
}, refresh();
}); }}
schema['title'] = values.title; >
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon; </Menu.Item>
field.title = values.title; <Menu.Divider />
updateSchema(schema); <Menu.Item
refresh(); onClick={async () => {
}} const displayName =
> schema?.['x-decorator-props']?.['displayName'];
const data = remove();
</Menu.Item> await removeSchema(data);
<Menu.Divider /> if (displayName) {
<Menu.Item displayed.remove(displayName);
onClick={async () => { }
const displayName = setVisible(false);
schema?.['x-decorator-props']?.['displayName']; }}
const data = remove(); >
await removeSchema(data);
if (displayName) { </Menu.Item>
displayed.remove(displayName); </Menu>
} }
setVisible(false); >
}} <MenuOutlined />
> </Dropdown>
</Space>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span> </span>
</div> </div>
); );
@ -1475,101 +1487,105 @@ Table.ExportActionDesignableBar = () => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.ItemGroup title={'导出字段'}> <Menu>
{fields.map((collectionField) => ( <Menu.ItemGroup title={'导出字段'}>
<SwitchMenuItem {fields.map((collectionField) => (
title={collectionField?.uiSchema?.title} <SwitchMenuItem
checked={fieldNames.includes(collectionField.name)} title={collectionField?.uiSchema?.title}
onChange={async (checked) => { checked={fieldNames.includes(collectionField.name)}
if (checked) { onChange={async (checked) => {
fieldNames.push(collectionField.name); if (checked) {
} else { fieldNames.push(collectionField.name);
const index = fieldNames.indexOf(collectionField.name); } else {
if (index > -1) { const index = fieldNames.indexOf(
fieldNames.splice(index, 1); collectionField.name,
);
if (index > -1) {
fieldNames.splice(index, 1);
}
} }
} console.log({ fieldNames, field });
console.log({ fieldNames, field }); schema['x-component-props']['fieldNames'] = fieldNames;
schema['x-component-props']['fieldNames'] = fieldNames; field.componentProps.fieldNames = fieldNames;
field.componentProps.fieldNames = fieldNames; updateSchema(schema);
updateSchema(schema); }}
}} />
/> ))}
))} </Menu.ItemGroup>
</Menu.ItemGroup> <Menu.Divider />
<Menu.Divider /> <Menu.Item
<Menu.Item onClick={async (e) => {
onClick={async (e) => { const values = await FormDialog('修改名称和图标', () => {
const values = await FormDialog('修改名称和图标', () => { return (
return ( <FormLayout layout={'vertical'}>
<FormLayout layout={'vertical'}> <SchemaField
<SchemaField schema={{
schema={{ type: 'object',
type: 'object', properties: {
properties: { title: {
title: { type: 'string',
type: 'string', title: '按钮名称',
title: '按钮名称', required: true,
required: true, 'x-decorator': 'FormItem',
'x-decorator': 'FormItem', 'x-component': 'Input',
'x-component': 'Input', },
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
}, },
icon: { }}
type: 'string', />
title: '按钮图标', </FormLayout>
'x-decorator': 'FormItem', );
'x-component': 'IconPicker', }).open({
}, initialValues: {
}, title: schema['title'],
}} icon: schema['x-component-props']?.['icon'],
/> },
</FormLayout> });
); schema['title'] = values.title;
}).open({ schema['x-component-props']['icon'] = values.icon;
initialValues: { field.componentProps.icon = values.icon;
title: schema['title'], field.title = values.title;
icon: schema['x-component-props']?.['icon'], updateSchema(schema);
}, refresh();
}); }}
schema['title'] = values.title; >
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon; </Menu.Item>
field.title = values.title; <Menu.Divider />
updateSchema(schema); <Menu.Item
refresh(); onClick={async () => {
}} const displayName =
> schema?.['x-decorator-props']?.['displayName'];
const data = remove();
</Menu.Item> await removeSchema(data);
<Menu.Divider /> if (displayName) {
<Menu.Item displayed.remove(displayName);
onClick={async () => { }
const displayName = setVisible(false);
schema?.['x-decorator-props']?.['displayName']; }}
const data = remove(); >
await removeSchema(data);
if (displayName) { </Menu.Item>
displayed.remove(displayName); </Menu>
} }
setVisible(false); >
}} <MenuOutlined />
> </Dropdown>
</Space>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span> </span>
</div> </div>
); );
@ -1580,9 +1596,7 @@ Table.Operation = observer((props: any) => {
return ( return (
<div className={'nb-table-column'}> <div className={'nb-table-column'}>
{designable && schema['x-designable-bar'] && ( <Table.Operation.DesignableBar path={props.path} />
<Table.Operation.DesignableBar path={props.path} />
)}
</div> </div>
); );
}); });
@ -1600,9 +1614,10 @@ Table.Operation.Cell = observer((props: any) => {
Table.Operation.DesignableBar = (props) => { Table.Operation.DesignableBar = (props) => {
const { schema, remove, refresh, appendChild } = useDesignable(props.path); const { schema, remove, refresh, appendChild } = useDesignable(props.path);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
if (!schema['x-designable-bar']) { if (!schema?.parent?.parent['x-designable-bar']) {
return null; return null;
} }
console.log('Table.Operation.DesignableBar', schema?.parent?.parent);
const map = new Map(); const map = new Map();
schema.mapProperties((s) => { schema.mapProperties((s) => {
if (!s['x-action-type']) { if (!s['x-action-type']) {
@ -1618,52 +1633,54 @@ Table.Operation.DesignableBar = (props) => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.ItemGroup title={'操作展示'}> <Menu>
{[ <Menu.ItemGroup title={'操作展示'}>
{ title: '查看', name: 'view' }, {[
{ title: '编辑', name: 'update' }, { title: '查看', name: 'view' },
{ title: '删除', name: 'destroy' }, { title: '编辑', name: 'update' },
].map((item) => ( { title: '删除', name: 'destroy' },
<SwitchMenuItem ].map((item) => (
key={item.name} <SwitchMenuItem
title={item.title} key={item.name}
checked={map.has(item.name)} title={item.title}
onChange={async (checked) => { checked={map.has(item.name)}
if (checked) { onChange={async (checked) => {
const s = generateMenuActionSchema(item.name); if (checked) {
const data = appendChild(s); const s = generateMenuActionSchema(item.name);
await createSchema(data); const data = appendChild(s);
} else if (map.get(item.name)) { await createSchema(data);
const removed = remove([ } else if (map.get(item.name)) {
...props.path, const removed = remove([
map.get(item.name), ...props.path,
]); map.get(item.name),
await removeSchema(removed); ]);
} await removeSchema(removed);
}} }
/> }}
))} />
</Menu.ItemGroup> ))}
<Menu.Divider /> </Menu.ItemGroup>
<Menu.SubMenu title={'自定义'}> <Menu.Divider />
<Menu.Item style={{ minWidth: 120 }}></Menu.Item> <Menu.SubMenu title={'自定义'}>
<Menu.Item></Menu.Item> <Menu.Item style={{ minWidth: 120 }}></Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item></Menu.Item>
</Menu.SubMenu> <Menu.Item></Menu.Item>
</Menu> </Menu.SubMenu>
} </Menu>
> }
<MenuOutlined /> >
</Dropdown> <MenuOutlined />
</Dropdown>
</Space>
</span> </span>
</div> </div>
); );
@ -1686,109 +1703,115 @@ Table.Action.DesignableBar = () => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.Item <Menu>
onClick={async (e) => { <Menu.Item
const values = await FormDialog('修改名称和图标', () => { onClick={async (e) => {
return ( const values = await FormDialog('修改名称和图标', () => {
<FormLayout layout={'vertical'}> return (
<SchemaField <FormLayout layout={'vertical'}>
schema={{ <SchemaField
type: 'object', schema={{
properties: { type: 'object',
title: { properties: {
type: 'string', title: {
title: '按钮名称', type: 'string',
required: true, title: '按钮名称',
'x-decorator': 'FormItem', required: true,
'x-component': 'Input', 'x-decorator': 'FormItem',
'x-component': 'Input',
},
icon: {
type: 'string',
title: '按钮图标',
'x-decorator': 'FormItem',
'x-component': 'IconPicker',
},
}, },
icon: { }}
type: 'string', />
title: '按钮图标', </FormLayout>
'x-decorator': 'FormItem', );
'x-component': 'IconPicker', }).open({
}, initialValues: {
}, title: schema['title'],
}} icon: schema['x-component-props']?.['icon'],
/> },
</FormLayout> });
); schema['title'] = values.title;
}).open({ schema['x-component-props']['icon'] = values.icon;
initialValues: { field.componentProps.icon = values.icon;
title: schema['title'], field.title = values.title;
icon: schema['x-component-props']?.['icon'], updateSchema(schema);
}, refresh();
}); }}
schema['title'] = values.title; >
schema['x-component-props']['icon'] = values.icon;
field.componentProps.icon = values.icon;
field.title = values.title;
updateSchema(schema);
refresh();
}}
>
</Menu.Item>
{isPopup && (
<Menu.Item>
{' '}
<Select
bordered={false}
size={'small'}
defaultValue={'Action.Modal'}
onChange={(value) => {
const s = Object.values(schema.properties).shift();
s['x-component'] = value;
refresh();
updateSchema(s);
// const f = field.query(getSchemaPath(s)).take()
// console.log('fffffff', { schema, f });
}}
>
<Select.Option value={'Action.Modal'}></Select.Option>
<Select.Option value={'Action.Drawer'}></Select.Option>
<Select.Option value={'Action.Window'}>
</Select.Option>
</Select>{' '}
</Menu.Item> </Menu.Item>
)} {isPopup && (
{!inActionBar && ( <Menu.Item>
<Menu.Item> {' '}
&nbsp;&nbsp; <Select
<Switch size={'small'} defaultChecked /> bordered={false}
size={'small'}
defaultValue={'Action.Modal'}
onChange={(value) => {
const s = Object.values(schema.properties).shift();
s['x-component'] = value;
refresh();
updateSchema(s);
// const f = field.query(getSchemaPath(s)).take()
// console.log('fffffff', { schema, f });
}}
>
<Select.Option value={'Action.Modal'}>
</Select.Option>
<Select.Option value={'Action.Drawer'}>
</Select.Option>
<Select.Option value={'Action.Window'}>
</Select.Option>
</Select>{' '}
</Menu.Item>
)}
{!inActionBar && (
<Menu.Item>
&nbsp;&nbsp;
<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);
}}
>
</Menu.Item> </Menu.Item>
)} </Menu>
<Menu.Divider /> }
<Menu.Item >
onClick={async () => { <MenuOutlined />
const displayName = </Dropdown>
schema?.['x-decorator-props']?.['displayName']; </Space>
const data = remove();
await removeSchema(data);
if (displayName) {
displayed.remove(displayName);
}
setVisible(false);
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span> </span>
</div> </div>
); );
@ -1860,77 +1883,80 @@ Table.Column.DesignableBar = () => {
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<DragHandle /> <Space>
<Dropdown <DragHandle />
trigger={['click']} <Dropdown
visible={visible} trigger={['click']}
onVisibleChange={(visible) => { visible={visible}
setVisible(visible); onVisibleChange={(visible) => {
}} setVisible(visible);
overlay={ }}
<Menu> overlay={
<Menu.Item <Menu>
onClick={async (e) => { <Menu.Item
const values = await FormDialog('修改列标题', () => { onClick={async (e) => {
return ( const values = await FormDialog('修改列标题', () => {
<FormLayout layout={'vertical'}> return (
<SchemaField <FormLayout layout={'vertical'}>
schema={{ <SchemaField
type: 'object', schema={{
properties: { type: 'object',
fieldName: { properties: {
type: 'string', fieldName: {
title: '原字段名称', type: 'string',
'x-read-pretty': true, title: '原字段名称',
'x-decorator': 'FormItem', 'x-read-pretty': true,
'x-component': 'Input', 'x-decorator': 'FormItem',
'x-component': 'Input',
},
title: {
type: 'string',
title: '自定义列标题',
'x-decorator': 'FormItem',
'x-component': 'Input',
},
}, },
title: { }}
type: 'string', />
title: '自定义列标题', </FormLayout>
'x-decorator': 'FormItem', );
'x-component': 'Input', }).open({
}, initialValues: {
}, fieldName: collectionField?.uiSchema?.title,
}} title: schema['title'],
/> },
</FormLayout> });
); const title = values.title || null;
}).open({ field.title = title;
initialValues: { schema.title = title;
fieldName: collectionField?.uiSchema?.title, refresh();
title: schema['title'], await updateSchema({
}, key: schema['key'],
}); title: title,
const title = values.title || null; });
field.title = title; setVisible(false);
schema.title = title; }}
refresh(); >
await updateSchema({
key: schema['key'], </Menu.Item>
title: title, <Menu.Divider />
}); <Menu.Item
setVisible(false); onClick={async () => {
}} const s = remove();
> const fieldName =
schema['x-component-props']?.['fieldName'];
</Menu.Item> displayed.remove(fieldName);
<Menu.Divider /> await removeSchema(s);
<Menu.Item }}
onClick={async () => { >
const s = remove();
const fieldName = schema['x-component-props']?.['fieldName']; </Menu.Item>
displayed.remove(fieldName); </Menu>
await removeSchema(s); }
}} >
> <MenuOutlined />
</Dropdown>
</Menu.Item> </Space>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</span> </span>
</div> </div>
); );
@ -1957,14 +1983,16 @@ Table.DesignableBar = observer((props) => {
console.log({ collectionName }); console.log({ collectionName });
return ( return (
<div className={cls('designable-bar', { active: visible })}> <div className={cls('designable-bar', { active: visible })}>
<div className={'designable-info'}>{collection?.title || collection?.name}</div> <div className={'designable-info'}>
{collection?.title || collection?.name}
</div>
<span <span
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<Space size={'small'}> <Space size={2}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost /> <AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />} {dragRef && <DragOutlined ref={dragRef} />}
<Dropdown <Dropdown
@ -2245,7 +2273,10 @@ Table.useActionLogDetailsResource = ({ onSuccess }) => {
}); });
const service = useRequest( const service = useRequest(
(params?: any) => { (params?: any) => {
return resource.get({ ...params, appends: ['changes', 'user', 'collection'] }); return resource.get({
...params,
appends: ['changes', 'user', 'collection'],
});
}, },
{ {
formatResult: (result) => result?.data, formatResult: (result) => result?.data,