注意: 重要的事: 值得注意的是, 这个home页面如果不需要切换的时候做页面缓存可以不做调整,cachedViews就是我的需要缓存的页面的数组,根据自己需求去获取就可以啦 下面的三直接就能做子组件引入进行切换和关闭刷新的操作(除了缓存页面都可以实现) 谁用这个部分的keep-alive 要求缓存的页面的name 一定要和系统设置中菜单的路由名称保持一致并且不能重复
一、图片展示功能
二、需求讲解
根据需求,增加将原有的父子级的面包屑改为tag标签的面包屑,切换的时候要将有缓存的页面进行缓存, 实现刷新页面,关闭全部标签,关闭左侧标签,关闭右侧标签,关闭当前标签
三、完整的子组件,可以直接复制使用
<!-- breadcrunb-tag.vue 子组件--!>
<template>
<div>
<!-- contextMenuVisible是右键弹窗--!>
<div
v-show="contextMenuVisible"
:style="{ left: menuLeft + 'px', top: menuTop + 'px' }"
class="zdiv"
>
<li @click="refresh">刷新页面</li>
<li @click="removeAll">关闭所有标签</li>
<li @click="closeLeft">关闭左边标签</li>
<li @click="closeRight">关闭右边标签</li>
<li @click="closeOther">关闭其他标签</li>
</div>
<el-tag
v-for="(tag, index) in tags"
:key="index"
:closable="tag.name !== activeHomeRputer.path"
:disable-transitions="false"
:effect="isActive(tag) ? 'dark' : 'plain'"
@close="handleClose(tag, index)"
@click="changeMenu(tag)"
@contextmenu.native.prevent="handleClickContextMenu($event)"
>
{{ tag.label }}
</el-tag>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
export default {
data() {
return {
tags: [],
tagIndex: 0, // 当前点击的tag的索引
menuLeft: '', // 右键菜单距离浏览器左边的距离
menuTop: '', // 右键菜单距离浏览器上边的距离
contextMenuVisible: false, // 是否显示菜单
currentTab: '', // 当前选中的tab
activeHomeRputer: {}, // 首页
};
},
computed: {
...mapState(['headeMenusName', 'breadcrumb']),
pathsName() {
return this.breadcrumb;
},
},
watch: {
tags: {
handler(newTags) {
localStorage.setItem('tags', JSON.stringify(newTags));
},
deep: true,
},
$route(to) {
const tag = { name: to.name, label: to.meta.title };
const isTagExist = this.tags.some((t) => t.name === tag.name);
if (!isTagExist) {
this.tags.push(tag);
}
},
},
mounted() {
this.activeHomeRputer = JSON.parse(
localStorage.getItem('activeHomeRputer') || '{}'
);
const tags = JSON.parse(localStorage.getItem('tags') || '[]');
if (tags.length > 0) {
this.tags = tags;
}
},
methods: {
// 刷新页面
refresh() {
this.$router.go(0)
},
// 关闭所有标签
removeAll() {
this.tags = [];
this.currentTab = '';
this.contextMenuVisible = false;
this.tags.push({name: this.activeHomeRputer.name, label: this.activeHomeRputer.title});
this.$router.push({ name: this.activeHomeRputer.name });
},
// 关闭左边标签
closeLeft() {
const activeIndex = this.tags.findIndex((tag) => this.isActive(tag));
if (activeIndex > 0) {
this.tags = this.tags.slice(activeIndex);
}
this.contextMenuVisible = false;
},
// 关闭右边标签
closeRight() {
const activeIndex = this.tags.findIndex((tag) => this.isActive(tag));
if (activeIndex > -1 && activeIndex <= this.tags.length - 1) {
// 截取从索引 0 到当前激活标签(包含)的元素
this.tags = this.tags.slice(0, activeIndex + 1);
}
this.contextMenuVisible = false;
},
// 关闭其他标签
closeOther() {
const activeIndex = this.tags.findIndex((tag) => this.isActive(tag));
if (activeIndex > -1) {
this.tags = [this.tags[activeIndex]];
}
this.contextMenuVisible = false;
},
handleClickContextMenu(event) {
const e = event || window.event;
const target = e.target;
this.currentTab = e.srcElement.innerText;
this.menuLeft = e.clientX; // 菜单出现的位置距离左侧的距离
this.menuTop = e.clientY; // 菜单出现的位置距离顶部的距离
this.tagIndex = Number(target.getAttribute('data-index')); // 获取当前右击菜单的索引。从0开始
this.contextMenuVisible = true; // 显示菜单
this.tag = this.tags[this.tagIndex]; // 当前右击的菜单信息
},
handleClose(tag, index) {
// 处理关闭标签的逻辑
if (tag.name !== this.activeHomeRputer.name) {
this.tags.splice(index, 1);
}
// 如果关闭的标签不是当前路由的话,就不跳转
if (tag.name !== this.$route.name) {
return;
}
const length = this.tags.length;
// 关闭的标签是最右边的话,往左边跳转一个
if (index === length) {
this.$router.push({ name: this.tags[index - 1].name });
} else {
// 否则往右边跳转
this.$router.push({ name: this.tags[index].name });
}
},
changeMenu(tag) {
// 跳转首页
if (tag.name === this.activeHomeRputer.path) {
if (this.$route.path !== this.activeHomeRputer.path) {
this.$router.push({ path: this.activeHomeRputer.path });
}
return;
}
// 处理点击标签的逻辑
if (tag.name !== this.activeHomeRputer.path) {
const toPath = this.$router.resolve({ name: tag.name }).href;
if (this.$route.path !== toPath) {
this.$router.push({ name: tag.name });
}
}
},
isActive(tag) {
// 判断当前标签是否为当前路由
return tag.name === this.$route.name;
},
},
};
</script>
<style lang="scss" scoped>
.el-tag--dark,
.el-tag--plain {
cursor: pointer;
margin-left: 10px;
}
.el-tag:nth-child(2n) {
cursor: pointer;
margin-left: 10px;
}
.zdiv {
margin: 0;
border: 1px solid rgb(187, 186, 186);
background: rgb(255, 255, 255);
z-index: 1000;
position: absolute;
list-style-type: none;
padding: 5px;
border-radius: 7px;
font-size: 12px;
color: #2e6dff;
}
.zdiv li {
margin: 0;
padding: 5px;
padding-top: 7px;
padding-bottom: 7px;
}
.zdiv li:hover {
background: #e1e1e1;
cursor: pointer;
}
</style>
四、在父组件中使用
<!-- BreadcrumbTags就是面包屑的子组件 这个引用的页面叫做header.vue也是个子组件 -->
<template>
<div class="rightContent">
<div class="headerNav">
<!-- <svg
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="25"
height="25"
@click="showCollapse"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg> -->
<i class="hamburger" :class="collapse?'el-icon-s-unfold':'el-icon-s-fold'" @click="showCollapse" />
<BreadcrumbTags></BreadcrumbTags>
<!-- <el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item v-for="(item, index) in pathsName" :key="index" :to="{ path: item.path }">{{ item.meta.title }}</el-breadcrumb-item> -->
<!-- <template v-for="(item, index) in breadcrumb">
<el-breadcrumb-item v-if="item.path" :key="index" :to="{ path: item.path }">{{ item.path }}-{{ item.meta.title }}</el-breadcrumb-item>
<el-breadcrumb-item v-else :key="index">{{ item.meta.title }}</el-breadcrumb-item>
</template> -->
<!-- </el-breadcrumb> -->
</div>
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<el-avatar icon="el-icon-user-solid" size="medium" />
<i class="el-icon-caret-bottom" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<el-dropdown-item @click.native="logout">
退出登录
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</template>
<script>
import { mapActions } from "vuex"
import BreadcrumbTags from "./breadcrunb-tag.vue"
export default {
components: { BreadcrumbTags },
data() {
return {
collapse: false,
breadcrumbList: [],
tags: []
}
},
methods: {
...mapActions("ret/menu", ["asideCollapseToggle"]),
...mapActions("user", ["logOut"]),
// 查询当前路径
makeAllmenus(muealist) {
if (!muealist) return
muealist.forEach(v => {
this.breadcrumbList.unshift(v.showDefaultCtx)
if (v.children.length !== 0) {
this.makeAllmenus(v.children)
}
})
},
// 获取当前路径对应面包屑
showCollapse() {
this.collapse = !this.collapse
this.$emit("showNavfn")
},
// 接收点击切换侧边栏的按钮
handleToggleAside() {
this.asideCollapseToggle();
},
async logout() {
this.$confirm("确认要退出登录吗,是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
this.logOut()
localStorage.removeItem("token");
})
.catch(() => {})
}
}
};
</script>
<style lang="scss" scoped>
.rightContent{
display: flex;
justify-content: space-between;
align-items: center;
margin:0 15px;
.headerNav{
padding: 15px;
padding-left: 0;
display: flex;
align-items: center;
.hamburger {
cursor: pointer;
font-size: 25px;
}
.el-breadcrumb{
margin-left: 10px;
}
}
.right-menu{
cursor: pointer;
// display: flex;
// align-items: center;
}
}
::v-deep .el-dropdown-selfdefine{
display: flex;
align-items: center;
}
</style>
五、这个页面是header 的父组件叫home(首页)
值得注意的是, 这个页面如果不需要切换的时候做页面缓存可以不做调整,cachedViews就是我的需要缓存的页面的数组,根据自己需求去获取就可以啦 上面的三直接就能做切换和关闭刷新的操作 谁用这个部分的keep-alive 要求缓存的页面的name 一定要和系统设置中菜单的路由名称保持一致并且不能重复
<!-- eslint-disable vue/multi-word-component-names -->
<template>
<div class="app-wrapper">
<el-scrollbar style="background:#304156 !important;height: 100%;">
<layOut :collapse="collapse" />
</el-scrollbar>
<div class="rigthPart">
<div>
<Header @showNavfn="showCollapse" />
</div>
<div class="mainContent">
<div v-if="$route.path=='/'" class="chart-box bgStyle">
<keep-alive :include="cachedViews">
<router-view :key="$route.path" />
</keep-alive>
</div>
<!-- <router-view /> -->
<!-- {{ cachedViews }} -->
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="$route.path" />
</keep-alive>
</transition>
</div>
</div>
</div>
</template>
<script>
import layOut from "@/layout"
import Header from "@/components/header"
import { mapState } from "vuex";
// import BarChart from "@/components/echarts/barChart.vue";
// import lineChart from "@/components/echarts/lineChart.vue";
// import PieChart from "@/components/echarts/pieChart.vue";
export default {
components: {
layOut,
Header,
// BarChart,
// lineChart,
// PieChart
},
data() {
return {
collapse: false,
menuList: []
}
},
computed: {
...mapState("tagsView", ["cachedViews"]),
},
mounted() {
this.menuList = JSON.parse(localStorage.getItem("menuList"))
if (this.menuList && this.menuList.length > 0) {
this.getLocalMenusList(this.menuList)
}
console.log("cachedViews:", this.cachedViews)
},
methods: {
findChildren(menu) {
if (!menu || !menu.children || menu.children.length === 0) {
return [];
}
let result = [];
menu.children.forEach(child => {
result.push(child);
result = result.concat(this.findChildren(child));
});
if (result[0].children.length <= 0 || !result[0].children) {
if (this.$route.path === "/") {
this.$router.push({
path: result[0].path,
name: result[0].name,
title: result[0].meta.title
});
const activeHomeRputer = {
path: result[0].path,
name: result[0].name,
title: result[0].meta.title
}
localStorage.setItem("activeHomeRputer", JSON.stringify(activeHomeRputer))
}
}
return result;
},
getLocalMenusList(menuList) {
this.findChildren(menuList[0])
},
showCollapse() {
this.collapse = !this.collapse
}
}
}
</script>
<style lang="scss" scoped>
.chart-box{
margin: 0;
min-width: 1120px;
}
.app-wrapper{
height: 100vh;
display: flex;
// width: 100%;
// background-color: orange;
}
.rigthPart{
max-height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
flex: 1;
}
.mainContent{
// width: calc(100vw - 220px);
// width: 100%;
height: 100vh;
overflow: auto;
padding: 20px;
flex: 1;
background-color: #F0F6FF;
}
::v-deep .el-scrollbar__wrap{
overflow-x: hidden !important;
}
// ::v-deep .el-scrollbar{
// overflow: visible;
// }
</style>