vue2+element-ui_el-tags动态菜单路由,面包屑切换实现刷新,关闭系列操作及缓存指定页面

504 阅读3分钟

注意: 重要的事: 值得注意的是, 这个home页面如果不需要切换的时候做页面缓存可以不做调整,cachedViews就是我的需要缓存的页面的数组,根据自己需求去获取就可以啦 下面的三直接就能做子组件引入进行切换和关闭刷新的操作(除了缓存页面都可以实现) 谁用这个部分的keep-alive 要求缓存的页面的name 一定要和系统设置中菜单的路由名称保持一致并且不能重复

一、图片展示功能

lQHPKHLHQzxNAH3M_M0HbrCgifcAutU6LQeG8EGKLdkA_1902_252.gif

image.png

image.png

二、需求讲解

根据需求,增加将原有的父子级的面包屑改为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>