|
@@ -1,676 +0,0 @@
|
|
|
-<template>
|
|
|
|
|
- <div
|
|
|
|
|
- :class="{
|
|
|
|
|
- 'tabs-view-default-background': getDarkTheme === false,
|
|
|
|
|
- 'tabs-view-dark-background': getDarkTheme === true,
|
|
|
|
|
- }"
|
|
|
|
|
- :style="getChangeStyle"
|
|
|
|
|
- class="tabs-view"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="tabs-view-main">
|
|
|
|
|
- <div ref="navWrap" :class="{ 'tabs-card-scrollable': scrollable }" class="tabs-card">
|
|
|
|
|
- <span :class="{ 'tabs-card-prev-hide': !scrollable }" class="tabs-card-prev" @click="scrollPrev">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16">
|
|
|
|
|
- <LeftOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </span>
|
|
|
|
|
- <span :class="{ 'tabs-card-next-hide': !scrollable }" class="tabs-card-next" @click="scrollNext">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16">
|
|
|
|
|
- <RightOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </span>
|
|
|
|
|
- <div ref="navScroll" class="tabs-card-scroll">
|
|
|
|
|
- <Draggable :list="tabsList" animation="300" class="flex" item-key="fullPath">
|
|
|
|
|
- <template #item="{ element, index }">
|
|
|
|
|
- <div
|
|
|
|
|
- :id="`tag${element.fullPath.split('/').join('\/')}`"
|
|
|
|
|
- :class="{ 'active-item': activeKey === element.fullPath }"
|
|
|
|
|
- class="shadow-sm tabs-card-scroll-item"
|
|
|
|
|
- @contextmenu="handleContextMenu($event, element, index)"
|
|
|
|
|
- @click.stop="goPage(element)"
|
|
|
|
|
- >
|
|
|
|
|
- <Render :ref="`renderDom_${index}`" :value="getRender(element.meta.title)" />
|
|
|
|
|
- <el-badge
|
|
|
|
|
- class="ml-1"
|
|
|
|
|
- style="vertical-align: -webkit-baseline-middle"
|
|
|
|
|
- v-if="element.meta.state === 'undone'"
|
|
|
|
|
- type="warning"
|
|
|
|
|
- is-dot
|
|
|
|
|
- />
|
|
|
|
|
- <el-icon
|
|
|
|
|
- class="el-input__icon"
|
|
|
|
|
- v-if="!element.meta.affix"
|
|
|
|
|
- size="14"
|
|
|
|
|
- @click.stop="closeTabItem(element)"
|
|
|
|
|
- >
|
|
|
|
|
- <CloseOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- </Draggable>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="tabs-close">
|
|
|
|
|
- <el-dropdown trigger="hover" @command="closeHandleSelect">
|
|
|
|
|
- <div class="tabs-close-btn">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16">
|
|
|
|
|
- <DownOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- </div>
|
|
|
|
|
- <template #dropdown>
|
|
|
|
|
- <el-dropdown-menu>
|
|
|
|
|
- <el-dropdown-item command="7">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16" v-if="getPageFullScreen">
|
|
|
|
|
- <CompressOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- <el-icon class="el-input__icon" size="16" v-else>
|
|
|
|
|
- <ExpandOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- {{ getPageFullScreen ? '退出全屏' : '内容全屏' }}
|
|
|
|
|
- </el-dropdown-item>
|
|
|
|
|
- <el-dropdown-item :disabled="refreshCurrent" command="1">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16">
|
|
|
|
|
- <ReloadOutlined />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- 刷新当前
|
|
|
|
|
- </el-dropdown-item>
|
|
|
|
|
- <el-dropdown-item :disabled="closeCurrent || isDisabled" command="2">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16"> <MinusOutlined /> </el-icon>关闭当前
|
|
|
|
|
- </el-dropdown-item>
|
|
|
|
|
- <el-dropdown-item :disabled="isDisabled" command="3">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16"> <SwapOutlined /> </el-icon>关闭其他</el-dropdown-item
|
|
|
|
|
- >
|
|
|
|
|
- <el-dropdown-item :disabled="closeLeftTab" command="5">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16"> <DoubleLeftOutlined /> </el-icon>关闭左侧</el-dropdown-item
|
|
|
|
|
- >
|
|
|
|
|
- <el-dropdown-item :disabled="closeRightTab" command="6">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16"> <DoubleRightOutlined /> </el-icon>关闭右侧</el-dropdown-item
|
|
|
|
|
- >
|
|
|
|
|
- <el-dropdown-item :disabled="isDisabled" command="4">
|
|
|
|
|
- <el-icon class="el-input__icon" size="16"> <CloseOutlined /> </el-icon>关闭全部</el-dropdown-item
|
|
|
|
|
- >
|
|
|
|
|
- </el-dropdown-menu>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-dropdown>
|
|
|
|
|
- </div>
|
|
|
|
|
- <!-- <el-dropdown
|
|
|
|
|
- :options="TabsMenuOptions"
|
|
|
|
|
- :show="showDropdown"
|
|
|
|
|
- :x="dropdownX"
|
|
|
|
|
- :y="dropdownY"
|
|
|
|
|
- placement="bottom-start"
|
|
|
|
|
- @clickoutside="onClickOutside"
|
|
|
|
|
- @select="closeHandleSelect"
|
|
|
|
|
- /> -->
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-</template>
|
|
|
|
|
-
|
|
|
|
|
-<script lang="ts" setup>
|
|
|
|
|
- import { computed, ref, unref, provide, watch, onMounted, nextTick, toRaw, inject } from 'vue';
|
|
|
|
|
- import { useRoute, useRouter } from 'vue-router';
|
|
|
|
|
- import { storage } from '@/utils/Storage';
|
|
|
|
|
- import { TABS_ROUTES } from '@/store/mutation-types';
|
|
|
|
|
- import { useTabsViewStore } from '@/store/modules/tabsView';
|
|
|
|
|
- import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
|
|
|
|
- import { RouteItem } from '@/store/modules/tabsView';
|
|
|
|
|
- import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
|
|
|
|
- import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
- import Draggable from 'vuedraggable';
|
|
|
|
|
- import { PageEnum } from '@/enums/pageEnum';
|
|
|
|
|
- import {
|
|
|
|
|
- DownOutlined,
|
|
|
|
|
- ReloadOutlined,
|
|
|
|
|
- CloseOutlined,
|
|
|
|
|
- SwapOutlined,
|
|
|
|
|
- LeftOutlined,
|
|
|
|
|
- RightOutlined,
|
|
|
|
|
- DoubleLeftOutlined,
|
|
|
|
|
- DoubleRightOutlined,
|
|
|
|
|
- MinusOutlined,
|
|
|
|
|
- CompressOutlined,
|
|
|
|
|
- ExpandOutlined,
|
|
|
|
|
- } from '@vicons/antd';
|
|
|
|
|
- // import { renderIcon } from '@/utils/index';
|
|
|
|
|
- import elementResizeDetectorMaker from 'element-resize-detector';
|
|
|
|
|
- import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
|
|
|
|
- import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
|
|
|
|
- import { useGo, useRedo } from '@/hooks/web/usePage';
|
|
|
|
|
- import { Render, getRender } from '@/components/Render';
|
|
|
|
|
-
|
|
|
|
|
- const props = defineProps({
|
|
|
|
|
- collapsed: {
|
|
|
|
|
- type: Boolean,
|
|
|
|
|
- },
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const emit = defineEmits(['pageFullScreen']);
|
|
|
|
|
-
|
|
|
|
|
- const { getDarkTheme, getAppTheme } = useDesignSetting();
|
|
|
|
|
- const { getNavMode, getMenuSetting } = useProjectSetting();
|
|
|
|
|
- const settingStore = useProjectSettingStore();
|
|
|
|
|
- const go = useGo();
|
|
|
|
|
- const route = useRoute();
|
|
|
|
|
- const router = useRouter();
|
|
|
|
|
- const tabsViewStore = useTabsViewStore();
|
|
|
|
|
- const asyncRouteStore = useAsyncRouteStore();
|
|
|
|
|
- const navScroll: any = ref(null);
|
|
|
|
|
- const navWrap: any = ref(null);
|
|
|
|
|
- const activeKey = ref(route.fullPath);
|
|
|
|
|
- const scrollable = ref(false);
|
|
|
|
|
- const dropdownX = ref(0);
|
|
|
|
|
- const dropdownY = ref(0);
|
|
|
|
|
- const showDropdown = ref(false);
|
|
|
|
|
- const closeLeftTab = ref(false);
|
|
|
|
|
- const closeRightTab = ref(false);
|
|
|
|
|
- const closeCurrent = ref(false);
|
|
|
|
|
- const currentTabRoute = ref(null);
|
|
|
|
|
- const refreshCurrent = ref(true);
|
|
|
|
|
-
|
|
|
|
|
- // 获取简易的路由对象
|
|
|
|
|
- const getSimpleRoute = (route): RouteItem => {
|
|
|
|
|
- const { fullPath, hash, meta, name, params, path, query } = route;
|
|
|
|
|
- return { fullPath, hash, meta, name, params, path, query };
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const isMixMenuNoneSub = computed(() => {
|
|
|
|
|
- const mixMenu = settingStore.menuSetting.mixMenu;
|
|
|
|
|
- const currentRoute = useRoute();
|
|
|
|
|
- const navMode = unref(getNavMode);
|
|
|
|
|
- if (unref(navMode) != 'horizontal-mix') return true;
|
|
|
|
|
- if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- return true;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- //动态组装样式 菜单缩进
|
|
|
|
|
- const getChangeStyle = computed(() => {
|
|
|
|
|
- const { collapsed } = props;
|
|
|
|
|
- const navMode = unref(getNavMode);
|
|
|
|
|
- const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
|
|
|
|
- let lenNum =
|
|
|
|
|
- navMode === 'horizontal' || !isMixMenuNoneSub.value ? '0px' : collapsed ? `${minMenuWidth}px` : `${menuWidth}px`;
|
|
|
|
|
- return {
|
|
|
|
|
- left: lenNum,
|
|
|
|
|
- width: `calc(100%)`,
|
|
|
|
|
- };
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const getPageFullScreen = inject('isPageFullScreen');
|
|
|
|
|
-
|
|
|
|
|
- //tags 右键右侧下拉菜单
|
|
|
|
|
- // const TabsMenuOptions = computed(() => {
|
|
|
|
|
- // const disabled = unref(tabsList).length === 1;
|
|
|
|
|
- // return [
|
|
|
|
|
- // {
|
|
|
|
|
- // label: unref(getPageFullScreen) ? '退出全屏' : '内容全屏',
|
|
|
|
|
- // key: '7',
|
|
|
|
|
- // icon: getPageFullScreen ? renderIcon(CompressOutlined) : renderIcon(ExpandOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: '刷新当前',
|
|
|
|
|
- // key: '1',
|
|
|
|
|
- // icon: renderIcon(ReloadOutlined),
|
|
|
|
|
- // disabled: refreshCurrent.value,
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: `关闭当前`,
|
|
|
|
|
- // key: '2',
|
|
|
|
|
- // disabled: closeCurrent.value || disabled,
|
|
|
|
|
- // icon: renderIcon(MinusOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: '关闭其他',
|
|
|
|
|
- // key: '3',
|
|
|
|
|
- // disabled: disabled,
|
|
|
|
|
- // icon: renderIcon(SwapOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: '关闭左侧',
|
|
|
|
|
- // key: '5',
|
|
|
|
|
- // disabled: closeLeftTab.value,
|
|
|
|
|
- // icon: renderIcon(DoubleLeftOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: '关闭右侧',
|
|
|
|
|
- // key: '6',
|
|
|
|
|
- // disabled: closeRightTab.value,
|
|
|
|
|
- // icon: renderIcon(DoubleRightOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // label: '关闭全部',
|
|
|
|
|
- // key: '4',
|
|
|
|
|
- // disabled: disabled,
|
|
|
|
|
- // icon: renderIcon(CloseOutlined),
|
|
|
|
|
- // },
|
|
|
|
|
- // ];
|
|
|
|
|
- // });
|
|
|
|
|
-
|
|
|
|
|
- let routes: RouteItem[] = [];
|
|
|
|
|
- try {
|
|
|
|
|
- const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
|
|
|
|
|
- routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)];
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- routes = [getSimpleRoute(route)];
|
|
|
|
|
- }
|
|
|
|
|
- // 初始化标签页
|
|
|
|
|
- tabsViewStore.initTabs(routes);
|
|
|
|
|
-
|
|
|
|
|
- // 移除缓存组件名称
|
|
|
|
|
- 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);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 标签页列表
|
|
|
|
|
- const tabsList: any = computed(() => tabsViewStore.tabsList);
|
|
|
|
|
- const whiteList: string[] = [PageEnum.BASE_LOGIN_NAME, PageEnum.REDIRECT_NAME, PageEnum.ERROR_PAGE_NAME];
|
|
|
|
|
-
|
|
|
|
|
- const isDisabled = computed(() => unref(tabsList).length === 1);
|
|
|
|
|
-
|
|
|
|
|
- watch(
|
|
|
|
|
- () => route.fullPath,
|
|
|
|
|
- (to) => {
|
|
|
|
|
- // 如果您用的路由模式是 hash 请去掉,|| route.hash 判断条件
|
|
|
|
|
- if (whiteList.includes(route.name as string) || route.hash || route.meta.tagView === false) return;
|
|
|
|
|
- activeKey.value = to;
|
|
|
|
|
- currentTabRoute.value = null;
|
|
|
|
|
- refreshCurrent.value = false;
|
|
|
|
|
- tabsViewStore.addTabs(getSimpleRoute(route));
|
|
|
|
|
- nextTick().then(() => {
|
|
|
|
|
- updateNavScroll(true);
|
|
|
|
|
- refreshTabDisabled(getTabIndex());
|
|
|
|
|
- });
|
|
|
|
|
- },
|
|
|
|
|
- { immediate: true },
|
|
|
|
|
- );
|
|
|
|
|
- // 在页面关闭或刷新之前,保存数据
|
|
|
|
|
- window.addEventListener('beforeunload', () => {
|
|
|
|
|
- storage.set(TABS_ROUTES, JSON.stringify(tabsList.value));
|
|
|
|
|
- });
|
|
|
|
|
- // 关闭当前页面
|
|
|
|
|
- const removeTab = (route) => {
|
|
|
|
|
- if (tabsList.value.length === 1) {
|
|
|
|
|
- return ElMessage({
|
|
|
|
|
- message: '这已经是最后一页,不能再关闭了!',
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- const routeInfo = unref(route);
|
|
|
|
|
- //判断当前页标签状态 阻止关闭 只是提供思路,更多个性化功能,自行调整
|
|
|
|
|
- if (routeInfo.meta.state && routeInfo.meta.state === 'undone') {
|
|
|
|
|
- const { content } = routeInfo.meta.dialogOptions;
|
|
|
|
|
- ElMessageBox.confirm(content, '提示', {
|
|
|
|
|
- confirmButtonText: '确定',
|
|
|
|
|
- cancelButtonText: '取消',
|
|
|
|
|
- type: 'warning',
|
|
|
|
|
- center: true,
|
|
|
|
|
- }).then(() => {
|
|
|
|
|
- directRemoveTab(route);
|
|
|
|
|
- });
|
|
|
|
|
- } else directRemoveTab(route);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- function directRemoveTab(route) {
|
|
|
|
|
- delKeepAliveCompName();
|
|
|
|
|
- tabsViewStore.closeCurrentTab(route);
|
|
|
|
|
- asyncRouteStore.removeKeepAliveComponents([route.value.name]);
|
|
|
|
|
- // 如果关闭的是当前页
|
|
|
|
|
- if (activeKey.value === route.value.fullPath) {
|
|
|
|
|
- const currentRoute = tabsList.value[Math.max(0, tabsList.value.length - 1)];
|
|
|
|
|
- activeKey.value = currentRoute.fullPath;
|
|
|
|
|
- router.push(currentRoute);
|
|
|
|
|
- }
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 刷新页面
|
|
|
|
|
- async function reloadPage() {
|
|
|
|
|
- delKeepAliveCompName();
|
|
|
|
|
- const redo = useRedo(router);
|
|
|
|
|
- await redo();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 注入刷新页面方法
|
|
|
|
|
- provide('reloadPage', reloadPage);
|
|
|
|
|
-
|
|
|
|
|
- // 关闭左侧
|
|
|
|
|
- const closeLeft = (route) => {
|
|
|
|
|
- tabsViewStore.closeLeftTabs(route, activeKey.value);
|
|
|
|
|
- router.replace(route.fullPath);
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 关闭右侧
|
|
|
|
|
- const closeRight = (route) => {
|
|
|
|
|
- tabsViewStore.closeRightTabs(route, activeKey.value);
|
|
|
|
|
- router.replace(route.fullPath);
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- };
|
|
|
|
|
- // 关闭其他
|
|
|
|
|
- const closeOther = (route) => {
|
|
|
|
|
- tabsViewStore.closeOtherTabs(route, activeKey.value);
|
|
|
|
|
- router.replace(route.fullPath);
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- };
|
|
|
|
|
- // 关闭全部
|
|
|
|
|
- const closeAll = () => {
|
|
|
|
|
- tabsViewStore.closeAllTabs();
|
|
|
|
|
- router.replace(PageEnum.BASE_HOME_REDIRECT);
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- //当前路由对象 或者 右键选择tab路由对象
|
|
|
|
|
- const getCurrentTabRoute = computed(() => (currentTabRoute.value ? currentTabRoute.value : route));
|
|
|
|
|
-
|
|
|
|
|
- //tab 操作
|
|
|
|
|
- const closeHandleSelect = (key) => {
|
|
|
|
|
- switch (key) {
|
|
|
|
|
- //刷新
|
|
|
|
|
- case '1':
|
|
|
|
|
- reloadPage();
|
|
|
|
|
- break;
|
|
|
|
|
- //关闭
|
|
|
|
|
- case '2':
|
|
|
|
|
- removeTab(getCurrentTabRoute);
|
|
|
|
|
- break;
|
|
|
|
|
- //关闭其他
|
|
|
|
|
- case '3':
|
|
|
|
|
- closeOther(getCurrentTabRoute);
|
|
|
|
|
- break;
|
|
|
|
|
- //关闭所有
|
|
|
|
|
- case '4':
|
|
|
|
|
- closeAll();
|
|
|
|
|
- break;
|
|
|
|
|
- //关闭左侧
|
|
|
|
|
- case '5':
|
|
|
|
|
- closeLeft(getCurrentTabRoute);
|
|
|
|
|
- break;
|
|
|
|
|
- //关闭右侧
|
|
|
|
|
- case '6':
|
|
|
|
|
- closeRight(getCurrentTabRoute);
|
|
|
|
|
- break;
|
|
|
|
|
- //内容页全屏
|
|
|
|
|
- case '7':
|
|
|
|
|
- emit('pageFullScreen');
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- updateNavScroll();
|
|
|
|
|
- showDropdown.value = 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 / 2 <= containerWidth) return;
|
|
|
|
|
- const scrollLeft =
|
|
|
|
|
- navWidth - currentScroll > containerWidth * 2 ? currentScroll + containerWidth : navWidth - containerWidth;
|
|
|
|
|
- scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * @param autoScroll 是否开启自动滚动功能
|
|
|
|
|
- */
|
|
|
|
|
- function updateNavScroll(autoScroll?: boolean) {
|
|
|
|
|
- if (!navScroll.value) return;
|
|
|
|
|
- const containerWidth = navScroll.value.offsetWidth;
|
|
|
|
|
- const navWidth = navScroll.value.scrollWidth;
|
|
|
|
|
- if (containerWidth < navWidth) {
|
|
|
|
|
- scrollable.value = true;
|
|
|
|
|
- if (autoScroll) {
|
|
|
|
|
- let tagList = navScroll.value.querySelectorAll('.tabs-card-scroll-item') || [];
|
|
|
|
|
- [...tagList].forEach((tag: HTMLElement) => {
|
|
|
|
|
- // fix SyntaxError
|
|
|
|
|
- if (tag.id === `tag${activeKey.value.split('/').join('\/')}`) {
|
|
|
|
|
- tag.scrollIntoView && tag.scrollIntoView();
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- scrollable.value = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function handleResize() {
|
|
|
|
|
- updateNavScroll(true);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function handleContextMenu(e, item, index) {
|
|
|
|
|
- e.preventDefault();
|
|
|
|
|
- showDropdown.value = false;
|
|
|
|
|
- currentTabRoute.value = toRaw(item);
|
|
|
|
|
- refreshCurrent.value = !(activeKey.value === item.fullPath);
|
|
|
|
|
- refreshTabDisabled(index);
|
|
|
|
|
-
|
|
|
|
|
- nextTick().then(() => {
|
|
|
|
|
- showDropdown.value = true;
|
|
|
|
|
- dropdownX.value = e.clientX;
|
|
|
|
|
- dropdownY.value = e.clientY;
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function getTabIndex() {
|
|
|
|
|
- return unref(tabsList).findIndex((item) => item.fullPath === activeKey.value);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // function onClickOutside() {
|
|
|
|
|
- // showDropdown.value = false;
|
|
|
|
|
- // currentTabRoute.value = null;
|
|
|
|
|
- // refreshCurrent.value = false;
|
|
|
|
|
- // const index = getTabIndex();
|
|
|
|
|
- // refreshTabDisabled(index);
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- function refreshTabDisabled(index) {
|
|
|
|
|
- closeCurrent.value = unref(tabsList)[index].meta?.affix ?? false;
|
|
|
|
|
- closeLeftTab.value = !(unref(tabsList).length > 0 && index > 0);
|
|
|
|
|
- closeRightTab.value = !(index < unref(tabsList).length && index != unref(tabsList).length - 1);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //tags 跳转页面
|
|
|
|
|
- function goPage(e) {
|
|
|
|
|
- const { fullPath } = e;
|
|
|
|
|
- closeCurrent.value = e.meta?.affix ?? false;
|
|
|
|
|
- if (fullPath === route.fullPath) return;
|
|
|
|
|
- activeKey.value = fullPath;
|
|
|
|
|
- go(e, true);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- //删除tab
|
|
|
|
|
- function closeTabItem(e) {
|
|
|
|
|
- const { fullPath } = e;
|
|
|
|
|
- const routeInfo = ref(tabsList.value.find((item) => item.fullPath == fullPath));
|
|
|
|
|
- removeTab(routeInfo);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- onMounted(() => {
|
|
|
|
|
- onElementResize();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- function onElementResize() {
|
|
|
|
|
- let observer;
|
|
|
|
|
- observer = elementResizeDetectorMaker();
|
|
|
|
|
- observer.listenTo(navWrap.value, handleResize);
|
|
|
|
|
- }
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-<style lang="scss" scoped>
|
|
|
|
|
- .tabs-view {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- padding: 6px 0;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- transition: all 0.2s ease-in-out;
|
|
|
|
|
-
|
|
|
|
|
- &-main {
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- max-width: 100%;
|
|
|
|
|
- min-width: 100%;
|
|
|
|
|
- padding: 0 10px;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card {
|
|
|
|
|
- -webkit-box-flex: 1;
|
|
|
|
|
- flex-grow: 1;
|
|
|
|
|
- flex-shrink: 1;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- position: relative;
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card-prev,
|
|
|
|
|
- .tabs-card-next {
|
|
|
|
|
- width: 32px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- line-height: 32px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
-
|
|
|
|
|
- .n-icon {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- width: 32px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card-prev {
|
|
|
|
|
- left: 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card-next {
|
|
|
|
|
- right: 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card-next-hide,
|
|
|
|
|
- .tabs-card-prev-hide {
|
|
|
|
|
- display: none;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &-scroll {
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
-
|
|
|
|
|
- &-item {
|
|
|
|
|
- // background: v-bind(getCardColor);
|
|
|
|
|
- // color: v-bind(getBaseColor);
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- color: #333;
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- padding: 6px 16px 4px;
|
|
|
|
|
- border-radius: 3px;
|
|
|
|
|
- margin-right: 6px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- display: inline-block;
|
|
|
|
|
- position: relative;
|
|
|
|
|
- flex: 0 0 auto;
|
|
|
|
|
-
|
|
|
|
|
- span {
|
|
|
|
|
- float: left;
|
|
|
|
|
- vertical-align: middle;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- color: #515a6e;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .el-icon {
|
|
|
|
|
- height: 22px;
|
|
|
|
|
- width: 21px;
|
|
|
|
|
- margin-right: -6px;
|
|
|
|
|
- position: relative;
|
|
|
|
|
- vertical-align: middle;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- color: #808695;
|
|
|
|
|
-
|
|
|
|
|
- &:hover {
|
|
|
|
|
- color: #515a6e !important;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- svg {
|
|
|
|
|
- height: 21px;
|
|
|
|
|
- display: inline-block;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .active-item {
|
|
|
|
|
- color: v-bind(getAppTheme);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-card-scrollable {
|
|
|
|
|
- padding: 0 32px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-close {
|
|
|
|
|
- min-width: 32px;
|
|
|
|
|
- width: 32px;
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- line-height: 32px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- border-radius: 2px;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- //margin-right: 10px;
|
|
|
|
|
- &-btn {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-view-default-background {
|
|
|
|
|
- background: #f5f7f9;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-view-dark-background {
|
|
|
|
|
- background: #101014;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-view-fix {
|
|
|
|
|
- position: fixed;
|
|
|
|
|
- z-index: 5;
|
|
|
|
|
- padding: 6px 19px 6px 10px;
|
|
|
|
|
- left: 200px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .tabs-view-fixed-header {
|
|
|
|
|
- top: 0;
|
|
|
|
|
- }
|
|
|
|
|
-</style>
|
|
|