mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	Refactor/tags (#332)
* refactor: tags * chore: update * chore: update * chore: update * chore: update
This commit is contained in:
		
							parent
							
								
									7c84d9eb70
								
							
						
					
					
						commit
						cbe539c727
					
				@ -123,13 +123,19 @@ const tabsRouter = {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      path: "/tabs/detail",
 | 
					      path: "/tabs/query-detail",
 | 
				
			||||||
      name: "TabDetail",
 | 
					      name: "TabQueryDetail",
 | 
				
			||||||
      meta: {
 | 
					      meta: {
 | 
				
			||||||
        title: "",
 | 
					        // 不在menu菜单中显示
 | 
				
			||||||
        showLink: false,
 | 
					        showLink: false
 | 
				
			||||||
        dynamicLevel: 3,
 | 
					      }
 | 
				
			||||||
        refreshRedirect: "/tabs/index"
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      path: "/tabs/params-detail/:id",
 | 
				
			||||||
 | 
					      component: "params-detail",
 | 
				
			||||||
 | 
					      name: "TabParamsDetail",
 | 
				
			||||||
 | 
					      meta: {
 | 
				
			||||||
 | 
					        showLink: false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
 | 
				
			|||||||
@ -156,7 +156,7 @@ watch(
 | 
				
			|||||||
            >
 | 
					            >
 | 
				
			||||||
              <el-divider class="tab-divider" border-style="dashed" />
 | 
					              <el-divider class="tab-divider" border-style="dashed" />
 | 
				
			||||||
              <el-scrollbar height="220px">
 | 
					              <el-scrollbar height="220px">
 | 
				
			||||||
                <ul class="flex flex-wrap px-2 ml-2">
 | 
					                <ul class="flex-wrap px-2 ml-2">
 | 
				
			||||||
                  <li
 | 
					                  <li
 | 
				
			||||||
                    v-for="(item, key) in pageList"
 | 
					                    v-for="(item, key) in pageList"
 | 
				
			||||||
                    :key="key"
 | 
					                    :key="key"
 | 
				
			||||||
 | 
				
			|||||||
@ -428,7 +428,7 @@ function scrollInitMove() {
 | 
				
			|||||||
      if (timer) clearTimeout(timer);
 | 
					      if (timer) clearTimeout(timer);
 | 
				
			||||||
      copyHtml.value = unref(slotList).innerHTML;
 | 
					      copyHtml.value = unref(slotList).innerHTML;
 | 
				
			||||||
      setTimeout(() => {
 | 
					      setTimeout(() => {
 | 
				
			||||||
        realBoxHeight.value = unref(realBox).offsetHeight;
 | 
					        realBoxHeight.value = unref(realBox)?.offsetHeight;
 | 
				
			||||||
        scrollMove();
 | 
					        scrollMove();
 | 
				
			||||||
      }, 0);
 | 
					      }, 0);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref, watch } from "vue";
 | 
					 | 
				
			||||||
import { isEqual } from "lodash-unified";
 | 
					import { isEqual } from "lodash-unified";
 | 
				
			||||||
import { transformI18n } from "/@/plugins/i18n";
 | 
					import { transformI18n } from "/@/plugins/i18n";
 | 
				
			||||||
 | 
					import { ref, watch, onMounted, toRaw } from "vue";
 | 
				
			||||||
import { getParentPaths, findRouteByPath } from "/@/router/utils";
 | 
					import { getParentPaths, findRouteByPath } from "/@/router/utils";
 | 
				
			||||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
					import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
				
			||||||
import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
 | 
					import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
 | 
				
			||||||
@ -14,19 +14,24 @@ const multiTags: any = useMultiTagsStoreHook().multiTags;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const isDashboard = (route: RouteLocationMatched): boolean | string => {
 | 
					const isDashboard = (route: RouteLocationMatched): boolean | string => {
 | 
				
			||||||
  const name = route && (route.name as string);
 | 
					  const name = route && (route.name as string);
 | 
				
			||||||
  if (!name) {
 | 
					  if (!name) return false;
 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
 | 
					  return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getBreadcrumb = (): void => {
 | 
					const getBreadcrumb = (): void => {
 | 
				
			||||||
  // 当前路由信息
 | 
					  // 当前路由信息
 | 
				
			||||||
  let currentRoute;
 | 
					  let currentRoute;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (Object.keys(route.query).length > 0) {
 | 
					  if (Object.keys(route.query).length > 0) {
 | 
				
			||||||
    multiTags.forEach(item => {
 | 
					    multiTags.forEach(item => {
 | 
				
			||||||
      if (isEqual(route.query, item?.query)) {
 | 
					      if (isEqual(route.query, item?.query)) {
 | 
				
			||||||
        currentRoute = item;
 | 
					        currentRoute = toRaw(item);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } else if (Object.keys(route.params).length > 0) {
 | 
				
			||||||
 | 
					    multiTags.forEach(item => {
 | 
				
			||||||
 | 
					      if (isEqual(route.params, item?.params)) {
 | 
				
			||||||
 | 
					        currentRoute = toRaw(item);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
@ -38,29 +43,12 @@ const getBreadcrumb = (): void => {
 | 
				
			|||||||
  let matched = [];
 | 
					  let matched = [];
 | 
				
			||||||
  // 获取每个父级路径对应的路由信息
 | 
					  // 获取每个父级路径对应的路由信息
 | 
				
			||||||
  parentRoutes.forEach(path => {
 | 
					  parentRoutes.forEach(path => {
 | 
				
			||||||
    if (path !== "/") {
 | 
					    if (path !== "/") matched.push(findRouteByPath(path, routes));
 | 
				
			||||||
      matched.push(findRouteByPath(path, routes));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  if (router.currentRoute.value.meta?.refreshRedirect) {
 | 
					 | 
				
			||||||
    matched.unshift(
 | 
					 | 
				
			||||||
      findRouteByPath(
 | 
					 | 
				
			||||||
        router.currentRoute.value.meta.refreshRedirect as string,
 | 
					 | 
				
			||||||
        routes
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    // 过滤与子级相同标题的父级路由
 | 
					 | 
				
			||||||
    matched = matched.filter(item => {
 | 
					 | 
				
			||||||
      return !item.redirect || (item.redirect && item.children.length !== 1);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (currentRoute?.path !== "/welcome") {
 | 
					 | 
				
			||||||
    matched.push(currentRoute);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const first = matched[0];
 | 
					  if (currentRoute?.path !== "/welcome") matched.push(currentRoute);
 | 
				
			||||||
  if (!isDashboard(first)) {
 | 
					
 | 
				
			||||||
 | 
					  if (!isDashboard(matched[0])) {
 | 
				
			||||||
    matched = [
 | 
					    matched = [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        path: "/welcome",
 | 
					        path: "/welcome",
 | 
				
			||||||
@ -70,60 +58,51 @@ const getBreadcrumb = (): void => {
 | 
				
			|||||||
    ].concat(matched);
 | 
					    ].concat(matched);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  matched.forEach((item, index) => {
 | 
				
			||||||
 | 
					    if (currentRoute.query || currentRoute.params) return;
 | 
				
			||||||
 | 
					    if (item?.children) {
 | 
				
			||||||
 | 
					      item.children.forEach(v => {
 | 
				
			||||||
 | 
					        if (v.meta.title === item.meta.title) {
 | 
				
			||||||
 | 
					          matched.splice(index, 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  levelList.value = matched.filter(
 | 
					  levelList.value = matched.filter(
 | 
				
			||||||
    item => item?.meta && item?.meta.title !== false
 | 
					    item => item?.meta && item?.meta.title !== false
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
getBreadcrumb();
 | 
					const handleLink = (item: RouteLocationMatched): void => {
 | 
				
			||||||
 | 
					  const { redirect, path } = item;
 | 
				
			||||||
 | 
					  if (redirect) {
 | 
				
			||||||
 | 
					    router.push(redirect as any);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    router.push(path);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  getBreadcrumb();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch(
 | 
					watch(
 | 
				
			||||||
  () => route.path,
 | 
					  () => route.path,
 | 
				
			||||||
  () => getBreadcrumb()
 | 
					  () => {
 | 
				
			||||||
);
 | 
					    getBreadcrumb();
 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => route.query,
 | 
					 | 
				
			||||||
  () => getBreadcrumb()
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const handleLink = (item: RouteLocationMatched): any => {
 | 
					 | 
				
			||||||
  const { redirect, path } = item;
 | 
					 | 
				
			||||||
  if (redirect) {
 | 
					 | 
				
			||||||
    router.push(redirect.toString());
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  router.push(path);
 | 
					);
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <el-breadcrumb class="app-breadcrumb select-none" separator="/">
 | 
					  <el-breadcrumb class="!leading-[50px] select-none" separator="/">
 | 
				
			||||||
    <transition-group appear name="breadcrumb">
 | 
					    <transition-group appear name="breadcrumb">
 | 
				
			||||||
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
 | 
					      <el-breadcrumb-item v-for="item in levelList" :key="item.path">
 | 
				
			||||||
        <span
 | 
					        <a @click.prevent="handleLink(item)">
 | 
				
			||||||
          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
 | 
					 | 
				
			||||||
          class="no-redirect"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          {{ transformI18n(item.meta.title) }}
 | 
					 | 
				
			||||||
        </span>
 | 
					 | 
				
			||||||
        <a v-else @click.prevent="handleLink(item)">
 | 
					 | 
				
			||||||
          {{ transformI18n(item.meta.title) }}
 | 
					          {{ transformI18n(item.meta.title) }}
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
      </el-breadcrumb-item>
 | 
					      </el-breadcrumb-item>
 | 
				
			||||||
    </transition-group>
 | 
					    </transition-group>
 | 
				
			||||||
  </el-breadcrumb>
 | 
					  </el-breadcrumb>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss" scoped>
 | 
					 | 
				
			||||||
.app-breadcrumb.el-breadcrumb {
 | 
					 | 
				
			||||||
  display: inline-block;
 | 
					 | 
				
			||||||
  font-size: 14px;
 | 
					 | 
				
			||||||
  line-height: 50px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .no-redirect {
 | 
					 | 
				
			||||||
    color: #97a8be;
 | 
					 | 
				
			||||||
    cursor: text;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,123 +1,53 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  ref,
 | 
					 | 
				
			||||||
  watch,
 | 
					 | 
				
			||||||
  unref,
 | 
					 | 
				
			||||||
  toRaw,
 | 
					 | 
				
			||||||
  reactive,
 | 
					 | 
				
			||||||
  nextTick,
 | 
					 | 
				
			||||||
  computed,
 | 
					 | 
				
			||||||
  ComputedRef,
 | 
					 | 
				
			||||||
  CSSProperties,
 | 
					 | 
				
			||||||
  onBeforeMount,
 | 
					 | 
				
			||||||
  getCurrentInstance
 | 
					 | 
				
			||||||
} from "vue";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import close from "/@/assets/svg/close.svg?component";
 | 
					 | 
				
			||||||
import refresh from "/@/assets/svg/refresh.svg?component";
 | 
					 | 
				
			||||||
import closeAll from "/@/assets/svg/close_all.svg?component";
 | 
					 | 
				
			||||||
import closeLeft from "/@/assets/svg/close_left.svg?component";
 | 
					 | 
				
			||||||
import closeOther from "/@/assets/svg/close_other.svg?component";
 | 
					 | 
				
			||||||
import closeRight from "/@/assets/svg/close_right.svg?component";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { useI18n } from "vue-i18n";
 | 
					 | 
				
			||||||
import { emitter } from "/@/utils/mitt";
 | 
					import { emitter } from "/@/utils/mitt";
 | 
				
			||||||
import type { StorageConfigs } from "/#/index";
 | 
					import { RouteConfigs } from "../../types";
 | 
				
			||||||
 | 
					import { useTags } from "../../hooks/useTag";
 | 
				
			||||||
import { routerArrays } from "/@/layout/types";
 | 
					import { routerArrays } from "/@/layout/types";
 | 
				
			||||||
import { useRoute, useRouter } from "vue-router";
 | 
					 | 
				
			||||||
import { isEqual, isEmpty } from "lodash-unified";
 | 
					import { isEqual, isEmpty } from "lodash-unified";
 | 
				
			||||||
import { transformI18n, $t } from "/@/plugins/i18n";
 | 
					import { toggleClass, removeClass } from "@pureadmin/utils";
 | 
				
			||||||
import { RouteConfigs, tagsViewsType } from "../../types";
 | 
					import { useResizeObserver, useDebounceFn } from "@vueuse/core";
 | 
				
			||||||
import { useSettingStoreHook } from "/@/store/modules/settings";
 | 
					import { useSettingStoreHook } from "/@/store/modules/settings";
 | 
				
			||||||
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
 | 
					import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
 | 
				
			||||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
					import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
				
			||||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
					import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
				
			||||||
import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core";
 | 
					import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  toggleClass,
 | 
					 | 
				
			||||||
  removeClass,
 | 
					 | 
				
			||||||
  hasClass,
 | 
					 | 
				
			||||||
  storageLocal
 | 
					 | 
				
			||||||
} from "@pureadmin/utils";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { t } = useI18n();
 | 
					const {
 | 
				
			||||||
const route = useRoute();
 | 
					  route,
 | 
				
			||||||
const router = useRouter();
 | 
					  router,
 | 
				
			||||||
const translateX = ref<number>(0);
 | 
					  visible,
 | 
				
			||||||
const activeIndex = ref<number>(-1);
 | 
					  showTags,
 | 
				
			||||||
let refreshButton = "refresh-button";
 | 
					  instance,
 | 
				
			||||||
const instance = getCurrentInstance();
 | 
					  multiTags,
 | 
				
			||||||
const pureSetting = useSettingStoreHook();
 | 
					  tagsViews,
 | 
				
			||||||
const tabDom = templateRef<HTMLElement | null>("tabDom", null);
 | 
					  buttonTop,
 | 
				
			||||||
const containerDom = templateRef<HTMLElement | null>("containerDom", null);
 | 
					  buttonLeft,
 | 
				
			||||||
const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null);
 | 
					  showModel,
 | 
				
			||||||
const showTags =
 | 
					  translateX,
 | 
				
			||||||
  ref(storageLocal.getItem<StorageConfigs>("responsive-configure").hideTabs) ??
 | 
					  activeIndex,
 | 
				
			||||||
  "false";
 | 
					  getTabStyle,
 | 
				
			||||||
// @ts-expect-error
 | 
					  iconIsActive,
 | 
				
			||||||
let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => {
 | 
					  linkIsActive,
 | 
				
			||||||
  return useMultiTagsStoreHook()?.multiTags;
 | 
					  currentSelect,
 | 
				
			||||||
});
 | 
					  scheduleIsActive,
 | 
				
			||||||
 | 
					  getContextMenuStyle,
 | 
				
			||||||
 | 
					  closeMenu,
 | 
				
			||||||
 | 
					  onMounted,
 | 
				
			||||||
 | 
					  onMouseenter,
 | 
				
			||||||
 | 
					  onMouseleave,
 | 
				
			||||||
 | 
					  transformI18n
 | 
				
			||||||
 | 
					} = useTags();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const linkIsActive = computed(() => {
 | 
					const tabDom = ref();
 | 
				
			||||||
  return item => {
 | 
					const containerDom = ref();
 | 
				
			||||||
    if (Object.keys(route.query).length === 0) {
 | 
					const scrollbarDom = ref();
 | 
				
			||||||
      if (route.path === item.path) {
 | 
					 | 
				
			||||||
        return "is-active";
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return "";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (isEqual(route?.query, item?.query)) {
 | 
					 | 
				
			||||||
        return "is-active";
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return "";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const scheduleIsActive = computed(() => {
 | 
					 | 
				
			||||||
  return item => {
 | 
					 | 
				
			||||||
    if (Object.keys(route.query).length === 0) {
 | 
					 | 
				
			||||||
      if (route.path === item.path) {
 | 
					 | 
				
			||||||
        return "schedule-active";
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return "";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (isEqual(route?.query, item?.query)) {
 | 
					 | 
				
			||||||
        return "schedule-active";
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return "";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const iconIsActive = computed(() => {
 | 
					 | 
				
			||||||
  return (item, index) => {
 | 
					 | 
				
			||||||
    if (index === 0) return;
 | 
					 | 
				
			||||||
    if (Object.keys(route.query).length === 0) {
 | 
					 | 
				
			||||||
      if (route.path === item.path) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (isEqual(route?.query, item?.query)) {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dynamicTagView = () => {
 | 
					const dynamicTagView = () => {
 | 
				
			||||||
  const index = multiTags.value.findIndex(item => {
 | 
					  const index = multiTags.value.findIndex(item => {
 | 
				
			||||||
    if (item?.query) {
 | 
					    if (item.query) {
 | 
				
			||||||
      return isEqual(route?.query, item?.query);
 | 
					      return isEqual(route.query, item.query);
 | 
				
			||||||
 | 
					    } else if (item.params) {
 | 
				
			||||||
 | 
					      return isEqual(route.params, item.params);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return item.path === route.path;
 | 
					      return item.path === route.path;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -125,23 +55,9 @@ const dynamicTagView = () => {
 | 
				
			|||||||
  moveToView(index);
 | 
					  moveToView(index);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch([route], () => {
 | 
					 | 
				
			||||||
  activeIndex.value = -1;
 | 
					 | 
				
			||||||
  dynamicTagView();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
useResizeObserver(
 | 
					 | 
				
			||||||
  scrollbarDom,
 | 
					 | 
				
			||||||
  useDebounceFn(() => {
 | 
					 | 
				
			||||||
    dynamicTagView();
 | 
					 | 
				
			||||||
  }, 200)
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const tabNavPadding = 10;
 | 
					 | 
				
			||||||
const moveToView = (index: number): void => {
 | 
					const moveToView = (index: number): void => {
 | 
				
			||||||
  if (!instance.refs["dynamic" + index]) {
 | 
					  const tabNavPadding = 10;
 | 
				
			||||||
    return;
 | 
					  if (!instance.refs["dynamic" + index]) return;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const tabItemEl = instance.refs["dynamic" + index][0];
 | 
					  const tabItemEl = instance.refs["dynamic" + index][0];
 | 
				
			||||||
  const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
 | 
					  const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
 | 
				
			||||||
  const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
 | 
					  const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
 | 
				
			||||||
@ -151,7 +67,6 @@ const moveToView = (index: number): void => {
 | 
				
			|||||||
    : 0;
 | 
					    : 0;
 | 
				
			||||||
  // 已有标签页总长度(包含溢出部分)
 | 
					  // 已有标签页总长度(包含溢出部分)
 | 
				
			||||||
  const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
 | 
					  const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
 | 
					  if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
 | 
				
			||||||
    translateX.value = 0;
 | 
					    translateX.value = 0;
 | 
				
			||||||
  } else if (tabItemElOffsetLeft < -translateX.value) {
 | 
					  } else if (tabItemElOffsetLeft < -translateX.value) {
 | 
				
			||||||
@ -200,71 +115,6 @@ const handleScroll = (offset: number): void => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tagsViews = reactive<Array<tagsViewsType>>([
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: refresh,
 | 
					 | 
				
			||||||
    text: $t("buttons.hsreload"),
 | 
					 | 
				
			||||||
    divided: false,
 | 
					 | 
				
			||||||
    disabled: false,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: close,
 | 
					 | 
				
			||||||
    text: $t("buttons.hscloseCurrentTab"),
 | 
					 | 
				
			||||||
    divided: false,
 | 
					 | 
				
			||||||
    disabled: multiTags.value.length > 1 ? false : true,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: closeLeft,
 | 
					 | 
				
			||||||
    text: $t("buttons.hscloseLeftTabs"),
 | 
					 | 
				
			||||||
    divided: true,
 | 
					 | 
				
			||||||
    disabled: multiTags.value.length > 1 ? false : true,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: closeRight,
 | 
					 | 
				
			||||||
    text: $t("buttons.hscloseRightTabs"),
 | 
					 | 
				
			||||||
    divided: false,
 | 
					 | 
				
			||||||
    disabled: multiTags.value.length > 1 ? false : true,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: closeOther,
 | 
					 | 
				
			||||||
    text: $t("buttons.hscloseOtherTabs"),
 | 
					 | 
				
			||||||
    divided: true,
 | 
					 | 
				
			||||||
    disabled: multiTags.value.length > 2 ? false : true,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    icon: closeAll,
 | 
					 | 
				
			||||||
    text: $t("buttons.hscloseAllTabs"),
 | 
					 | 
				
			||||||
    divided: false,
 | 
					 | 
				
			||||||
    disabled: multiTags.value.length > 1 ? false : true,
 | 
					 | 
				
			||||||
    show: true
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 显示模式,默认灵动模式显示
 | 
					 | 
				
			||||||
const showModel = ref(
 | 
					 | 
				
			||||||
  storageLocal.getItem<StorageConfigs>("responsive-configure")?.showModel ||
 | 
					 | 
				
			||||||
    "smart"
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
if (!showModel.value) {
 | 
					 | 
				
			||||||
  const configure = storageLocal.getItem<StorageConfigs>(
 | 
					 | 
				
			||||||
    "responsive-configure"
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  configure.showModel = "card";
 | 
					 | 
				
			||||||
  storageLocal.setItem("responsive-configure", configure);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let visible = ref(false);
 | 
					 | 
				
			||||||
let buttonLeft = ref(0);
 | 
					 | 
				
			||||||
let buttonTop = ref(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 当前右键选中的路由信息
 | 
					 | 
				
			||||||
let currentSelect = ref({});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function dynamicRouteTag(value: string, parentPath: string): void {
 | 
					function dynamicRouteTag(value: string, parentPath: string): void {
 | 
				
			||||||
  const hasValue = multiTags.value.some(item => {
 | 
					  const hasValue = multiTags.value.some(item => {
 | 
				
			||||||
    return item.path === value;
 | 
					    return item.path === value;
 | 
				
			||||||
@ -292,8 +142,9 @@ function dynamicRouteTag(value: string, parentPath: string): void {
 | 
				
			|||||||
  concatPath(router.options.routes as any, value, parentPath);
 | 
					  concatPath(router.options.routes as any, value, parentPath);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 重新加载
 | 
					/** 刷新路由 */
 | 
				
			||||||
function onFresh() {
 | 
					function onFresh() {
 | 
				
			||||||
 | 
					  const refreshButton = "refresh-button";
 | 
				
			||||||
  toggleClass(true, refreshButton, document.querySelector(".rotate"));
 | 
					  toggleClass(true, refreshButton, document.querySelector(".rotate"));
 | 
				
			||||||
  const { fullPath, query } = unref(route);
 | 
					  const { fullPath, query } = unref(route);
 | 
				
			||||||
  router.replace({
 | 
					  router.replace({
 | 
				
			||||||
@ -313,6 +164,10 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
				
			|||||||
      if (item.path === obj.path) {
 | 
					      if (item.path === obj.path) {
 | 
				
			||||||
        return item.query === obj.query;
 | 
					        return item.query === obj.query;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (item.params) {
 | 
				
			||||||
 | 
					      if (item.path === obj.path) {
 | 
				
			||||||
 | 
					        return item.params === obj.params;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return item.path === obj.path;
 | 
					      return item.path === obj.path;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -351,24 +206,25 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
				
			|||||||
      : handleAliveRoute(route.matched, "delete");
 | 
					      : handleAliveRoute(route.matched, "delete");
 | 
				
			||||||
    // 如果删除当前激活tag就自动切换到最后一个tag
 | 
					    // 如果删除当前激活tag就自动切换到最后一个tag
 | 
				
			||||||
    if (tag === "left") return;
 | 
					    if (tag === "left") return;
 | 
				
			||||||
    nextTick(() => {
 | 
					    if (newRoute[0]?.query) {
 | 
				
			||||||
      router.push({
 | 
					      router.push({ name: newRoute[0].name, query: newRoute[0].query });
 | 
				
			||||||
        path: newRoute[0].path,
 | 
					    } else if (newRoute[0]?.params) {
 | 
				
			||||||
        query: newRoute[0].query
 | 
					      router.push({ name: newRoute[0].name, params: newRoute[0].params });
 | 
				
			||||||
      });
 | 
					    } else {
 | 
				
			||||||
    });
 | 
					      router.push({ path: newRoute[0].path });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    // 删除缓存路由
 | 
					    // 删除缓存路由
 | 
				
			||||||
    tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
 | 
					    tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
 | 
				
			||||||
    if (!multiTags.value.length) return;
 | 
					    if (!multiTags.value.length) return;
 | 
				
			||||||
    let isHasActiveTag = multiTags.value.some(item => {
 | 
					    if (multiTags.value.some(item => item.path === route.path)) return;
 | 
				
			||||||
      return item.path === route.path;
 | 
					    if (newRoute[0]?.query) {
 | 
				
			||||||
    });
 | 
					      router.push({ name: newRoute[0].name, query: newRoute[0].query });
 | 
				
			||||||
    !isHasActiveTag &&
 | 
					    } else if (newRoute[0]?.params) {
 | 
				
			||||||
      router.push({
 | 
					      router.push({ name: newRoute[0].name, params: newRoute[0].params });
 | 
				
			||||||
        path: newRoute[0].path,
 | 
					    } else {
 | 
				
			||||||
        query: newRoute[0].query
 | 
					      router.push({ path: newRoute[0].path });
 | 
				
			||||||
      });
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -385,7 +241,8 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
 | 
				
			|||||||
      path: selectRoute.path,
 | 
					      path: selectRoute.path,
 | 
				
			||||||
      meta: selectRoute.meta,
 | 
					      meta: selectRoute.meta,
 | 
				
			||||||
      name: selectRoute.name,
 | 
					      name: selectRoute.name,
 | 
				
			||||||
      query: selectRoute.query
 | 
					      query: selectRoute?.query,
 | 
				
			||||||
 | 
					      params: selectRoute?.params
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    selectTagRoute = { path: route.path, meta: route.meta };
 | 
					    selectTagRoute = { path: route.path, meta: route.meta };
 | 
				
			||||||
@ -394,7 +251,7 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
 | 
				
			|||||||
  // 当前路由信息
 | 
					  // 当前路由信息
 | 
				
			||||||
  switch (key) {
 | 
					  switch (key) {
 | 
				
			||||||
    case 0:
 | 
					    case 0:
 | 
				
			||||||
      // 重新加载
 | 
					      // 刷新路由
 | 
				
			||||||
      onFresh();
 | 
					      onFresh();
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    case 1:
 | 
					    case 1:
 | 
				
			||||||
@ -433,15 +290,11 @@ function handleCommand(command: any) {
 | 
				
			|||||||
  onClickDrop(key, item);
 | 
					  onClickDrop(key, item);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 触发右键中菜单的点击事件
 | 
					/** 触发右键中菜单的点击事件 */
 | 
				
			||||||
function selectTag(key, item) {
 | 
					function selectTag(key, item) {
 | 
				
			||||||
  onClickDrop(key, item, currentSelect.value);
 | 
					  onClickDrop(key, item, currentSelect.value);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function closeMenu() {
 | 
					 | 
				
			||||||
  visible.value = false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function showMenus(value: boolean) {
 | 
					function showMenus(value: boolean) {
 | 
				
			||||||
  Array.of(1, 2, 3, 4, 5).forEach(v => {
 | 
					  Array.of(1, 2, 3, 4, 5).forEach(v => {
 | 
				
			||||||
    tagsViews[v].show = value;
 | 
					    tagsViews[v].show = value;
 | 
				
			||||||
@ -454,7 +307,7 @@ function disabledMenus(value: boolean) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页
 | 
					/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
 | 
				
			||||||
function showMenuModel(
 | 
					function showMenuModel(
 | 
				
			||||||
  currentPath: string,
 | 
					  currentPath: string,
 | 
				
			||||||
  query: object = {},
 | 
					  query: object = {},
 | 
				
			||||||
@ -514,7 +367,7 @@ function openMenu(tag, e) {
 | 
				
			|||||||
    // 右键菜单为首页,只显示刷新
 | 
					    // 右键菜单为首页,只显示刷新
 | 
				
			||||||
    showMenus(false);
 | 
					    showMenus(false);
 | 
				
			||||||
    tagsViews[0].show = true;
 | 
					    tagsViews[0].show = true;
 | 
				
			||||||
  } else if (route.path !== tag.path) {
 | 
					  } else if (route.path !== tag.path && route.name !== tag.name) {
 | 
				
			||||||
    // 右键菜单不匹配当前路由,隐藏刷新
 | 
					    // 右键菜单不匹配当前路由,隐藏刷新
 | 
				
			||||||
    tagsViews[0].show = false;
 | 
					    tagsViews[0].show = false;
 | 
				
			||||||
    showMenuModel(tag.path, tag.query);
 | 
					    showMenuModel(tag.path, tag.query);
 | 
				
			||||||
@ -542,63 +395,36 @@ function openMenu(tag, e) {
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    buttonLeft.value = left;
 | 
					    buttonLeft.value = left;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  pureSetting.hiddenSideBar
 | 
					  useSettingStoreHook().hiddenSideBar
 | 
				
			||||||
    ? (buttonTop.value = e.clientY)
 | 
					    ? (buttonTop.value = e.clientY)
 | 
				
			||||||
    : (buttonTop.value = e.clientY - 40);
 | 
					    : (buttonTop.value = e.clientY - 40);
 | 
				
			||||||
  setTimeout(() => {
 | 
					  nextTick(() => {
 | 
				
			||||||
    visible.value = true;
 | 
					    visible.value = true;
 | 
				
			||||||
  }, 10);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 触发tags标签切换
 | 
					 | 
				
			||||||
function tagOnClick(item) {
 | 
					 | 
				
			||||||
  router.push({
 | 
					 | 
				
			||||||
    path: item?.path,
 | 
					 | 
				
			||||||
    query: item?.query
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  showMenuModel(item?.path, item?.query);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 鼠标移入
 | 
					/** 触发tags标签切换 */
 | 
				
			||||||
function onMouseenter(index) {
 | 
					function tagOnClick(item) {
 | 
				
			||||||
  if (index) activeIndex.value = index;
 | 
					  const { name, path } = item;
 | 
				
			||||||
  if (unref(showModel) === "smart") {
 | 
					  if (name) {
 | 
				
			||||||
    if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
 | 
					    if (item.query) {
 | 
				
			||||||
      return;
 | 
					      router.push({
 | 
				
			||||||
    toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]);
 | 
					        name,
 | 
				
			||||||
    toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]);
 | 
					        query: item.query
 | 
				
			||||||
  } else {
 | 
					      });
 | 
				
			||||||
    if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
 | 
					    } else if (item.params) {
 | 
				
			||||||
    toggleClass(true, "card-in", instance.refs["dynamic" + index][0]);
 | 
					      router.push({
 | 
				
			||||||
    toggleClass(false, "card-out", instance.refs["dynamic" + index][0]);
 | 
					        name,
 | 
				
			||||||
  }
 | 
					        params: item.params
 | 
				
			||||||
}
 | 
					      });
 | 
				
			||||||
 | 
					 | 
				
			||||||
// 鼠标移出
 | 
					 | 
				
			||||||
function onMouseleave(index) {
 | 
					 | 
				
			||||||
  activeIndex.value = -1;
 | 
					 | 
				
			||||||
  if (unref(showModel) === "smart") {
 | 
					 | 
				
			||||||
    if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]);
 | 
					 | 
				
			||||||
    toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
 | 
					 | 
				
			||||||
    toggleClass(false, "card-in", instance.refs["dynamic" + index][0]);
 | 
					 | 
				
			||||||
    toggleClass(true, "card-out", instance.refs["dynamic" + index][0]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch(
 | 
					 | 
				
			||||||
  () => visible.value,
 | 
					 | 
				
			||||||
  val => {
 | 
					 | 
				
			||||||
    if (val) {
 | 
					 | 
				
			||||||
      document.body.addEventListener("click", closeMenu);
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      document.body.removeEventListener("click", closeMenu);
 | 
					      router.push({ name });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    router.push({ path });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
);
 | 
					  // showMenuModel(item?.path, item?.query);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onBeforeMount(() => {
 | 
					onBeforeMount(() => {
 | 
				
			||||||
  if (!instance) return;
 | 
					  if (!instance) return;
 | 
				
			||||||
@ -626,14 +452,18 @@ onBeforeMount(() => {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getTabStyle = computed((): CSSProperties => {
 | 
					watch([route], () => {
 | 
				
			||||||
  return {
 | 
					  activeIndex.value = -1;
 | 
				
			||||||
    transform: `translateX(${translateX.value}px)`
 | 
					  dynamicTagView();
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getContextMenuStyle = computed((): CSSProperties => {
 | 
					onMounted(() => {
 | 
				
			||||||
  return { left: buttonLeft.value + "px", top: buttonTop.value + "px" };
 | 
					  useResizeObserver(
 | 
				
			||||||
 | 
					    scrollbarDom,
 | 
				
			||||||
 | 
					    useDebounceFn(() => {
 | 
				
			||||||
 | 
					      dynamicTagView();
 | 
				
			||||||
 | 
					    }, 200)
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -705,7 +535,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
 | 
				
			|||||||
        >
 | 
					        >
 | 
				
			||||||
          <li v-if="item.show" @click="selectTag(key, item)">
 | 
					          <li v-if="item.show" @click="selectTag(key, item)">
 | 
				
			||||||
            <component :is="toRaw(item.icon)" :key="key" />
 | 
					            <component :is="toRaw(item.icon)" :key="key" />
 | 
				
			||||||
            {{ t(item.text) }}
 | 
					            {{ transformI18n(item.text) }}
 | 
				
			||||||
          </li>
 | 
					          </li>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </ul>
 | 
					      </ul>
 | 
				
			||||||
@ -714,7 +544,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
 | 
				
			|||||||
    <ul class="right-button">
 | 
					    <ul class="right-button">
 | 
				
			||||||
      <li>
 | 
					      <li>
 | 
				
			||||||
        <span
 | 
					        <span
 | 
				
			||||||
          :title="t('buttons.hsrefreshRoute')"
 | 
					          :title="transformI18n('buttons.hsrefreshRoute')"
 | 
				
			||||||
          class="el-icon-refresh-right rotate"
 | 
					          class="el-icon-refresh-right rotate"
 | 
				
			||||||
          @click="onFresh"
 | 
					          @click="onFresh"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
@ -742,7 +572,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
 | 
				
			|||||||
                  :key="key"
 | 
					                  :key="key"
 | 
				
			||||||
                  style="margin-right: 6px"
 | 
					                  style="margin-right: 6px"
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                {{ t(item.text) }}
 | 
					                {{ transformI18n(item.text) }}
 | 
				
			||||||
              </el-dropdown-item>
 | 
					              </el-dropdown-item>
 | 
				
			||||||
            </el-dropdown-menu>
 | 
					            </el-dropdown-menu>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										218
									
								
								src/layout/hooks/useTag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								src/layout/hooks/useTag.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,218 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ref,
 | 
				
			||||||
 | 
					  unref,
 | 
				
			||||||
 | 
					  watch,
 | 
				
			||||||
 | 
					  computed,
 | 
				
			||||||
 | 
					  reactive,
 | 
				
			||||||
 | 
					  onMounted,
 | 
				
			||||||
 | 
					  CSSProperties,
 | 
				
			||||||
 | 
					  getCurrentInstance
 | 
				
			||||||
 | 
					} from "vue";
 | 
				
			||||||
 | 
					import { tagsViewsType } from "../types";
 | 
				
			||||||
 | 
					import { isEqual } from "lodash-unified";
 | 
				
			||||||
 | 
					import type { StorageConfigs } from "/#/index";
 | 
				
			||||||
 | 
					import { useEventListener } from "@vueuse/core";
 | 
				
			||||||
 | 
					import { useRoute, useRouter } from "vue-router";
 | 
				
			||||||
 | 
					import { transformI18n, $t } from "/@/plugins/i18n";
 | 
				
			||||||
 | 
					import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
				
			||||||
 | 
					import { storageLocal, toggleClass, hasClass } from "@pureadmin/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import close from "/@/assets/svg/close.svg?component";
 | 
				
			||||||
 | 
					import refresh from "/@/assets/svg/refresh.svg?component";
 | 
				
			||||||
 | 
					import closeAll from "/@/assets/svg/close_all.svg?component";
 | 
				
			||||||
 | 
					import closeLeft from "/@/assets/svg/close_left.svg?component";
 | 
				
			||||||
 | 
					import closeOther from "/@/assets/svg/close_other.svg?component";
 | 
				
			||||||
 | 
					import closeRight from "/@/assets/svg/close_right.svg?component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function useTags() {
 | 
				
			||||||
 | 
					  const route = useRoute();
 | 
				
			||||||
 | 
					  const router = useRouter();
 | 
				
			||||||
 | 
					  const instance = getCurrentInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const buttonTop = ref(0);
 | 
				
			||||||
 | 
					  const buttonLeft = ref(0);
 | 
				
			||||||
 | 
					  const translateX = ref(0);
 | 
				
			||||||
 | 
					  const visible = ref(false);
 | 
				
			||||||
 | 
					  const activeIndex = ref(-1);
 | 
				
			||||||
 | 
					  // 当前右键选中的路由信息
 | 
				
			||||||
 | 
					  const currentSelect = ref({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** 显示模式,默认灵动模式 */
 | 
				
			||||||
 | 
					  const showModel = ref(
 | 
				
			||||||
 | 
					    storageLocal.getItem<StorageConfigs>("responsive-configure")?.showModel ||
 | 
				
			||||||
 | 
					      "smart"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  /** 是否隐藏标签页,默认显示 */
 | 
				
			||||||
 | 
					  const showTags =
 | 
				
			||||||
 | 
					    ref(
 | 
				
			||||||
 | 
					      storageLocal.getItem<StorageConfigs>("responsive-configure").hideTabs
 | 
				
			||||||
 | 
					    ) ?? ref("false");
 | 
				
			||||||
 | 
					  const multiTags: any = computed(() => {
 | 
				
			||||||
 | 
					    return useMultiTagsStoreHook().multiTags;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const tagsViews = reactive<Array<tagsViewsType>>([
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: refresh,
 | 
				
			||||||
 | 
					      text: $t("buttons.hsreload"),
 | 
				
			||||||
 | 
					      divided: false,
 | 
				
			||||||
 | 
					      disabled: false,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: close,
 | 
				
			||||||
 | 
					      text: $t("buttons.hscloseCurrentTab"),
 | 
				
			||||||
 | 
					      divided: false,
 | 
				
			||||||
 | 
					      disabled: multiTags.value.length > 1 ? false : true,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: closeLeft,
 | 
				
			||||||
 | 
					      text: $t("buttons.hscloseLeftTabs"),
 | 
				
			||||||
 | 
					      divided: true,
 | 
				
			||||||
 | 
					      disabled: multiTags.value.length > 1 ? false : true,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: closeRight,
 | 
				
			||||||
 | 
					      text: $t("buttons.hscloseRightTabs"),
 | 
				
			||||||
 | 
					      divided: false,
 | 
				
			||||||
 | 
					      disabled: multiTags.value.length > 1 ? false : true,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: closeOther,
 | 
				
			||||||
 | 
					      text: $t("buttons.hscloseOtherTabs"),
 | 
				
			||||||
 | 
					      divided: true,
 | 
				
			||||||
 | 
					      disabled: multiTags.value.length > 2 ? false : true,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      icon: closeAll,
 | 
				
			||||||
 | 
					      text: $t("buttons.hscloseAllTabs"),
 | 
				
			||||||
 | 
					      divided: false,
 | 
				
			||||||
 | 
					      disabled: multiTags.value.length > 1 ? false : true,
 | 
				
			||||||
 | 
					      show: true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function conditionHandle(item, previous, next) {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      Object.keys(route.query).length === 0 &&
 | 
				
			||||||
 | 
					      Object.keys(route.params).length === 0
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return route.path === item.path ? previous : next;
 | 
				
			||||||
 | 
					    } else if (Object.keys(route.query).length > 0) {
 | 
				
			||||||
 | 
					      return isEqual(route.query, item.query) ? previous : next;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return isEqual(route.params, item.params) ? previous : next;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const iconIsActive = computed(() => {
 | 
				
			||||||
 | 
					    return (item, index) => {
 | 
				
			||||||
 | 
					      if (index === 0) return;
 | 
				
			||||||
 | 
					      return conditionHandle(item, true, false);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const linkIsActive = computed(() => {
 | 
				
			||||||
 | 
					    return item => {
 | 
				
			||||||
 | 
					      return conditionHandle(item, "is-active", "");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const scheduleIsActive = computed(() => {
 | 
				
			||||||
 | 
					    return item => {
 | 
				
			||||||
 | 
					      return conditionHandle(item, "schedule-active", "");
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getTabStyle = computed((): CSSProperties => {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      transform: `translateX(${translateX.value}px)`
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const getContextMenuStyle = computed((): CSSProperties => {
 | 
				
			||||||
 | 
					    return { left: buttonLeft.value + "px", top: buttonTop.value + "px" };
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const closeMenu = () => {
 | 
				
			||||||
 | 
					    visible.value = false;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** 鼠标移入添加激活样式 */
 | 
				
			||||||
 | 
					  function onMouseenter(index) {
 | 
				
			||||||
 | 
					    if (index) activeIndex.value = index;
 | 
				
			||||||
 | 
					    if (unref(showModel) === "smart") {
 | 
				
			||||||
 | 
					      if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]);
 | 
				
			||||||
 | 
					      toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
 | 
				
			||||||
 | 
					      toggleClass(true, "card-in", instance.refs["dynamic" + index][0]);
 | 
				
			||||||
 | 
					      toggleClass(false, "card-out", instance.refs["dynamic" + index][0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** 鼠标移出恢复默认样式 */
 | 
				
			||||||
 | 
					  function onMouseleave(index) {
 | 
				
			||||||
 | 
					    activeIndex.value = -1;
 | 
				
			||||||
 | 
					    if (unref(showModel) === "smart") {
 | 
				
			||||||
 | 
					      if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]);
 | 
				
			||||||
 | 
					      toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
 | 
				
			||||||
 | 
					      toggleClass(false, "card-in", instance.refs["dynamic" + index][0]);
 | 
				
			||||||
 | 
					      toggleClass(true, "card-out", instance.refs["dynamic" + index][0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMounted(() => {
 | 
				
			||||||
 | 
					    if (!showModel.value) {
 | 
				
			||||||
 | 
					      const configure = storageLocal.getItem<StorageConfigs>(
 | 
				
			||||||
 | 
					        "responsive-configure"
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      configure.showModel = "card";
 | 
				
			||||||
 | 
					      storageLocal.setItem("responsive-configure", configure);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  watch(
 | 
				
			||||||
 | 
					    () => visible.value,
 | 
				
			||||||
 | 
					    () => {
 | 
				
			||||||
 | 
					      useEventListener(document, "click", closeMenu);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    route,
 | 
				
			||||||
 | 
					    router,
 | 
				
			||||||
 | 
					    visible,
 | 
				
			||||||
 | 
					    showTags,
 | 
				
			||||||
 | 
					    instance,
 | 
				
			||||||
 | 
					    multiTags,
 | 
				
			||||||
 | 
					    showModel,
 | 
				
			||||||
 | 
					    tagsViews,
 | 
				
			||||||
 | 
					    buttonTop,
 | 
				
			||||||
 | 
					    buttonLeft,
 | 
				
			||||||
 | 
					    translateX,
 | 
				
			||||||
 | 
					    activeIndex,
 | 
				
			||||||
 | 
					    getTabStyle,
 | 
				
			||||||
 | 
					    iconIsActive,
 | 
				
			||||||
 | 
					    linkIsActive,
 | 
				
			||||||
 | 
					    currentSelect,
 | 
				
			||||||
 | 
					    scheduleIsActive,
 | 
				
			||||||
 | 
					    getContextMenuStyle,
 | 
				
			||||||
 | 
					    $t,
 | 
				
			||||||
 | 
					    closeMenu,
 | 
				
			||||||
 | 
					    onMounted,
 | 
				
			||||||
 | 
					    onMouseenter,
 | 
				
			||||||
 | 
					    onMouseleave,
 | 
				
			||||||
 | 
					    transformI18n
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,6 +22,7 @@ export type RouteConfigs = {
 | 
				
			|||||||
  path?: string;
 | 
					  path?: string;
 | 
				
			||||||
  parentPath?: string;
 | 
					  parentPath?: string;
 | 
				
			||||||
  query?: object;
 | 
					  query?: object;
 | 
				
			||||||
 | 
					  params?: object;
 | 
				
			||||||
  meta?: routeMetaType;
 | 
					  meta?: routeMetaType;
 | 
				
			||||||
  children?: RouteConfigs[];
 | 
					  children?: RouteConfigs[];
 | 
				
			||||||
  name?: string;
 | 
					  name?: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -8,17 +8,14 @@ import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
				
			|||||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
					import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Router,
 | 
					  Router,
 | 
				
			||||||
  RouteMeta,
 | 
					 | 
				
			||||||
  createRouter,
 | 
					  createRouter,
 | 
				
			||||||
  RouteRecordRaw,
 | 
					  RouteRecordRaw,
 | 
				
			||||||
  RouteComponent,
 | 
					  RouteComponent
 | 
				
			||||||
  RouteRecordName
 | 
					 | 
				
			||||||
} from "vue-router";
 | 
					} from "vue-router";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  ascending,
 | 
					  ascending,
 | 
				
			||||||
  initRouter,
 | 
					  initRouter,
 | 
				
			||||||
  getHistoryMode,
 | 
					  getHistoryMode,
 | 
				
			||||||
  getParentPaths,
 | 
					 | 
				
			||||||
  findRouteByPath,
 | 
					  findRouteByPath,
 | 
				
			||||||
  handleAliveRoute,
 | 
					  handleAliveRoute,
 | 
				
			||||||
  formatTwoStageRoutes,
 | 
					  formatTwoStageRoutes,
 | 
				
			||||||
@ -148,69 +145,22 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
				
			|||||||
      if (usePermissionStoreHook().wholeMenus.length === 0)
 | 
					      if (usePermissionStoreHook().wholeMenus.length === 0)
 | 
				
			||||||
        initRouter(name.username).then((router: Router) => {
 | 
					        initRouter(name.username).then((router: Router) => {
 | 
				
			||||||
          if (!useMultiTagsStoreHook().getMultiTagsCache) {
 | 
					          if (!useMultiTagsStoreHook().getMultiTagsCache) {
 | 
				
			||||||
            const handTag = (
 | 
					            const { path } = to;
 | 
				
			||||||
              path: string,
 | 
					            const index = findIndex(remainingRouter, v => {
 | 
				
			||||||
              parentPath: string,
 | 
					              return v.path == path;
 | 
				
			||||||
              name: RouteRecordName,
 | 
					            });
 | 
				
			||||||
              meta: RouteMeta
 | 
					            const routes: any =
 | 
				
			||||||
            ): void => {
 | 
					              index === -1
 | 
				
			||||||
 | 
					                ? router.options.routes[0].children
 | 
				
			||||||
 | 
					                : router.options.routes;
 | 
				
			||||||
 | 
					            const route = findRouteByPath(path, routes);
 | 
				
			||||||
 | 
					            // query、params模式路由传参数的标签页不在此处处理
 | 
				
			||||||
 | 
					            if (route && route.meta?.title) {
 | 
				
			||||||
              useMultiTagsStoreHook().handleTags("push", {
 | 
					              useMultiTagsStoreHook().handleTags("push", {
 | 
				
			||||||
                path,
 | 
					                path: route.path,
 | 
				
			||||||
                parentPath,
 | 
					                name: route.name,
 | 
				
			||||||
                name,
 | 
					                meta: route.meta
 | 
				
			||||||
                meta
 | 
					 | 
				
			||||||
              });
 | 
					              });
 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            // 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对静态路由)
 | 
					 | 
				
			||||||
            if (to.meta?.refreshRedirect) {
 | 
					 | 
				
			||||||
              const routes: any = router.options.routes;
 | 
					 | 
				
			||||||
              const { refreshRedirect } = to.meta;
 | 
					 | 
				
			||||||
              const { name, meta } = findRouteByPath(refreshRedirect, routes);
 | 
					 | 
				
			||||||
              handTag(
 | 
					 | 
				
			||||||
                refreshRedirect,
 | 
					 | 
				
			||||||
                getParentPaths(refreshRedirect, routes)[1],
 | 
					 | 
				
			||||||
                name,
 | 
					 | 
				
			||||||
                meta
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
              return router.push(refreshRedirect);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              const { path } = to;
 | 
					 | 
				
			||||||
              const index = findIndex(remainingRouter, v => {
 | 
					 | 
				
			||||||
                return v.path == path;
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
              const routes: any =
 | 
					 | 
				
			||||||
                index === -1
 | 
					 | 
				
			||||||
                  ? router.options.routes[0].children
 | 
					 | 
				
			||||||
                  : router.options.routes;
 | 
					 | 
				
			||||||
              const route = findRouteByPath(path, routes);
 | 
					 | 
				
			||||||
              const routePartent = getParentPaths(path, routes);
 | 
					 | 
				
			||||||
              // 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对动态路由)
 | 
					 | 
				
			||||||
              if (
 | 
					 | 
				
			||||||
                path !== routes[0].path &&
 | 
					 | 
				
			||||||
                route?.meta?.rank !== 0 &&
 | 
					 | 
				
			||||||
                routePartent.length === 0
 | 
					 | 
				
			||||||
              ) {
 | 
					 | 
				
			||||||
                if (!route?.meta?.refreshRedirect) return;
 | 
					 | 
				
			||||||
                const { name, meta } = findRouteByPath(
 | 
					 | 
				
			||||||
                  route.meta.refreshRedirect,
 | 
					 | 
				
			||||||
                  routes
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                handTag(
 | 
					 | 
				
			||||||
                  route.meta?.refreshRedirect,
 | 
					 | 
				
			||||||
                  getParentPaths(route.meta?.refreshRedirect, routes)[0],
 | 
					 | 
				
			||||||
                  name,
 | 
					 | 
				
			||||||
                  meta
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                return router.push(route.meta?.refreshRedirect);
 | 
					 | 
				
			||||||
              } else {
 | 
					 | 
				
			||||||
                handTag(
 | 
					 | 
				
			||||||
                  route.path,
 | 
					 | 
				
			||||||
                  routePartent[routePartent.length - 1],
 | 
					 | 
				
			||||||
                  route.name,
 | 
					 | 
				
			||||||
                  route.meta
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
                return router.push(path);
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          router.push(to.fullPath);
 | 
					          router.push(to.fullPath);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ import { RouteLocationNormalized } from "vue-router";
 | 
				
			|||||||
export interface toRouteType extends RouteLocationNormalized {
 | 
					export interface toRouteType extends RouteLocationNormalized {
 | 
				
			||||||
  meta: {
 | 
					  meta: {
 | 
				
			||||||
    keepAlive?: boolean;
 | 
					    keepAlive?: boolean;
 | 
				
			||||||
    refreshRedirect: string;
 | 
					 | 
				
			||||||
    dynamicLevel?: string;
 | 
					    dynamicLevel?: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import {
 | 
				
			|||||||
  RouteRecordNormalized
 | 
					  RouteRecordNormalized
 | 
				
			||||||
} from "vue-router";
 | 
					} from "vue-router";
 | 
				
			||||||
import { router } from "./index";
 | 
					import { router } from "./index";
 | 
				
			||||||
 | 
					import { isProxy, toRaw } from "vue";
 | 
				
			||||||
import { loadEnv } from "../../build";
 | 
					import { loadEnv } from "../../build";
 | 
				
			||||||
import { cloneDeep } from "lodash-unified";
 | 
					import { cloneDeep } from "lodash-unified";
 | 
				
			||||||
import { useTimeoutFn } from "@vueuse/core";
 | 
					import { useTimeoutFn } from "@vueuse/core";
 | 
				
			||||||
@ -86,7 +87,7 @@ function getParentPaths(path: string, routes: RouteRecordRaw[]) {
 | 
				
			|||||||
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
 | 
					function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
 | 
				
			||||||
  let res = routes.find((item: { path: string }) => item.path == path);
 | 
					  let res = routes.find((item: { path: string }) => item.path == path);
 | 
				
			||||||
  if (res) {
 | 
					  if (res) {
 | 
				
			||||||
    return res;
 | 
					    return isProxy(res) ? toRaw(res) : res;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    for (let i = 0; i < routes.length; i++) {
 | 
					    for (let i = 0; i < routes.length; i++) {
 | 
				
			||||||
      if (
 | 
					      if (
 | 
				
			||||||
@ -95,7 +96,7 @@ function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
 | 
				
			|||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
        res = findRouteByPath(path, routes[i].children);
 | 
					        res = findRouteByPath(path, routes[i].children);
 | 
				
			||||||
        if (res) {
 | 
					        if (res) {
 | 
				
			||||||
          return res;
 | 
					          return isProxy(res) ? toRaw(res) : res;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -48,9 +48,13 @@ export const useMultiTagsStore = defineStore({
 | 
				
			|||||||
        case "push":
 | 
					        case "push":
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            const tagVal = value as multiType;
 | 
					            const tagVal = value as multiType;
 | 
				
			||||||
 | 
					            // 不添加到标签页
 | 
				
			||||||
 | 
					            if (tagVal?.meta?.hiddenTag) return;
 | 
				
			||||||
 | 
					            // 如果是外链无需添加信息到标签页
 | 
				
			||||||
            if (isUrl(tagVal?.name)) return;
 | 
					            if (isUrl(tagVal?.name)) return;
 | 
				
			||||||
 | 
					            // 如果title为空拒绝添加空信息到标签页
 | 
				
			||||||
            if (tagVal?.meta?.title.length === 0) return;
 | 
					            if (tagVal?.meta?.title.length === 0) return;
 | 
				
			||||||
            const tagPath = tagVal?.path;
 | 
					            const tagPath = tagVal.path;
 | 
				
			||||||
            // 判断tag是否已存在
 | 
					            // 判断tag是否已存在
 | 
				
			||||||
            const tagHasExits = this.multiTags.some(tag => {
 | 
					            const tagHasExits = this.multiTags.some(tag => {
 | 
				
			||||||
              return tag.path === tagPath;
 | 
					              return tag.path === tagPath;
 | 
				
			||||||
@ -58,20 +62,24 @@ export const useMultiTagsStore = defineStore({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // 判断tag中的query键值是否相等
 | 
					            // 判断tag中的query键值是否相等
 | 
				
			||||||
            const tagQueryHasExits = this.multiTags.some(tag => {
 | 
					            const tagQueryHasExits = this.multiTags.some(tag => {
 | 
				
			||||||
              return isEqual(tag.query, tagVal?.query);
 | 
					              return isEqual(tag?.query, tagVal?.query);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (tagHasExits && tagQueryHasExits) return;
 | 
					            // 判断tag中的params键值是否相等
 | 
				
			||||||
 | 
					            const tagParamsHasExits = this.multiTags.some(tag => {
 | 
				
			||||||
 | 
					              return isEqual(tag?.params, tagVal?.params);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (tagHasExits && tagQueryHasExits && tagParamsHasExits) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 动态路由可打开的最大数量
 | 
				
			||||||
            const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;
 | 
					            const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;
 | 
				
			||||||
            if (dynamicLevel > 0) {
 | 
					            if (dynamicLevel > 0) {
 | 
				
			||||||
              // dynamicLevel动态路由可打开的数量
 | 
					 | 
				
			||||||
              // 获取到已经打开的动态路由数, 判断是否大于dynamicLevel
 | 
					 | 
				
			||||||
              if (
 | 
					              if (
 | 
				
			||||||
                this.multiTags.filter(e => e?.path === tagPath).length >=
 | 
					                this.multiTags.filter(e => e?.path === tagPath).length >=
 | 
				
			||||||
                dynamicLevel
 | 
					                dynamicLevel
 | 
				
			||||||
              ) {
 | 
					              ) {
 | 
				
			||||||
                // 关闭第一个
 | 
					                // 如果当前已打开的动态路由数大于dynamicLevel,替换第一个动态路由标签
 | 
				
			||||||
                const index = this.multiTags.findIndex(
 | 
					                const index = this.multiTags.findIndex(
 | 
				
			||||||
                  item => item?.path === tagPath
 | 
					                  item => item?.path === tagPath
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ export type multiType = {
 | 
				
			|||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
  meta: any;
 | 
					  meta: any;
 | 
				
			||||||
  query?: object;
 | 
					  query?: object;
 | 
				
			||||||
 | 
					  params?: object;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type setType = {
 | 
					export type setType = {
 | 
				
			||||||
 | 
				
			|||||||
@ -5,26 +5,48 @@ import { onBeforeMount } from "vue";
 | 
				
			|||||||
export function useDetail() {
 | 
					export function useDetail() {
 | 
				
			||||||
  const route = useRoute();
 | 
					  const route = useRoute();
 | 
				
			||||||
  const router = useRouter();
 | 
					  const router = useRouter();
 | 
				
			||||||
  const id = route.query?.id ?? -1;
 | 
					  const id = route.query?.id ? route.query?.id : route.params?.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function toDetail(index: number | string | string[] | number[]) {
 | 
					  function toDetail(
 | 
				
			||||||
    useMultiTagsStoreHook().handleTags("push", {
 | 
					    index: number | string | string[] | number[],
 | 
				
			||||||
      path: `/tabs/detail`,
 | 
					    model: string
 | 
				
			||||||
      parentPath: route.matched[0].path,
 | 
					  ) {
 | 
				
			||||||
      name: "TabDetail",
 | 
					    if (model === "query") {
 | 
				
			||||||
      query: { id: String(index) },
 | 
					      // 保存信息到标签页
 | 
				
			||||||
      meta: {
 | 
					      useMultiTagsStoreHook().handleTags("push", {
 | 
				
			||||||
        title: { zh: `No.${index} - 详情信息`, en: `No.${index} - DetailInfo` },
 | 
					        path: `/tabs/query-detail`,
 | 
				
			||||||
        showLink: false,
 | 
					        name: "TabQueryDetail",
 | 
				
			||||||
        dynamicLevel: 3
 | 
					        query: { id: String(index) },
 | 
				
			||||||
      }
 | 
					        meta: {
 | 
				
			||||||
    });
 | 
					          title: {
 | 
				
			||||||
    router.push({ name: "TabDetail", query: { id: String(index) } });
 | 
					            zh: `No.${index} - 详情信息`,
 | 
				
			||||||
 | 
					            en: `No.${index} - DetailInfo`
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          // 最大打开标签数
 | 
				
			||||||
 | 
					          dynamicLevel: 3
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      // 路由跳转
 | 
				
			||||||
 | 
					      router.push({ name: "TabQueryDetail", query: { id: String(index) } });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      useMultiTagsStoreHook().handleTags("push", {
 | 
				
			||||||
 | 
					        path: `/tabs/params-detail/:id`,
 | 
				
			||||||
 | 
					        name: "TabParamsDetail",
 | 
				
			||||||
 | 
					        params: { id: String(index) },
 | 
				
			||||||
 | 
					        meta: {
 | 
				
			||||||
 | 
					          title: {
 | 
				
			||||||
 | 
					            zh: `No.${index} - 详情信息`,
 | 
				
			||||||
 | 
					            en: `No.${index} - DetailInfo`
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      router.push({ name: "TabParamsDetail", params: { id: String(index) } });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function initToDetail() {
 | 
					  function initToDetail(model) {
 | 
				
			||||||
    onBeforeMount(() => {
 | 
					    onBeforeMount(() => {
 | 
				
			||||||
      if (id) toDetail(id);
 | 
					      if (id) toDetail(id, model);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,8 @@ import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
				
			|||||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
					import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  deleteChildren,
 | 
					  deleteChildren,
 | 
				
			||||||
  appendFieldByUniqueId,
 | 
					  getNodeByUniqueId,
 | 
				
			||||||
  getNodeByUniqueId
 | 
					  appendFieldByUniqueId
 | 
				
			||||||
} from "@pureadmin/utils";
 | 
					} from "@pureadmin/utils";
 | 
				
			||||||
import { useDetail } from "./hooks";
 | 
					import { useDetail } from "./hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,9 +50,32 @@ function onCloseTags() {
 | 
				
			|||||||
    <template #header>
 | 
					    <template #header>
 | 
				
			||||||
      <div>标签页复用,超出限制自动关闭(使用场景: 动态路由)</div>
 | 
					      <div>标签页复用,超出限制自动关闭(使用场景: 动态路由)</div>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <el-button v-for="index in 6" :key="index" @click="toDetail(index)">
 | 
					    <div class="flex-wrap items-center">
 | 
				
			||||||
      打开{{ index }}详情页
 | 
					      <p>query传参模式:</p>
 | 
				
			||||||
    </el-button>
 | 
					      <el-button
 | 
				
			||||||
 | 
					        class="m-2"
 | 
				
			||||||
 | 
					        v-for="index in 6"
 | 
				
			||||||
 | 
					        :key="index"
 | 
				
			||||||
 | 
					        @click="toDetail(index, 'query')"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        打开{{ index }}详情页
 | 
				
			||||||
 | 
					      </el-button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <el-divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="flex-wrap items-center">
 | 
				
			||||||
 | 
					      <p>params传参模式:</p>
 | 
				
			||||||
 | 
					      <el-button
 | 
				
			||||||
 | 
					        class="m-2"
 | 
				
			||||||
 | 
					        v-for="index in 6"
 | 
				
			||||||
 | 
					        :key="index"
 | 
				
			||||||
 | 
					        @click="toDetail(index, 'params')"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        打开{{ index }}详情页
 | 
				
			||||||
 | 
					      </el-button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-divider />
 | 
					    <el-divider />
 | 
				
			||||||
    <TreeSelect
 | 
					    <TreeSelect
 | 
				
			||||||
      class="w-300px"
 | 
					      class="w-300px"
 | 
				
			||||||
@ -80,19 +103,27 @@ function onCloseTags() {
 | 
				
			|||||||
        <span>{{ transformI18n(data.meta.title) }}</span>
 | 
					        <span>{{ transformI18n(data.meta.title) }}</span>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </TreeSelect>
 | 
					    </TreeSelect>
 | 
				
			||||||
    <el-button class="ml-2" @click="onCloseTags">关闭标签</el-button>
 | 
					    <el-button class="m-2" @click="onCloseTags">关闭标签</el-button>
 | 
				
			||||||
    <br />
 | 
					
 | 
				
			||||||
    <p class="mt-4">
 | 
					    <el-divider />
 | 
				
			||||||
      注意:此demo并未开启标签页缓存,如果需要在
 | 
					    <el-button @click="$router.push({ name: 'Menu1-2-2' })">
 | 
				
			||||||
      <span class="text-red-500">刷新页面</span>
 | 
					      跳转页内菜单(传name对象,优先推荐)
 | 
				
			||||||
      的时候同时
 | 
					    </el-button>
 | 
				
			||||||
      <span class="text-red-500">保留标签页的显示</span>
 | 
					    <el-button @click="$router.push('/nested/menu1/menu1-2/menu1-2-2')">
 | 
				
			||||||
      或者
 | 
					      跳转页内菜单(直接传要跳转的路径)
 | 
				
			||||||
      <span class="text-red-500">保留url的参数</span>
 | 
					    </el-button>
 | 
				
			||||||
      ,那么就需要开启标签页持久化。
 | 
					    <el-button
 | 
				
			||||||
      <br />
 | 
					      @click="$router.push({ path: '/nested/menu1/menu1-2/menu1-2-2' })"
 | 
				
			||||||
      开启方式:在页面最右上角有个设置的小图标,点进去,会看到项目配置面板,找到标签页持久化开启即可。
 | 
					    >
 | 
				
			||||||
    </p>
 | 
					      跳转页内菜单(传path对象)
 | 
				
			||||||
 | 
					    </el-button>
 | 
				
			||||||
 | 
					    <el-link
 | 
				
			||||||
 | 
					      class="ml-4"
 | 
				
			||||||
 | 
					      href="https://router.vuejs.org/zh/guide/essentials/navigation.html#%E5%AF%BC%E8%88%AA%E5%88%B0%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BD%8D%E7%BD%AE"
 | 
				
			||||||
 | 
					      target="_blank"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      点击查看更多跳转方式
 | 
				
			||||||
 | 
					    </el-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <el-divider />
 | 
					    <el-divider />
 | 
				
			||||||
    <el-button @click="$router.push({ name: 'Empty' })">
 | 
					    <el-button @click="$router.push({ name: 'Empty' })">
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								src/views/tabs/params-detail.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/views/tabs/params-detail.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useDetail } from "./hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defineOptions({
 | 
				
			||||||
 | 
					  name: "TabParamsDetail"
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { initToDetail, id } = useDetail();
 | 
				
			||||||
 | 
					initToDetail("params");
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>{{ id }} - 详情页内容在此(params传参)</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
@ -2,13 +2,13 @@
 | 
				
			|||||||
import { useDetail } from "./hooks";
 | 
					import { useDetail } from "./hooks";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
defineOptions({
 | 
					defineOptions({
 | 
				
			||||||
  name: "TabDetail"
 | 
					  name: "TabQueryDetail"
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { initToDetail, id } = useDetail();
 | 
					const { initToDetail, id } = useDetail();
 | 
				
			||||||
initToDetail();
 | 
					initToDetail("query");
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>{{ id }} - 详情页内容在此</div>
 | 
					  <div>{{ id }} - 详情页内容在此(query传参)</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@ -94,10 +94,10 @@ export interface RouteChildrenConfigsTable {
 | 
				
			|||||||
      /** 离场动画 */
 | 
					      /** 离场动画 */
 | 
				
			||||||
      leaveTransition?: string;
 | 
					      leaveTransition?: string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    // 是否不添加信息到标签页,(默认`false`)
 | 
				
			||||||
 | 
					    hiddenTag?: boolean;
 | 
				
			||||||
    /** 动态路由可打开的最大数量 `可选` */
 | 
					    /** 动态路由可打开的最大数量 `可选` */
 | 
				
			||||||
    dynamicLevel?: number;
 | 
					    dynamicLevel?: number;
 | 
				
			||||||
    /** 刷新重定向(用于未开启标签页缓存,刷新页面获取不到动态`title`)`可选` */
 | 
					 | 
				
			||||||
    refreshRedirect?: string;
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  /** 子路由配置项 */
 | 
					  /** 子路由配置项 */
 | 
				
			||||||
  children?: Array<RouteChildrenConfigsTable>;
 | 
					  children?: Array<RouteChildrenConfigsTable>;
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ export default defineConfig({
 | 
				
			|||||||
  shortcuts: {
 | 
					  shortcuts: {
 | 
				
			||||||
    "bg-dark": "bg-bg_color",
 | 
					    "bg-dark": "bg-bg_color",
 | 
				
			||||||
    "wh-full": "w-full h-full",
 | 
					    "wh-full": "w-full h-full",
 | 
				
			||||||
    "cp-on": "cursor-pointer outline-none",
 | 
					    "flex-wrap": "flex flex-wrap",
 | 
				
			||||||
    "flex-c": "flex justify-center items-center",
 | 
					    "flex-c": "flex justify-center items-center",
 | 
				
			||||||
    "flex-ac": "flex justify-around items-center",
 | 
					    "flex-ac": "flex justify-around items-center",
 | 
				
			||||||
    "flex-bc": "flex justify-between items-center",
 | 
					    "flex-bc": "flex justify-between items-center",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user