Просмотр исходного кода

feat: 左侧菜单支持三级路由,并优化iframe显示

louhangfei 5 месяцев назад
Родитель
Сommit
9a7da4fe73

+ 1 - 1
src/constant/nav.ts

@@ -18,7 +18,7 @@ export const NAV_LIST = [
   },
   },
   {
   {
     name: '生产安全',
     name: '生产安全',
-    path: '',
+    path: '/work-safety',
     meta: {
     meta: {
       title: '生产安全',
       title: '生产安全',
     },
     },

+ 6 - 145
src/layout/MenuLayout.vue

@@ -1,148 +1,9 @@
-<!-- 带有二级菜单的layout -->
 <template>
 <template>
-  <div class="component-container home-container">
-    <aside class="aside">
-      <header class="aside__header" />
-      <main class="aside__main">
-        <el-menu :default-active="selectedKeys" :default-openeds="openKeys" router class="el-menu-vertical">
-          <template v-for="item in subMenus" :key="item.name">
-            <el-sub-menu v-if="item.children" :index="item.path">
-              <template #title>
-                <component v-if="item.meta?.icon" :is="item.meta.icon" />
-                <span class="menu-title">{{ item.meta?.title }}</span>
-              </template>
-              <el-menu-item v-for="child in item.children" :key="child.name" :index="child.path">
-                <div style="margin-left: 12px">
-                  {{ child.meta?.title }}
-                </div>
-              </el-menu-item>
-            </el-sub-menu>
-            <el-menu-item v-else :index="item.path">
-              <component v-if="item.meta?.icon" :is="item.meta?.icon" />
-              <span class="menu-title">
-                {{ item.meta?.title }}
-              </span>
-            </el-menu-item>
-          </template>
-        </el-menu>
-      </main>
-    </aside>
-    <div class="main">
-      <router-view></router-view>
-    </div>
-  </div>
+  <LeftMenu>
+    <router-view></router-view>
+  </LeftMenu>
 </template>
 </template>
-
-<script setup lang="ts">
-  import { ref, watch, computed } from 'vue';
-  import { useRoute } from 'vue-router';
-
-  // 当前路由
-  const currentRoute = useRoute();
-  const activeMenu = currentRoute.meta?.activeMenu || null; // activeMenu undefined,null 或 空字符串,统一变为 null。
-  const selectedKeys = ref<string>((activeMenu ?? currentRoute.path) as string);
-
-  const openKeys = ref<string[]>([]);
-
-  // 将菜单数组过滤掉隐藏的菜单
-  const filterHiddenMenus = (menus: any[]) => {
-    return menus.filter((menu) => !menu.meta?.hidden);
-  };
-
-  function filterHiddenItems(arr: any[]): any[] {
-    return arr.filter((item) => {
-      if (item.meta && item.meta.hidden) {
-        return false;
-      }
-      if (item.children && Array.isArray(item.children)) {
-        item.children = filterHiddenItems(item.children);
-      }
-      return true;
-    });
-  }
-
-  const subMenus = computed(() => {
-    return filterHiddenItems(currentRoute.matched[0].children);
-  });
-
-  // 跟随页面路由变化,切换菜单选中状态
-  watch(
-    () => currentRoute.fullPath,
-    () => {
-      const matched = currentRoute.matched;
-      openKeys.value = matched.map((item) => item.name) as string[];
-      const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
-      selectedKeys.value = activeMenu ? activeMenu : (currentRoute.path as string);
-    },
-  );
+<script lang="ts" setup>
+  import LeftMenu from './components/left-menu/LeftMenu.vue';
 </script>
 </script>
-
-<style scoped lang="scss">
-  :deep(.el-menu-item) {
-    transition: unset;
-  }
-  .home-container {
-    display: flex;
-    gap: 10px;
-    padding: 10px;
-    height: 100%;
-  }
-  .aside {
-    display: flex;
-    flex-direction: column;
-    width: 228px;
-    height: 100%;
-    flex-shrink: 0;
-    border-radius: 4px;
-    background-color: $white-color;
-    &__header {
-      width: inherit;
-      height: 10px;
-      flex-shrink: 0;
-    }
-    &__main {
-      width: inherit;
-      flex: 1;
-    }
-  }
-  .main {
-    flex: 1;
-    overflow: auto;
-    border-radius: 4px;
-  }
-  .el-menu-vertical {
-    width: 100%;
-    height: 100%;
-    border: none;
-    border-radius: 4px;
-    .el-menu-item,
-    :deep(.el-sub-menu__title) {
-      font-size: 16px;
-      color: #333;
-    }
-    .el-menu-item.is-active {
-      color: $white-color;
-      background-color: $primary-color;
-    }
-  }
-
-  .el-menu-item,
-  .el-sub-menu {
-    svg {
-      color: $primary-color;
-    }
-  }
-  .el-menu-item {
-    &.is-active {
-      svg {
-        color: #fff;
-      }
-    }
-  }
-  :deep(.el-sub-menu__title),
-  :deep(.el-menu-item) {
-    display: flex;
-    align-items: center;
-    gap: 12px;
-  }
-</style>
+<style scoped></style>

+ 154 - 0
src/layout/components/left-menu/LeftMenu.vue

@@ -0,0 +1,154 @@
+<!-- 带有二级菜单的layout -->
+<template>
+  <div class="component-container home-container">
+    <aside class="aside">
+      <header class="aside__header" />
+      <main class="aside__main">
+        <el-menu :default-active="selectedKeys" :default-openeds="openKeys" router class="el-menu-vertical">
+          <template v-for="item in subMenus" :key="item.name">
+            <el-sub-menu v-if="item.children" :index="item.path">
+              <template #title>
+                <BottomMenuItem :item="item" style="margin-left: 0px" />
+              </template>
+
+              <div v-for="child in item.children" :key="child.name">
+                <el-sub-menu v-if="child.children?.length > 0" :index="child.path">
+                  <template #title>
+                    <BottomMenuItem :item="child" />
+                  </template>
+                  <el-menu-item v-for="ch in child.children" :key="ch.name" :index="ch.path">
+                    <BottomMenuItem :item="ch" />
+                  </el-menu-item>
+                </el-sub-menu>
+                <el-menu-item :index="child.path" v-else>
+                  <BottomMenuItem :item="child" />
+                </el-menu-item>
+              </div>
+            </el-sub-menu>
+            <el-menu-item v-else :index="item.path">
+              <BottomMenuItem :item="item" style="margin-left: 0px" />
+            </el-menu-item>
+          </template>
+        </el-menu>
+      </main>
+    </aside>
+    <div class="main">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, watch, computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import BottomMenuItem from '../../components/menu-tree/BottomMenuItem.vue';
+  // 当前路由
+  const currentRoute = useRoute();
+  const activeMenu = currentRoute.meta?.activeMenu || null; // activeMenu undefined,null 或 空字符串,统一变为 null。
+  const selectedKeys = ref<string>((activeMenu ?? currentRoute.path) as string);
+
+  const openKeys = ref<string[]>([]);
+
+  function filterHiddenItems(arr: any[]): any[] {
+    return arr.filter((item) => {
+      if (item.meta && item.meta.hidden) {
+        return false;
+      }
+      if (item.children && Array.isArray(item.children)) {
+        item.children = filterHiddenItems(item.children);
+      }
+      return true;
+    });
+  }
+
+  const subMenus = computed(() => {
+    return filterHiddenItems(currentRoute.matched[0].children);
+  });
+
+  // 跟随页面路由变化,切换菜单选中状态
+  watch(
+    () => currentRoute.fullPath,
+    () => {
+      const matched = currentRoute.matched;
+      openKeys.value = matched.map((item) => item.name) as string[];
+      const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
+      selectedKeys.value = activeMenu ? activeMenu : (currentRoute.path as string);
+    },
+    {
+      immediate: true,
+    },
+  );
+</script>
+<style lang="scss">
+  .aside__main {
+    .el-menu-item,
+    .el-sub-menu {
+      svg {
+        color: $primary-color;
+      }
+    }
+    .el-menu-item {
+      &.is-active {
+        svg {
+          color: #fff;
+        }
+      }
+    }
+  }
+</style>
+<style scoped lang="scss">
+  :deep(.el-menu-item) {
+    transition: unset;
+  }
+  .home-container {
+    display: flex;
+    gap: 10px;
+    padding: 10px;
+    height: 100%;
+  }
+  .aside {
+    display: flex;
+    flex-direction: column;
+    width: 228px;
+    height: 100%;
+    flex-shrink: 0;
+    border-radius: 4px;
+    background-color: $white-color;
+    &__header {
+      width: inherit;
+      height: 10px;
+      flex-shrink: 0;
+    }
+    &__main {
+      width: inherit;
+      flex: 1;
+    }
+  }
+  .main {
+    flex: 1;
+    overflow: auto;
+    border-radius: 4px;
+  }
+  .el-menu-vertical {
+    width: 100%;
+    height: 100%;
+    border: none;
+    border-radius: 4px;
+    .el-menu-item,
+    :deep(.el-sub-menu__title) {
+      font-size: 16px;
+      color: #333;
+    }
+    .el-menu-item.is-active {
+      color: $white-color;
+      background-color: $primary-color;
+    }
+  }
+
+  :deep(.el-sub-menu__title),
+  :deep(.el-menu-item) {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+</style>

+ 19 - 0
src/layout/components/menu-tree/BottomMenuItem.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="menu-title">
+    <component v-if="item.meta?.icon" :is="item.meta?.icon" class="menu-title-icon" />
+    <span>
+      {{ item.meta?.title }}
+    </span>
+  </div>
+</template>
+<script lang="ts" setup>
+  const props = defineProps<{ item: any }>();
+</script>
+<style scoped>
+  .menu-title {
+    margin-left: 12px;
+  }
+  .menu-title-icon {
+    margin-right: 10px;
+  }
+</style>

+ 2 - 2
src/router/full-routes.ts

@@ -68,7 +68,7 @@ export function getChildRoutesView(parentRouteName?: string | null): _RouteViewI
   // 未指定 parentRouteName, 则返回 level1 的路由
   // 未指定 parentRouteName, 则返回 level1 的路由
   if (parentRouteName == null) {
   if (parentRouteName == null) {
     return fullRoutes.map((route) => ({
     return fullRoutes.map((route) => ({
-      label: route.meta.title as string,
+      label: route.meta?.title as string,
       value: route.name,
       value: route.name,
     }));
     }));
   }
   }
@@ -77,7 +77,7 @@ export function getChildRoutesView(parentRouteName?: string | null): _RouteViewI
   const parentRoute: AppRouteRecordRaw = cloneDeep(getTreeItem(fullRoutes, parentRouteName, 'name'));
   const parentRoute: AppRouteRecordRaw = cloneDeep(getTreeItem(fullRoutes, parentRouteName, 'name'));
   if (parentRoute.children && parentRoute.children.length > 0) {
   if (parentRoute.children && parentRoute.children.length > 0) {
     return parentRoute.children.map((route) => ({
     return parentRoute.children.map((route) => ({
-      label: route.meta.title as string,
+      label: route.meta?.title as string,
       value: route.name,
       value: route.name,
     }));
     }));
   } else {
   } else {

+ 19 - 12
src/views/iframe/index.vue

@@ -1,27 +1,34 @@
 <template>
 <template>
-  <VerticalFlexLayout>
-    <template #static>
-      <Breadcrumb />
-    </template>
-    <div class="frame" v-loading="loading">
-      <iframe :src="frameSrc" class="frame-iframe" ref="frameRef"></iframe>
-    </div>
-  </VerticalFlexLayout>
+  <LeftMenu>
+    <VerticalFlexLayout>
+      <template #static>
+        <Breadcrumb />
+      </template>
+      <div class="frame" v-loading="loading">
+        <iframe :src="frameSrc" class="frame-iframe" ref="frameRef"></iframe>
+      </div>
+    </VerticalFlexLayout>
+  </LeftMenu>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
-  import { ref, unref, onMounted, nextTick } from 'vue';
+  import { ref, unref, onMounted, nextTick, watch } from 'vue';
   import { useRoute } from 'vue-router';
   import { useRoute } from 'vue-router';
   import VerticalFlexLayout from '@/components/VerticalFlexLayout.vue';
   import VerticalFlexLayout from '@/components/VerticalFlexLayout.vue';
   import Breadcrumb from '@/components/Breadcrumb.vue';
   import Breadcrumb from '@/components/Breadcrumb.vue';
+  import LeftMenu from '@/layout/components/left-menu/LeftMenu.vue';
 
 
   const currentRoute = useRoute();
   const currentRoute = useRoute();
   const loading = ref(false);
   const loading = ref(false);
   const frameRef = ref<HTMLFrameElement | null>(null);
   const frameRef = ref<HTMLFrameElement | null>(null);
   const frameSrc = ref<string>('');
   const frameSrc = ref<string>('');
 
 
-  if (unref(currentRoute.meta)?.frameSrc) {
-    frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
-  }
+  watch(
+    () => currentRoute.meta,
+    (meta) => {
+      frameSrc.value = unref(meta)?.frameSrc as string;
+    },
+    { immediate: true },
+  );
 
 
   function hideLoading() {
   function hideLoading() {
     loading.value = false;
     loading.value = false;