mirror of https://gitee.com/xiaonuobase/snowy
【更新】调整多页签交互,增加右键操作
parent
e42c4ddc3a
commit
09c4fb24b5
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<div :style="style" v-show="show" @mousedown.stop @contextmenu.prevent>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'XnContextMenu',
|
||||||
|
props: {
|
||||||
|
target: null,
|
||||||
|
show: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
triggerShowFn: () => {},
|
||||||
|
triggerHideFn: () => {},
|
||||||
|
x: null,
|
||||||
|
y: null,
|
||||||
|
style: {},
|
||||||
|
binded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show(show) {
|
||||||
|
if (show) {
|
||||||
|
this.bindHideEvents()
|
||||||
|
} else {
|
||||||
|
this.unbindHideEvents()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target(target) {
|
||||||
|
this.bindEvents()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.bindEvents()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化事件
|
||||||
|
bindEvents() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (!this.target || this.binded) return
|
||||||
|
this.triggerShowFn = this.contextMenuHandler.bind(this)
|
||||||
|
this.target.addEventListener('contextmenu', this.triggerShowFn)
|
||||||
|
this.binded = true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 取消绑定事件
|
||||||
|
unbindEvents() {
|
||||||
|
if (!this.target) return
|
||||||
|
this.target.removeEventListener('contextmenu', this.triggerShowFn)
|
||||||
|
},
|
||||||
|
// 绑定隐藏菜单事件
|
||||||
|
bindHideEvents() {
|
||||||
|
this.triggerHideFn = this.clickDocumentHandler.bind(this)
|
||||||
|
document.addEventListener('mousedown', this.triggerHideFn)
|
||||||
|
document.addEventListener('mousewheel', this.triggerHideFn)
|
||||||
|
},
|
||||||
|
// 取消绑定隐藏菜单事件
|
||||||
|
unbindHideEvents() {
|
||||||
|
document.removeEventListener('mousedown', this.triggerHideFn)
|
||||||
|
document.removeEventListener('mousewheel', this.triggerHideFn)
|
||||||
|
},
|
||||||
|
// 鼠标按压事件处理器
|
||||||
|
clickDocumentHandler(e) {
|
||||||
|
this.$emit('update:show', false)
|
||||||
|
},
|
||||||
|
// 右键事件事件处理
|
||||||
|
contextMenuHandler(e) {
|
||||||
|
this.x = e.clientX
|
||||||
|
this.y = e.clientY
|
||||||
|
this.layout()
|
||||||
|
this.$emit('update:show', true)
|
||||||
|
this.$emit('get-context-menu', e)
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
// 布局
|
||||||
|
layout() {
|
||||||
|
this.style = {
|
||||||
|
left: this.x + 'px',
|
||||||
|
top: this.y + 'px',
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,10 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="snowy-tags">
|
<div class="snowy-tags">
|
||||||
|
<xn-context-menu
|
||||||
|
class="right-menu"
|
||||||
|
:target="contextMenuTarget"
|
||||||
|
:show="contextMenuVisible"
|
||||||
|
@update:show="(show) => (contextMenuVisible = show)"
|
||||||
|
@get-context-menu="handleTabContextMenu"
|
||||||
|
>
|
||||||
|
<div class="right-menu-item" @click="refreshTab">
|
||||||
|
<reload-outlined class="snowy-header-tags-right" />
|
||||||
|
<div class="pl-3">刷新</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-menu-item" @click="closeTabs">
|
||||||
|
<close-outlined class="snowy-header-tags-right" />
|
||||||
|
<div class="pl-3">关闭</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-menu-item" @click="closeOtherTabs">
|
||||||
|
<close-outlined class="snowy-header-tags-right" />
|
||||||
|
<div class="pl-3">关闭其他标签</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-menu-item" @click="maximize">
|
||||||
|
<expand-outlined class="snowy-header-tags-right" />
|
||||||
|
<div class="pl-3">最大化</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-menu-item" @click="openWindow">
|
||||||
|
<select-outlined class="snowy-header-tags-right" />
|
||||||
|
<div class="pl-3">新窗口打开</div>
|
||||||
|
</div>
|
||||||
|
</xn-context-menu>
|
||||||
<a-tabs
|
<a-tabs
|
||||||
v-model:activeKey="activeKey"
|
v-model:activeKey="activeKey"
|
||||||
type="editable-card"
|
type="editable-card"
|
||||||
class="snowy-admin-tabs"
|
class="snowy-admin-tabs"
|
||||||
hide-add
|
hide-add
|
||||||
|
ref="tabs"
|
||||||
@edit="onTabRemove"
|
@edit="onTabRemove"
|
||||||
@tabClick="onTabClick"
|
@tabClick="onTabClick"
|
||||||
>
|
>
|
||||||
|
@ -17,41 +49,8 @@
|
||||||
<div class="snowy-admin-tabs-arrow" @click="scrollRight">
|
<div class="snowy-admin-tabs-arrow" @click="scrollRight">
|
||||||
<right-outlined />
|
<right-outlined />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<a-dropdown>
|
|
||||||
<div class="snowy-admin-tabs-drop">
|
|
||||||
<DownOutlined />
|
|
||||||
</div>
|
|
||||||
<template #overlay>
|
|
||||||
<a-menu>
|
|
||||||
<a-menu-item>
|
|
||||||
<div class="layout-items-center" @click="refreshTab">
|
|
||||||
<reload-outlined class="snowy-header-tags-right" />
|
|
||||||
<div class="pl-3">刷新</div>
|
|
||||||
</div>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item>
|
|
||||||
<div class="layout-items-center" @click="closeOtherTabs">
|
|
||||||
<close-outlined class="snowy-header-tags-right" />
|
|
||||||
<div class="pl-3">关闭其他标签</div>
|
|
||||||
</div>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item>
|
|
||||||
<div class="layout-items-center" @click="maximize">
|
|
||||||
<expand-outlined class="snowy-header-tags-right" />
|
|
||||||
<div class="pl-3">最大化</div>
|
|
||||||
</div>
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item>
|
|
||||||
<div class="layout-items-center" @click="openWindow">
|
|
||||||
<select-outlined class="snowy-header-tags-right" />
|
|
||||||
<div class="pl-3">在新的窗口中打开</div>
|
|
||||||
</div>
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</template>
|
|
||||||
<a-tab-pane v-for="tag in tagList" :key="tag.fullPath" :tab="tag.meta.title" :closable="!tag.meta.affix">
|
<a-tab-pane v-for="tag in tagList" :key="tag.fullPath" :tab="tag.meta.title" :closable="!tag.meta.affix">
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
|
@ -59,22 +58,26 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
|
|
||||||
import Sortable from 'sortablejs'
|
|
||||||
import tool from '@/utils/tool'
|
import tool from '@/utils/tool'
|
||||||
|
import XnContextMenu from '@/components/XnContextMenu/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tags',
|
name: 'Tags',
|
||||||
|
components: { XnContextMenu },
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tagList: this.$store.state.viewTags.viewTags,
|
tagList: this.$store.state.viewTags.viewTags,
|
||||||
activeKey: this.$route.fullPath
|
activeKey: this.$route.fullPath,
|
||||||
|
maxTabs: 20,
|
||||||
|
contextMenuTarget: null,
|
||||||
|
contextMenuVisible: false,
|
||||||
|
currentContextMenuTabIndex: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(e) {
|
$route(to) {
|
||||||
this.addViewTags(e)
|
this.addViewTags(to)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -88,12 +91,27 @@
|
||||||
this.addViewTags(this.$route)
|
this.addViewTags(this.$route)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
const tabNavList = document.querySelector('.ant-tabs-nav-list')
|
||||||
|
if (tabNavList) {
|
||||||
|
this.contextMenuTarget = tabNavList
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleTabContextMenu(evt) {
|
||||||
|
evt.preventDefault()
|
||||||
|
let target = evt.target
|
||||||
|
if (target.classList.contains('ant-tabs-tab-btn')) {
|
||||||
|
target = target.parentNode
|
||||||
|
}
|
||||||
|
const tabList = document.querySelectorAll('.ant-tabs-nav-list .ant-tabs-tab')
|
||||||
|
this.currentContextMenuTabIndex = Array.from(tabList).findIndex((tab) => tab === target)
|
||||||
|
},
|
||||||
onTabClick(tab) {
|
onTabClick(tab) {
|
||||||
this.$router.push(tab)
|
this.$router.push(tab)
|
||||||
},
|
},
|
||||||
getCurrentTag() {
|
getCurrentTag() {
|
||||||
return this.tagList.find((tag) => tag.fullPath === this.activeKey)
|
return this.tagList[this.currentContextMenuTabIndex]
|
||||||
},
|
},
|
||||||
onTabRemove(tabKey, action) {
|
onTabRemove(tabKey, action) {
|
||||||
if (action === 'remove') {
|
if (action === 'remove') {
|
||||||
|
@ -132,10 +150,15 @@
|
||||||
// 增加tag
|
// 增加tag
|
||||||
addViewTags(route) {
|
addViewTags(route) {
|
||||||
this.activeKey = route.fullPath
|
this.activeKey = route.fullPath
|
||||||
|
|
||||||
if (route.name && !route.meta.fullpage) {
|
if (route.name && !route.meta.fullpage) {
|
||||||
this.$store.commit('pushViewTags', route)
|
this.$store.commit('pushViewTags', route)
|
||||||
this.$store.commit('pushKeepLive', route.name)
|
this.$store.commit('pushKeepLive', route.name)
|
||||||
}
|
}
|
||||||
|
if (this.tagList.length - 1 > this.maxTabs) {
|
||||||
|
const firstTag = this.tagList[1]
|
||||||
|
this.$store.commit('removeViewTags', firstTag)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 高亮tag
|
// 高亮tag
|
||||||
isActive(route) {
|
isActive(route) {
|
||||||
|
@ -157,6 +180,7 @@
|
||||||
},
|
},
|
||||||
// TAB 刷新
|
// TAB 刷新
|
||||||
refreshTab() {
|
refreshTab() {
|
||||||
|
this.contextMenuVisible = false
|
||||||
const nowTag = this.getCurrentTag()
|
const nowTag = this.getCurrentTag()
|
||||||
// 判断是否当前路由,否的话跳转
|
// 判断是否当前路由,否的话跳转
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
|
@ -178,6 +202,7 @@
|
||||||
},
|
},
|
||||||
// TAB 关闭
|
// TAB 关闭
|
||||||
closeTabs() {
|
closeTabs() {
|
||||||
|
this.contextMenuVisible = false
|
||||||
const nowTag = this.getCurrentTag()
|
const nowTag = this.getCurrentTag()
|
||||||
if (!nowTag.meta.affix) {
|
if (!nowTag.meta.affix) {
|
||||||
this.closeSelectedTag(nowTag)
|
this.closeSelectedTag(nowTag)
|
||||||
|
@ -185,6 +210,7 @@
|
||||||
},
|
},
|
||||||
// TAB 关闭其他
|
// TAB 关闭其他
|
||||||
closeOtherTabs() {
|
closeOtherTabs() {
|
||||||
|
this.contextMenuVisible = false
|
||||||
const nowTag = this.getCurrentTag()
|
const nowTag = this.getCurrentTag()
|
||||||
// 判断是否当前路由,否的话跳转
|
// 判断是否当前路由,否的话跳转
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
|
@ -206,6 +232,7 @@
|
||||||
},
|
},
|
||||||
// TAB 最大化(包括标签栏)
|
// TAB 最大化(包括标签栏)
|
||||||
maximize() {
|
maximize() {
|
||||||
|
this.contextMenuVisible = false
|
||||||
const nowTag = this.getCurrentTag()
|
const nowTag = this.getCurrentTag()
|
||||||
// 判断是否当前路由,否的话跳转
|
// 判断是否当前路由,否的话跳转
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
|
@ -219,6 +246,7 @@
|
||||||
},
|
},
|
||||||
// 新窗口打开
|
// 新窗口打开
|
||||||
openWindow() {
|
openWindow() {
|
||||||
|
this.contextMenuVisible = false
|
||||||
const nowTag = this.getCurrentTag()
|
const nowTag = this.getCurrentTag()
|
||||||
const url = nowTag.href || '/'
|
const url = nowTag.href || '/'
|
||||||
if (!nowTag.meta.affix) {
|
if (!nowTag.meta.affix) {
|
||||||
|
@ -284,4 +312,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.right-menu {
|
||||||
|
position: fixed;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 999;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
box-shadow: 0 0.5em 1em 0 rgb(0 0 0 / 10%);
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.snowy-tags {
|
||||||
|
.right-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: var(--primary-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue