Quellcode durchsuchen

feat: 新增菜单配置页

xieyonghong vor 3 Jahren
Ursprung
Commit
5cef98fa7b

+ 2 - 2
config/proxy.ts

@@ -8,7 +8,7 @@
  */
  */
 export default {
 export default {
   dev: {
   dev: {
-    '/jetlinks': {
+    '/api': {
       // target: 'http://192.168.32.8:8844/',
       // target: 'http://192.168.32.8:8844/',
       // ws: 'ws://192.168.32.8:8844/',
       // ws: 'ws://192.168.32.8:8844/',
       // 开发环境
       // 开发环境
@@ -22,7 +22,7 @@ export default {
       // ws: 'ws://demo.jetlinks.cn/jetlinks',
       // ws: 'ws://demo.jetlinks.cn/jetlinks',
       // target: 'http://demo.jetlinks.cn/jetlinks',
       // target: 'http://demo.jetlinks.cn/jetlinks',
       changeOrigin: true,
       changeOrigin: true,
-      pathRewrite: { '^/jetlinks': '' },
+      pathRewrite: { '^/api': '' },
     },
     },
   },
   },
   test: {
   test: {

+ 2 - 2
docker/nginx.conf

@@ -15,8 +15,8 @@ server {
         index  index.html;
         index  index.html;
     }
     }
 
 
-    location ^~/jetlinks/ {
-        if ($request_uri ~* ^/jetlinks/(.*)$) {
+    location ^~/api/ {
+        if ($request_uri ~* ^/api/(.*)$) {
             proxy_pass http://host.docker.internal:8840/$1;
             proxy_pass http://host.docker.internal:8840/$1;
         }
         }
         #proxy_pass http://host.docker.internal:8840/;
         #proxy_pass http://host.docker.internal:8840/;

+ 1 - 0
package.json

@@ -94,6 +94,7 @@
     "omit.js": "^2.0.2",
     "omit.js": "^2.0.2",
     "react": "^17.0.0",
     "react": "^17.0.0",
     "react-amap": "^1.2.8",
     "react-amap": "^1.2.8",
+    "react-beautiful-dnd": "^13.1.0",
     "react-custom-scrollbars": "^4.2.1",
     "react-custom-scrollbars": "^4.2.1",
     "react-dev-inspector": "^1.1.1",
     "react-dev-inspector": "^1.1.1",
     "react-dom": "^17.0.0",
     "react-dom": "^17.0.0",

+ 4 - 2
src/hooks/websocket/useWebSocket.ts

@@ -144,11 +144,13 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
     setReadyState(ws?.readyState);
     setReadyState(ws?.readyState);
     if (readyState === ReadyState.Open) {
     if (readyState === ReadyState.Open) {
       ws.send(message);
       ws.send(message);
-    } else {
+    } else if (ws) {
       connectWs();
       connectWs();
       // todo 考虑重写
       // todo 考虑重写
       setTimeout(() => {
       setTimeout(() => {
-        ws.send(message);
+        if (readyState === ReadyState.Open) {
+          ws.send(message);
+        }
       }, 3000);
       }, 3000);
       // throw new Error('WebSocket disconnected');
       // throw new Error('WebSocket disconnected');
     }
     }

+ 22 - 9
src/pages/device/Instance/index.tsx

@@ -644,17 +644,30 @@ const Instance = () => {
                   }),
                   }),
                   onConfirm: async () => {
                   onConfirm: async () => {
                     if (record.state.value !== 'notActive') {
                     if (record.state.value !== 'notActive') {
-                      await service.undeployDevice(record.id);
+                      await service.undeployDevice(record.id).then((res) => {
+                        if (res.status === 200) {
+                          onlyMessage(
+                            intl.formatMessage({
+                              id: 'pages.data.option.success',
+                              defaultMessage: '操作成功!',
+                            }),
+                          );
+                          actionRef.current?.reload();
+                        }
+                      });
                     } else {
                     } else {
-                      await service.deployDevice(record.id);
+                      await service.deployDevice(record.id).then((res) => {
+                        if (res.status === 200) {
+                          onlyMessage(
+                            intl.formatMessage({
+                              id: 'pages.data.option.success',
+                              defaultMessage: '操作成功!',
+                            }),
+                          );
+                          actionRef.current?.reload();
+                        }
+                      });
                     }
                     }
-                    onlyMessage(
-                      intl.formatMessage({
-                        id: 'pages.data.option.success',
-                        defaultMessage: '操作成功!',
-                      }),
-                    );
-                    actionRef.current?.reload();
                   },
                   },
                 }}
                 }}
               >
               >

+ 125 - 0
src/pages/system/Menu/Setting/baseMenu.ts

@@ -0,0 +1,125 @@
+export default [
+  // 物联网
+  {
+    code: 'iot',
+    name: '物联网',
+    id: '1',
+    children: [
+      { code: 'home', name: '首页', parentId: '1', id: '1-1' },
+      { code: 'notice', name: '通知管理', parentId: '1', id: '1-2' },
+
+      {
+        code: 'device',
+        name: '设备管理',
+        parentId: '1',
+        id: '1-3',
+        children: [
+          { code: 'device/DashBoard', name: '仪表盘', parentId: '1-3', id: '1-3-1' },
+          { code: 'device/Product', name: '产品', parentId: '1-3', id: '1-3-2' },
+          { code: 'device/Instance', name: '设备', parentId: '1-3', id: '1-3-3' },
+          { code: 'device/Category', name: '产品分类', parentId: '1-3', id: '1-3-4' },
+        ],
+      },
+
+      {
+        code: 'link',
+        name: '运维管理',
+        parentId: '1',
+        id: '1-4',
+        children: [
+          { code: 'link/DashBoard', name: '仪表盘', parentId: '1-4', id: '1-4-1' },
+          { code: 'link/AccessConfig', name: '设备接入网关', parentId: '1-4', id: '1-4-2' },
+          { code: 'link/Protocol', name: '协议管理', parentId: '1-4', id: '1-4-3' },
+          { code: 'Log', name: '日志管理', parentId: '1-4', id: '1-4-4' },
+          { code: 'link/Type', name: '网络组件', parentId: '1-4', id: '1-4-5' },
+          { code: 'link/Certificate', name: '证书管理', parentId: '1-4', id: '1-4-6' },
+          { code: 'media/Stream', name: '流媒体服务', parentId: '1-4', id: '1-4-7' },
+          {
+            code: 'link/Channel',
+            name: '通道配置',
+            parentId: '1-4',
+            id: '1-4-8',
+            children: [
+              { code: 'link/Channel/Opcua', name: 'OPC UA', parentId: '1-4-8', id: '1-4-8-1' },
+              { code: 'link/Channel/Modbus', name: 'Modbus', parentId: '1-4-8', id: '1-4-8-2' },
+            ],
+          },
+        ],
+      },
+
+      {
+        code: 'notice',
+        name: '通知管理',
+        parentId: '1',
+        id: '1-5',
+        children: [
+          { code: 'rule-engine/Alarm', name: '告警中心', parentId: '1-5', id: '1-5-1' },
+          { code: 'rule-engine/DashBoard', name: '仪表盘', parentId: '1-5', id: '1-5-2' },
+          {
+            code: 'rule-engine/Alarm/Configuration',
+            name: '告警配置',
+            parentId: '1-5',
+            id: '1-5-3',
+          },
+          { code: 'rule-engine/Alarm/Config', name: '基础配置', parentId: '1-5', id: '1-5-4' },
+          { code: 'rule-engine/Alarm/Log', name: '告警记录', parentId: '1-5', id: '1-5-5' },
+        ],
+      },
+
+      {
+        code: 'Northbound',
+        name: '北向输出',
+        parentId: '1',
+        id: '1-6',
+        children: [
+          { code: 'Northbound/DuerOS', name: 'DuerOS', parentId: '1-6', id: '1-6-1' },
+          { code: 'Northbound/AliCloud', name: '阿里云', parentId: '1-6', id: '1-6-2' },
+        ],
+      },
+
+      {
+        code: 'rule-engine',
+        name: '规则引擎',
+        parentId: '1',
+        id: '1-7',
+        children: [
+          { code: 'rule-engine/Instance', name: '规则编排', parentId: '1-7', id: '1-7-1' },
+          { code: 'rule-engine/Scene', name: '场景联动', parentId: '1-7', id: '1-7-2' },
+        ],
+      },
+    ],
+  },
+
+  // 视频中心
+  {
+    code: 'media',
+    name: '视频中心',
+    id: '2',
+    children: [
+      { code: 'media/Home', name: '首页', parentId: '2', id: '2-1' },
+      { code: 'media/DashBoard', name: '仪表盘', parentId: '2', id: '2-2' },
+      { code: 'media/Device', name: '视频设备', parentId: '2', id: '2-3' },
+      { code: 'media/SplitScreen', name: '分屏展示', parentId: '2', id: '2-4' },
+      { code: 'media/Cascade', name: '国标级联', parentId: '2', id: '2-5' },
+    ],
+  },
+
+  // 系统管理
+  {
+    code: 'system',
+    name: '系统管理',
+    id: '3',
+    children: [
+      { code: 'system/Basis', name: '基础配置', parentId: '3', id: '3-1' },
+      { code: 'system/User', name: '用户管理', parentId: '3', id: '3-2' },
+      { code: 'system/Department', name: '部门管理', parentId: '3', id: '3-3' },
+      { code: 'system/Role', name: '角色管理', parentId: '3', id: '3-4' },
+      { code: 'system/Menu', name: '菜单管理', parentId: '3', id: '3-5' },
+      { code: 'system/Permission', name: '权限管理', parentId: '3', id: '3-6' },
+      { code: 'system/Platforms', name: '第三方平台', parentId: '3', id: '3-7' },
+      { code: 'system/Relationship', name: '关系配置', parentId: '3', id: '3-8' },
+      { code: 'system/DataSource', name: '数据源管理', parentId: '3', id: '3-9' },
+      { code: 'system/Platforms/Setting', name: 'API配置', parentId: '3', id: '3-10' },
+    ],
+  },
+];

+ 24 - 0
src/pages/system/Menu/Setting/dragItem.tsx

@@ -0,0 +1,24 @@
+import { Draggable } from 'react-beautiful-dnd';
+
+interface DragItemProps {
+  data: any;
+  type: string;
+}
+
+const DragItem = (props: DragItemProps) => {
+  return (
+    <Draggable
+      draggableId={props.type + '&' + props.data.id}
+      index={props.type + '&' + props.data.id}
+      isCombineEnabled={true}
+    >
+      {(provided) => (
+        <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
+          {props.data.name}
+        </div>
+      )}
+    </Draggable>
+  );
+};
+
+export default DragItem;

+ 67 - 0
src/pages/system/Menu/Setting/index.less

@@ -0,0 +1,67 @@
+@bgColor: #f6f6f6;
+
+.menu-setting-warp {
+  display: flex;
+  flex-direction: column;
+  padding: 24px;
+  background-color: #fff;
+
+  .menu-setting-tip {
+    margin-bottom: 24px;
+    padding: 10px 24px;
+    color: rgba(0, 0, 0, 0.55);
+    background-color: @bgColor;
+  }
+
+  .menu-tree-content {
+    display: flex;
+    flex-grow: 1;
+    gap: 16px;
+    justify-content: space-between;
+    height: 0;
+
+    .menu-tree {
+      display: flex;
+      flex: 1 auto;
+      flex-direction: column;
+      justify-content: space-between;
+
+      .menu-tree-title {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 16px;
+        padding: 14px 16px;
+        font-weight: 400;
+        font-size: 16px;
+        background-color: #f3f4f4;
+
+        > span {
+          margin-left: 16px;
+        }
+      }
+
+      .tree-content {
+        display: flex;
+        flex-direction: column;
+        flex-grow: 1;
+        padding: 12px;
+        border: 1px solid #e0e0e0;
+        border-radius: 4px;
+
+        .tree-body {
+          flex: 1 auto;
+          height: 0;
+          min-height: 300px;
+          margin-top: 16px;
+          overflow-y: auto;
+        }
+      }
+    }
+
+    .menu-tree-drag-btn {
+      padding: 8px;
+      border: 1px solid #e0e0e0;
+      border-radius: 2px;
+    }
+  }
+}

+ 168 - 0
src/pages/system/Menu/Setting/index.tsx

@@ -0,0 +1,168 @@
+import { PageContainer } from '@ant-design/pro-layout';
+import { useDomFullHeight } from '@/hooks';
+import {
+  ExclamationCircleOutlined,
+  QuestionCircleOutlined,
+  RightOutlined,
+} from '@ant-design/icons';
+import Tree from './tree';
+import './index.less';
+import { Button, Tooltip } from 'antd';
+import BaseTreeData from './baseMenu';
+import { useCallback, useEffect, useState } from 'react';
+import { DragDropContext } from 'react-beautiful-dnd';
+
+export default () => {
+  const { minHeight } = useDomFullHeight(`.menu-setting-warp`);
+  const [menuData, setMenuData] = 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.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],
+  );
+
+  useEffect(() => {
+    setBaseMenu(BaseTreeData);
+  }, []);
+
+  return (
+    <PageContainer>
+      <div className={'menu-setting-warp'} style={{ minHeight }}>
+        <div className={'menu-setting-tip'}>
+          <ExclamationCircleOutlined />
+          基于系统源代码中的菜单数据,配置系统菜单。
+        </div>
+        <div className={'menu-tree-content'}>
+          <DragDropContext onDragEnd={onDragEnd}>
+            <div className={'menu-tree left-tree'}>
+              <div className={'menu-tree-title'}>
+                <div>
+                  源菜单
+                  <Tooltip title={'根据系统代码自动读取的菜单数据'}>
+                    <QuestionCircleOutlined />
+                  </Tooltip>
+                </div>
+                <Button type={'primary'} ghost>
+                  一键拷贝
+                </Button>
+              </div>
+              <Tree treeData={baseMenu} droppableId={'source'} />
+            </div>
+            <div style={{ display: 'flex', alignItems: 'center' }}>
+              <div className={'menu-tree-drag-btn'}>
+                请拖动至右侧
+                <RightOutlined />
+              </div>
+            </div>
+
+            <div className={'menu-tree right-tree'}>
+              <div className={'menu-tree-title'}>
+                <div>
+                  系统菜单
+                  <Tooltip title={'菜单管理页面配置的菜单数据'}>
+                    <QuestionCircleOutlined />
+                  </Tooltip>
+                </div>
+              </div>
+              <Tree treeData={menuData} droppableId={'menu'} />
+            </div>
+          </DragDropContext>
+        </div>
+        <div>
+          <Button type={'primary'} style={{ marginTop: 24 }}>
+            保存
+          </Button>
+        </div>
+      </div>
+    </PageContainer>
+  );
+};

+ 58 - 0
src/pages/system/Menu/Setting/tree.tsx

@@ -0,0 +1,58 @@
+import { Input, Tree } from 'antd';
+import { SearchOutlined } from '@ant-design/icons';
+import DragItem from '@/pages/system/Menu/Setting/dragItem';
+import { Droppable } from 'react-beautiful-dnd';
+
+interface TreeBodyProps {
+  treeData: any[];
+  droppableId: string;
+}
+
+const { TreeNode } = Tree;
+
+export default (props: TreeBodyProps) => {
+  const createTreeNode = (data: any[], type: string): React.ReactNode => {
+    return data.map((item: any) => {
+      if (item.children) {
+        return (
+          <TreeNode title={<DragItem data={item} type={type} />}>
+            {createTreeNode(item.children, type)}
+          </TreeNode>
+        );
+      }
+      return <TreeNode title={<DragItem data={item} type={type} />}></TreeNode>;
+    });
+  };
+
+  return (
+    <div className={'tree-content'}>
+      <div style={{ width: '75%' }}>
+        <Input
+          prefix={<SearchOutlined style={{ color: '#B3B3B3' }} />}
+          placeholder={'请输入菜单名称'}
+        />
+      </div>
+      <div className={'tree-body'}>
+        <Droppable
+          droppableId={props.droppableId}
+          direction="horizontal"
+          type="COLUMN"
+          isCombineEnabled={true}
+        >
+          {(provided) => (
+            <div
+              className="columns"
+              {...provided.droppableProps}
+              ref={provided.innerRef}
+              style={{ height: '100%' }}
+            >
+              <Tree draggable={props.droppableId === 'menu'}>
+                {createTreeNode(props.treeData, props.droppableId)}
+              </Tree>
+            </div>
+          )}
+        </Droppable>
+      </div>
+    </div>
+  );
+};

+ 13 - 51
src/pages/system/Menu/index.tsx

@@ -18,7 +18,7 @@ import SearchComponent from '@/components/SearchComponent';
 import Service from './service';
 import Service from './service';
 import type { MenuItem } from './typing';
 import type { MenuItem } from './typing';
 import moment from 'moment';
 import moment from 'moment';
-import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { PermissionButton } from '@/components';
 import { PermissionButton } from '@/components';
 import { useDomFullHeight } from '@/hooks';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
 import { onlyMessage } from '@/utils/util';
@@ -272,7 +272,7 @@ export default observer(() => {
             status: response.status,
             status: response.status,
           };
           };
         }}
         }}
-        headerTitle={
+        headerTitle={[
           <PermissionButton
           <PermissionButton
             isPermission={permission.add}
             isPermission={permission.add}
             onClick={() => {
             onClick={() => {
@@ -286,56 +286,18 @@ export default observer(() => {
               id: 'pages.data.option.add',
               id: 'pages.data.option.add',
               defaultMessage: '新增',
               defaultMessage: '新增',
             })}
             })}
-          </PermissionButton>
-        }
+          </PermissionButton>,
+          <Button
+            style={{ marginLeft: 12 }}
+            onClick={() => {
+              console.log(getMenuPathByCode('system/Menu/Setting'));
+              history.push(getMenuPathByCode('system/Menu/Setting'));
+            }}
+          >
+            菜单配置
+          </Button>,
+        ]}
       />
       />
-      {/*<Modal*/}
-      {/*  title={intl.formatMessage({*/}
-      {/*    id: State.current.parentId*/}
-      {/*      ? 'pages.system.menu.option.addChildren'*/}
-      {/*      : 'pages.data.option.add',*/}
-      {/*    defaultMessage: '新增',*/}
-      {/*  })}*/}
-      {/*  visible={State.visible}*/}
-      {/*  width={660}*/}
-      {/*  onOk={saveData}*/}
-      {/*  onCancel={modalCancel}*/}
-      {/*>*/}
-      {/*  <Form form={form} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>*/}
-      {/*    <Form.Item*/}
-      {/*      name="code"*/}
-      {/*      label={intl.formatMessage({*/}
-      {/*        id: 'page.system.menu.encoding',*/}
-      {/*        defaultMessage: '编码',*/}
-      {/*      })}*/}
-      {/*      required={true}*/}
-      {/*      rules={[*/}
-      {/*        { required: true, message: '请输入编码' },*/}
-      {/*        { max: 64, message: '最多可输入64个字符' },*/}
-      {/*        {*/}
-      {/*          pattern: /^[a-zA-Z0-9`!@#$%^&*()_+\-={}|\\\]\[;':",.\/<>?]+$/,*/}
-      {/*          message: '请输入英文+数字+特殊字符(`!@#$%^&*()_+-={}|\\][;\':",./<>?)',*/}
-      {/*        },*/}
-      {/*      ]}*/}
-      {/*    >*/}
-      {/*      <Input />*/}
-      {/*    </Form.Item>*/}
-      {/*    <Form.Item*/}
-      {/*      name="name"*/}
-      {/*      label={intl.formatMessage({*/}
-      {/*        id: 'pages.table.name',*/}
-      {/*        defaultMessage: '名称',*/}
-      {/*      })}*/}
-      {/*      required={true}*/}
-      {/*      rules={[*/}
-      {/*        { required: true, message: '请输入名称' },*/}
-      {/*        { max: 64, message: '最多可输入64个字符' },*/}
-      {/*      ]}*/}
-      {/*    >*/}
-      {/*      <Input />*/}
-      {/*    </Form.Item>*/}
-      {/*  </Form>*/}
-      {/*</Modal>*/}
     </PageContainer>
     </PageContainer>
   );
   );
 });
 });

+ 1 - 1
src/utils/const.ts

@@ -1,5 +1,5 @@
 class SystemConst {
 class SystemConst {
-  static API_BASE = 'jetlinks';
+  static API_BASE = 'api';
 
 
   static SYSTEM_NAME = 'Jetlinks';
   static SYSTEM_NAME = 'Jetlinks';
 
 

+ 3 - 1
src/utils/menu/index.ts

@@ -73,6 +73,9 @@ const extraRouteObj = {
   'system/DataSource': {
   'system/DataSource': {
     children: [{ code: 'Management', name: '管理' }],
     children: [{ code: 'Management', name: '管理' }],
   },
   },
+  'system/Menu': {
+    children: [{ code: 'Setting', name: '菜单配置' }],
+  },
 };
 };
 //额外路由
 //额外路由
 export const extraRouteArr = [
 export const extraRouteArr = [
@@ -183,7 +186,6 @@ export const handleRoutes = (routes?: MenuItem[], level = 1): MenuItem[] => {
     ? routes.map((item) => {
     ? routes.map((item) => {
         // 判断当前是否有额外子路由
         // 判断当前是否有额外子路由
         const extraRoutes = extraRouteObj[item.code];
         const extraRoutes = extraRouteObj[item.code];
-
         if (extraRoutes) {
         if (extraRoutes) {
           if (extraRoutes.children) {
           if (extraRoutes.children) {
             const eRoutes = findExtraRoutes(item.code, extraRoutes.children, item.url);
             const eRoutes = findExtraRoutes(item.code, extraRoutes.children, item.url);

+ 1 - 0
src/utils/menu/router.ts

@@ -84,6 +84,7 @@ export enum MENUS_CODE {
   'system/Department/Member' = 'system/Department/Member',
   'system/Department/Member' = 'system/Department/Member',
   'system/Department' = 'system/Department',
   'system/Department' = 'system/Department',
   'system/Menu' = 'system/Menu',
   'system/Menu' = 'system/Menu',
+  'system/Menu/Setting' = 'system/Menu/Setting',
   'system/OpenAPI' = 'system/OpenAPI',
   'system/OpenAPI' = 'system/OpenAPI',
   'system/Permission' = 'system/Permission',
   'system/Permission' = 'system/Permission',
   'system/Role/Detail' = 'system/Role/Detail',
   'system/Role/Detail' = 'system/Role/Detail',

+ 39 - 2
yarn.lock

@@ -7842,6 +7842,13 @@ css-blank-pseudo@^0.1.4:
   dependencies:
   dependencies:
     postcss "^7.0.5"
     postcss "^7.0.5"
 
 
+css-box-model@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+  integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+  dependencies:
+    tiny-invariant "^1.0.6"
+
 css-has-pseudo@^0.10.0:
 css-has-pseudo@^0.10.0:
   version "0.10.0"
   version "0.10.0"
   resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee"
   resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee"
@@ -16167,6 +16174,11 @@ quickselect@^2.0.0:
   resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
   resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
   integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
   integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
 
 
+raf-schd@^4.0.2:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+  integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
 raf@^3.1.0, raf@^3.3.2, raf@^3.4.0, raf@^3.4.1:
 raf@^3.1.0, raf@^3.3.2, raf@^3.4.0, raf@^3.4.1:
   version "3.4.1"
   version "3.4.1"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -16783,6 +16795,19 @@ react-base16-styling@^0.6.0:
     lodash.flow "^3.3.0"
     lodash.flow "^3.3.0"
     pure-color "^1.2.0"
     pure-color "^1.2.0"
 
 
+react-beautiful-dnd@^13.1.0:
+  version "13.1.0"
+  resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
+  integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
+  dependencies:
+    "@babel/runtime" "^7.9.2"
+    css-box-model "^1.2.0"
+    memoize-one "^5.1.1"
+    raf-schd "^4.0.2"
+    react-redux "^7.2.0"
+    redux "^4.0.4"
+    use-memo-one "^1.1.1"
+
 react-color@2.17.1:
 react-color@2.17.1:
   version "2.17.1"
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.1.tgz#f114811c83f5d80a1bd1b80466c2f7ddcc58da9d"
   resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.1.tgz#f114811c83f5d80a1bd1b80466c2f7ddcc58da9d"
@@ -17093,7 +17118,7 @@ react-redux@=4.4.10:
     loose-envify "^1.4.0"
     loose-envify "^1.4.0"
     prop-types "^15.7.2"
     prop-types "^15.7.2"
 
 
-react-redux@^7.1.0:
+react-redux@^7.1.0, react-redux@^7.2.0:
   version "7.2.8"
   version "7.2.8"
   resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
   resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
   integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==
   integrity sha512-6+uDjhs3PSIclqoCk0kd6iX74gzrGc3W5zcAjbrFgEdIjRSQObdIwfx80unTkVUYvbQ95Y8Av3OvFHq1w5EOUw==
@@ -17443,6 +17468,13 @@ redux@^4.0.0, redux@^4.0.1:
   dependencies:
   dependencies:
     "@babel/runtime" "^7.9.2"
     "@babel/runtime" "^7.9.2"
 
 
+redux@^4.0.4:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13"
+  integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==
+  dependencies:
+    "@babel/runtime" "^7.9.2"
+
 reflect-metadata@^0.1.13:
 reflect-metadata@^0.1.13:
   version "0.1.13"
   version "0.1.13"
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@@ -19748,7 +19780,7 @@ timers-ext@^0.1.7:
     es5-ext "~0.10.46"
     es5-ext "~0.10.46"
     next-tick "1"
     next-tick "1"
 
 
-tiny-invariant@^1.0.2:
+tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
   version "1.2.0"
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
   integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
   integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==
@@ -20565,6 +20597,11 @@ use-media-antd-query@^1.0.6, use-media-antd-query@^1.1.0:
   resolved "https://registry.yarnpkg.com/use-media-antd-query/-/use-media-antd-query-1.1.0.tgz#f083ad7e292c1c0261b6bbfaac0edc3e0920d85d"
   resolved "https://registry.yarnpkg.com/use-media-antd-query/-/use-media-antd-query-1.1.0.tgz#f083ad7e292c1c0261b6bbfaac0edc3e0920d85d"
   integrity sha512-B6kKZwNV4R+l4Rl11sWO7HqOay9alzs1Vp1b4YJqjz33YxbltBCZtt/yxXxkXN9rc1S7OeEL/GbwC30Wmqhw6Q==
   integrity sha512-B6kKZwNV4R+l4Rl11sWO7HqOay9alzs1Vp1b4YJqjz33YxbltBCZtt/yxXxkXN9rc1S7OeEL/GbwC30Wmqhw6Q==
 
 
+use-memo-one@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
+  integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
+
 use-subscription@1.4.1:
 use-subscription@1.4.1:
   version "1.4.1"
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069"
   resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069"