layout
import { RouterView } from 'vue-router'
import store from '@/store'
import Sidebar from '@/layout/components/Sidebar/index.vue'
import Navbar from '@/layout/components/Navbar/index.vue'
import Tabs from '@/layout/components/Tabs/index.vue'
import { computed } from 'vue'
const theme = computed(() => store.appStore.theme)
const layoutHeaderHeight = computed(() => {
if (!theme.value.isTabsView) {
return 'height:var(--theme-header-height) !important'
} else {
return ''
}
})
const layoutMainHeight = computed(() => {
if (!theme.value.isTabsView) {
return 'min-height: calc(100vh - var(--theme-header-height) - 30px) !important'
} else {
return ''
}
})
router
import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import store from '@/store'
import { i18n } from '@/i18n'
import { pathToCamel } from '@/utils/tool'
NProgress.configure({ showSpinner: false })
const constantRoutes: RouteRecordRaw[] = [
{
path: '/redirect',
component: () => import('../layout/index.vue'),
children: [
{
path: '/redirect/:path(.*)',
component: () => import('../layout/components/Router/Redirect.vue')
}
]
},
{
path: '/login',
component: () => import('../views/login/login.vue')
},
{
path: '/404',
component: () => import('../views/404.vue')
}
]
const asyncRoutes: RouteRecordRaw = {
path: '/',
component: () => import('../layout/index.vue'),
redirect: '/home',
children: [
{
path: '/home',
name: 'Home',
component: () => import('../views/home.vue'),
meta: {
title: i18n.global.t('router.home'),
affix: true
}
},
{
path: '/profile/password',
name: 'ProfilePassword',
component: () => import('../views/profile/password.vue'),
meta: {
title: i18n.global.t('router.profilePassword'),
cache: true
}
}
]
}
export const errorRoute: RouteRecordRaw = {
path: '/:pathMatch(.*)',
redirect: '/404'
}
export const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes
})
// 白名单列表
const whiteList = ['/login']
// 路由加载前
router.beforeEach(async (to, from, next) => {
NProgress.start()
// token存在的情况
if (store.userStore.token) {
if (to.path === '/login') {
next('/home')
} else {
// 用户信息不存在,则重新拉取用户等信息
if (!store.userStore.user.id) {
await store.userStore.getUserInfoAction()
const menuRoutes = await store.routerStore.getMenuRoutes()
console.log('menu',menuRoutes)
// 根据后端菜单路由,生成KeepAlive路由
const keepAliveRoutes = getKeepAliveRoutes(menuRoutes, [])
console.log('menu11',keepAliveRoutes)
// 添加菜单路由
asyncRoutes.children?.push(...keepAliveRoutes)
router.addRoute(asyncRoutes)
// 错误路由
router.addRoute(errorRoute)
// 保存路由数据
store.routerStore.setRoutes(constantRoutes.concat(asyncRoutes))
// 搜索菜单需要使用
store.routerStore.setSearchMenu(keepAliveRoutes)
next({ ...to, replace: true })
} else {
next()
}
}
} else {
// 没有token的情况下,可以进入白名单
if (whiteList.indexOf(to.path) > -1) {
next()
} else {
next('/login')
}
}
})
// 路由加载后
router.afterEach(() => {
NProgress.done()
})
// 获取扁平化路由,将多级路由转换成一级路由
export const getKeepAliveRoutes = (rs: RouteRecordRaw[], breadcrumb: string[]): RouteRecordRaw[] => {
const routerList: RouteRecordRaw[] = []
rs.forEach((item: any) => {
if (item.meta.title) {
breadcrumb.push(item.meta.title)
}
if (item.children && item.children.length > 0) {
routerList.push(...getKeepAliveRoutes(item.children, breadcrumb))
} else {
item.meta.breadcrumb.push(...breadcrumb)
routerList.push(item)
}
breadcrumb.pop()
})
return routerList
}
// 加载vue组件
const layoutModules = import.meta.glob('/src/views/**/*.vue')
// 根据路径,动态获取vue组件
const getDynamicComponent = (path: string): any => {
const component = layoutModules[`/src/views/${path}.vue`]
if (!component) {
console.error('component error', path)
}
return component
}
// 根据菜单列表,生成路由数据
export const generateRoutes = (menuList: any): RouteRecordRaw[] => {
const routerList: RouteRecordRaw[] = []
menuList.forEach((menu: any) => {
let component
let path
if (menu.children && menu.children.length > 0) {
component = () => import('@/layout/index.vue')
path = '/p/' + menu.id
} else {
component = getDynamicComponent(menu.url)
path = '/' + menu.url
}
const route: RouteRecordRaw = {
path: path,
name: pathToCamel(path),
component: component,
children: [],
meta: {
title: menu.name,
icon: menu.icon,
id: '' + menu.id,
cache: true,
_blank: menu.openStyle === 1,
breadcrumb: []
}
}
// 有子菜单的情况
if (menu.children && menu.children.length > 0) {
route.children?.push(...generateRoutes(menu.children))
}
routerList.push(route)
})
return routerList
}
tabs
v-for="tab in store.tabsStore.visitedViews" :key="tab" :label="tab.title" :name="tab.path" :closable="!isAffix(tab)" >
import { watch, onMounted, ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import store from '@/store'
import { closeAllTabs, closeOthersTabs, closeTab } from '@/utils/tabs'
import { ArrowDown, Close, CircleClose, CircleCloseFilled } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
const activeTabName = ref(route.path)
const tabsStyleClass = computed(() => 'tabs-item-' + store.appStore.theme.tabsStyle)
// 是否固定
const isAffix = (tab: any) => {
return tab.meta && tab.meta.affix
}
watch(route, () => {
// 当前路由,添加到tabs里
if (route.name) {
addTab()
}
})
onMounted(() => {
// 初始化
initTabs()
addTab()
})
// 初始化固定tab
const initTabs = () => {
const affixTabs = getAffixTabs(store.routerStore.routes)
for (const tab of affixTabs) {
// 需要有tab名称
if (tab.name) {
store.tabsStore.addView(tab)
}
}
}
// 获取需要固定的tabs
const getAffixTabs = (routes: any) => {
let tabs: any[] = []
routes.forEach((route: any) => {
if (route.meta && route.meta.affix) {
tabs.push({
fullPath: route.path,
path: route.path,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTabs = getAffixTabs(route.children)
if (tempTabs.length >= 1) {
tabs = [...tabs, ...tempTabs]
}
}
})
return tabs
}
// 添加tab
const addTab = () => {
store.tabsStore.addView(route)
store.tabsStore.addCachedView(route)
activeTabName.value = route.path
}
// tab被选中
const tabClick = (tab: any) => {
tab.props.name && router.push(tab.props.name)
}
// 点击关闭tab
const tabRemove = (path: string) => {
const tab = store.tabsStore.visitedViews.filter((tab: any) => tab.path === path)
closeTab(router, tab[0])
}
// dropdown 关闭事件
const onClose = (type: string) => {
switch (type) {
case 'close':
closeTab(router, route)
break
case 'closeOthers':
closeOthersTabs(router, route)
break
case 'closeAll':
closeAllTabs(router, route)
break
}
}
.tabs-container {
display: flex;
position: relative;
z-index: 6;
height: 40px;
.tabs-item {
transition: left 0.3s;
flex-grow: 1;
overflow: hidden;
::v-deep(.el-tabs__nav-prev) {
padding: 0 10px;
border-right: var(--el-border-color-extra-light) 1px solid;
}
::v-deep(.el-tabs__nav-next) {
padding: 0 10px;
border-left: var(--el-border-color-extra-light) 1px solid;
}
::v-deep(.is-scrollable) {
padding: 0 32px;
}
::v-deep(.el-tabs__active-bar) {
height: 0;
}
::v-deep(.el-tabs__item) {
.is-icon-close {
transition: none !important;
&:hover {
color: var(--el-color-primary-light-9);
background-color: var(--el-color-primary);
border-radius: 50%;
}
}
}
}
}
.tabs-item-style-1 {
::v-deep(.el-tabs__item) {
padding: 0 15px !important;
border-right: var(--el-border-color-extra-light) 1px solid;
user-select: none;
color: #8c8c8c;
&:hover {
color: #444;
background: rgba(0, 0, 0, 0.02);
}
&.is-active {
color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
border-bottom: var(--el-border-color-light) 2px solid;
&::before {
background-color: var(--el-color-primary);
}
}
&::before {
content: '';
width: 9px;
height: 9px;
margin-right: 8px;
display: inline-block;
background-color: #ddd;
border-radius: 50%;
}
}
}
.tabs-item-style-2 {
::v-deep(.el-tabs__item) {
padding: 0 15px !important;
border-right: none;
user-select: none;
color: #8c8c8c;
display: inline-block;
&:hover {
color: #444;
background: rgba(0, 0, 0, 0.02);
border-bottom: var(--el-color-primary) 2px solid;
}
&.is-active {
color: var(--el-color-primary) !important;
background-color: var(--el-color-primary-light-9) !important;
border-bottom: var(--el-color-primary) 2px solid;
&::before {
background-color: var(--el-color-primary);
}
}
}
}
.tabs-action {
height: 40px;
line-height: 40px;
box-sizing: border-box;
padding: 0 12px;
align-items: center;
cursor: pointer;
color: #666;
border-left: var(--el-border-color-extra-light) 1px solid;
border-bottom: var(--el-border-color-light) 2px solid;
}
side
import store from '@/store'
import Logo from './components/Logo.vue'
import Menu from './components/Menu.vue'
import { computed } from 'vue'
const sidebarClass = computed(() => {
const sidebarOpened = store.appStore.sidebarOpened ? 'aside-expend' : 'aside-compress'
const isDark = store.appStore.theme.sidebarStyle === 'dark' ? 'sidebar-dark' : ''
return sidebarOpened + ' ' + isDark
})
menu
:default-active="defaultActive" :collapse="!store.appStore.sidebarOpened" :unique-opened="store.appStore.theme.uniqueOpened" background-color="transparent" :collapse-transition="false" mode="vertical" router >
import store from '@/store'
import MenuItem from './MenuItem.vue'
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
const defaultActive = computed(() => {
const { path } = route
return path
})
MenuItem
{{ menu.meta.title }}
{{ menu.meta.title }}
import { PropType } from 'vue'
defineProps({
menu: {
type: Object as PropType
required: true
}
})