Ver código fonte

增加导航栏模式

tongshangming 2 anos atrás
pai
commit
1264018f93

+ 1 - 2
src/App.vue

@@ -4,8 +4,7 @@ import { useThemeStore } from '@/stores/theme'
 const locale = zhCn
 
 const themeStore = useThemeStore()
-themeStore.initThemeColor()
-themeStore.initThemeStyle()
+themeStore.initTheme()
 </script>
 
 <template>

+ 26 - 0
src/assets/svg/nav-h.svg

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="52px" height="45px"
+     viewBox="0 0 52 45" enable-background="new 0 0 52 45" xml:space="preserve">  <image id="image0"
+                                                                                         width="52"
+                                                                                         height="45"
+                                                                                         x="0" y="0"
+                                                                                         href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAtCAMAAADWf7iKAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
+AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAkFBMVEX+/v78/Pz6+vr39/f0
+9PTx8fHv7+/u7u729vbHyc1ESVsxNkgwNkg7QFFscH3T1NfGyMwxN0k9QlPa3N5vdID4+Pjz8/Pr
+6+s6QVLw8PDm5ubk5OTy8vLj4+Pt7e3u8PPw8vXu8PTn5+fw8fXs7Oz5+fnp6ene3t7b29vc3Nzf
+39/q6ur7+/vo6Oj9/f3///855aJlAAAAAWJLR0QvI9QgEQAAAAd0SU1FB+UHAxEtKCKzaD0AAAC8
+SURBVEjH7dbLGoIgEIbhEUHI6JyYgmWWnTzd/92FTzcws2vBt38f2P0DELGYi0SiSgSPWQTeqEW6
+1MhW643yiqntDkvm9gfFIBYpxWh9FBmYHP23X6dcQVHSjNYlB2mpyDqQFRVVDs7/ji41rYACCiig
+gHBIWiqyxo/alYga7ufzRkR35YeaaGrX+pOgeFTPF673x3Yi7ueDxcluKDE1Qy5N1o8AY98qbgQm
+Z+Yrx5sJxqhnrEXF2PzM9AV+UvDCWyYgmAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wNy0wM1Qw
+OTo0NTo0MCswODowMOjZqbAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDctMDNUMDk6NDU6NDAr
+MDg6MDCZhBEMAAAAIHRFWHRzb2Z0d2FyZQBodHRwczovL2ltYWdlbWFnaWNrLm9yZ7zPHZ0AAAAY
+dEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAXdEVYdFRodW1iOjpJbWFnZTo6SGVp
+Z2h0ADQ1+dH7kAAAABZ0RVh0VGh1bWI6OkltYWdlOjpXaWR0aAA1MoYBn/8AAAAZdEVYdFRodW1i
+OjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTYyNTI3Njc0MKmy
+pv8AAAASdEVYdFRodW1iOjpTaXplADEzMzJCQoe7yB0AAABGdEVYdFRodW1iOjpVUkkAZmlsZTov
+Ly9hcHAvdG1wL2ltYWdlbGMvaW1ndmlldzJfOV8xNjIzOTEyMDA2MDA1NDY4Ml8yMl9bMF2ZTW7W
+AAAAAElFTkSuQmCC"></image>
+</svg>

+ 1 - 0
src/components.d.ts

@@ -18,6 +18,7 @@ declare module '@vue/runtime-core' {
     GlobalHeader: typeof import('./components/GlobalHeader.vue')['default']
     GlobalMenu: typeof import('./components/GlobalMenu.vue')['default']
     GlobalSetting: typeof import('./components/GlobalSetting.vue')['default']
+    GlobalSubMenu: typeof import('./components/GlobalSubMenu.vue')['default']
     GlobalTabs: typeof import('./components/GlobalTabs.vue')['default']
     OrgLayout: typeof import('./components/org/OrgLayout.vue')['default']
     OrgList: typeof import('./components/org/OrgList.vue')['default']

+ 47 - 6
src/components/GlobalHeader.vue

@@ -4,8 +4,10 @@ import { useUserStore } from '@/stores/user'
 import { useThemeStore } from '@/stores/theme'
 import avatar from '@/assets/images/avatar.png'
 import { ElMessageBox } from 'element-plus'
+import { isAbsolutePath } from '@/utils/utils'
 
 const themeStore = useThemeStore()
+// 整体风格
 const themeStyle = computed(() => themeStore.themeStyle)
 const headerStyle = computed(() => {
   if (themeStyle.value.name === 'header-dark') {
@@ -20,6 +22,7 @@ const headerStyle = computed(() => {
   }
 })
 
+// 导航栏模式
 const iconSize = ref(20)
 
 // 菜单收缩展开切换
@@ -29,10 +32,26 @@ const handleToggle = () => {
   toggle()
   menuStore.setCollapse(value.value)
 }
+const topMenuList = computed(() => {
+  if (themeStore.themeNav === 'top') {
+    return menuStore.allMenuList
+  } else {
+    return []
+  }
+})
 
-// 面包屑
 const route = useRoute()
-const matched = computed(() => route.matched)
+const handleMenuSelect = (key: string) => {
+  if (isAbsolutePath(key)) {
+    window.open(key)
+  } else {
+    menuStore.setActiveTopMenu(topMenuList.value.find((item: any) => item.path === key))
+  }
+}
+
+// 面包屑
+// const route = useRoute()
+// const matched = computed(() => route.matched)
 
 // 全屏切换
 const { toggle: toggleScreen, isFullscreen } = useFullscreen()
@@ -58,17 +77,32 @@ const settingVisible = ref(false)
 <template>
   <header class="layout-header" :style="headerStyle">
     <div class="flex flex-grow items-center justify-between px-4">
-      <el-space :size="20">
+      <div class="flex flex-grow">
         <el-button link @click="handleToggle">
           <icon-menu-unfold-one :size="iconSize" :fill="headerStyle.color" v-if="!value" />
           <icon-menu-fold-one :size="iconSize" :fill="headerStyle.color" v-else />
         </el-button>
-        <el-breadcrumb class="breadcrumb">
+        <!-- <el-breadcrumb class="breadcrumb">
           <el-breadcrumb-item v-for="item in matched.slice(1, matched.length)" :key="item.path">
             {{ item.meta.title }}
           </el-breadcrumb-item>
-        </el-breadcrumb>
-      </el-space>
+        </el-breadcrumb> -->
+        <div class="flex-grow px-20px">
+          <el-menu
+            :default-active="menuStore.activeTopMenu.path"
+            :class="{ 'top-menu': themeStyle.name === 'nav-dark' }"
+            mode="horizontal"
+            @select="handleMenuSelect"
+          >
+            <el-menu-item :index="item.path" v-for="item in topMenuList" :key="item.path">
+              <el-icon><component :is="item.meta?.icon"></component></el-icon>
+              <template #title>
+                <span>{{ item.meta.title }}</span>
+              </template>
+            </el-menu-item>
+          </el-menu>
+        </div>
+      </div>
       <div class="flex items-center">
         <el-button link @click="toggleScreen">
           <icon-off-screen :size="iconSize" :fill="headerStyle.color" v-if="isFullscreen" />
@@ -121,6 +155,13 @@ const settingVisible = ref(false)
     width: calc(var(--el-menu-icon-width) + var(--el-menu-base-level-padding) * 2);
   }
 }
+.top-menu {
+  --el-menu-bg-color: #fff;
+  --el-menu-text-color: #303133;
+}
+.el-menu--horizontal {
+  border-bottom: none;
+}
 .hidden {
   display: none;
 }

+ 18 - 39
src/components/GlobalMenu.vue

@@ -1,35 +1,15 @@
 <script setup lang="ts">
-import { useRouterStore } from '@/stores/router'
 import { useThemeStore } from '@/stores/theme'
 import { useMenuStore } from '@/stores/menu'
+import { isAbsolutePath } from '@/utils/utils'
 
 const themeStore = useThemeStore()
 const themeStyle = computed(() => themeStore.themeStyle)
 
 const menuStore = useMenuStore()
-
-// 生成菜单部分开始
-const routerStore = useRouterStore()
-const menuRouter = routerStore.menuRouter
-const generatorMenu = (routes: any[]) => {
-  routes.forEach(route => {
-    if (route.children && route.children.length) {
-      route.menuName = 'el-sub-menu'
-      generatorMenu(route.children)
-    } else {
-      route.menuName = 'el-menu-item'
-    }
-  })
-}
-generatorMenu(menuRouter)
-// 生成菜单部分结束
-
 const router = useRouter()
 const route = useRoute()
 
-const isAbsolutePath = (path: string) => {
-  return path.startsWith('http')
-}
 const handleSelect = (index: string) => {
   if (isAbsolutePath(index)) {
     window.open(index)
@@ -37,6 +17,21 @@ const handleSelect = (index: string) => {
     router.push(index)
   }
 }
+
+// 生成菜单部分开始
+const menuRouter = computed(() => menuStore.leftMenuList)
+watch(
+  menuRouter,
+  val => {
+    // generatorMenu(val)
+    val[0] && handleSelect(menuStore.activeRoutePath || val[0].path)
+  },
+  {
+    immediate: true
+  }
+)
+// generatorMenu(menuRouter.value)
+// 生成菜单部分结束
 </script>
 
 <template>
@@ -44,28 +39,12 @@ const handleSelect = (index: string) => {
     :default-active="route.path"
     :collapse="menuStore.collapse"
     :collapse-transition="false"
+    unique-opened
     class="layout-menu"
     :class="{ 'menu-normal': !menuStore.collapse }"
     :style="{ color: themeStyle.textColor }"
-    @select="handleSelect"
   >
-    <component :is="item.menuName" :index="item.path" v-for="(item, index) in menuRouter" :key="index">
-      <el-icon v-if="item.menuName === 'el-menu-item'"><component :is="item.meta?.icon"></component></el-icon>
-      <template #title>
-        <el-icon v-if="item.menuName === 'el-sub-menu'"><component :is="item.meta?.icon"></component></el-icon>
-        <span>{{ item.meta.title }}</span>
-      </template>
-      <el-menu-item
-        :index="isAbsolutePath(subItem.path) ? subItem.path : item.path + '/' + subItem.path"
-        v-for="(subItem, key) in item.children"
-        :key="key"
-      >
-        <template #title>
-          <el-icon v-if="subItem.meta?.icon"><component :is="subItem.meta?.icon"></component></el-icon>
-          <span>{{ subItem.meta.title }}</span>
-        </template>
-      </el-menu-item>
-    </component>
+    <global-sub-menu v-for="item in menuRouter" :key="item.name" :menu="item"></global-sub-menu>
   </el-menu>
 </template>
 

+ 18 - 1
src/components/GlobalSetting.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import { useThemeStore } from '@/stores/theme'
-import { themeStyleList } from '@/utils/constants'
+import { themeStyleList, themeNavList } from '@/utils/constants'
 
 const props = defineProps({
   modelValue: Boolean
@@ -42,6 +42,7 @@ const themeColors = [
 ]
 
 const themeStyle = computed(() => themeStore.themeStyle)
+const themeNav = computed(() => themeStore.themeNav)
 </script>
 
 <template>
@@ -58,6 +59,22 @@ const themeStyle = computed(() => themeStore.themeStyle)
         <el-icon size="22" color="#fff" v-if="themeColor === item"><icon-check></icon-check></el-icon>
       </div>
     </el-space>
+
+    <el-divider>导航栏模式</el-divider>
+    <el-space wrap :size="20">
+      <div
+        class="relative"
+        v-for="(item, index) in themeNavList"
+        :key="index"
+        @click="themeStore.setThemeNav(item.name)"
+      >
+        <img :src="item.img" alt="" />
+        <div class="absolute right-10px bottom-10px" v-if="item.name === themeNav">
+          <el-icon :color="themeColor" size="18"><Check /></el-icon>
+        </div>
+      </div>
+    </el-space>
+
     <el-divider>整体风格</el-divider>
     <el-space wrap :size="20">
       <div

+ 41 - 0
src/components/GlobalSubMenu.vue

@@ -0,0 +1,41 @@
+<script lang="ts" setup>
+import { useThemeStore } from '@/stores/theme'
+import { useMenuStore } from '@/stores/menu'
+import { isAbsolutePath } from '@/utils/utils'
+
+interface Props {
+  menu: any
+}
+
+const props = defineProps<Props>()
+
+const themeStore = useThemeStore()
+
+const router = useRouter()
+const menuStore = useMenuStore()
+
+const handleClick = (menu: any) => {
+  const path = menu.index
+  if (isAbsolutePath(path)) {
+    window.open(path)
+  } else {
+    router.push(path)
+  }
+}
+</script>
+
+<template>
+  <el-sub-menu v-if="menu.children?.length" :index="menu.name">
+    <template #title>
+      <el-icon v-if="menu.meta?.icon"><component :is="menu.meta?.icon"></component></el-icon>
+      <span>{{ menu.meta.title }}</span>
+    </template>
+    <global-sub-menu v-for="item in menu.children" :key="item.path" :menu="item"></global-sub-menu>
+  </el-sub-menu>
+  <el-menu-item v-else :index="menu.path" @click="handleClick">
+    <el-icon v-if="menu.meta?.icon"><component :is="menu.meta?.icon"></component></el-icon>
+    <span>{{ menu.meta.title }}</span>
+  </el-menu-item>
+</template>
+
+<style lang="scss" scoped></style>

+ 40 - 1
src/components/GlobalTabs.vue

@@ -1,6 +1,8 @@
 <script lang="ts" setup>
 import type { TabPaneName } from 'element-plus'
 import config from '@/config/defaultSetting'
+import { useThemeStore } from '@/stores/theme'
+import { useMenuStore } from '@/stores/menu'
 
 interface Tab {
   name: string
@@ -45,9 +47,46 @@ const setTab = (tab: Tab) => {
   }
   tabs.value.push(tab)
 }
+const themeStore = useThemeStore()
+const menuStore = useMenuStore()
+const activeMenu = ref({})
+const findActiveMenu = (path: any) => {
+  const findDeep = (list: any): boolean => {
+    let flag = false
+    for (let index = 0; index < list.length; index++) {
+      const element = list[index]
+      if (element.path === path) {
+        flag = true
+        break
+      }
+      if (element.children?.length) {
+        flag = findDeep(element.children)
+      }
+    }
+    return flag
+  }
+  for (let index = 0; index < menuStore.allMenuList.length; index++) {
+    const element = menuStore.allMenuList[index]
+    if (element.path === path) {
+      activeMenu.value = element
+      break
+    }
+    if (element.children?.length && findDeep(element.children)) {
+      activeMenu.value = element
+      break
+    }
+  }
+}
 const changeTab = (name: TabPaneName) => {
   activeIndex.value = findActiveIndex(name)
-  router.push(tabs.value.find(item => item.fullPath === name))
+  const menu = tabs.value.find(item => item.fullPath === name)
+  router.push(menu)
+
+  if (themeStore.themeNav === 'top') {
+    menuStore.activeRoutePath = name
+    findActiveMenu(name)
+    menuStore.setActiveTopMenu(activeMenu.value)
+  }
 }
 const removeTab = (name: TabPaneName) => {
   const index = findActiveIndex(name)

+ 2 - 1
src/config/defaultSetting.ts

@@ -3,5 +3,6 @@ export default {
   logo: '/logo.png',
   homeRouteName: 'home',
   themeColor: '#1890ff',
-  themeStyle: 'nav-light' // nav-dark  nav-light  header-dark
+  themeStyle: 'nav-light', // nav-dark  nav-light  header-dark
+  themeNav: 'left' // left | top
 }

+ 40 - 15
src/router/asyncRouter.ts

@@ -19,7 +19,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'user',
+        path: '/system/user',
         name: 'systemUser',
         component: () => import('@/views/system/User.vue'),
         meta: {
@@ -28,7 +28,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'role',
+        path: '/system/role',
         name: 'systemRole',
         component: () => import('@/views/system/Role.vue'),
         meta: {
@@ -37,7 +37,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'dict',
+        path: '/system/dict',
         name: 'systemDict',
         component: () => import('@/views/system/Dict.vue'),
         meta: {
@@ -46,7 +46,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'menu',
+        path: '/system/menu',
         name: 'systemMenu',
         component: () => import('@/views/system/Menu.vue'),
         meta: {
@@ -65,7 +65,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'orgList',
+        path: '/org/orgList',
         name: 'orgList',
         component: () => import('@/views/org/Org.vue'),
         meta: {
@@ -84,7 +84,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'banner',
+        path: '/miniprogram/banner',
         name: 'miniBanner',
         component: () => import('@/views/miniprogram/Banner.vue'),
         meta: {
@@ -93,7 +93,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'question',
+        path: '/miniprogram/question',
         name: 'miniQuestion',
         component: () => import('@/views/miniprogram/Question.vue'),
         meta: {
@@ -102,7 +102,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'feedback',
+        path: '/miniprogram/feedback',
         name: 'miniFeedback',
         component: () => import('@/views/miniprogram/Feedback.vue'),
         meta: {
@@ -111,7 +111,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'about',
+        path: '/miniprogram/about',
         name: 'miniAbout',
         component: () => import('@/views/miniprogram/About.vue'),
         meta: {
@@ -120,7 +120,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'version',
+        path: '/miniprogram/version',
         name: 'miniVersion',
         component: () => import('@/views/miniprogram/Version.vue'),
         meta: {
@@ -139,7 +139,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'cardList',
+        path: '/list/cardList',
         name: 'cardList',
         component: () => import('@/views/list/CardList.vue'),
         meta: {
@@ -158,7 +158,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'basic',
+        path: '/form/basic',
         name: 'formBasic',
         component: () => import('@/views/form/Basic.vue'),
         meta: {
@@ -166,12 +166,37 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'advanced',
+        path: '/form/advanced',
         name: 'formAdvanced',
         component: () => import('@/views/form/Advanced.vue'),
         meta: {
           title: '高级表单'
         }
+      },
+      {
+        path: '/form/test',
+        name: 'test',
+        meta: {
+          title: 'ceshi'
+        },
+        children: [
+          {
+            path: '/form/basict',
+            name: 'formBasict',
+            component: () => import('@/views/form/Basic.vue'),
+            meta: {
+              title: '基础表单t'
+            }
+          },
+          {
+            path: '/form/advancedt',
+            name: 'formAdvancedt',
+            component: () => import('@/views/form/Advanced.vue'),
+            meta: {
+              title: '高级表单t'
+            }
+          }
+        ]
       }
     ]
   },
@@ -184,7 +209,7 @@ const asyncRouter: RouteRecordRaw[] = [
     },
     children: [
       {
-        path: 'success',
+        path: '/result/success',
         name: 'resultSuccess',
         component: () => import('@/views/result/Success.vue'),
         meta: {
@@ -192,7 +217,7 @@ const asyncRouter: RouteRecordRaw[] = [
         }
       },
       {
-        path: 'error',
+        path: '/result/error',
         name: 'resultError',
         component: () => import('@/views/result/Error.vue'),
         meta: {

+ 35 - 8
src/stores/menu.ts

@@ -1,11 +1,38 @@
-export const useMenuStore = defineStore({
-  id: 'menu',
-  state: () => ({
-    collapse: false
-  }),
-  actions: {
-    setCollapse(collapse: boolean) {
-      this.collapse = collapse
+import { useRouterStore } from './router'
+import { useThemeStore } from './theme'
+
+export const useMenuStore = defineStore('menu', () => {
+  const routerStore = useRouterStore()
+
+  const collapse = ref(false)
+  const setCollapse = (flag: boolean) => {
+    collapse.value = flag
+  }
+
+  const allMenuList = computed(() => routerStore.menuRouter)
+  const activeRoutePath: any = useSessionStorage('activeRoutePath', '')
+
+  const activeTopMenu: any = useSessionStorage('activeTopMenu', {})
+  const setActiveTopMenu = (menu: any) => {
+    activeTopMenu.value = menu
+  }
+
+  const leftMenuList = computed(() => {
+    const themeStore = useThemeStore()
+    if (themeStore.themeNav === 'top') {
+      return activeTopMenu.value?.children || []
+    } else {
+      return allMenuList.value
     }
+  })
+
+  return {
+    collapse,
+    setCollapse,
+    allMenuList,
+    leftMenuList,
+    activeRoutePath,
+    activeTopMenu,
+    setActiveTopMenu
   }
 })

+ 15 - 1
src/stores/theme.ts

@@ -36,12 +36,26 @@ export const useThemeStore = defineStore('theme', () => {
     document.querySelector('html')!.className = style.name
   }
 
+  // 导航栏模式
+  const themeNav = useStorage('themeNav', config.themeNav)
+  const setThemeNav = (name: string) => {
+    themeNav.value = name
+  }
+
+  const initTheme = () => {
+    initThemeColor()
+    initThemeStyle()
+  }
+
   return {
     themeColor,
     setThemeColor,
     initThemeColor,
     themeStyle,
     setThemeStyle,
-    initThemeStyle
+    initThemeStyle,
+    themeNav,
+    setThemeNav,
+    initTheme
   }
 })

+ 12 - 0
src/utils/constants.ts

@@ -2,6 +2,7 @@ import type { BasicForm } from '@/types/form'
 import type { themeStyle } from '@/types/themeStyle'
 import navDark from '@/assets/svg/nav-theme-dark.svg'
 import navLight from '@/assets/svg/nav-theme-light.svg'
+import navH from '@/assets/svg/nav-h.svg'
 import headerDark from '@/assets/svg/header-theme-dark.svg'
 
 export const orgFormConfig = reactive<BasicForm>({
@@ -68,4 +69,15 @@ export const themeStyleList: themeStyle[] = [
   }
 ]
 
+export const themeNavList = [
+  {
+    name: 'left',
+    img: navDark
+  },
+  {
+    name: 'top',
+    img: navH
+  }
+]
+
 export const ACCESS_TOKEN = 'access_token'

+ 4 - 0
src/utils/utils.ts

@@ -3,3 +3,7 @@ import dayjs from 'dayjs'
 export const formatDate = (date: any, format = 'YYYY-MM-DD HH:mm') => {
   return dayjs(date).format(format)
 }
+
+export const isAbsolutePath = (path: string) => {
+  return path.startsWith('http')
+}