fix: 修改TagsView样式问题

This commit is contained in:
戴业伟 2024-02-05 11:07:48 +08:00
parent 6fff76e3f8
commit 58337f771e

View File

@ -2,8 +2,8 @@
<div <div
class="box-border tabs-view" class="box-border tabs-view"
:class="{ :class="{
'tabs-view-fix': state.multiTabsSetting.fixed, 'tabs-view-fix': multiTabsSetting.fixed,
'tabs-view-fixed-header': state.isMultiHeaderFixed, 'tabs-view-fixed-header': isMultiHeaderFixed,
'tabs-view-default-background': getDarkTheme === false, 'tabs-view-default-background': getDarkTheme === false,
'tabs-view-dark-background': getDarkTheme === true, 'tabs-view-dark-background': getDarkTheme === true,
}" }"
@ -13,11 +13,11 @@
<div <div
ref="navWrap" ref="navWrap"
class="tabs-card" class="tabs-card"
:class="{ 'tabs-card-scrollable': state.scrollable }" :class="{ 'tabs-card-scrollable': scrollable }"
> >
<span <span
class="tabs-card-prev" class="tabs-card-prev"
:class="{ 'tabs-card-prev-hide': !state.scrollable }" :class="{ 'tabs-card-prev-hide': !scrollable }"
@click="scrollPrev" @click="scrollPrev"
> >
<n-icon size="16" color="#515a6e"> <n-icon size="16" color="#515a6e">
@ -26,7 +26,7 @@
</span> </span>
<span <span
class="tabs-card-next" class="tabs-card-next"
:class="{ 'tabs-card-next-hide': !state.scrollable }" :class="{ 'tabs-card-next-hide': !scrollable }"
@click="scrollNext" @click="scrollNext"
> >
<n-icon size="16" color="#515a6e"> <n-icon size="16" color="#515a6e">
@ -44,7 +44,7 @@
<div <div
:id="`tag${element.fullPath.split('/').join('\/')}`" :id="`tag${element.fullPath.split('/').join('\/')}`"
class="tabs-card-scroll-item" class="tabs-card-scroll-item"
:class="{ 'active-item': state.activeKey === element.fullPath }" :class="{ 'active-item': activeKey === element.fullPath }"
@click.stop="goPage(element)" @click.stop="goPage(element)"
@contextmenu="handleContextMenu($event, element)" @contextmenu="handleContextMenu($event, element)"
> >
@ -76,9 +76,9 @@
</n-dropdown> </n-dropdown>
</div> </div>
<n-dropdown <n-dropdown
:show="state.showDropdown" :show="showDropdown"
:x="state.dropdownX" :x="dropdownX"
:y="state.dropdownY" :y="dropdownY"
@clickoutside="onClickOutside" @clickoutside="onClickOutside"
placement="bottom-start" placement="bottom-start"
@select="closeHandleSelect" @select="closeHandleSelect"
@ -88,13 +88,26 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts">
import {
defineComponent,
reactive,
computed,
ref,
toRefs,
provide,
watch,
onMounted,
nextTick,
} from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { storage } from '@/utils'; import { storage } from '@/utils';
import { StorageEnum } from '@/enums/storageEnum';
import { useTabsViewStore } from '@/store/modules/tabsView'; import { useTabsViewStore } from '@/store/modules/tabsView';
import { useAsyncRouteStore } from '@/store/modules/asyncRoute'; import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
import { RouteItem } from '@/store/modules/tabsView'; import { RouteItem } from '@/store/modules/tabsView';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting'; import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { useMessage } from 'naive-ui';
import Draggable from 'vuedraggable'; import Draggable from 'vuedraggable';
import { PageEnum } from '@/enums/pageEnum'; import { PageEnum } from '@/enums/pageEnum';
import { import {
@ -107,390 +120,432 @@ import {
RightOutlined, RightOutlined,
} from '@vicons/antd'; } from '@vicons/antd';
import { renderIcon } from '@/utils'; import { renderIcon } from '@/utils';
// import elementResizeDetectorMaker from "element-resize-detector"; // import elementResizeDetectorMaker from 'element-resize-detector';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'; import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
import { useProjectSettingStore } from '@/store/modules/projectSetting'; import { useProjectSettingStore } from '@/store/modules/projectSetting';
import { useThemeVars } from 'naive-ui'; import { useThemeVars } from 'naive-ui';
import { useGo } from '@/hooks/web/usePage'; import { useGo } from '@/hooks/web/usePage';
import { StorageEnum } from '@/enums/storageEnum';
const props = defineProps({ export default defineComponent({
collapsed: { name: 'TabsView',
type: Boolean, components: {
DownOutlined,
CloseOutlined,
LeftOutlined,
RightOutlined,
Draggable,
}, },
}); props: {
const { getDarkTheme, getAppTheme } = useDesignSetting(); collapsed: {
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } = type: Boolean,
useProjectSetting(); },
const settingStore = useProjectSettingStore(); },
setup(props) {
const { getDarkTheme, getAppTheme } = useDesignSetting();
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
useProjectSetting();
const settingStore = useProjectSettingStore();
const route = useRoute(); const message = useMessage();
const router = useRouter(); const route = useRoute();
const tabsViewStore = useTabsViewStore(); const router = useRouter();
const asyncRouteStore = useAsyncRouteStore(); const tabsViewStore = useTabsViewStore();
const navScroll: any = ref(null); const asyncRouteStore = useAsyncRouteStore();
const navWrap: any = ref(null); const navScroll: any = ref(null);
const isCurrent = ref(false); const navWrap: any = ref(null);
const go = useGo(); const isCurrent = ref(false);
const go = useGo();
const themeVars = useThemeVars(); const themeVars = useThemeVars();
const getCardColor = computed(() => { const getcardcolor = computed(() => {
return themeVars.value.cardColor; return themeVars.value.cardColor;
}); });
const getBaseColor = computed(() => { const getbasecolor = computed(() => {
return themeVars.value.textColor1; return themeVars.value.textColor1;
}); });
const state = reactive({ const state = reactive({
activeKey: route.fullPath, activeKey: route.fullPath,
scrollable: false, scrollable: false,
dropdownX: 0, dropdownX: 0,
dropdownY: 0, dropdownY: 0,
showDropdown: false, showDropdown: false,
isMultiHeaderFixed: false, isMultiHeaderFixed: false,
multiTabsSetting: multiTabsSetting, multiTabsSetting: multiTabsSetting,
}); });
// //
const getSimpleRoute = (route): RouteItem => { const getSimpleRoute = (route): RouteItem => {
const { fullPath, hash, meta, name, params, path, query } = route; const { fullPath, hash, meta, name, params, path, query } = route;
return { fullPath, hash, meta, name, params, path, query }; return { fullPath, hash, meta, name, params, path, query };
};
const isMixMenuNoneSub = computed(() => {
const mixMenu = settingStore.menuSetting.mixMenu;
const currentRoute = useRoute();
if (navMode.value != 'horizontal-mix') return true;
return !(
navMode.value === 'horizontal-mix' &&
mixMenu &&
currentRoute.meta.isRoot
);
});
//
const getChangeStyle = computed(() => {
const { collapsed } = props;
const { minMenuWidth, menuWidth }: any = menuSetting.value;
const { fixed }: any = multiTabsSetting.value;
let lenNum =
navMode.value === 'horizontal' || !isMixMenuNoneSub.value
? '0px'
: collapsed
? `${minMenuWidth}px`
: `${menuWidth}px`;
if (isMobile.value) {
return {
left: '0px',
width: '100%',
}; };
}
return {
left: lenNum,
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
};
});
//tags const isMixMenuNoneSub = computed(() => {
const TabsMenuOptions = computed(() => { const mixMenu = settingStore.menuSetting.mixMenu;
const isDisabled = tabsList.value.length <= 1; const currentRoute = useRoute();
return [ if (navMode.value != 'horizontal-mix') return true;
{ return !(
label: '刷新当前', navMode.value === 'horizontal-mix' &&
key: '1', mixMenu &&
icon: renderIcon(ReloadOutlined), currentRoute.meta.isRoot
}, );
{ });
label: `关闭当前`,
key: '2',
disabled: isCurrent.value || isDisabled,
icon: renderIcon(CloseOutlined),
},
{
label: '关闭其他',
key: '3',
disabled: isDisabled,
icon: renderIcon(ColumnWidthOutlined),
},
{
label: '关闭全部',
key: '4',
disabled: isDisabled,
icon: renderIcon(MinusOutlined),
},
];
});
let cacheRoutes: RouteItem[] = []; //
const simpleRoute = getSimpleRoute(route); const getChangeStyle = computed(() => {
try { const { collapsed } = props;
const routesStr = storage.get(StorageEnum.ZS_TABS_ROUTES) as const { minMenuWidth, menuWidth }: any = menuSetting.value;
| string const { fixed }: any = multiTabsSetting.value;
| null let lenNum =
| undefined; navMode.value === 'horizontal' || !isMixMenuNoneSub.value
cacheRoutes = routesStr ? JSON.parse(routesStr) : [simpleRoute]; ? '0px'
} catch (e) { : collapsed
cacheRoutes = [simpleRoute]; ? `${minMenuWidth}px`
} : `${menuWidth}px`;
// localStorage if (isMobile.value) {
const routes = router.getRoutes(); return {
cacheRoutes.forEach((cacheRoute) => { left: '0px',
const route = routes.find((route) => route.path === cacheRoute.path); width: '100%',
if (route) { };
cacheRoute.meta = route.meta || cacheRoute.meta; }
cacheRoute.name = (route.name || cacheRoute.name) as string; return {
} left: lenNum,
}); width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
};
});
// //tags
tabsViewStore.initTabs(cacheRoutes); const TabsMenuOptions = computed(() => {
const isDisabled = tabsList.value.length <= 1;
return [
{
label: '刷新当前',
key: '1',
icon: renderIcon(ReloadOutlined),
},
{
label: `关闭当前`,
key: '2',
disabled: isCurrent.value || isDisabled,
icon: renderIcon(CloseOutlined),
},
{
label: '关闭其他',
key: '3',
disabled: isDisabled,
icon: renderIcon(ColumnWidthOutlined),
},
{
label: '关闭全部',
key: '4',
disabled: isDisabled,
icon: renderIcon(MinusOutlined),
},
];
});
// let cacheRoutes: RouteItem[] = [];
function onScroll(e) { const simpleRoute = getSimpleRoute(route);
let scrollTop = try {
e.target.scrollTop || const routesStr = storage.get(StorageEnum.ZS_TABS_ROUTES) as
document.documentElement.scrollTop || | string
window.pageYOffset || | null
document.body.scrollTop; // | undefined;
state.isMultiHeaderFixed = !!( cacheRoutes = routesStr ? JSON.parse(routesStr) : [simpleRoute];
!headerSetting.value.fixed && } catch (e) {
multiTabsSetting.value.fixed && cacheRoutes = [simpleRoute];
scrollTop >= 64
);
}
window.addEventListener('scroll', onScroll, true);
//
const delKeepAliveCompName = () => {
if (route.meta.keepAlive) {
const name = router.currentRoute.value.matched.find(
(item) => item.name == route.name
)?.components?.default.name;
if (name) {
asyncRouteStore.keepAliveComponents =
asyncRouteStore.keepAliveComponents.filter((item) => item != name);
} }
}
};
// // localStorage
const tabsList: any = computed(() => tabsViewStore.tabsList); const routes = router.getRoutes();
const whiteList: string[] = [ cacheRoutes.forEach((cacheRoute) => {
PageEnum.BASE_LOGIN_NAME, const route = routes.find((route) => route.path === cacheRoute.path);
PageEnum.REDIRECT_NAME, if (route) {
PageEnum.ERROR_PAGE_NAME, cacheRoute.meta = route.meta || cacheRoute.meta;
]; cacheRoute.name = (route.name || cacheRoute.name) as string;
}
});
watch( //
() => route.fullPath, tabsViewStore.initTabs(cacheRoutes);
(to) => {
if (whiteList.includes(route.name as string)) return;
state.activeKey = to;
tabsViewStore.addTab(getSimpleRoute(route));
updateNavScroll(true);
},
{ immediate: true }
);
// //
window.addEventListener('beforeunload', () => { function onScroll(e) {
storage.set(StorageEnum.ZS_TABS_ROUTES, JSON.stringify(tabsList.value)); let scrollTop =
}); e.target.scrollTop ||
document.documentElement.scrollTop ||
window.pageYOffset ||
document.body.scrollTop; //
state.isMultiHeaderFixed = !!(
!headerSetting.value.fixed &&
multiTabsSetting.value.fixed &&
scrollTop >= 64
);
}
// window.addEventListener('scroll', onScroll, true);
const removeTab = (route) => {
if (tabsList.value.length === 1) {
return window['$message'].warning('这已经是最后一页,不能再关闭了!');
}
delKeepAliveCompName();
tabsViewStore.closeCurrentTab(route);
//
if (state.activeKey === route.fullPath) {
const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)];
state.activeKey = currentRoute.fullPath;
router.push(currentRoute);
}
updateNavScroll();
};
// //
const reloadPage = () => { const delKeepAliveCompName = () => {
delKeepAliveCompName(); if (route.meta.keepAlive) {
router.push({ const name = router.currentRoute.value.matched.find(
path: '/redirect' + route.fullPath, (item) => item.name == route.name
}); )?.components?.default.name;
}; if (name) {
asyncRouteStore.keepAliveComponents =
// asyncRouteStore.keepAliveComponents.filter((item) => item != name);
provide('reloadPage', reloadPage);
//
const closeLeft = (route) => {
tabsViewStore.closeLeftTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeRight = (route) => {
tabsViewStore.closeRightTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeOther = (route) => {
tabsViewStore.closeOtherTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeAll = () => {
tabsViewStore.closeAllTabs();
router.replace(PageEnum.BASE_HOME);
updateNavScroll();
};
//tab
const closeHandleSelect = (key) => {
switch (key) {
//
case '1':
reloadPage();
break;
//
case '2':
removeTab(route);
break;
//
case '3':
closeOther(route);
break;
//
case '4':
closeAll();
break;
}
updateNavScroll();
state.showDropdown = false;
};
/**
* @param value 要滚动到的位置
* @param amplitude 每次滚动的长度
*/
function scrollTo(value: number, amplitude: number) {
const currentScroll = navScroll.value.scrollLeft;
const scrollWidth =
(amplitude > 0 && currentScroll + amplitude >= value) ||
(amplitude < 0 && currentScroll + amplitude <= value)
? value
: currentScroll + amplitude;
navScroll.value && navScroll.value.scrollTo(scrollWidth, 0);
if (scrollWidth === value) return;
return window.requestAnimationFrame(() => scrollTo(value, amplitude));
}
function scrollPrev() {
const containerWidth = navScroll.value.offsetWidth;
const currentScroll = navScroll.value.scrollLeft;
if (!currentScroll) return;
const scrollLeft =
currentScroll > containerWidth ? currentScroll - containerWidth : 0;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
function scrollNext() {
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
const currentScroll = navScroll.value.scrollLeft;
if (navWidth - currentScroll <= containerWidth) return;
const scrollLeft =
navWidth - currentScroll > containerWidth * 2
? currentScroll + containerWidth
: navWidth - containerWidth;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
/**
* @param autoScroll 是否开启自动滚动功能
*/
async function updateNavScroll(autoScroll?: boolean) {
await nextTick();
if (!navScroll.value) return;
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
if (containerWidth < navWidth) {
state.scrollable = true;
if (autoScroll) {
let tagList =
navScroll.value.querySelectorAll('.tabs-card-scroll-item') || [];
[...tagList].forEach((tag: HTMLElement) => {
// fix SyntaxError
if (tag.id === `tag${state.activeKey.split('/').join('\/')}`) {
tag.scrollIntoView && tag.scrollIntoView();
} }
}
};
//
const tabsList: any = computed(() => tabsViewStore.tabsList);
const whiteList: string[] = [
PageEnum.BASE_LOGIN_NAME,
PageEnum.REDIRECT_NAME,
PageEnum.ERROR_PAGE_NAME,
];
watch(
() => route.fullPath,
(to) => {
if (whiteList.includes(route.name as string)) return;
state.activeKey = to;
tabsViewStore.addTab(getSimpleRoute(route));
updateNavScroll(true);
},
{ immediate: true }
);
//
window.addEventListener('beforeunload', () => {
storage.set(StorageEnum.ZS_TABS_ROUTES, JSON.stringify(tabsList.value));
});
//
const removeTab = (route) => {
if (tabsList.value.length === 1) {
return message.warning('这已经是最后一页,不能再关闭了!');
}
delKeepAliveCompName();
tabsViewStore.closeCurrentTab(route);
//
if (state.activeKey === route.fullPath) {
const currentRoute =
tabsList.value[Math.max(0, tabsList.value.length - 1)];
state.activeKey = currentRoute.fullPath;
router.push(currentRoute);
}
updateNavScroll();
};
//
const reloadPage = () => {
delKeepAliveCompName();
router.push({
path: '/redirect' + route.fullPath,
});
};
//
provide('reloadPage', reloadPage);
//
const closeLeft = (route) => {
tabsViewStore.closeLeftTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeRight = (route) => {
tabsViewStore.closeRightTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeOther = (route) => {
tabsViewStore.closeOtherTabs(route);
state.activeKey = route.fullPath;
router.replace(route.fullPath);
updateNavScroll();
};
//
const closeAll = () => {
tabsViewStore.closeAllTabs();
router.replace(PageEnum.BASE_HOME);
updateNavScroll();
};
//tab
const closeHandleSelect = (key) => {
switch (key) {
//
case '1':
reloadPage();
break;
//
case '2':
removeTab(route);
break;
//
case '3':
closeOther(route);
break;
//
case '4':
closeAll();
break;
}
updateNavScroll();
state.showDropdown = false;
};
/**
* @param value 要滚动到的位置
* @param amplitude 每次滚动的长度
*/
function scrollTo(value: number, amplitude: number) {
const currentScroll = navScroll.value.scrollLeft;
const scrollWidth =
(amplitude > 0 && currentScroll + amplitude >= value) ||
(amplitude < 0 && currentScroll + amplitude <= value)
? value
: currentScroll + amplitude;
navScroll.value && navScroll.value.scrollTo(scrollWidth, 0);
if (scrollWidth === value) return;
return window.requestAnimationFrame(() => scrollTo(value, amplitude));
}
function scrollPrev() {
const containerWidth = navScroll.value.offsetWidth;
const currentScroll = navScroll.value.scrollLeft;
if (!currentScroll) return;
const scrollLeft =
currentScroll > containerWidth ? currentScroll - containerWidth : 0;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
function scrollNext() {
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
const currentScroll = navScroll.value.scrollLeft;
if (navWidth - currentScroll <= containerWidth) return;
const scrollLeft =
navWidth - currentScroll > containerWidth * 2
? currentScroll + containerWidth
: navWidth - containerWidth;
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
}
/**
* @param autoScroll 是否开启自动滚动功能
*/
async function updateNavScroll(autoScroll?: boolean) {
await nextTick();
if (!navScroll.value) return;
const containerWidth = navScroll.value.offsetWidth;
const navWidth = navScroll.value.scrollWidth;
if (containerWidth < navWidth) {
state.scrollable = true;
if (autoScroll) {
let tagList =
navScroll.value.querySelectorAll('.tabs-card-scroll-item') || [];
[...tagList].forEach((tag: HTMLElement) => {
// fix SyntaxError
if (tag.id === `tag${state.activeKey.split('/').join('\/')}`) {
tag.scrollIntoView && tag.scrollIntoView();
}
});
}
} else {
state.scrollable = false;
}
}
function handleResize() {
updateNavScroll(true);
}
function handleContextMenu(e, item) {
e.preventDefault();
isCurrent.value = PageEnum.BASE_HOME === item.path;
state.showDropdown = false;
nextTick().then(() => {
state.showDropdown = true;
state.dropdownX = e.clientX;
state.dropdownY = e.clientY;
}); });
} }
} else {
state.scrollable = false;
}
}
function handleResize() { function onClickOutside() {
updateNavScroll(true); state.showDropdown = false;
} }
function handleContextMenu(e, item) { //tags
e.preventDefault(); function goPage(e) {
isCurrent.value = PageEnum.BASE_HOME === item.path; const { fullPath } = e;
state.showDropdown = false; if (fullPath === route.fullPath) return;
nextTick().then(() => { state.activeKey = fullPath;
state.showDropdown = true; go(e, true);
state.dropdownX = e.clientX; }
state.dropdownY = e.clientY;
});
}
function onClickOutside() { //tab
state.showDropdown = false; function closeTabItem(e) {
} const { fullPath } = e;
const routeInfo = tabsList.value.find(
(item) => item.fullPath == fullPath
);
removeTab(routeInfo);
}
//tags onMounted(() => {
function goPage(e) { // onElementResize();
const { fullPath } = e; });
if (fullPath === route.fullPath) return;
state.activeKey = fullPath;
go(e, true);
}
//tab // function onElementResize() {
function closeTabItem(e) { // let observer;
const { fullPath } = e; // observer = elementResizeDetectorMaker();
const routeInfo = tabsList.value.find((item) => item.fullPath == fullPath); // observer.listenTo(navWrap.value, handleResize);
removeTab(routeInfo); // }
}
onMounted(() => { return {
// onElementResize(); ...toRefs(state),
navWrap,
navScroll,
route,
tabsList,
goPage,
closeTabItem,
closeLeft,
closeRight,
closeOther,
closeAll,
reloadPage,
getChangeStyle,
TabsMenuOptions,
closeHandleSelect,
scrollNext,
scrollPrev,
handleContextMenu,
onClickOutside,
getDarkTheme,
getAppTheme,
getcardcolor,
getbasecolor,
};
},
}); });
// function onElementResize() {
// let observer;
// observer = elementResizeDetectorMaker();
// observer.listenTo(navWrap.value, handleResize);
// }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>