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

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { tagsSS, activeTag, tags, WITHOUT_TAG_PATHS } from './helpers' import { tagsSS, activeTag, tags, WITHOUT_TAG_PATHS } from './helpers'
import { router } from '@/router'
export const useTagsStore = defineStore('tag', { export const useTagsStore = defineStore('tag', {
state() { state() {
@ -13,14 +14,47 @@ export const useTagsStore = defineStore('tag', {
this.activeTag = path this.activeTag = path
tagsSS.set('activeTag', path) tagsSS.set('activeTag', path)
}, },
setTags(tags) {
this.tags = tags
tagsSS.set('tags', tags)
},
addTag(tag = {}) { addTag(tag = {}) {
this.setActiveTag(tag.path)
if (WITHOUT_TAG_PATHS.includes(tag.path) || this.tags.some((item) => item.path === tag.path)) return if (WITHOUT_TAG_PATHS.includes(tag.path) || this.tags.some((item) => item.path === tag.path)) return
this.tags.push(tag) this.setTags([...this.tags, tag])
tagsSS.set('tags', this.tags)
}, },
removeTag(path) { removeTag(path) {
this.tags = this.tags.filter((tag) => tag.path !== path) if (path === this.activeTag) {
tagsSS.set('tags', this.tags) 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)
}
}, },
}, },
}) })