0


后台-vite中import.meta.glob的具体用法 与 动态路由和递归组件菜单栏

1.获取图标文件里的所有图标

​​<template>
    <div class="icon-select">
        <el-input v-model="iconName" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons">
        </el-input>
        <div class="icon-select__list">
            <div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
                <svg-icon color="#999" :icon-class="item" style="height: 30px; width: 16px; margin-right: 5px" />
                <span>{{ item }}</span>
            </div>
        </div>
    </div>
</template>
<script setup lang="ts">
    import { ref } from 'vue'
    import SvgIcon from '@/components/SvgIcon/index.vue'

    const icons = [] as string[]
    //获取图标文件
    const modules = import.meta.glob('../../assets/icons/*.svg')
    for (const path in modules) {
        const p = path.split('assets/icons/')[1].split('.svg')[0]
        //icons为图标文件名 数组
        icons.push(p)
    }
    const iconList = ref(icons)

    const iconName = ref('')

    const emit = defineEmits(['selected'])

    function filterIcons() {
        iconList.value = icons
        if (iconName.value) {
            iconList.value = icons.filter((item) => item.indexOf(iconName.value) !== -1)
        }
    }

    function selectedIcon(name: string) {
        emit('selected', name)
        document.body.click()
    }

    function reset() {
        iconName.value = ''
        iconList.value = icons
    }

    defineExpose({
        reset,
    })
</script>

<style lang="scss" scoped>
    .icon-select {
        width: 100%;
        padding: 10px;

        &__list {
            height: 200px;
            overflow-y: scroll;

            div {
                height: 30px;
                line-height: 30px;
                margin-bottom: -5px;
                cursor: pointer;
                width: 33%;
                float: left;
            }

            span {
                display: inline-block;
                vertical-align: -0.15em;
                fill: currentColor;
                overflow: hidden;
            }
        }
    }
</style>

modules

path.split('assets/icons/')

**p **

2.动态路由

store/modules/permission.ts

import { PermissionState } from '@/types/store/permission'
import { RouteRecordRaw } from 'vue-router'
import { defineStore } from 'pinia'
import { constantRoutes } from '@/router'
import { listRoutes } from '@/api/system/menu'

//获取view下所有的vue文件
const modules = import.meta.glob('../../views/**/*.vue')
export const Layout = () => import('@/layout/index.vue')

// 递归拼接组成路由的component
export const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
    const res: RouteRecordRaw[] = []
    routes.forEach(
        (route) => {
            const tmp = { ...route } as any

            //if (hasPermission(roles, tmp)) {
            if (tmp.component == 'Layout') {
                tmp.component = Layout
            } else {
                const component = modules[`../../views/${tmp.component}.vue`] as any
                if (component) {
                    tmp.component = modules[`../../views/${tmp.component}.vue`]
                } else {
                    tmp.component = modules[`../../views/error-page/404.vue`]
                }
            }
            tmp.name = tmp.path
            res.push(tmp)

            if (tmp.children) {
                tmp.children = filterAsyncRoutes(tmp.children, roles)
            }
        }
        //}
    )
    return res
}

const usePermissionStore = defineStore({
    id: 'permission',
    state: (): PermissionState => ({
        routes: [],
        addRoutes: [],
    }),
    actions: {
        setRoutes(routes: RouteRecordRaw[]) {
            this.addRoutes = routes
            // this.routes供左边菜单栏使用,constantRoutes为路由文件原有的登陆、首页等
            this.routes = constantRoutes.concat(routes)
        },
        generateRoutes(roles: string[]) {
            return new Promise((resolve, reject) => {
                listRoutes()
                    .then((response) => {
                        // asyncRoutes:获取后端返回的路由
                        const asyncRoutes = response.data
                        //  accessedRoutes:拼接成功后想要的路由
                        const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
                        this.setRoutes(accessedRoutes)
                        resolve(accessedRoutes)
                    })
                    .catch((error) => {
                        reject(error)
                    })
            })
        },
    },
})

export default usePermissionStore

modules

** asyncRoutes:获取后端返回的路由**

** accessedRoutes:拼接成功后想要的路由**

router/index.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import useStore from '@/store';

export const Layout = () => import('@/layout/index.vue');

// 参数说明: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
// 静态路由
export const constantRoutes: Array<RouteRecordRaw> = [
  {
    path: '/redirect',
    component: Layout,
    meta: { hidden: true },
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index.vue')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index.vue'),
    meta: { hidden: true }
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404.vue'),
    meta: { hidden: true }
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index.vue'),
        name: 'Dashboard',
        meta: { title: '首页', icon: 'homepage', affix: true }
      },
      {
        path: '401',
        component: () => import('@/views/error-page/401.vue'),
        meta: { hidden: true }
      },
    ]
  }

  // 外部链接
  /*{
        path: '/external-link',
        component: Layout,
        children: [
            {
                path: 'https://www.cnblogs.com/haoxianrui/',
                meta: { title: '外部链接', icon: 'link' }
            }
        ]
    }*/
  // 多级嵌套路由
  /* {
         path: '/nested',
         component: Layout,
         redirect: '/nested/level1/level2',
         name: 'Nested',
         meta: {title: '多级菜单', icon: 'nested'},
         children: [
             {
                 path: 'level1',
                 component: () => import('@/views/nested/level1/index.vue'),
                 name: 'Level1',
                 meta: {title: '菜单一级'},
                 redirect: '/nested/level1/level2',
                 children: [
                     {
                         path: 'level2',
                         component: () => import('@/views/nested/level1/level2/index.vue'),
                         name: 'Level2',
                         meta: {title: '菜单二级'},
                         redirect: '/nested/level1/level2/level3',
                         children: [
                             {
                                 path: 'level3-1',
                                 component: () => import('@/views/nested/level1/level2/level3/index1.vue'),
                                 name: 'Level3-1',
                                 meta: {title: '菜单三级-1'}
                             },
                             {
                                 path: 'level3-2',
                                 component: () => import('@/views/nested/level1/level2/level3/index2.vue'),
                                 name: 'Level3-2',
                                 meta: {title: '菜单三级-2'}
                             }
                         ]
                     }
                 ]
             },
         ]
     }*/
];

// 创建路由
const router = createRouter({
  history: createWebHashHistory(),
  routes: constantRoutes as RouteRecordRaw[],
  // 刷新时,滚动条位置还原
  scrollBehavior: () => ({ left: 0, top: 0 })
});

// 重置路由
export function resetRouter() {
  const { permission } = useStore();
  permission.routes.forEach(route => {
    const name = route.name;
    if (name && router.hasRoute(name)) {
      router.removeRoute(name);
    }
  });
}

export default router;

与App.vue同级 permission.ts

import router from '@/router'
import useStore from '@/store'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏
// 白名单路由
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
    if (to.meta.title) {
        //判断是否有标题
        document.title = `智-admin-${to.meta.title}  `
    } else {
        document.title = `智-admin`
    }
    NProgress.start()
    const { user, permission } = useStore()
    const hasToken = user.token
    if (hasToken) {
        // 登录成功,跳转到首页
        if (to.path == '/login') {
            next({ path: '/' })
            NProgress.done()
        } else {
            const hasGetUserInfo = user.roles.length > 0
            // 第一步.hasGetUserInfo一开始为false
            if (hasGetUserInfo) {
                if (to.matched.length == 0) {
                    from.name ? next({ name: from.name as any }) : next('/401')
                } else {
                    // 第四步
                    next()
                }
            } else {
                try {
                    // 第二步
                    await user.getUserInfo()
                    const roles = user.roles
                    const accessRoutes: any = await permission.generateRoutes(roles)
                    accessRoutes.forEach((route: any) => {
                        router.addRoute(route)
                    })
                    // 第三步
                    // 如果 addRoutes 并未完成,路由守卫会一层一层的执行执行,直到 addRoutes 完成,找到对应的路由
                    next({ ...to, replace: true })
                } catch (error) {
                    // 移除 token 并跳转登录页
                    await user.resetToken()
                    next(`/login?redirect=${to.path}`)
                    NProgress.done()
                }
            }
        }
    } else {
        // 未登录可以访问白名单页面(登录页面)
        if (whiteList.indexOf(to.path) !== -1) {
            next()
        } else {
            next(`/login?redirect=${to.path}`)
            NProgress.done()
        }
    }
})

router.afterEach(() => {
    NProgress.done()
})

最后在main.ts导入permission.ts

next({ ...to, replace: true }) 意思:VUE 路由守卫 next() / next({ ...to, replace: true }) / next(‘/‘) 说明_路由守卫next_anne都的博客-CSDN博客

3.动态递归组件侧边菜单栏

1.src/layout/components/sidebar/index.vue

<template>
    <div :class="{ 'has-logo': showLogo }">
        <logo v-if="showLogo" :collapse="isCollapse" />
        <el-scrollbar>
            <el-menu
                :default-active="activeMenu"
                :collapse="isCollapse"
                :background-color="variables.menuBg"
                :text-color="variables.menuText"
                :active-text-color="variables.menuActiveText"
                :unique-opened="true"
                :collapse-transition="false"
                mode="vertical"
                @open="handleOpen">
                <sidebar-item v-for="route in routes" :item="route" :key="route.path" :base-path="route.path" :is-collapse="isCollapse" />
            </el-menu>
        </el-scrollbar>
    </div>
</template>

<script setup lang="ts">
    import { computed } from 'vue'
    import { useRoute, useRouter } from 'vue-router'
    import SidebarItem from './SidebarItem.vue'
    import Logo from './Logo.vue'
    import variables from '@/styles/variables.module.scss'
    import useStore from '@/store'
    const { permission, setting, app } = useStore()

    const route = useRoute()
    const router = useRouter()
    const routes = computed(() => permission.routes)
    const showLogo = computed(() => setting.sidebarLogo)
    const isCollapse = computed(() => !app.sidebar.opened)

    const activeMenu = computed(() => {
        const { meta, path } = route
        if (meta.activeMenu) {
            return meta.activeMenu as string
        }
        return path
    })
    // 默认选中第一个
    const handleOpen = (key: string, keyPath: string[]) => {
        router.push(key)
    }
</script>

routes


sidebarItem组件

<template>
    <div v-if="!item.meta || !item.meta.hidden">
        <!-- 没有子路由,子菜单,没有展开项 -->
        <template
            v-if="
                hasOneShowingChild(item.children, item) &&
                (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
                (!item.meta || !item.meta.alwaysShow)
            ">
            <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
                <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
                    <svg-icon v-if="onlyOneChild.meta && onlyOneChild.meta.icon" :icon-class="onlyOneChild.meta.icon" />
                    <template #title>
                        {{ generateTitle(onlyOneChild.meta.title) }}
                    </template>
                </el-menu-item>
            </app-link>
        </template>
        <!-- 有子路由,有展开项 -->
        <el-sub-menu v-else :index="resolvePath(item.path)" teleported>
            <template #title>
                <svg-icon v-if="item.meta && item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
                <span v-if="item.meta && item.meta.title">{{ generateTitle(item.meta.title) }}</span>
            </template>
            <!-- 递归组件 -->
            <sidebar-item
                v-for="child in item.children"
                :key="child.path"
                :item="child"
                :is-nest="true"
                :base-path="resolvePath(child.path)"
                class="nest-menu" />
        </el-sub-menu>
    </div>
</template>

<script setup lang="ts">
    import { ref } from 'vue'
    import path from 'path-browserify'
    import { isExternal } from '@/utils/validate'
    import AppLink from './Link.vue'
    import { generateTitle } from '@/utils/i18n'
    import SvgIcon from '@/components/SvgIcon/index.vue'

    const props = defineProps({
        item: {
            type: Object,
            required: true,
        },
        isNest: {
            type: Boolean,
            required: false,
        },
        basePath: {
            type: String,
            required: true,
        },
    })

    const onlyOneChild = ref()

    function hasOneShowingChild(children = [] as any, parent: any) {
        if (!children) {
            children = []
        }
        const showingChildren = children.filter((item: any) => {
            if (item.meta && item.meta.hidden) {
                return false
            } else {
                // 过滤出子元素
                onlyOneChild.value = item
                return true
            }
        })
        // 当只有一个子路由,该子路由显示子菜单,没有用展开项
        if (showingChildren.length == 1) {
            return true
        }

        // 没有子路由则显示父路由
        if (showingChildren.length == 0) {
            onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
            return true
        }

        return false
    }
    // 解析路径
    function resolvePath(routePath: string) {
        // isExternal 判断是否为网址
        if (isExternal(routePath)) {
            return routePath
        }
        if (isExternal(props.basePath)) {
            return props.basePath
        }
        // path.resolve('/partner', '/business')  为 /partner/business
        return path.resolve(props.basePath, routePath)
    }
</script>

<style lang="scss" scoped></style>

appLink组件

<template>
  <a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
    <slot />
  </a>
  <div v-else @click="push">
    <slot />
  </div>
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue';
import { isExternal } from '@/utils/validate';
import { useRouter } from 'vue-router';

import useStore from '@/store';

const { app } = useStore();

const sidebar = computed(() => app.sidebar);
const device = computed(() => app.device);

export default defineComponent({
  props: {
    to: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const router = useRouter();
    const push = () => {
      if (device.value == 'mobile' && sidebar.value.opened == true) {
        app.closeSideBar(false);
      }
      router.push(props.to).catch(err => {
        console.log(err);
      });
    };
    return {
      push,
      isExternal
    };
  }
});
</script>
标签: javascript 前端 html

本文转载自: https://blog.csdn.net/hktkkkk/article/details/129808736
版权归原作者 codingFunTime 所有, 如有侵权,请联系我们删除。

“后台-vite中import.meta.glob的具体用法 与 动态路由和递归组件菜单栏”的评论:

还没有评论