已实现的功能


<template>
<div class="tags-view-container">
<el-scrollbar class="tags-view-wrapper" ref="scrollContainer">
<router-link class="tags-view-item" :to="item" :key="item.path" :class="isActive(item)?'active':''" v-for="(item) in visitedViews" @contextmenu.prevent.native="openMenu(item,$event)">
{{item.tagsName?item.tagsName:item.name}}
<span class='el-icon-close' @click.prevent.stop='closeSelectedTag(item)' v-if="item.path != '/index'"></span>
</router-link>
</el-scrollbar>
<ul class="contextmenu" v-show="visible" :style="{left:left+'px',top:'20px' }">
<li @click="refresh(selectTag)">刷新</li>
<li v-if="selectTag.path !== '/index'" @click="closeSelectedTag(selectTag)">关闭</li>
<li @click="closeAllTags">全部关闭</li>
</ul>
</div>
</template>
<script>
export default {
watch:{
$route(){
this.addTags()
},//地址栏变化了就触发这个添加方法
visible(value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},```
data () {
return {
viewWidth: 100,
left: 0,
visible: false,
selectTag: {}
}
},
computed: {
visitedViews() {
return this.$store.state.tagsView.visitedViews
}
},
methods: {
openMenu(tag, e){ // 右键打开tags菜单
this.selectTag = tag
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.visible = true
},
closeMenu(){ // 点击右键菜单以后关闭右键菜单
this.visible = false
},
addTags(){
const route = this.$route;//获取地址栏路由
if (route.path !== "/") {
this.$store.commit({
type:'addTags',
route
})
}
return false
},
closeAllTags(){
// 关闭全部tags的操作
this.$store.dispatch('closeALLTags').then(()=>{
this.$router.push('/index')
})
},
refresh(selectTag){ // 刷新
const route = this.$route.path
if(route === selectTag.path){
this.$router.replace({
path: '/empty'
})
this.$router.replace({
path: selectTag.path,
query: selectTag.query
})
}
else {
this.$router.replace({
path: selectTag.path,
query: selectTag.query
})
}
},
changeViewWidth(viewWidth){
if(this.$refs.scrollContainer){
this.$refs.scrollContainer.$refs.wrap.firstElementChild.style.width = viewWidth + 'px'
}
},
isActive(route) { // 当前地址栏路径是否与渲染的路径相同 样式匹配
return route.path === this.$route.path
},
closeSelectedTag(view){
this.$store.dispatch("closeTags", view).then((views)=>{
// 此时的views是指的被删除后的visitedViews数组中存在的元素
if (this.isActive(view)) { // 关闭的如果是自己就打开别的,否则不做操作
let latestView
if (this.visitedViews[views.index]) { // 如果被关闭标签的后一个存在 则展示
latestView = this.visitedViews[views.index]
}
else if (this.visitedViews[views.index - 1]){ // 否则展示被关闭的前一个(前一个永远存在)
latestView = this.visitedViews[views.index - 1]
}
if (latestView) {
this.$router.push(latestView) //如果数组不为空则让选中的标签为紧邻关闭标签的那一个
}
else {
this.$router.push({path: '/index'}); //如果为空则页面跳转到首页
}
}
})
}
},
mounted() {
this.addTags()
window.setInterval(() => { // 这个是为了解决tags数量在超出以后 滚动条只有在页面页面刷新才出现的问题 目的是触发回流让el-scrollbar组件重新计算 目前只是暂时解决的,本不应该出现滚动条问题
if(this.viewWidth === 100){
this.viewWidth = 200
}
else {
this.viewWidth = 100
}
setTimeout(this.changeViewWidth(this.viewWidth), 0)
}, 1000)
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.tags-view-container {
height: 34px;
width: 100%;
overflow: auto;
background: #fff;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
white-space: nowrap;
position: relative;
left: 0;
top: 0;
overflow: hidden;
width: 100%;
height: 100%;
/deep/ {
.el-scrollbar__bar {
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
.el-scrollbar__view {
height: 100%;
}
}
}
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
.is-vertical {
display: none;
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style>
<style lang="scss">
/*reset element css of el-icon-close*/
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
width: 16px;
height: 16px;
vertical-align: 2px;
border-radius: 50%;
text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%;
&:before {
transform: scale(.6);
display: inline-block;
vertical-align: -3px;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
</style>
在组件中的mounted里面的轮查机制的写法实属无奈,el-scrollbar的滚动条在应该出现的时候,他得刷新一下才可以。所以我写了轮查来手动触发回流。
vuex中的配置 tagsViews.js
const tagsView = {
state: {
visitedViews: [
{
path: "/index",
name: "首页",
query: {},
menuId: 1
}
]
},
mutations: {
addTags(state, payload) {
let flag
let tags = JSON.parse(localStorage.getItem("tags")) // 获取刷新页面以后保存的tags
localStorage.removeItem("tags") // 获取之后删除session,避免关闭tags后的错误
if (tags) {
state.visitedViews = tags
}
flag = state.visitedViews.some(
item => item.path === payload.route.path
)//打开标签后,判断数组中是否已经存在该路由
if (!flag) {
state.visitedViews.push(
Object.assign(
{},
{
path: payload.route.path,
name: payload.route.name,
query: payload.route.query,
tagsName: payload.route.query.name?payload.route.query.name:""// 动态渲染的路由就给他添加一个tagsName 比如在列表页打开详情
}
)
)
} //数组中路由存在不push ,单击左侧路由变化,点击标签路由变化均触发
}
},
actions: {
closeTags(state, payload) {
for (const [key ,item] of state.state.visitedViews.entries()) {
if (item.path === payload.path) {
return {
delEl: state.state.visitedViews.splice(key, 1),
index: key
}
}
}
},
closeALLTags(state) {
state.state.visitedViews = [
{
path: "/index",
name: "首页",
query: {},
menuId: 1
}
]
}
}
}
export default tagsView
在app.vue中还要添加如下代码
mounted () { // 用于监听浏览器的刷新事件
window.addEventListener('beforeunload', e => {
if(this.$store.state.tagsView.visitedViews.length > 1) {
localStorage.setItem('tags', JSON.stringify(this.$store.state.tagsView.visitedViews))
}
})
}
// 也可以在删除和添加tags的时候 每次都更新一下localStorage 这样就能不在app.vue里面写这些了
本身的需求是 刷新页面时tags不会被关闭掉
getters.js
const getters = {
visitedViews: state => state.tagsView.visitedViews,
}
export default getters
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import tagsView from './modules/tagsView'
import getters from './getters'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
tagsView
},
getters
})
export default store
以上右侧打开tags的需求就基本实现了