|
|
@@ -1,5 +1,5 @@
|
|
|
-import { PageContainer } from '@ant-design/pro-layout';
|
|
|
-import { useDomFullHeight } from '@/hooks';
|
|
|
+import {PageContainer} from '@ant-design/pro-layout';
|
|
|
+import {useDomFullHeight} from '@/hooks';
|
|
|
import {
|
|
|
ExclamationCircleOutlined,
|
|
|
QuestionCircleOutlined,
|
|
|
@@ -7,163 +7,245 @@ import {
|
|
|
} from '@ant-design/icons';
|
|
|
import Tree from './tree';
|
|
|
import './index.less';
|
|
|
-import { Button, Tooltip } from 'antd';
|
|
|
+import {Button, Tooltip} from 'antd';
|
|
|
import BaseTreeData from './baseMenu';
|
|
|
-import { useEffect, useState } from 'react';
|
|
|
-import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
|
-import { DndProvider } from 'react-dnd';
|
|
|
-
|
|
|
-export default () => {
|
|
|
- const { minHeight } = useDomFullHeight(`.menu-setting-warp`);
|
|
|
- const [menuData] = useState<any[]>([]);
|
|
|
- const [baseMenu, setBaseMenu] = useState<any[]>([]);
|
|
|
-
|
|
|
- // const removeItem = (data: any[], id: string): any[] => {
|
|
|
- // return data.filter(item => {
|
|
|
- // if (item.id === id) {
|
|
|
- // return false
|
|
|
- // }
|
|
|
- //
|
|
|
- // if (item.children) {
|
|
|
- // item.children = removeItem(item.children, id)
|
|
|
- // }
|
|
|
- // return true
|
|
|
- // })
|
|
|
- // }
|
|
|
-
|
|
|
- // const findItem = (data: any[], id: string) => {
|
|
|
- // let object = null;
|
|
|
- // data.some((item) => {
|
|
|
- // if (item.id === id) {
|
|
|
- // object = item;
|
|
|
- // return true;
|
|
|
- // }
|
|
|
- //
|
|
|
- // if (item.children) {
|
|
|
- // object = findItem(item.children, id);
|
|
|
- // return !!object;
|
|
|
- // }
|
|
|
- //
|
|
|
- // return false;
|
|
|
- // });
|
|
|
- // return object;
|
|
|
- // };
|
|
|
-
|
|
|
- // const finedIndex = (data: any[], id: string): { index: number; menus: any[] } => {
|
|
|
- // let object = {
|
|
|
- // index: data.length,
|
|
|
- // menus: data,
|
|
|
- // };
|
|
|
- // data.some((item, index) => {
|
|
|
- // if (item.id === id) {
|
|
|
- // object = {
|
|
|
- // index,
|
|
|
- // menus: data,
|
|
|
- // };
|
|
|
- // return true;
|
|
|
- // }
|
|
|
- //
|
|
|
- // if (item.children) {
|
|
|
- // object = finedIndex(item.children, id);
|
|
|
- // return !!object;
|
|
|
- // }
|
|
|
- //
|
|
|
- // return false;
|
|
|
- // });
|
|
|
- //
|
|
|
- // return object;
|
|
|
- // };
|
|
|
-
|
|
|
- // const onDragEnd = useCallback(
|
|
|
- // (result: any) => {
|
|
|
- // console.log(result);
|
|
|
- // if (result.source.droppableId.includes('source')) {
|
|
|
- // if (result.combine && result.combine.droppableId.includes('menu')) {
|
|
|
- // const sourceIndex = result.source.index.replace(/(source|menu)&/, '');
|
|
|
- // const draggableIdIndex = result.combine?.draggableId.replace(/(source|menu)&/, '');
|
|
|
- // const sourceItem = findItem(baseMenu, sourceIndex);
|
|
|
- // const newMenus = [...menuData];
|
|
|
- // const { index, menus } = finedIndex(newMenus, draggableIdIndex);
|
|
|
- // console.log(index, menus);
|
|
|
- // menus.splice(index + 1, 0, sourceItem);
|
|
|
- // console.log(newMenus);
|
|
|
- // setMenuData([...newMenus]);
|
|
|
- // } else if (result.destination && result.destination.droppableId.includes('menu')) {
|
|
|
- // const sourceIndex = result.source.index.replace(/(source|menu)&/, '');
|
|
|
- // const sourceItem = findItem(baseMenu, sourceIndex);
|
|
|
- // const newMenus = [...menuData];
|
|
|
- // if (sourceItem) {
|
|
|
- // if (newMenus.length) {
|
|
|
- // const destinationIndex = result.destination?.index.replace(/(source|menu)&/, '');
|
|
|
- // // 获取右侧menu的位置
|
|
|
- // const { index, menus } = finedIndex(newMenus, destinationIndex);
|
|
|
- // console.log(index, menus);
|
|
|
- // menus.splice(index + 1, 0, sourceItem);
|
|
|
- // } else {
|
|
|
- // newMenus.push(sourceItem);
|
|
|
- // }
|
|
|
- // console.log(newMenus);
|
|
|
- // setMenuData([...newMenus]);
|
|
|
- // }
|
|
|
- // }
|
|
|
- // }
|
|
|
- // },
|
|
|
- // [menuData, baseMenu],
|
|
|
- // );
|
|
|
+import { useEffect, useState} from 'react';
|
|
|
+import {HTML5Backend} from 'react-dnd-html5-backend';
|
|
|
+import {DndProvider} from 'react-dnd';
|
|
|
+import {cloneDeep} from 'lodash'
|
|
|
+import { observable } from '@formily/reactive';
|
|
|
+import { Observer, observer } from '@formily/react'
|
|
|
+import { service } from '../index'
|
|
|
+import type {TreeProps, DataNode} from 'antd/es/tree'
|
|
|
+
|
|
|
+type MenuSettingModelType = {
|
|
|
+ menuData: any[]
|
|
|
+ notDragKeys: (string | number)[]
|
|
|
+}
|
|
|
+
|
|
|
+export const MenuSettingModel = observable<MenuSettingModelType>({
|
|
|
+ menuData: [],
|
|
|
+ notDragKeys: []
|
|
|
+})
|
|
|
+
|
|
|
+export default observer(() => {
|
|
|
+ const {minHeight} = useDomFullHeight(`.menu-setting-warp`);
|
|
|
+ const [baseMenu, setBaseMenu] = useState<any[]>(BaseTreeData);
|
|
|
+ const [loading, setLoading] = useState(false)
|
|
|
+
|
|
|
+ const finedObject = (data: any[], code: string | number, callback: (index: number, arr: any[], item: any) => void) => {
|
|
|
+
|
|
|
+ data.forEach((item, index) => {
|
|
|
+ if (item.code === code) {
|
|
|
+ return callback(index, data, item)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (item.children) {
|
|
|
+ finedObject(item.children, code, callback);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const onTreeDrop: TreeProps['onDrop'] = (info) => {
|
|
|
+ const dropKey = info.node.key;
|
|
|
+ const dragKey = info.dragNode.key;
|
|
|
+ const dropPos = info.node.pos.split('-');
|
|
|
+ const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
|
|
|
+
|
|
|
+ const data = [...MenuSettingModel.menuData];
|
|
|
+ let dragObj: DataNode & {parentId: string};
|
|
|
+
|
|
|
+ finedObject(data, dragKey, (index, arr, item) => {
|
|
|
+ arr.splice(index, 1);
|
|
|
+ dragObj = item;
|
|
|
+ dragObj.parentId = '';
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!info.dropToGap) {
|
|
|
+ finedObject(data, dropKey, (_i, _arr, item) => {
|
|
|
+ item.children = item.children || [];
|
|
|
+ dragObj.parentId = item.id
|
|
|
+
|
|
|
+ item.children.unshift(dragObj);
|
|
|
+ });
|
|
|
+ } else if (
|
|
|
+ ((info.node as any).props.children || []).length > 0 &&
|
|
|
+ (info.node as any).props.expanded &&
|
|
|
+ dropPosition === 1
|
|
|
+ ) {
|
|
|
+ finedObject(data, dropKey, (_i, _arr, item) => {
|
|
|
+ item.children = item.children || [];
|
|
|
+ dragObj.parentId = item.id
|
|
|
+
|
|
|
+ item.children.unshift(dragObj);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ let ar: DataNode[] = [];
|
|
|
+ let i: number;
|
|
|
+ finedObject(data, dropKey, (index, arr, item) => {
|
|
|
+ ar = arr;
|
|
|
+ i = index;
|
|
|
+ dragObj.parentId = item.parentId;
|
|
|
+ });
|
|
|
+ if (dropPosition === -1) {
|
|
|
+ ar.splice(i!, 0, dragObj!);
|
|
|
+ } else {
|
|
|
+ ar.splice(i! + 1, 0, dragObj!);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ MenuSettingModel.menuData = [...data]
|
|
|
+ }
|
|
|
+
|
|
|
+ const getKeys = (data: any[]): (string|number)[] => {
|
|
|
+ return data.reduce((pre: (string|number)[] , next: any) => {
|
|
|
+ const childrenKeys = next.children ? getKeys(next.children) : []
|
|
|
+ return [...pre, next.code, ...childrenKeys]
|
|
|
+ }, [])
|
|
|
+ }
|
|
|
+
|
|
|
+ const filterItem = (data: any[], dragKeys: (string | number)[]) => {
|
|
|
+ return data.filter(item => {
|
|
|
+ if (dragKeys.includes(item.code)) {
|
|
|
+ return false
|
|
|
+ } else if (item.children) {
|
|
|
+ item.children = filterItem(item.children, dragKeys)
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const onDrop = (item: any, dropIndex: string, type?: string) => {
|
|
|
+ const newItem = cloneDeep(item);
|
|
|
+
|
|
|
+ if (dropIndex) { // 切换层级或者挪动
|
|
|
+ // const newMenus = cloneDeep(menuData);
|
|
|
+ if (type === 'source') {
|
|
|
+ finedObject(MenuSettingModel.menuData, dropIndex, (index, arr) => {
|
|
|
+ arr.splice(index + 1, 0, newItem);
|
|
|
+ });
|
|
|
+ MenuSettingModel.menuData = [...MenuSettingModel.menuData];
|
|
|
+ }
|
|
|
+ } else { // 新增
|
|
|
+ newItem.parentId = ''
|
|
|
+ // 过滤掉内部已选择的节点
|
|
|
+ if (newItem.children && MenuSettingModel.notDragKeys.length) {
|
|
|
+ newItem.children = filterItem(newItem.children, MenuSettingModel.notDragKeys)
|
|
|
+ }
|
|
|
+ MenuSettingModel.menuData.push(newItem)
|
|
|
+ MenuSettingModel.notDragKeys = getKeys(MenuSettingModel.menuData)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const removeDragItem = (id: string | number) => {
|
|
|
+ finedObject(MenuSettingModel.menuData, id, (index, arr) => {
|
|
|
+ arr.splice(index, 1);
|
|
|
+ });
|
|
|
+ MenuSettingModel.menuData = [...MenuSettingModel.menuData]
|
|
|
+ MenuSettingModel.notDragKeys = getKeys(MenuSettingModel.menuData)
|
|
|
+ }
|
|
|
+
|
|
|
+ const getSystemMenu = () => {
|
|
|
+ service.queryMenuThree({ paging: false }).then(res => {
|
|
|
+ if (res.status === 200) {
|
|
|
+ MenuSettingModel.menuData = [...res.result]
|
|
|
+ MenuSettingModel.notDragKeys = getKeys(res.result)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
useEffect(() => {
|
|
|
+ getSystemMenu()
|
|
|
setBaseMenu(BaseTreeData);
|
|
|
}, []);
|
|
|
|
|
|
+ const updateMenu = () => {
|
|
|
+ setLoading(true)
|
|
|
+ service.updateMenus(MenuSettingModel.menuData).then(res => {
|
|
|
+ setLoading(false)
|
|
|
+ if (res.status === 200) {
|
|
|
+
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
return (
|
|
|
<PageContainer>
|
|
|
- <div className={'menu-setting-warp'} style={{ minHeight }}>
|
|
|
+ <div className={'menu-setting-warp'} style={{minHeight}}>
|
|
|
<div className={'menu-setting-tip'}>
|
|
|
- <ExclamationCircleOutlined />
|
|
|
- 基于系统源代码中的菜单数据,配置系统菜单。
|
|
|
+ <ExclamationCircleOutlined/>
|
|
|
+ <span style={{paddingLeft: 12}}>
|
|
|
+ 基于系统源代码中的菜单数据,配置系统菜单。
|
|
|
+ </span>
|
|
|
</div>
|
|
|
<div className={'menu-tree-content'}>
|
|
|
<DndProvider backend={HTML5Backend}>
|
|
|
<div className={'menu-tree left-tree'}>
|
|
|
- <div className={'menu-tree-title'}>
|
|
|
+ <div className={'menu-tree-title'} style={{padding: '10px 16px'}}>
|
|
|
<div>
|
|
|
+ <span style={{paddingRight: 12}}>
|
|
|
源菜单
|
|
|
+ </span>
|
|
|
<Tooltip title={'根据系统代码自动读取的菜单数据'}>
|
|
|
- <QuestionCircleOutlined />
|
|
|
+ <QuestionCircleOutlined/>
|
|
|
</Tooltip>
|
|
|
</div>
|
|
|
- <Button type={'primary'} ghost>
|
|
|
+ <Button type={'primary'} ghost onClick={() => {
|
|
|
+ MenuSettingModel.menuData = cloneDeep(baseMenu)
|
|
|
+ }}>
|
|
|
一键拷贝
|
|
|
</Button>
|
|
|
</div>
|
|
|
- <Tree treeData={baseMenu} droppableId={'source'} />
|
|
|
+ <Observer>
|
|
|
+ {() => (
|
|
|
+ <Tree
|
|
|
+ onDrop={onDrop}
|
|
|
+ treeData={baseMenu}
|
|
|
+ droppableId={'source'}
|
|
|
+ notDragKeys={MenuSettingModel.notDragKeys}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </Observer>
|
|
|
</div>
|
|
|
- <div style={{ display: 'flex', alignItems: 'center' }}>
|
|
|
+ <div style={{display: 'flex', alignItems: 'center'}}>
|
|
|
<div className={'menu-tree-drag-btn'}>
|
|
|
请拖动至右侧
|
|
|
- <RightOutlined />
|
|
|
+ <RightOutlined/>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div className={'menu-tree right-tree'}>
|
|
|
<div className={'menu-tree-title'}>
|
|
|
<div>
|
|
|
+ <span style={{paddingRight: 12}}>
|
|
|
系统菜单
|
|
|
+ </span>
|
|
|
<Tooltip title={'菜单管理页面配置的菜单数据'}>
|
|
|
- <QuestionCircleOutlined />
|
|
|
+ <QuestionCircleOutlined/>
|
|
|
</Tooltip>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <Tree treeData={menuData} droppableId={'menu'} />
|
|
|
+ <Observer>
|
|
|
+ {() => (
|
|
|
+ <>
|
|
|
+ <Tree
|
|
|
+ onTreeDrop={onTreeDrop}
|
|
|
+ onDrop={onDrop}
|
|
|
+ treeData={MenuSettingModel.menuData}
|
|
|
+ droppableId={'menu'}
|
|
|
+ removeDragItem={removeDragItem}
|
|
|
+ />
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </Observer>
|
|
|
</div>
|
|
|
</DndProvider>
|
|
|
</div>
|
|
|
<div>
|
|
|
- <Button type={'primary'} style={{ marginTop: 24 }}>
|
|
|
+ <Button type={'primary'} style={{ marginTop: 24 }} onClick={updateMenu} loading={loading}>
|
|
|
保存
|
|
|
</Button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</PageContainer>
|
|
|
);
|
|
|
-};
|
|
|
+});
|