1.路由文件 第一步与第三步
import router from '@/router'
import useStore from '@/store'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { chatUtil } from '@/utils/ChatUtil'
import { getNodeTree } from '@/api/demand/demand'
import { getCurrentInstance, ref } from 'vue'
import { app as VueApp } from './main'
import { localStorage as Local } from '@/utils/storage'
NProgress.configure({ showSpinner: false }) // 进度环显示/隐藏
const map = ref<any>({})
// 全局获取nodeTree
const getAllNodeTree = async () => {
VueApp.config.globalProperties.$treeNodeMap = (id: any) => {
return map.value[id]
}
const { data } = await getNodeTree()
Local.set('qt_nodeTree', JSON.stringify(data))
// 设置全局id和name的字典库
loopData(data)
}
const loopData = (arr: any) => {
for (let i = 0; i < arr.length; i++) {
const current = arr[i]
map.value[current.nodeId] = current.name
if (current.children) {
loopData(current.children)
}
}
}
let firstTime = true
// 白名单路由
const whiteList = ['/login']
const vm = getCurrentInstance()
router.beforeEach(async (to, from, next) => {
// 全局获取nodeTree
if (Local.get('token') && firstTime) {
firstTime = false
getAllNodeTree()
}
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
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.get(1).forEach((route: any) => {
router.addRoute(route)
})
accessRoutes.get(2).forEach((route: any) => {
router.addRoute(route)
})
accessRoutes.get(3).forEach((route: any) => {
router.addRoute(route)
})
// 初始化聊天窗口
chatUtil.initChatDialog()
next({ ...to, replace: true })
} catch (error) {
// 销毁聊天弹出框
await chatUtil.destoryChatDialog()
// 移除 token 并跳转登录页
await user.resetToken()
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
// 未登录可以访问白名单页面(登录页面)
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
// 销毁聊天弹出框
await chatUtil.destoryChatDialog()
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
2.store文件 第二步
import { PermissionState } from '@/types/store/permission'
import { RouteRecordRaw } from 'vue-router'
import { defineStore } from 'pinia'
import { constantRoutes } from '@/router'
import { listRoutes, listRoutesToMap } from '@/api/system/menu'
const modules = import.meta.glob('../../views/**/**.vue')
export const Layout = () => import('@/layout/index.vue')
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)
// 默认选中第一个
tmp.redirect = tmp.children[0].path
}
}
//}
)
return res
}
const usePermissionStore = defineStore({
id: 'permission',
state: (): PermissionState => ({
routes: [],
routesDesignMake: [],
routesPurchase: [],
addRoutes: [],
addRoutesDesignMake: [],
addRoutesPurchase: [],
}),
actions: {
setRoutes(routes: RouteRecordRaw[]) {
this.addRoutes = routes
this.routes = constantRoutes.concat(routes)
},
setRoutessDesignMake(routes: RouteRecordRaw[]) {
this.addRoutesDesignMake = routes
this.routesDesignMake = constantRoutes.concat(routes)
},
setRoutesPurchase(routes: RouteRecordRaw[]) {
this.addRoutesPurchase = routes
this.routesPurchase = constantRoutes.concat(routes)
},
generateRoutes(roles: string[]) {
// 第二步
return new Promise((resolve, reject) => {
listRoutesToMap()
.then((response) => {
const mapData = new Map()
console.log(response.data)
// const asyncRoutes = response.data[1]
// const accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
mapData.set(1, filterAsyncRoutes(response.data[1], roles))
mapData.set(2, filterAsyncRoutes(response.data[2], roles))
mapData.set(3, filterAsyncRoutes(response.data[3], roles))
this.setRoutes(mapData.get(1))
this.setRoutessDesignMake(mapData.get(2))
this.setRoutesPurchase(mapData.get(3))
resolve(mapData)
})
.catch((error) => {
reject(error)
})
})
},
},
})
export default usePermissionStore
3.头部菜单栏点击事件 第四步
<template>
<div class="navbar">
<div class="top">
<el-menu class="el-menu-demo" mode="horizontal" :default-active="activeIndex">
<!-- <el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
@select="handleSelect"
> -->
<el-menu-item index="0" @click="handoffTypes(0)">首页</el-menu-item>
<el-menu-item index="1" @click="handoffTypes(1)">产业平台</el-menu-item>
<el-menu-item index="2" @click="handoffTypes(2)">设计制造</el-menu-item>
<el-menu-item index="3" @click="handoffTypes(3)">供应链协同平台</el-menu-item>
</el-menu>
<div class="right-menu">
<template v-if="device !== 'mobile'">
<!-- <search id="header-search" class="right-menu-item" />
<error-log class="errLog-container right-menu-item hover-effect" />-->
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<!-- <el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip> -->
<!-- <lang-select class="right-menu-item hover-effect" /> -->
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click" popper-class="fs-popper">
<div class="avatar-wrapper">
<!-- <img :src="avatar + '?imageView2/1/w/80/h/80'" class="user-avatar" /> -->
<el-avatar :size="40" :src="avatar">
<img src="@/assets/images/avatar-default.png" />
</el-avatar>
<CaretBottom style="width: 0.6em; height: 0.6em; margin-left: 5px" />
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item divided @click="logout">
{{ $t('navbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
</div>
</template>
<script setup lang="ts">
import { computed, watch, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessageBox } from 'element-plus'
import useStore from '@/store'
import { localStorage } from '@/utils/storage'
// 组件依赖
import Breadcrumb from '@/components/Breadcrumb/index.vue'
import Hamburger from '@/components/Hamburger/index.vue'
import Screenfull from '@/components/Screenfull/index.vue'
import { chatUtil } from '@/utils/ChatUtil'
// 图标依赖
import { CaretBottom } from '@element-plus/icons-vue'
const { app, user, tagsView } = useStore()
const route = useRoute()
const router = useRouter()
const sidebar = computed(() => app.sidebar)
const device = computed(() => app.device)
const avatar = computed(() => user.avatar)
function toggleSideBar() {
app.toggleSidebar()
}
const activeIndex = ref('1')
// 第四步
watch(
() => app.applicationCode,
(value) => {
if (localStorage.get('applicationCode') == null) {
activeIndex.value = '1'
} else {
activeIndex.value = '' + localStorage.get('applicationCode')
}
console.log(activeIndex.value, 'activeIndex.value')
},
{ immediate: true }
)
const handoffTypes = (type: number) => {
localStorage.set('applicationCode', type)
app.applicationCode = type
}
// 去除元素
const removeEle = (ele: any) => {
for (let i = 0; i < ele.length; i++) {
const element = ele[i]
element.parentNode?.removeChild(element)
}
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
// 销毁聊天弹出框
await chatUtil.destoryChatDialog((flag: any) => {
if (flag) {
user.logout()
.then(() => {
tagsView.delAllViews()
})
.then(() => {
router.push(`/login?redirect=${route.fullPath}`)
})
} else {
alert('注销失败,请先结束聊天已接入的会话!')
}
})
})
}
</script>
<style lang="scss" scoped>
ul {
list-style: none;
margin: 0;
padding: 0;
}
.el-menu-demo {
height: 50px;
}
.navbar {
/* height: 50px; */
overflow: hidden;
position: relative;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
z-index: 999 !important;
position: relative;
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.top {
display: flex;
justify-content: space-between;
.el-menu {
flex: 1;
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
border-bottom: 1px solid #dcdfe6;
height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>
4.侧边菜单栏 第五步
<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 currentRoutes"
:item="route"
:key="route.path"
:base-path="route.path"
:is-collapse="isCollapse" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import { computed, watch } 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 routesDesignMake = computed(() => permission.routesDesignMake)
const routesPurchase = computed(() => permission.routesPurchase)
const showLogo = computed(() => setting.sidebarLogo)
const isCollapse = computed(() => !app.sidebar.opened)
// 当前菜单
const currentRoutes = computed(() => {
if (app.applicationCode == 1) {
return routes.value
} else if (app.applicationCode == 2) {
return routesDesignMake.value
} else if (app.applicationCode == 3) {
return routesPurchase.value
} else {
return routes.value
}
})
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu as string
}
return path
})
// 默认选中第一个
const handleOpen = (key: string, keyPath: string[]) => {
// 解决刷新时自动选中第一项
if (activeMenu.value.indexOf(key) >= 0) {
return
}
router.push(key)
}
</script>