Selaa lähdekoodia

1. 完善Menu类型定义 2. 前端路由表

lfeish 1 vuosi sitten
vanhempi
commit
9077db16e2

+ 4 - 4
src/api/system/menu.ts

@@ -1,5 +1,5 @@
 import { http } from '@/utils/http/axios';
-import { MenuDetail } from '@/types/menu/type';
+import { MenuDetailTree, MenuDetailItem } from '@/types/menu/type';
 
 /**
  * @description: 获取动态菜单
@@ -16,7 +16,7 @@ export function getRouters() {
  * v4:获取整个菜单树
  */
 export function queryFullMenuTree() {
-  return http.request<MenuDetail[]>({
+  return http.request<MenuDetailTree>({
     url: '/admin/menu/queryAllMenuTree',
     method: 'post',
   });
@@ -26,7 +26,7 @@ export function queryFullMenuTree() {
  * 添加菜单
  * @param params
  */
-export function addMenu(data: MenuDetail) {
+export function addMenu(data: MenuDetailItem) {
   return http.request({
     url: '/admin/menu/saveMenu',
     method: 'POST',
@@ -38,7 +38,7 @@ export function addMenu(data: MenuDetail) {
  * 编辑菜单
  * @param params
  */
-export function editMenu(data: MenuDetail) {
+export function editMenu(data: MenuDetailItem) {
   return http.request({
     url: '/admin/menu/updateMenu',
     method: 'POST',

+ 578 - 0
src/router/full-routes.ts

@@ -0,0 +1,578 @@
+/**
+ * 1. 前端维护的完整路由表, 用于创建"菜单"
+ * 2. component 是 string,并不是 组件对象
+ * 3. 生成菜单,关注的 path,name,component,icon
+ * 4. 以用户填写的数据为准
+ */
+import { AppRouteRecordRaw } from './types';
+import { getTreeItem } from '@/utils';
+import { cloneDeep } from 'lodash-es';
+
+const fullRoutes: AppRouteRecordRaw[] = [
+  /**
+   * Dashboard
+   */
+  {
+    path: '/dashboard',
+    name: 'Dashboard',
+    component: 'LAYOUT',
+    meta: {
+      icon: 'DashboardOutlined',
+      title: 'Dashboard',
+    },
+    children: [
+      {
+        // 主控台
+        path: 'console',
+        name: 'DashboardConsole',
+        component: '/dashboard/home/Home',
+        meta: { 
+          icon: '',
+          title: '主控台',
+        },
+      }
+    ]
+  },
+
+
+  /**
+   * 场景管理
+   */
+  {
+    path: '/scene',
+    name: 'Scene',
+    component: 'LAYOUT',
+    meta: { 
+      icon: 'ApartmentOutlined',
+      title: '场景管理',
+    },
+    children: [
+      {
+        // 模板管理
+        path: 'template',
+        name: 'SceneTemplate',
+        component: '/templateManage/templateManage',
+        meta: { 
+          icon: '',
+          title: '模板管理',
+        },
+      },
+      {
+        // 车间管理
+        path: 'workshop',
+        name: 'SceneWorkshop',
+        component: '/system-config/scene-manage/SceneManage',
+        meta: { 
+          icon: '',
+          title: '车间管理' 
+        },
+      },
+      {
+        // 业务场景管理
+        path: 'business',
+        name: 'SceneBusiness',
+        component: '/system-config/business-scene/PageBusinessScene',
+        meta: { 
+          icon: '',
+          title: '业务创建管理',
+        },
+      }
+    ]
+  },
+
+  /**
+   * 设备管理
+   */
+  {
+    path: '/device',
+    name: 'Device',
+    component: 'LAYOUT',
+    meta: { 
+      icon: 'CameraOutlined',
+      title: '设备管理'
+    },
+    children: [
+      {
+        // 相机设备
+        path: 'camera',
+        name: 'DeviceCamera',
+        component: '/cameras/overview/CamerasOverview',
+        meta: { 
+          icon: '',
+          title: '相机设备'
+        },
+      },
+      {
+        // NVR设备
+        path: 'nvr',
+        name: 'DeviceNVR',
+        component: '/cameras/nvrlist/NvrList',
+        meta: { 
+          icon: '',
+          title: 'NVR设备'
+        },
+      }
+    ]
+  },
+
+  /**
+   * 布局管理
+   */
+  {
+    path: '/layout',
+    name: 'Layout',
+    component: 'LAYOUT',
+    meta: { 
+      icon: 'PictureOutlined',
+      title: '布局管理',
+    },
+    children: [
+      {
+        // 场景布局
+        path: 'scene',
+        name: 'LayoutScene',
+        component: '/page-config/PageConfig',
+        meta: { 
+          icon: '',
+          title: '场景布局', 
+        },
+      },
+      {
+        // 相机布局
+        path: 'camera',
+        name: 'LayoutCamera',
+        component: '/map-config/mini-map/MiniMapConfig',
+        meta: { 
+          icon: '',
+          title: '相机布局',
+        }
+      }
+    ]
+  },
+
+  /**
+   * 算法管理
+   */
+  {
+    path: '/algorithm',
+    name: 'Algorithm',
+    component: 'LAYOUT',
+    meta: { 
+      icon: 'SettingOutlined',
+      title: '算法管理',
+    },
+    children: [
+      {
+        // 算法预览
+        path: 'preview',
+        name: 'AlgorithmPreview',
+        component: '/cameras/algo-management/algoManagement',
+        meta: { 
+          icon: '',
+          title: '算法预览',
+        },
+      },
+      {
+        // 算法配置
+        path: 'config',
+        name: 'AlgorithmConfig',
+        component: '/cameras/preview/CameraPreview',
+        meta: { 
+          ico: '',
+          title: '算法配置',
+        },
+      }
+    ]
+  },
+
+  /**
+   * 用户管理
+   */
+  {
+    path: '/user',
+    name: 'User',
+    component: 'LAYOUT',
+    meta: { 
+      icon: '',
+      title: '用户管理',
+    },
+    children: [
+      {
+        // 账号管理
+        path: 'account',
+        name: 'UserAccount',
+        component: '/system/user/user',
+        meta: { 
+          icon: '',
+          title: '账号管理',
+        }
+      },
+      {
+        // 角色管理
+        path: 'role',
+        name: 'UserRole',
+        component: '/system/role/role',
+        meta: { 
+          icon: '',
+          title: '角色管理',
+        }
+      },
+      {
+        // 组织管理
+        path: 'department',
+        name: 'UserDepartment',
+        component: '/auth/dept/dept',
+        meta: { 
+          icon: '',
+          title: '组织管理',
+        }
+      }
+    ]
+  },
+
+
+  /**
+   * 消息管理
+   */
+  {
+    path: '/message',
+    name: 'Message',
+    component: 'LAYOUT',
+    meta: { 
+      icon: '',
+      title: '消息管理',
+    },
+    children: [
+      {
+        // 报表推送 
+        path: 'report',
+        name: 'MessageReport',
+        component: '/message/reportmessage/ReportMessage',
+        meta: { 
+          icon: '',
+          title: '报表推送',
+        },
+      },
+      {
+        // 报表推送配置(菜单不可见)
+        path: 'report-config',
+        name: 'MessageReportConfig',
+        component: '/message/reportmessage/ReportOperation',
+        meta: { 
+          icon: '',
+          title: '报表推送配置(菜单不可见)' 
+        },
+      },
+      {
+        // 报警推送
+        path: 'alarm',
+        name: 'MessageAlarm',
+        component: '/message/alarmMessages/alarmMessages',
+        meta: { 
+          icon: '',
+          title: '报警推送',
+        },
+      },
+      {
+        // 报警推送配置 (菜单不可见)
+        path: 'alarm-config',
+        name: 'MessageAlarmConfig',
+        component: '/message/alarm-config/AlarmConfig',
+        meta: { 
+          icon: '',
+          title: '报警推送配置(菜单不可见)',
+        },
+      },
+      {
+        // 系统通知
+        path: 'sys-notification',
+        name: 'MessageSysNotification',
+        component: '/message/systemNotifications/systemNotifications',
+        meta: { 
+          icon: '',
+          title: '系统通知',
+        },
+      },
+      {
+        // 系统通知配置(菜单不可见)
+        path: 'sys-notification-config',
+        name: 'MessageSysNotificationConfig',
+        component: '/message/sysnotion-config/SysnotionConfig',
+        meta: { 
+          icon: '',
+          title: '系统通知配置(菜单不可见)',
+        },
+      }, 
+      {
+        // 人员分组
+        path: 'personnel-group',
+        name: 'MessagePersonnelGroup',
+        component: '/message/persongroup/UserGroup',
+        meta: { 
+          icon: '',
+          title: '人员分组',
+        },
+      }
+
+    ]
+  },
+
+  /**
+   * 数据管理
+   */
+  {
+    path: '/data',
+    name: 'Data',
+    component: 'LAYOUT',
+    meta: {
+      icon: '',
+      title: '数据管理',
+    },
+    children: [
+      {
+        // 平台统计
+        path: 'platform',
+        name: 'DataPlatform',
+        component: '/datamanager/platformdata/PlatformData',
+        meta: {
+          icon: '',
+          title: '平台统计'
+        }
+      },
+      {
+        // 历史视频 (视频会看)
+        path: 'playback',
+        name: 'DataPlayback',
+        component: '/datamanager/playback/Playback',
+        meta: {
+          icon: '',
+          title: '历史视频'
+        }
+      },
+      {
+        // 违规问题
+        path: 'violation',
+        name: 'DataViolation',
+        component: '/datamanager/alertformdata/AlertformData',
+        meta: {
+          icon: '',
+          title: '违规问题'
+        }
+      }
+    ]
+  },
+
+
+
+  /**
+   * 系统管理 (只有超管可见)
+   */
+  {
+    path: '/system',
+    name: 'System',
+    component: 'LAYOUT',
+    meta: { 
+      icon: 'OptionsSharp',
+      title: '系统管理',
+    },
+    children: [
+      {
+        // 租户管理
+        path: 'tenant',
+        name: 'SystemTenant',
+        component: '/system/tenant/tenant',
+        meta: { 
+          icon: '',
+          title: '租户管理',
+        }
+      },
+      {
+        // 菜单管理
+        path: 'menu',
+        name: 'SystemMenu',
+        component: '/system/menu/menu',
+        meta: { 
+          icon: '',
+          title: '菜单管理',
+        }
+      },
+      {
+        // 权限管理
+        path: 'permission',
+        name: 'SystemPermission',
+        component: '/system/permssion/PagePermission',
+        meta: { 
+          icon: '',
+          title: '权限管理',
+        }
+      },
+      {
+        // 平台反馈
+        path: 'feedback',
+        name: 'SystemFeedback',
+        component: '/feedback/feedback',
+        meta: { 
+          icon: '',
+          title: '平台反馈',
+        }
+      },
+      {
+        // 字典管理
+        path: 'dictionary',
+        name: 'SystemDictionary',
+        component: '/system/dictionary/dictionary',
+        meta: { 
+          icon: '',
+          title: '字典管理',
+        }
+      },
+      {
+        // 日志管理
+        path: 'logs',
+        name: 'SytemLogs',
+        component: 'ParentLayout',
+        meta: { 
+          icon: '',
+          title: '日志管理',
+        },
+        children: [
+          {
+            // 操作日志
+            path: 'operation',
+            name: 'SystemLogsOperation',
+            component: '/system/logs/operlog',
+            meta: { 
+              icon: '',
+              title: '操作日志',
+            },
+          },
+          {
+            // 登录日志
+            path: 'login',
+            name: 'SystemLogsLogin',
+            component: '/system/logs/logininfor',
+            meta: { 
+              icon: '',
+              title: '登录日志',
+            }
+          }
+        ]
+      }
+    ]
+  },
+
+  /**
+ * 异常页面
+ */
+  {
+    path: '/exception',
+    name: 'Exception',
+    component: 'LAYOUT',
+    meta: {
+      icon: 'ExclamationCircleOutlined',
+      title: '异常页面', 
+    },
+    children: [
+      {
+        // 403,
+        path: '403',
+        name: 'Exception403',
+        component: '/exception/403',
+        meta: {
+          icon: '',
+          title: '403',
+        },
+      }
+    ]
+  },
+
+  /**
+   * 测试
+   */
+  {
+    path: '/lf-test',
+    name: 'LFTest',
+    component: 'LAYOUT',
+    meta: {
+      icon: '',
+      title: '测试菜单,可删除'
+    },
+    children: [
+      {
+        path: 'test1',
+        name: 'LFTest-1',
+        component: '/exception/403',
+        meta: {
+          icon: '',
+          title: '403',
+        }, 
+      }
+    ]
+  }
+] as const;
+
+export interface _RouteViewItem {
+  label: string;
+  value: string;
+}
+export type _RouteView = _RouteViewItem & { children?: _RouteViewItem[] | null };
+export type _RouteViewTree = _RouteView[];
+
+/**
+ * 获取指定 父路由 下的子路由。
+ * 若 父级路由 未指定,则返回一级路由
+ */
+export function getChildRoutesView(parentRouteName?: string | null ): _RouteViewItem[] {
+  // 未指定 parentRouteName, 则返回 level1 的路由
+  if (parentRouteName == null) {
+    return fullRoutes.map(route => ({
+      label: route.meta.title as string,
+      value: route.name,
+    }));
+  }
+
+  // 获取指定父路由的子路由
+  const parentRoute: AppRouteRecordRaw = cloneDeep(getTreeItem(fullRoutes, parentRouteName, 'name'));
+  if (parentRoute.children && parentRoute.children.length > 0) {
+    return parentRoute.children.map(route => ({
+      label: route.meta.title as string,
+      value: route.name,
+    }));
+  } else {
+    return [];
+  }
+}
+
+/**
+ * 获取指定的 路由信息
+ */
+export function getRouteByName(routeName: string) {
+  return cloneDeep(getTreeItem(fullRoutes, routeName, 'name')) as AppRouteRecordRaw;
+}
+
+/**
+ * 转换成 el-tree-select 的数据结构形式
+ */
+export function transformToRouteViewTree(routes?: AppRouteRecordRaw[] | null) {
+  const tree: _RouteViewTree = [];
+  if (routes == null) return tree;
+
+  for(const route of routes) {
+    const treeItem: _RouteView = {
+      value: route.name,
+      label: route.meta.title as string,
+      children: null
+    }
+
+    if (route.children && route.children.length > 0) {
+      treeItem.children = transformToRouteViewTree(route.children);
+      tree.push(treeItem);
+    } else {
+      tree.push(treeItem);
+    }
+  }
+  
+  return tree;
+}
+
+export const routeViewTree = transformToRouteViewTree(fullRoutes);

+ 11 - 6
src/types/menu/type.ts

@@ -2,7 +2,7 @@
  * 后端保存菜单的详细信息
  * 其中 0 - false,1 - true
  */
-export interface MenuDetail {
+export interface MenuDetailItem {
   // 主键ID
   id: number | null;
   // 父级菜单主键ID
@@ -55,15 +55,20 @@ export interface MenuDetail {
   updatedBy?: string;
   // 路由是否删除(逻辑删除)
   isDeleted?: number;
-  // 子菜单
-  children?: MenuDetail[];
 }
 
+export type MenuDetail = MenuDetailItem & { children?: MenuDetailItem[] | null };
+export type MenuDetailTree = MenuDetail[];
+
+
+
 /**
  * 用于 element-plus 树形控件 展示
  */
-export interface MenuSimple {
+export interface MenuItem {
   label: string;
   key: number | string;
-  children?: MenuSimple[] | null;
-}
+}
+
+export type Menu = MenuItem & { children?: MenuItem[] | null };
+export type MenuTree = Menu[];

+ 17 - 13
src/views/system/menu/MenuForm.vue

@@ -147,8 +147,8 @@
             </div>
           </template>
           <el-radio-group v-model="formParams.isDisabled">
-            <el-radio-button :label="0">正常</el-radio-button>
-            <el-radio-button :label="1">停用</el-radio-button>
+            <el-radio-button label="正常" :value="0" />
+            <el-radio-button label="停用" :value="1" />
           </el-radio-group>
         </el-form-item>
       </el-col>
@@ -185,8 +185,8 @@
             </div>
           </template>
           <el-radio-group v-model="formParams.isHidden">
-            <el-radio-button :label="0">显示</el-radio-button>
-            <el-radio-button :label="1">隐藏</el-radio-button>
+            <el-radio-button label="显示" :value="0" />
+            <el-radio-button label="隐藏" :value="1" />
           </el-radio-group>
         </el-form-item>
       </el-col>
@@ -206,9 +206,9 @@
             </div>
           </template>
           <el-radio-group v-model="formParams.isCache" name="isCacheGroup">
-            <el-radio-button :label="0">不缓存</el-radio-button>
-            <el-radio-button :label="1">缓存</el-radio-button>
-          </el-radio-group>
+            <el-radio-button label="不缓存" :value="0" />
+            <el-radio-button label="缓存" :value="1" />
+          </el-radio-group>  
         </el-form-item>
       </el-col>
     </el-row>
@@ -228,8 +228,8 @@
             </div>
           </template>
           <el-radio-group v-model="formParams.isFrame">
-            <el-radio-button :label="0">否</el-radio-button>
-            <el-radio-button :label="1">是</el-radio-button>
+            <el-radio-button label="否" :value="0" />
+            <el-radio-button label="是" :value="1" />
           </el-radio-group>
         </el-form-item>
       </el-col>
@@ -274,7 +274,7 @@
   import { QuestionCircleOutlined } from '@vicons/antd';
   import { replaceParams } from '@/utils/helper/treeHelper';
   import { cloneDeep } from 'lodash-es';
-  import { MenuDetail } from '@/types/menu/type';
+  import { MenuDetailItem } from '@/types/menu/type';
 
   const emit = defineEmits(['change']);
 
@@ -292,7 +292,7 @@
   const formRef = ref<FormInstance>();
   const subLoading = ref(false);
 
-  const defaultFormParams: MenuDetail = {
+  const defaultFormParams: MenuDetailItem = {
     id: null,
     parentId: null,
     menuName: '',
@@ -313,7 +313,6 @@
     frameSrc: '',
     openType: 1, 
     query: '',
-    children: []
   };
 
   const formParams = ref({ ...defaultFormParams });
@@ -330,6 +329,11 @@
   });
 
   const rules = {
+    parentId: {
+      required: true,
+      message: '上级菜单',
+      trigger: 'change',
+    },
     menuName: {
       required: true,
       message: '菜单名称',
@@ -357,7 +361,7 @@
     },
   };
 
-  function setData(data: MenuDetail) {
+  function setData(data: MenuDetailItem) {
     formParams.value = data;
     formRef.value?.resetFields();
   }

+ 12 - 12
src/views/system/menu/menu.vue

@@ -93,14 +93,14 @@
   import { AlignLeftOutlined, FileAddOutlined } from '@vicons/antd';
   import { deleteMenu } from '@/api/system/menu';
   import { queryFullMenuTree } from '@/api/system/menu';
-  import { MenuDetail, MenuSimple } from '@/types/menu/type';
+  import { MenuDetailTree, MenuTree, MenuDetail, Menu } from '@/types/menu/type';
   import { getTreeItem } from '@/utils';
   import CreateDrawer from './CreateDrawer.vue';
   import MenuForm from './MenuForm.vue';
   import { cloneDeep } from 'lodash-es';
 
-  const menuDetailTree = shallowRef<MenuDetail[]>([]); // 菜单详情树
-  const menuTree = shallowRef<MenuSimple[]>([]); // 菜单树,用于展示
+  const menuDetailTree = shallowRef<MenuDetailTree>([]); // 菜单详情树
+  const menuTree = shallowRef<MenuTree>([]); // 菜单树,用于展示
   const selectedMenuId = ref<MenuDetail['id']>(null); // 选中的菜单
   const loading = ref(true); // 左侧菜单树加载
   const expandedKeys = ref([]); 
@@ -132,16 +132,16 @@
     openDrawer();
   }
 
-  async function selectedTree(checkedInfo: MenuSimple) {
+  async function selectedTree(checkedInfo: Menu) {
     const currentKey = checkedInfo.key as number;
     selectedMenuId.value = currentKey;
 
     if (currentKey) {
-      const treeItem: MenuSimple = getTreeItem(menuTree.value, currentKey);
-      treeItemTitle.value = treeItem.label;
+      const menu: Menu = getTreeItem(menuTree.value, currentKey);
+      treeItemTitle.value = menu.label;
 
-      const info: MenuDetail = getTreeItem(menuDetailTree.value, currentKey, 'id');
-      const menuFormData = cloneDeep(info);
+      const menuDetail: MenuDetail = getTreeItem(menuDetailTree.value, currentKey, 'id');
+      const menuFormData = cloneDeep(menuDetail);
       delete menuFormData.children;
       // console.log('menu form data', menuFormData)
       menuFormRef.value.setData(menuFormData);
@@ -174,17 +174,17 @@
     loading.value = false;
   }
 
-  function transformToSimpleMenus(menus: MenuDetail[] | null): MenuSimple[] {
+  function transformToSimpleMenus(menus: MenuDetailTree | null): MenuTree {
     if (menus == null) {
       return [];
     }
   
-    let tree: MenuSimple[] = [];
+    const tree: MenuTree = [];
     for (const item of menus) {
-      const treeItem: MenuSimple = { 
+      const treeItem: Menu = { 
         key: item.id!, 
         label: item.menuName, 
-        children: null 
+        children: null
       };
 
       if (Array.isArray(item.children) && item.children?.length > 0) {