mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-05-21 00:09:16 +08:00
feat: add scrollable tab bar (#49)
* feat: add scrollbar support and enhance tab navigation finish:1/2 * update * feat: 添加当前tab滚动功能并优化滚动条样式 * feat: 重构tab滚动逻辑,新增useTabScroll钩子以优化当前tab滚动体验 * feat: 优化TabBar组件,移除冗余代码并整合useTabScroll钩子 * fix: silly bug * refactor: Remove the debugging log --------- Co-authored-by: Vigo.zhou <eq1024@foxmail.com>
This commit is contained in:
parent
44ebd5f19e
commit
27d081cb23
64
src/hooks/useTabScroll.ts
Normal file
64
src/hooks/useTabScroll.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import type { NScrollbar } from 'naive-ui'
|
||||
import { ref, watchEffect, type Ref } from 'vue'
|
||||
import { throttle } from 'radash'
|
||||
|
||||
export function useTabScroll(currentTabPath: Ref<string>) {
|
||||
const scrollbar = ref<InstanceType<typeof NScrollbar>>()
|
||||
const safeArea = ref(150)
|
||||
|
||||
const handleTabSwitch = (distance: number) => {
|
||||
scrollbar.value?.scrollTo({
|
||||
left: distance,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToCurrentTab = () => {
|
||||
nextTick(() => {
|
||||
const currentTabElement = document.querySelector(`[data-tab-path="${currentTabPath.value}"]`) as HTMLElement
|
||||
const tabBarScrollWrapper = document.querySelector('.tab-bar-scroller-wrapper .n-scrollbar-container')
|
||||
const tabBarScrollContent = document.querySelector('.tab-bar-scroller-content')
|
||||
|
||||
if (currentTabElement && tabBarScrollContent && tabBarScrollWrapper) {
|
||||
const tabLeft = currentTabElement.offsetLeft
|
||||
const tabBarLeft = tabBarScrollWrapper.scrollLeft
|
||||
const wrapperWidth = tabBarScrollWrapper.getBoundingClientRect().width
|
||||
const tabWidth = currentTabElement.getBoundingClientRect().width
|
||||
const containerPR = Number.parseFloat(window.getComputedStyle(tabBarScrollContent).paddingRight)
|
||||
|
||||
if (tabLeft + tabWidth + safeArea.value + containerPR > wrapperWidth + tabBarLeft) {
|
||||
handleTabSwitch(tabLeft + tabWidth + containerPR - wrapperWidth + safeArea.value)
|
||||
} else if (tabLeft - safeArea.value < tabBarLeft) {
|
||||
handleTabSwitch(tabLeft - safeArea.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleScroll = throttle({ interval: 120 }, (step: number) => {
|
||||
scrollbar.value?.scrollBy({
|
||||
left: step * 400,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
})
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
e.preventDefault()
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
handleScroll(e.deltaY > 0 ? 1 : -1)
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (currentTabPath.value) {
|
||||
scrollToCurrentTab()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
scrollbar,
|
||||
onWheel,
|
||||
safeArea,
|
||||
handleTabSwitch
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import { useAppStore, useTabStore } from '@/store'
|
||||
import { useTabScroll } from '@/hooks/useTabScroll'
|
||||
import { useDraggable } from 'vue-draggable-plus'
|
||||
import IconClose from '~icons/icon-park-outline/close'
|
||||
import IconDelete from '~icons/icon-park-outline/delete-four'
|
||||
@ -17,6 +18,8 @@ const tabStore = useTabStore()
|
||||
const { tabs } = storeToRefs(useTabStore())
|
||||
const appStore = useAppStore()
|
||||
|
||||
const {scrollbar, onWheel } = useTabScroll(computed(() => tabStore.currentTabPath))
|
||||
|
||||
const router = useRouter()
|
||||
function handleTab(route: RouteLocationNormalized) {
|
||||
router.push(route.fullPath)
|
||||
@ -111,6 +114,7 @@ useDraggable(el, tabs, {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-scrollbar ref="scrollbar" class="relative flex tab-bar-scroller-wrapper" content-class="pr-34 tab-bar-scroller-content" :x-scrollable="true" @wheel="onWheel">
|
||||
<div class="p-l-2 flex w-full relative">
|
||||
<div class="flex items-end">
|
||||
<TabBarItem
|
||||
@ -120,7 +124,12 @@ useDraggable(el, tabs, {
|
||||
</div>
|
||||
<div ref="el" class="flex items-end flex-1">
|
||||
<TabBarItem
|
||||
v-for="item in tabStore.tabs" :key="item.fullPath" :value="tabStore.currentTabPath" :route="item" closable
|
||||
v-for="item in tabStore.tabs"
|
||||
:key="item.fullPath"
|
||||
:value="tabStore.currentTabPath"
|
||||
:route="item"
|
||||
closable
|
||||
:data-tab-path="item.fullPath"
|
||||
@close="tabStore.closeTab"
|
||||
@click="handleTab(item)"
|
||||
@contextmenu="handleContextMenu($event, item)"
|
||||
@ -130,13 +139,13 @@ useDraggable(el, tabs, {
|
||||
:on-clickoutside="onClickoutside" @select="handleSelect"
|
||||
/>
|
||||
</div>
|
||||
<!-- <span class="m-l-auto" /> -->
|
||||
<n-el class="absolute right-0 flex items-center gap-1 bg-[var(--base-color)] h-full">
|
||||
</div>
|
||||
<n-el class="absolute right-0 top-0 flex items-center gap-1 bg-[var(--base-color)] h-full">
|
||||
<Reload />
|
||||
<ContentFullScreen />
|
||||
<DropTabs />
|
||||
</n-el>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
Loading…
x
Reference in New Issue
Block a user