perf: 代码优化 layout/TagsView
This commit is contained in:
parent
46747e7ce2
commit
8706398eb7
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type PropType, computed, ref, watch, nextTick } from "vue"
|
import { type PropType, ref, watch, nextTick } from "vue"
|
||||||
import { RouterLink, useRoute } from "vue-router"
|
import { RouterLink, useRoute } from "vue-router"
|
||||||
import { ElScrollbar } from "element-plus"
|
import { ElScrollbar } from "element-plus"
|
||||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue"
|
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue"
|
||||||
@ -16,7 +16,9 @@ const props = defineProps({
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
|
/** 滚动条组件元素的引用 */
|
||||||
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
|
const scrollbarRef = ref<InstanceType<typeof ElScrollbar>>()
|
||||||
|
/** 滚动条内容元素的引用 */
|
||||||
const scrollbarContentRef = ref<HTMLDivElement>()
|
const scrollbarContentRef = ref<HTMLDivElement>()
|
||||||
|
|
||||||
/** 当前滚动条距离左边的距离 */
|
/** 当前滚动条距离左边的距离 */
|
||||||
@ -92,6 +94,7 @@ const moveTo = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 监听路由变化,移动到目标位置 */
|
||||||
watch(
|
watch(
|
||||||
route,
|
route,
|
||||||
() => {
|
() => {
|
||||||
@ -101,10 +104,6 @@ watch(
|
|||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const showScreenfull = computed(() => {
|
|
||||||
return settingsStore.showScreenfull
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -120,7 +119,7 @@ const showScreenfull = computed(() => {
|
|||||||
<el-icon class="arrow right" @click="scrollTo('right')">
|
<el-icon class="arrow right" @click="scrollTo('right')">
|
||||||
<ArrowRight />
|
<ArrowRight />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<Screenfull v-if="showScreenfull" element=".app-main" open-tips="内容区全屏" class="screenfull" />
|
<Screenfull v-if="settingsStore.showScreenfull" element=".app-main" open-tips="内容区全屏" class="screenfull" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -143,7 +142,7 @@ const showScreenfull = computed(() => {
|
|||||||
}
|
}
|
||||||
.el-scrollbar {
|
.el-scrollbar {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
// 横向超出窗口长度时,显示滚动条
|
// 防止换行(超出宽度时,显示滚动条)
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
.scrollbar-content {
|
.scrollbar-content {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -13,26 +13,35 @@ const route = useRoute()
|
|||||||
const tagsViewStore = useTagsViewStore()
|
const tagsViewStore = useTagsViewStore()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
/** 标签页组件元素的引用数组 */
|
||||||
const tagRefs = ref<InstanceType<typeof RouterLink>[]>([])
|
const tagRefs = ref<InstanceType<typeof RouterLink>[]>([])
|
||||||
|
|
||||||
|
/** 右键菜单的状态 */
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
/** 右键菜单的 top 位置 */
|
||||||
const top = ref(0)
|
const top = ref(0)
|
||||||
|
/** 右键菜单的 left 位置 */
|
||||||
const left = ref(0)
|
const left = ref(0)
|
||||||
|
/** 当前正在右键操作的标签页 */
|
||||||
const selectedTag = ref<TagView>({})
|
const selectedTag = ref<TagView>({})
|
||||||
|
/** 固定的标签页 */
|
||||||
let affixTags: TagView[] = []
|
let affixTags: TagView[] = []
|
||||||
|
|
||||||
|
/** 判断标签页是否激活 */
|
||||||
const isActive = (tag: TagView) => {
|
const isActive = (tag: TagView) => {
|
||||||
return tag.path === route.path
|
return tag.path === route.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断标签页是否固定 */
|
||||||
const isAffix = (tag: TagView) => {
|
const isAffix = (tag: TagView) => {
|
||||||
return tag.meta?.affix
|
return tag.meta?.affix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选出固定标签页 */
|
||||||
const filterAffixTags = (routes: RouteRecordRaw[], basePath = "/") => {
|
const filterAffixTags = (routes: RouteRecordRaw[], basePath = "/") => {
|
||||||
let tags: TagView[] = []
|
const tags: TagView[] = []
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
if (route.meta?.affix) {
|
if (isAffix(route)) {
|
||||||
const tagPath = path.resolve(basePath, route.path)
|
const tagPath = path.resolve(basePath, route.path)
|
||||||
tags.push({
|
tags.push({
|
||||||
fullPath: tagPath,
|
fullPath: tagPath,
|
||||||
@ -43,24 +52,22 @@ const filterAffixTags = (routes: RouteRecordRaw[], basePath = "/") => {
|
|||||||
}
|
}
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
const childTags = filterAffixTags(route.children, route.path)
|
const childTags = filterAffixTags(route.children, route.path)
|
||||||
if (childTags.length >= 1) {
|
tags.push(...childTags)
|
||||||
tags = tags.concat(childTags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 初始化标签页 */
|
||||||
const initTags = () => {
|
const initTags = () => {
|
||||||
affixTags = filterAffixTags(permissionStore.routes)
|
affixTags = filterAffixTags(permissionStore.routes)
|
||||||
for (const tag of affixTags) {
|
for (const tag of affixTags) {
|
||||||
// 必须含有 name 属性
|
// 必须含有 name 属性
|
||||||
if (tag.name) {
|
tag.name && tagsViewStore.addVisitedView(tag)
|
||||||
tagsViewStore.addVisitedView(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 添加标签页 */
|
||||||
const addTags = () => {
|
const addTags = () => {
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
tagsViewStore.addVisitedView(route)
|
tagsViewStore.addVisitedView(route)
|
||||||
@ -68,40 +75,43 @@ const addTags = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 刷新当前正在右键操作的标签页 */
|
||||||
const refreshSelectedTag = (view: TagView) => {
|
const refreshSelectedTag = (view: TagView) => {
|
||||||
tagsViewStore.delCachedView(view)
|
tagsViewStore.delCachedView(view)
|
||||||
router.replace({ path: "/redirect" + view.path, query: view.query })
|
router.replace({ path: "/redirect" + view.path, query: view.query })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 关闭当前正在右键操作的标签页 */
|
||||||
const closeSelectedTag = (view: TagView) => {
|
const closeSelectedTag = (view: TagView) => {
|
||||||
tagsViewStore.delVisitedView(view)
|
tagsViewStore.delVisitedView(view)
|
||||||
tagsViewStore.delCachedView(view)
|
tagsViewStore.delCachedView(view)
|
||||||
if (isActive(view)) {
|
isActive(view) && toLastView(tagsViewStore.visitedViews, view)
|
||||||
toLastView(tagsViewStore.visitedViews, view)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 关闭其他标签页 */
|
||||||
const closeOthersTags = () => {
|
const closeOthersTags = () => {
|
||||||
if (selectedTag.value.fullPath !== route.path && selectedTag.value.fullPath !== undefined) {
|
const fullPath = selectedTag.value.fullPath
|
||||||
router.push(selectedTag.value.fullPath)
|
if (fullPath !== route.path && fullPath !== undefined) {
|
||||||
|
router.push(fullPath)
|
||||||
}
|
}
|
||||||
tagsViewStore.delOthersVisitedViews(selectedTag.value)
|
tagsViewStore.delOthersVisitedViews(selectedTag.value)
|
||||||
tagsViewStore.delOthersCachedViews(selectedTag.value)
|
tagsViewStore.delOthersCachedViews(selectedTag.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 关闭所有标签页 */
|
||||||
const closeAllTags = (view: TagView) => {
|
const closeAllTags = (view: TagView) => {
|
||||||
tagsViewStore.delAllVisitedViews()
|
tagsViewStore.delAllVisitedViews()
|
||||||
tagsViewStore.delAllCachedViews()
|
tagsViewStore.delAllCachedViews()
|
||||||
if (affixTags.some((tag) => tag.path === route.path)) {
|
if (affixTags.some((tag) => tag.path === route.path)) return
|
||||||
return
|
|
||||||
}
|
|
||||||
toLastView(tagsViewStore.visitedViews, view)
|
toLastView(tagsViewStore.visitedViews, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 跳转到最后一个标签页 */
|
||||||
const toLastView = (visitedViews: TagView[], view: TagView) => {
|
const toLastView = (visitedViews: TagView[], view: TagView) => {
|
||||||
const latestView = visitedViews.slice(-1)[0]
|
const latestView = visitedViews.slice(-1)[0]
|
||||||
if (latestView !== undefined && latestView.fullPath !== undefined) {
|
const fullPath = latestView?.fullPath
|
||||||
router.push(latestView.fullPath)
|
if (fullPath !== undefined) {
|
||||||
|
router.push(fullPath)
|
||||||
} else {
|
} else {
|
||||||
// 如果 TagsView 全部被关闭了,则默认重定向到主页
|
// 如果 TagsView 全部被关闭了,则默认重定向到主页
|
||||||
if (view.name === "Dashboard") {
|
if (view.name === "Dashboard") {
|
||||||
@ -113,26 +123,26 @@ const toLastView = (visitedViews: TagView[], view: TagView) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 打开右键菜单面板 */
|
||||||
const openMenu = (tag: TagView, e: MouseEvent) => {
|
const openMenu = (tag: TagView, e: MouseEvent) => {
|
||||||
const menuMinWidth = 105
|
const menuMinWidth = 105
|
||||||
// container margin left
|
// 当前组件距离浏览器左端的距离
|
||||||
const offsetLeft = instance!.proxy!.$el.getBoundingClientRect().left
|
const offsetLeft = instance!.proxy!.$el.getBoundingClientRect().left
|
||||||
// container width
|
// 当前组件宽度
|
||||||
const offsetWidth = instance!.proxy!.$el.offsetWidth
|
const offsetWidth = instance!.proxy!.$el.offsetWidth
|
||||||
// left boundary
|
// 面板的最大左边距
|
||||||
const maxLeft = offsetWidth - menuMinWidth
|
const maxLeft = offsetWidth - menuMinWidth
|
||||||
// 15: margin right
|
// 面板距离鼠标指针的距离
|
||||||
const left15 = e.clientX - offsetLeft + 15
|
const left15 = e.clientX - offsetLeft + 15
|
||||||
if (left15 > maxLeft) {
|
left.value = left15 > maxLeft ? maxLeft : left15
|
||||||
left.value = maxLeft
|
|
||||||
} else {
|
|
||||||
left.value = left15
|
|
||||||
}
|
|
||||||
top.value = e.clientY
|
top.value = e.clientY
|
||||||
|
// 显示面板
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
// 更新当前正在右键操作的标签页
|
||||||
selectedTag.value = tag
|
selectedTag.value = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 关闭右键菜单面板 */
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
}
|
}
|
||||||
@ -148,11 +158,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(visible, (value) => {
|
watch(visible, (value) => {
|
||||||
if (value) {
|
value ? document.body.addEventListener("click", closeMenu) : document.body.removeEventListener("click", closeMenu)
|
||||||
document.body.addEventListener("click", closeMenu)
|
|
||||||
} else {
|
|
||||||
document.body.removeEventListener("click", closeMenu)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -168,10 +174,10 @@ onMounted(() => {
|
|||||||
ref="tagRefs"
|
ref="tagRefs"
|
||||||
v-for="tag in tagsViewStore.visitedViews"
|
v-for="tag in tagsViewStore.visitedViews"
|
||||||
:key="tag.path"
|
:key="tag.path"
|
||||||
:class="isActive(tag) ? 'active' : ''"
|
:class="{ active: isActive(tag) }"
|
||||||
:to="{ path: tag.path, query: tag.query }"
|
|
||||||
class="tags-view-item"
|
class="tags-view-item"
|
||||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
:to="{ path: tag.path, query: tag.query }"
|
||||||
|
@click.middle="!isAffix(tag) && closeSelectedTag(tag)"
|
||||||
@contextmenu.prevent="openMenu(tag, $event)"
|
@contextmenu.prevent="openMenu(tag, $event)"
|
||||||
>
|
>
|
||||||
{{ tag.meta?.title }}
|
{{ tag.meta?.title }}
|
||||||
@ -180,7 +186,7 @@ onMounted(() => {
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</router-link>
|
</router-link>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
<ul v-show="visible" class="contextmenu" :style="{ left: left + 'px', top: top + 'px' }">
|
||||||
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
|
||||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
|
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
|
||||||
<li @click="closeOthersTags">关闭其它</li>
|
<li @click="closeOthersTags">关闭其它</li>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user