feat: 集成多标签右键菜单

This commit is contained in:
张传龙 2022-04-23 19:23:12 +08:00
parent bf63fb5ab7
commit cf1b83d3f1
4 changed files with 212 additions and 23 deletions

View File

@ -8,5 +8,10 @@ export { default as IconLink } from '~icons/mdi/link-variant'
export { default as IconAlert } from '~icons/mdi/alert-circle-outline'
export { default as IconCircle } from '~icons/mdi/circle-outline'
export { default as IconMenu } from '~icons/mdi/menu'
export { default as IconRefresh } from '~icons/mdi/refresh'
export { default as IconClose } from '~icons/mdi/close'
export { default as IconExpand } from '~icons/mdi/arrow-expand-horizontal'
export { default as IconExpandLeft } from '~icons/mdi/arrow-expand-left'
export { default as IconExpandRight } from '~icons/mdi/arrow-expand-right'
export { default as IconLogo } from './IconLogo.vue'

View File

@ -0,0 +1,129 @@
<template>
<n-dropdown
:show="dropdownShow"
:options="options"
:x="x"
:y="y"
placement="bottom-start"
@clickoutside="handleHideDropdown"
@select="handleSelect"
/>
</template>
<script setup>
import { computed } from 'vue'
import { useTagsStore } from '@/store/modules/tags'
import { IconRefresh, IconClose, IconExpand, IconExpandLeft, IconExpandRight } from '@/components/AppIcons'
import { renderIcon } from '@/utils/icon'
import { useAppStore } from '@/store/modules/app'
const props = defineProps({
show: {
type: Boolean,
default: false,
},
currentPath: {
type: String,
default: '',
},
x: {
type: Number,
default: 0,
},
y: {
type: Number,
default: 0,
},
})
const emit = defineEmits(['update:show'])
const tagsStore = useTagsStore()
const appStore = useAppStore()
const options = computed(() => [
{
label: '重新加载',
key: 'reload',
disabled: props.currentPath !== tagsStore.activeTag,
icon: renderIcon(IconRefresh, { size: '14px' }),
},
{
label: '关闭',
key: 'close',
disabled: tagsStore.tags.length <= 1,
icon: renderIcon(IconClose, { size: '14px' }),
},
{
label: '关闭其他',
key: 'close-other',
disabled: tagsStore.tags.length <= 1,
icon: renderIcon(IconExpand, { size: '14px' }),
},
{
label: '关闭左侧',
key: 'close-left',
disabled: tagsStore.tags.length <= 1 || props.currentPath === tagsStore.tags[0].path,
icon: renderIcon(IconExpandLeft, { size: '14px' }),
},
{
label: '关闭右侧',
key: 'close-right',
disabled: tagsStore.tags.length <= 1 || props.currentPath === tagsStore.tags[tagsStore.tags.length - 1].path,
icon: renderIcon(IconExpandRight, { size: '14px' }),
},
])
const dropdownShow = computed({
get() {
return props.show
},
set(show) {
emit('update:show', show)
},
})
const actionMap = new Map([
[
'reload',
() => {
appStore.reloadPage()
},
],
[
'close',
() => {
tagsStore.removeTag(props.currentPath)
},
],
[
'close-other',
() => {
tagsStore.removeOther(props.currentPath)
},
],
[
'close-left',
() => {
tagsStore.removeLeft(props.currentPath)
},
],
[
'close-right',
() => {
tagsStore.removeRight(props.currentPath)
},
],
])
function handleHideDropdown() {
dropdownShow.value = false
}
function handleSelect(key) {
const actionFn = actionMap.get(key)
actionFn && actionFn()
handleHideDropdown()
}
</script>
<style lang="scss" scoped></style>

View File

@ -2,56 +2,77 @@
<div class="tags-wrapper" :style="{ height: useTheme.tags.height + 'px' }">
<n-space>
<n-tag
v-for="tag in useTags.tags"
v-for="tag in tagsStore.tags"
:key="tag.path"
:type="useTags.activeTag === tag.path ? 'primary' : 'default'"
:closable="useTags.tags.length > 1"
:type="tagsStore.activeTag === tag.path ? 'primary' : 'default'"
:closable="tagsStore.tags.length > 1"
@click="handleTagClick(tag.path)"
@close.stop="handleClose(tag.path)"
@close.stop="tagsStore.removeTag(tag.path)"
@contextmenu.prevent="handleContextMenu($event, tag)"
>
{{ tag.title }}
</n-tag>
</n-space>
</div>
<ContextMenu
v-model:show="contextMenuOption.show"
:current-path="contextMenuOption.currentPath"
:x="contextMenuOption.x"
:y="contextMenuOption.y"
/>
</template>
<script setup name="Tags">
import { watch } from 'vue'
import ContextMenu from './ContextMenu.vue'
import { nextTick, reactive, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTagsStore } from '@/store/modules/tags'
import { useThemeStore } from '@/store/modules/theme'
const route = useRoute()
const router = useRouter()
const useTags = useTagsStore()
const tagsStore = useTagsStore()
const useTheme = useThemeStore()
const contextMenuOption = reactive({
show: false,
x: 0,
y: 0,
currentPath: '',
})
watch(
() => route.path,
() => {
const { name, path } = route
const title = route.meta?.title
useTags.addTag({ name, path, title })
useTags.setActiveTag(path)
tagsStore.addTag({ name, path, title })
},
{ immediate: true }
)
const handleTagClick = (path) => {
useTags.setActiveTag(path)
tagsStore.setActiveTag(path)
router.push(path)
}
const handleClose = (path) => {
if (path === useTags.activeTag) {
const activeIndex = useTags.tags.findIndex((item) => item.path === path)
if (activeIndex > 0) {
router.push(useTags.tags[activeIndex - 1].path)
} else {
router.push(useTags.tags[activeIndex + 1].path)
}
}
useTags.removeTag(path)
function showContextMenu() {
contextMenuOption.show = true
}
function hideContextMenu() {
contextMenuOption.show = false
}
function setContextMenu(x, y, currentPath) {
Object.assign(contextMenuOption, { x, y, currentPath })
}
//
async function handleContextMenu(e, tagItem) {
const { clientX, clientY } = e
hideContextMenu()
setContextMenu(clientX, clientY, tagItem.path)
await nextTick()
showContextMenu()
}
</script>

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { tagsSS, activeTag, tags, WITHOUT_TAG_PATHS } from './helpers'
import { router } from '@/router'
export const useTagsStore = defineStore('tag', {
state() {
@ -13,14 +14,47 @@ export const useTagsStore = defineStore('tag', {
this.activeTag = path
tagsSS.set('activeTag', path)
},
setTags(tags) {
this.tags = tags
tagsSS.set('tags', tags)
},
addTag(tag = {}) {
this.setActiveTag(tag.path)
if (WITHOUT_TAG_PATHS.includes(tag.path) || this.tags.some((item) => item.path === tag.path)) return
this.tags.push(tag)
tagsSS.set('tags', this.tags)
this.setTags([...this.tags, tag])
},
removeTag(path) {
this.tags = this.tags.filter((tag) => tag.path !== path)
tagsSS.set('tags', this.tags)
if (path === this.activeTag) {
const activeIndex = this.tags.findIndex((item) => item.path === path)
if (activeIndex > 0) {
router.push(this.tags[activeIndex - 1].path)
} else {
router.push(this.tags[activeIndex + 1].path)
}
}
this.setTags(this.tags.filter((tag) => tag.path !== path))
},
removeOther(curPath = this.activeTag) {
this.setTags(this.tags.filter((tag) => tag.path === curPath))
if (curPath !== this.activeTag) {
router.push(this.tags[this.tags.length - 1].path)
}
},
removeLeft(curPath) {
const curIndex = this.tags.findIndex((item) => item.path === curPath)
const filterTags = this.tags.filter((item, index) => index >= curIndex)
this.setTags(filterTags)
if (!filterTags.find((item) => item.path === this.activeTag)) {
router.push(filterTags[filterTags.length - 1].path)
}
},
removeRight(curPath) {
const curIndex = this.tags.findIndex((item) => item.path === curPath)
const filterTags = this.tags.filter((item, index) => index <= curIndex)
this.setTags(filterTags)
if (!filterTags.find((item) => item.path === this.activeTag)) {
router.push(filterTags[filterTags.length - 1].path)
}
},
},
})