perf: improve tabs-view scrolling (#4164)
This commit is contained in:
@@ -9,6 +9,7 @@ export {
|
||||
Bell,
|
||||
BookOpenText,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
CircleHelp,
|
||||
Copy,
|
||||
|
@@ -75,7 +75,7 @@ function scrollIntoView() {
|
||||
id="tabs-scrollbar"
|
||||
class="tabs-chrome__scrollbar h-full"
|
||||
horizontal
|
||||
scroll-bar-class="z-10"
|
||||
scroll-bar-class="z-10 hidden"
|
||||
>
|
||||
<!-- footer -> 4px -->
|
||||
<div
|
||||
|
@@ -72,12 +72,12 @@ function scrollIntoView() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex-1 overflow-hidden">
|
||||
<div class="size-full flex-1 overflow-hidden">
|
||||
<VbenScrollbar
|
||||
id="tabs-scrollbar"
|
||||
class="tabs-scrollbar h-full"
|
||||
horizontal
|
||||
scroll-bar-class="z-10"
|
||||
scroll-bar-class="z-10 hidden"
|
||||
>
|
||||
<div
|
||||
:class="contentClass"
|
||||
|
@@ -7,8 +7,10 @@ import type { TabsProps } from './types';
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { useForwardPropsEmits, useSortable } from '@vben-core/composables';
|
||||
import { ChevronLeft, ChevronRight } from '@vben-core/icons';
|
||||
|
||||
import { Tabs, TabsChrome } from './components';
|
||||
import { useTabsViewScroll } from './use-tabs-view-scroll';
|
||||
|
||||
interface Props extends TabsProps {}
|
||||
|
||||
@@ -30,6 +32,8 @@ const emit = defineEmits<{
|
||||
|
||||
const forward = useForwardPropsEmits(props, emit);
|
||||
|
||||
const { initScrollbar, scrollDirection } = useTabsViewScroll();
|
||||
|
||||
const sortableInstance = ref<null | Sortable>(null);
|
||||
|
||||
// 可能会找到拖拽的子元素,这里需要确保拖拽的dom时tab元素
|
||||
@@ -104,13 +108,21 @@ async function initTabsSortable() {
|
||||
sortableInstance.value = await initializeSortable();
|
||||
}
|
||||
|
||||
onMounted(initTabsSortable);
|
||||
async function init() {
|
||||
await nextTick();
|
||||
initTabsSortable();
|
||||
initScrollbar();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.styleType,
|
||||
() => {
|
||||
sortableInstance.value?.destroy();
|
||||
initTabsSortable();
|
||||
init();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -120,6 +132,32 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabsChrome v-if="styleType === 'chrome'" v-bind="forward" />
|
||||
<Tabs v-else v-bind="forward" />
|
||||
<div
|
||||
:class="{
|
||||
'overflow-hidden': styleType !== 'chrome',
|
||||
}"
|
||||
class="flex h-full flex-1"
|
||||
>
|
||||
<!-- 左侧滚动按钮 -->
|
||||
<span
|
||||
class="hover:bg-muted text-muted-foreground cursor-pointer border-r px-2"
|
||||
@click="scrollDirection('left')"
|
||||
>
|
||||
<ChevronLeft class="size-4 h-full" />
|
||||
</span>
|
||||
|
||||
<TabsChrome
|
||||
v-if="styleType === 'chrome'"
|
||||
v-bind="{ ...forward, ...$attrs, ...$props }"
|
||||
/>
|
||||
<Tabs v-else v-bind="{ ...forward, ...$attrs, ...$props }" />
|
||||
|
||||
<!-- 左侧滚动按钮 -->
|
||||
<span
|
||||
class="hover:bg-muted text-muted-foreground cursor-pointer border-l px-2"
|
||||
@click="scrollDirection('right')"
|
||||
>
|
||||
<ChevronRight class="size-4 h-full" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
59
packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts
Normal file
59
packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { nextTick, ref } from 'vue';
|
||||
|
||||
type El = Element | null | undefined;
|
||||
|
||||
export function useTabsViewScroll(scrollDistance: number = 150) {
|
||||
const scrollbarEl = ref<El>(null);
|
||||
const scrollViewportEl = ref<El>(null);
|
||||
|
||||
function getScrollClientWidth() {
|
||||
if (!scrollbarEl.value || !scrollViewportEl.value) return {};
|
||||
|
||||
const scrollbarWidth = scrollbarEl.value.clientWidth;
|
||||
const scrollViewWidth = scrollViewportEl.value.clientWidth;
|
||||
|
||||
return {
|
||||
scrollbarWidth,
|
||||
scrollViewWidth,
|
||||
};
|
||||
}
|
||||
|
||||
function scrollDirection(
|
||||
direction: 'left' | 'right',
|
||||
distance: number = scrollDistance,
|
||||
) {
|
||||
const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
|
||||
|
||||
if (!scrollbarWidth || !scrollViewWidth) return;
|
||||
|
||||
if (scrollbarWidth > scrollViewWidth) return;
|
||||
|
||||
scrollViewportEl.value?.scrollBy({
|
||||
behavior: 'smooth',
|
||||
left:
|
||||
direction === 'left'
|
||||
? -(scrollbarWidth - distance)
|
||||
: +(scrollbarWidth - distance),
|
||||
});
|
||||
}
|
||||
|
||||
async function initScrollbar() {
|
||||
await nextTick();
|
||||
const barEl = document.querySelector('#tabs-scrollbar');
|
||||
|
||||
const viewportEl = barEl?.querySelector(
|
||||
'div[data-radix-scroll-area-viewport]',
|
||||
);
|
||||
|
||||
scrollbarEl.value = barEl;
|
||||
scrollViewportEl.value = viewportEl;
|
||||
|
||||
const activeItem = viewportEl?.querySelector('.is-active');
|
||||
activeItem?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
return {
|
||||
initScrollbar,
|
||||
scrollDirection,
|
||||
};
|
||||
}
|
@@ -20,7 +20,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-icons/vue": "^1.0.0",
|
||||
"@vben-core/layout-ui": "workspace:*",
|
||||
"@vben-core/menu-ui": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useContentMaximize, useTabs } from '@vben/hooks';
|
||||
@@ -12,9 +12,6 @@ import {
|
||||
TabsView,
|
||||
} from '@vben-core/tabs-ui';
|
||||
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue';
|
||||
|
||||
import { useTabViewScroll } from './use-tab-view-scroll';
|
||||
import { useTabbar } from './use-tabbar';
|
||||
|
||||
defineOptions({
|
||||
@@ -52,26 +49,9 @@ const menus = computed(() => {
|
||||
if (!preferences.tabbar.persist) {
|
||||
tabbarStore.closeOtherTabs(route);
|
||||
}
|
||||
|
||||
const { scrollDirection, setScrollBarEl, setScrollViewEl } = useTabViewScroll();
|
||||
|
||||
onMounted(() => {
|
||||
const scrollBarEl: HTMLElement | null =
|
||||
document.querySelector('#tabs-scrollbar');
|
||||
|
||||
const scrollViewportEl: HTMLElement | null | undefined =
|
||||
scrollBarEl?.querySelector('div[data-radix-scroll-area-viewport]');
|
||||
|
||||
setScrollBarEl(scrollBarEl);
|
||||
setScrollViewEl(scrollViewportEl);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ChevronLeftIcon
|
||||
class="mx-2 h-full cursor-pointer"
|
||||
@click="scrollDirection('left')"
|
||||
/>
|
||||
<TabsView
|
||||
:active="currentActive"
|
||||
:class="theme"
|
||||
@@ -86,10 +66,6 @@ onMounted(() => {
|
||||
@update:active="handleClick"
|
||||
/>
|
||||
<div class="flex-center h-full">
|
||||
<ChevronRightIcon
|
||||
class="mx-2 h-full cursor-pointer"
|
||||
@click="scrollDirection('right')"
|
||||
/>
|
||||
<TabsToolRefresh
|
||||
v-if="preferences.tabbar.showRefresh"
|
||||
@refresh="refreshTab"
|
||||
|
Reference in New Issue
Block a user