|
@@ -1,109 +1,130 @@
|
|
|
<template>
|
|
|
<div class="_tagList">
|
|
|
<div class="tag-view">
|
|
|
- <ul>
|
|
|
- <ContextMenu
|
|
|
- :ref="itemRefs.set"
|
|
|
- :schema="[
|
|
|
- {
|
|
|
- icon: 'ep:refresh',
|
|
|
- label: t('common.reload'),
|
|
|
- disabled: selectedTag?.fullPath !== item.fullPath,
|
|
|
- command: () => {
|
|
|
- refreshSelectedTag(item)
|
|
|
+ <draggable
|
|
|
+ class="tags-draggable-list"
|
|
|
+ v-model="visitedViews"
|
|
|
+ item-key="fullPath"
|
|
|
+ ghost-class="draggable-ghost"
|
|
|
+ filter=".unDrag"
|
|
|
+ @end="onDragEnd"
|
|
|
+ :animation="400"
|
|
|
+ >
|
|
|
+ <template #item="{ element: item, index }">
|
|
|
+ <ContextMenu
|
|
|
+ :ref="itemRefs.set"
|
|
|
+ :schema="[
|
|
|
+ {
|
|
|
+ icon: 'ep:refresh',
|
|
|
+ label: t('common.reload'),
|
|
|
+ disabled: selectedTag?.fullPath !== item.fullPath,
|
|
|
+ command: () => {
|
|
|
+ refreshSelectedTag(item)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ icon: 'ep:close',
|
|
|
+ label: t('common.closeTab'),
|
|
|
+ disabled: (!!visitedViews?.length && selectedTag?.meta.affix) || index === 0,
|
|
|
+ command: () => {
|
|
|
+ closeSelectedTag(item)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ divided: true,
|
|
|
+ icon: 'ep:d-arrow-left',
|
|
|
+ label: t('common.closeTheLeftTab'),
|
|
|
+ disabled:
|
|
|
+ !!visitedViews?.length &&
|
|
|
+ (item.fullPath === visitedViews[0].fullPath ||
|
|
|
+ selectedTag?.fullPath !== item.fullPath),
|
|
|
+ command: () => {
|
|
|
+ closeLeftTags()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ icon: 'ep:d-arrow-right',
|
|
|
+ label: t('common.closeTheRightTab'),
|
|
|
+ disabled:
|
|
|
+ !!visitedViews?.length &&
|
|
|
+ (item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||
|
|
|
+ selectedTag?.fullPath !== item.fullPath),
|
|
|
+ command: () => {
|
|
|
+ closeRightTags()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ divided: true,
|
|
|
+ icon: 'ep:discount',
|
|
|
+ label: t('common.closeOther'),
|
|
|
+ disabled: selectedTag?.fullPath !== item.fullPath,
|
|
|
+ command: () => {
|
|
|
+ closeOthersTags()
|
|
|
+ }
|
|
|
}
|
|
|
- },
|
|
|
- {
|
|
|
- icon: 'ep:close',
|
|
|
- label: t('common.closeTab'),
|
|
|
- disabled: !!visitedViews?.length && selectedTag?.meta.affix,
|
|
|
- command: () => {
|
|
|
- closeSelectedTag(item)
|
|
|
+ ]"
|
|
|
+ :tag-item="item"
|
|
|
+ :class="[
|
|
|
+ 'tag-body',
|
|
|
+ `${prefixCls}__item`,
|
|
|
+ item?.meta?.affix ? `${prefixCls}__item--affix unDrag` : '',
|
|
|
+ {
|
|
|
+ 'is-route-tag-active': isActive(item)
|
|
|
}
|
|
|
- },
|
|
|
- {
|
|
|
- divided: true,
|
|
|
- icon: 'ep:d-arrow-left',
|
|
|
- label: t('common.closeTheLeftTab'),
|
|
|
- disabled:
|
|
|
- !!visitedViews?.length &&
|
|
|
- (item.fullPath === visitedViews[0].fullPath ||
|
|
|
- selectedTag?.fullPath !== item.fullPath),
|
|
|
- command: () => {
|
|
|
- closeLeftTags()
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- icon: 'ep:d-arrow-right',
|
|
|
- label: t('common.closeTheRightTab'),
|
|
|
- disabled:
|
|
|
- !!visitedViews?.length &&
|
|
|
- (item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||
|
|
|
- selectedTag?.fullPath !== item.fullPath),
|
|
|
- command: () => {
|
|
|
- closeRightTags()
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- divided: true,
|
|
|
- icon: 'ep:discount',
|
|
|
- label: t('common.closeOther'),
|
|
|
- disabled: selectedTag?.fullPath !== item.fullPath,
|
|
|
- command: () => {
|
|
|
- closeOthersTags()
|
|
|
- }
|
|
|
- }
|
|
|
- ]"
|
|
|
- v-for="(item, index) in visitedViews"
|
|
|
- :key="index"
|
|
|
- :tag-item="item"
|
|
|
- :class="[
|
|
|
- `${prefixCls}__item`,
|
|
|
- item?.meta?.affix ? `${prefixCls}__item--affix` : '',
|
|
|
- {
|
|
|
- 'is-active': isActive(item)
|
|
|
- }
|
|
|
- ]"
|
|
|
- @visible-change="visibleChange"
|
|
|
- >
|
|
|
- <li>
|
|
|
- <router-link :to="{ ...item }" exact v-slot="{ navigate }" v-if="index == 0">
|
|
|
+ ]"
|
|
|
+ @visible-change="visibleChange"
|
|
|
+ >
|
|
|
+ <router-link :to="{ ...item }" exact v-slot="{ navigate }" v-if="index === 0">
|
|
|
<div class="homeBox" @click="navigate">
|
|
|
- <div @click="tagsHomeClick()" class="tagsFlex">
|
|
|
- <Icon :icon="item.meta.icon || ''" />
|
|
|
- <p>{{ item.meta.title }}</p>
|
|
|
- </div>
|
|
|
+ <Icon :icon="item.meta.icon || ''" :size="18" />
|
|
|
+ <p>{{ item.meta.title }}</p>
|
|
|
</div>
|
|
|
</router-link>
|
|
|
<router-link :to="{ ...item }" exact v-slot="{ navigate }" v-else>
|
|
|
<div class="fhomeBox" @click="navigate">
|
|
|
- <div class="tagsFlex">
|
|
|
- <div class="pBox">
|
|
|
- <p>{{ item.meta.title }}</p>
|
|
|
- </div>
|
|
|
- <div @click.stop.prevent="closeSelectedTag(item)" class="close">
|
|
|
- <span>×</span>
|
|
|
- </div>
|
|
|
+ <div class="pBox">
|
|
|
+ <el-tooltip
|
|
|
+ effect="dark"
|
|
|
+ :content="`${item.meta.title}${(item?.query?.name ?? '') !== '' ? `( ${item?.query?.name} )` : ''}`"
|
|
|
+ placement="top-start"
|
|
|
+ >
|
|
|
+ <p>
|
|
|
+ {{ item.meta.title }}
|
|
|
+ <!-- 用于多开页面的区分展示(例如详情页)-->
|
|
|
+ {{ (item?.query?.name ?? '') !== '' ? `(${item?.query?.name})` : '' }}
|
|
|
+ </p>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ <div @click.stop.prevent="closeSelectedTag(item)" class="close">
|
|
|
+ <span>×</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</router-link>
|
|
|
- </li>
|
|
|
- </ContextMenu>
|
|
|
- </ul>
|
|
|
+ </ContextMenu>
|
|
|
+ </template>
|
|
|
+ </draggable>
|
|
|
+ <div v-if="(visitedViews?.length ?? 0) > 1" class="all-tags-close" @click="allTagsClose">
|
|
|
+ 关闭全部
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
|
+import 'vuedraggable'
|
|
|
+import { computed, onMounted, nextTick, ref, watch } from 'vue'
|
|
|
import { Icon } from '@/components/Icon'
|
|
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { usePermissionStore } from '@/store/modules/permission'
|
|
|
-import { useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
+import { isSameRouteTag, useTagsViewStore } from '@/store/modules/tagsView'
|
|
|
import { filterAffixTags } from './helper'
|
|
|
import { useTemplateRefsList } from '@vueuse/core'
|
|
|
import { ContextMenu, ContextMenuExpose } from '@/layout/components/ContextMenu'
|
|
|
import { useI18n } from '@/hooks/web/useI18n'
|
|
|
import { useDesign } from '@/hooks/web/useDesign'
|
|
|
+import draggable from 'vuedraggable'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
const { getPrefixCls } = useDesign()
|
|
|
const prefixCls = getPrefixCls('tags-view')
|
|
|
const { t } = useI18n()
|
|
@@ -115,8 +136,11 @@ const tagsViewStore = useTagsViewStore()
|
|
|
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
|
|
|
// 所有右键菜单组件的元素
|
|
|
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
|
|
|
-const visitedViews = computed(() => tagsViewStore.getVisitedViews)
|
|
|
|
|
|
+const visitedViews = computed({
|
|
|
+ get: () => tagsViewStore.visitedViews,
|
|
|
+ set: (views) => tagsViewStore.allResetView(views)
|
|
|
+})
|
|
|
// 初始化tag
|
|
|
const initTags = () => {
|
|
|
affixTagArr.value = filterAffixTags(unref(routers))
|
|
@@ -165,7 +189,7 @@ const closeLeftTags = () => {
|
|
|
const closeRightTags = () => {
|
|
|
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
|
|
}
|
|
|
-const tagsHomeClick = () => {}
|
|
|
+
|
|
|
// 新增tag
|
|
|
const addTags = () => {
|
|
|
const { name } = unref(currentRoute)
|
|
@@ -186,13 +210,12 @@ const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
|
|
|
|
|
|
// 是否是当前tag
|
|
|
const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
|
|
- return route.path === unref(currentRoute).path
|
|
|
+ return isSameRouteTag(route, unref(currentRoute))
|
|
|
}
|
|
|
|
|
|
// 跳转到最后一个
|
|
|
const toLastView = () => {
|
|
|
- const visitedViews = tagsViewStore.getVisitedViews
|
|
|
- const latestView = visitedViews.slice(-1)[0]
|
|
|
+ const latestView = unref(visitedViews).slice(-1)[0]
|
|
|
if (latestView) {
|
|
|
push(latestView)
|
|
|
} else {
|
|
@@ -208,6 +231,20 @@ const toLastView = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 关闭全部标签
|
|
|
+const allTagsClose = (): void => {
|
|
|
+ visitedViews.value = [unref(visitedViews)[0]]
|
|
|
+ router.replace('/home')
|
|
|
+}
|
|
|
+
|
|
|
+// 拖拽结束事件
|
|
|
+const onDragEnd = (event): void => {
|
|
|
+ // 不允许拖拽到第一项
|
|
|
+ if (event.newIndex === 0) {
|
|
|
+ unref(visitedViews)[0] = unref(visitedViews).splice(1, 1, unref(visitedViews)[0])[0]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
watch(
|
|
|
() => currentRoute.value,
|
|
|
() => {
|
|
@@ -221,144 +258,135 @@ onMounted(() => {
|
|
|
})
|
|
|
</script>
|
|
|
<style lang="scss" scoped>
|
|
|
-div,
|
|
|
-h1,
|
|
|
-h2,
|
|
|
-h3,
|
|
|
-h4,
|
|
|
-h5,
|
|
|
-h6,
|
|
|
-p,
|
|
|
-ul,
|
|
|
-li {
|
|
|
- box-sizing: border-box;
|
|
|
-}
|
|
|
._tagList {
|
|
|
width: 100%;
|
|
|
- height: 36px;
|
|
|
+ padding: 0 12px;
|
|
|
+ height: 54px;
|
|
|
+ background: rgba(255, 255, 255, 0.6);
|
|
|
+ border-radius: 20px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- background: rgba(255, 255, 255, 1);
|
|
|
- border-radius: 10px;
|
|
|
+ color: #2d333c;
|
|
|
+
|
|
|
.tag-view {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
- ul {
|
|
|
- width: 100%;
|
|
|
+ padding-right: 10px;
|
|
|
+
|
|
|
+ .tags-draggable-list {
|
|
|
+ width: calc(100% - 64px);
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- li {
|
|
|
- height: 100%;
|
|
|
- cursor: pointer;
|
|
|
+
|
|
|
+ .tag-body {
|
|
|
+ min-width: 60px;
|
|
|
+ margin-right: 12px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ p,
|
|
|
+ .el-icon {
|
|
|
+ color: #2e77e6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.v-tags-view__item--affix {
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
a {
|
|
|
- display: block;
|
|
|
- width: auto;
|
|
|
- height: 100%;
|
|
|
- border-radius: 10px;
|
|
|
+ width: 100%;
|
|
|
color: inherit;
|
|
|
text-decoration: none;
|
|
|
- padding: 2px;
|
|
|
- box-sizing: border-box;
|
|
|
}
|
|
|
- .homeBox {
|
|
|
- width: 70px;
|
|
|
- height: 16px;
|
|
|
+
|
|
|
+ &.is-route-tag-active {
|
|
|
+ .homeBox,
|
|
|
+ .fhomeBox {
|
|
|
+ border: 2px solid #2e77e6;
|
|
|
+
|
|
|
+ p,
|
|
|
+ .el-icon,
|
|
|
+ .close {
|
|
|
+ color: #2e77e6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .homeBox,
|
|
|
+ .fhomeBox {
|
|
|
+ height: 42px;
|
|
|
+ padding: 0 20px;
|
|
|
+ background: #eaf0f8;
|
|
|
+ border-radius: 16px;
|
|
|
opacity: 1;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- .tagsFlex {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: 17px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .close {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- }
|
|
|
- p {
|
|
|
+ flex-shrink: 0;
|
|
|
margin-left: 5px;
|
|
|
- font-size: 16px;
|
|
|
+ font-weight: 200;
|
|
|
+ font-size: 24px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #2e77e6;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
.fhomeBox {
|
|
|
- height: 16px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
+ padding-right: 18px;
|
|
|
justify-content: space-between;
|
|
|
- padding: 0 15px;
|
|
|
- border-right: 2px solid rgb(209, 197, 197);
|
|
|
- .tagsFlex {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- }
|
|
|
+
|
|
|
.pBox {
|
|
|
+ width: calc(100% - 17px);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+
|
|
|
p {
|
|
|
- font-size: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
}
|
|
|
- .close {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- width: 12px;
|
|
|
- height: 12px;
|
|
|
- background-color: #878b91;
|
|
|
- border-radius: 50%;
|
|
|
- margin-left: 10px;
|
|
|
- span {
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
- }
|
|
|
- img {
|
|
|
- cursor: pointer;
|
|
|
- margin-top: 4px;
|
|
|
- display: block;
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
-::v-deep .router-link-active {
|
|
|
- // border: 2px solid #2e77e6;
|
|
|
- p {
|
|
|
- color: #2e77e6;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
- .el-icon {
|
|
|
- color: #2e77e6;
|
|
|
- }
|
|
|
- .close {
|
|
|
- background-color: #2e77e6 !important;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.el-dropdown:hover {
|
|
|
- p {
|
|
|
- color: #2e77e6;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
-}
|
|
|
+ .all-tags-close {
|
|
|
+ flex-shrink: 0;
|
|
|
+ font-size: 17px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #121518;
|
|
|
+ cursor: pointer;
|
|
|
|
|
|
-.tag-view {
|
|
|
- ul {
|
|
|
- .el-dropdown:nth-child(2) {
|
|
|
- .fhomeBox {
|
|
|
- border-left: 2px solid rgb(209, 197, 197);
|
|
|
- }
|
|
|
- }
|
|
|
- .el-dropdown:last-child {
|
|
|
- .fhomeBox {
|
|
|
- border-right: 0;
|
|
|
+ &:hover {
|
|
|
+ color: #2e77e6;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.draggable-ghost {
|
|
|
+ background: #f7fafc;
|
|
|
+ border: 1px solid #2e77e6;
|
|
|
+ border-radius: 16px;
|
|
|
+ opacity: 0.5;
|
|
|
+}
|
|
|
</style>
|