导航

组件

​https://element-plus.gitee.io/zh-CN/component/tabs.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A2%9E%E5%8A%A0%E6%A0%87%E7%AD%BE%E9%A1%B5%E8%A7%A6%E5%8F%91%E5%99%A8​

router -> index.js

import { createRouter, createWebHistory } from 'vue-router'
import store from '@/store'
import { showFullLoading, hideFullLoading, toastError } from '@/utils'


import PugAdmin from '@/views/PugAdmin.vue'
import Dashboard from '@/views/dashboard/Index.vue'

import ProductList from '@/views/product/List.vue'
import CategoryList from '@/views/category/List.vue'
import CouponList from '@/views/coupon/List.vue'
import UserList from '@/views/user/List.vue'
import OrderList from '@/views/order/List.vue'
import ImageList from '@/views/image/List.vue'
import NoticeList from '@/views/notice/List.vue'
import LevelList from '@/views/level/List.vue'

import ManagerList from '@/views/manager/List.vue'
import RoleList from '@/views/role/List.vue'
import PermissionList from '@/views/permission/List.vue'


// 动态路由,用于匹配菜单动态添加路由
const asyncRoutes = [{
path: '/',
name: "dashboard",
meta: { title: '后台首页' },
component: Dashboard,
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/manager/list',
name: "/manager/list",
meta: { title: '后台管理员' },
component: ManagerList
}, {
path: '/role/list',
name: "/role/list",
meta: { title: '角色管理' },
component: RoleList
}, {
path: '/permission/list',
name: "/permission/list",
meta: { title: '权限管理' },
component: PermissionList
}, {
path: '/user/list',
name: "/user/list",
meta: { title: '用户管理' },
component: UserList
}, {
path: '/level/list',
name: "/level/list",
meta: { title: '会员等级' },
component: LevelList
}, {
path: "/category/list",
name: "/category/list",
component: CategoryList,
meta: { title: "分类列表" }
}, {
path: "/coupon/list",
name: "/coupon/list",
component: CouponList,
meta: { title: "优惠券管理" }
}, {
path: "/order/list",
name: "/order/list",
component: OrderList,
meta: {
title: "订单列表"
}
}, {
path: "/image/list",
name: "/image/list",
component: ImageList,
meta: {
title: "图库列表"
}
}, {
path: "/notice/list",
name: "/notice/list",
component: NoticeList,
meta: {
title: "公告列表"
}
}, {
path: '/product/list',
name: "/product/list",
meta: { title: '产品列表' },
component: ProductList
}];


//4 :定义路由配置规则
const routes = [{
path: "/",
meta: { title: "首页" },
name: "PugAdmin",
component: PugAdmin
}, {
path: "/login",
name: "login",
meta: { title: "登录" },
component: () =>
import ('../views/Login.vue')
}, {
path: "/toLogin",
redirect: "/login"
}, { //----------------新增代码,建议把注释删掉
path: '/:pathMatch(.*)*',
name: '404',
meta: { title: "404" },
component: () =>
import ('../views/error/404.vue')
}]


//2 :创建路由对象
const router = createRouter({
// 引入访问模式
history: createWebHistory(),
routes
})
// 动态注册路由方法- 考虑到
export function registerRoutes(menuList) {
// 是否有新的路由
let hasNewRoutes = false
const findAndAddRoutesByMenus = (arr) => {
arr.forEach(e => {
// 一定要查询我router.js中asyncRoutes存在的路由,才去绑定,否则不绑定
let item = asyncRoutes.find(o => o.path == e.path)
// item存在,并且没有绑定过router.hasRoute,
if (item && !router.hasRoute(item.path)) {
// 如果没有绑定,就开始绑定。这里就是admin,代表不论多个子元素,最终绑定全部挂载admin
// 也就意味着:后续所有的子元素,孙子元素等的路由访问,都跳转到PugAdmin.vue的router-view的位置
router.addRoute("PugAdmin", item)
hasNewRoutes = true
}

// 递归,如果当前子菜单还有子元素,
if (e.children && e.children.length > 0) {
findAndAddRoutesByMenus(e.children)
}
})
}

// 开始递归调用动态绑定路由关系
findAndAddRoutesByMenus(menuList)

// 第一次绑定:就是 true,
// 如果已经全部绑定过就是: false
// 返回这个作用:就是为了让后续的路由访问只绑定一次,没必要每次访问都绑定
return hasNewRoutes
}


/*
这个开关,是用来控制动态路由注册只绑定一次
只要不刷新、F5,第一次就是false, 后面永远都是 true
*/
let loadNewRoute = false;
// 定义后置守卫--拦截器思想
router.beforeEach(async(to, from, next) => {
// 全屏动画开启
showFullLoading()

// 判断是否已经登录
var isLogin = store.getters["user/isLogin"];

// 没有登录,强制跳转回登录页
if (!isLogin && to.path != "/login") {
toastError("请先登录", "error")
next("/toLogin")
}

// 防止重复登录
if (isLogin && to.path == "/login") {
toastError("请勿重复登录", "error")
next({ path: from.path ? from.path : "/" })
}


let hasNewRoute = false; //F5的问题
if (isLogin && !loadNewRoute) {
// 锁住
loadNewRoute = true;

// 从数据库查询菜单信息,开始进行动态注册, 这里会同步一次状态管理
let menusList = await store.dispatch("menu/asyncGetMenuList")

// 动态注册路由
hasNewRoute = registerRoutes(menusList);
}

// hasNewRoute=true,初次加载动态路由注册完毕。
// hasNewRoute=false,后续访问路由的,动态路由注册完毕。永远都是false
// 因为用户每次刷新,其实动态路由的注册都重新开始,所以 : hasNewRoute=true 。抓住这个特点
// next(to.fullPath) 刷新在哪里,跳转到自己这里, 因为如果直接next刷新就进入404
// next() 非刷新的情况的正常路由跳转。
hasNewRoute ? next(to.fullPath) : next();
})



/* 后置守卫 */
router.afterEach((to, from) => {
// 结束全屏动画
hideFullLoading();
// 标题切换
document.title = to.meta.title + "-PugAdmin-后台管理系统" || "PugAdmin-后台管理系统";
})
// 3: 导出即可生效
export default router

store ->menu.js

编写页面

页面导航栏_前端


页面导航栏_List_02

<template>
<div class="pug-tagbox" :style="{'left':$store.state.menu.slideWidth}">
<el-tabs
v-model="activeTab"
type="card"
class="demo-tabs"
@tab-remove="removeTab"
@tab-change="changeTab"
>
<el-tab-pane
:closable="item.path!='/'"
v-for="item in editableTabs"
:key="item.path"
:label="item.title"
:name="item.path"
>
</el-tab-pane>
</el-tabs>
<div class="pug-tab-dropdown">
<el-dropdown @command="handlerClose">
<span class="el-dropdown-link">
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="other">关闭其他</el-dropdown-item>
<el-dropdown-item command="all">全部关闭</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>


<script setup>
import { ref } from 'vue'
import {useRouter,onBeforeRouteUpdate} from 'vue-router'

// 定义路由对象
const router = useRouter();

// 用于路由激活
const activeTab = ref("/");

/*初始化首页卡片,记住这个卡片永远都不能删除,一定要保留 */
const editableTabs = ref([ {title: '后台首页',path: '/'}]);

// 点击卡片切换路由
const changeTab = (item)=>{
// 跳转路由
router.push(item);
}

// 监听路由的变化,如果发生了变化就添加路由
onBeforeRouteUpdate((to, from) =>{
//添加导航卡片
addTab({title: to.meta.title,path:to.path})
// 激活当前卡片选中
activeTab.value = to.path;
});

// 添加路由
const addTab = (tabItem) => {
// 根据传递进来tabItem,查询是否存在导航集合中,如果存在就不添加了
var exist = editableTabs.value.findIndex(item=>item.path == tabItem.path);
// 如果没有找到 -1 ,
if(exist == -1){
// 就追加
editableTabs.value.push(tabItem)
}
}


const removeTab = (targetName) => {

// 获取所有的卡片元素
const tabs = editableTabs.value

// 获取当前激活的元素
let currentItemPath = activeTab.value

// 如果targetName(删除的目标元素)和当前激活元素是一个。
if (currentItemPath === targetName) {
// 开始遍历所有卡片元素。 这个代码的意思删除自己以后,就去激活下一个元素。
// 没有下一个元素就去激活前一个元素
tabs.forEach((tab, index) => {
//如果找到了这个元素
if (tab.path === targetName) {
// 就找到当前删除元素的,下一个或者前一个元素。
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
currentItemPath = nextTab.path
}
}
})
}

// 激活下一个元素
activeTab.value = currentItemPath
//let targetIndex = editableTabs.value.findIndex(item=>item.path==targetName);
//editableTabs.value.splice(targetIndex,1);
// 把删除元素过滤即可
editableTabs.value = tabs.filter((tab) => tab.path !== targetName)
}

// 下来关闭
const handlerClose = (command)=>{
CommandEvent[command]();
}

const CommandEvent = {
other(){
editableTabs.value = editableTabs.value.filter((item) => (item.path == "/" || item.path == activeTab.value));
},
all(){
// 全部关闭
// 利用数组覆盖,保留首页
editableTabs.value =[ {title: '后台首页',path: '/'}]
// 把激活变成当前
activeTab.value = "/"
}
}
</script>


<style>
.pug-tagbox{
@apply fixed text-center px-2 flex items-center;
height:38px;
right:0;
top:48px;
background:#f8f8f8;
}

.pug-tagbox .el-tabs{
height:30px!important;
}

.pug-tagbox .el-tabs__header{
border:0!important;
margin-bottom:0!important;
}

.pug-tagbox .el-tabs__nav{
border:0!important;
}

.pug-tagbox .el-tabs__item{
height:30px;
line-height:30px;
border-radius:6px;
border:0;
background:#eee;
margin:0 2px;
}

/*下拉关闭 */
.pug-tab-dropdown{
@apply bg-white rounded ml-auto flex items-center justify-center px-2;
height:30px;
}


</style>

刷新丢失问题

使用:sessionStorage,localStorage, Cookie解决:

如果要支持cookie要按照组件

npm install js-cookie

封装cache.js如下:

import Cookies from 'js-cookie'
import defaultSettings from '@/settings'
// 缓存模板
const cacheTemplate = function(flag) {
return {
set(key, value) {
const storage = window[flag ? 'sessionStorage' : 'localStorage'];
const skey = defaultSettings.cacheSuffix + key;
if (storage) {
storage.setItem(skey, value);
} else {
Cookies.set(skey, value);
}
},

get(key) {
const storage = window[flag ? 'sessionStorage' : 'localStorage'];
const skey = defaultSettings.cacheSuffix + key;
if (storage) {
return storage.getItem(skey);
} else {
return Cookies.get(skey);
}
},

remove(key) {
const storage = window[flag ? 'sessionStorage' : 'localStorage'];
const skey = defaultSettings.cacheSuffix + key;
if (storage) {
storage.removeItem(skey);
} else {
return Cookies.remove(skey);
}
},

setJSON(key, jsonValue) {
this.set(key, JSON.stringify(jsonValue))
},

getJSON(key) {
const value = this.get(key)
return JSON.parse(value)
}
}
}

export default {
/**
* 会话级缓存
*/
session: cacheTemplate(true),
/**
* 本地缓存
*/
local: cacheTemplate(false)
}

使用缓存技术改进刷新

页面导航栏_javascript_03


当添加删除导航卡片时会更新缓存

页面导航栏_缓存_04


页面导航栏_javascript_05