parent
434ab1c560
commit
4122685803
@ -302,6 +302,53 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function',
|
||||
path: '/function',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'function_tab',
|
||||
path: '/function/tab',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-detail',
|
||||
path: '/function/tab-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-multi-detail',
|
||||
path: '/function/tab-multi-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Multi Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
multiTab: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '功能',
|
||||
icon: 'ri:function-line',
|
||||
order: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
@ -341,7 +388,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 6
|
||||
order: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -395,7 +442,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '多级菜单',
|
||||
icon: 'carbon:menu',
|
||||
order: 7
|
||||
order: 8
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -407,7 +454,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
order: 9
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -704,6 +751,53 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function',
|
||||
path: '/function',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'function_tab',
|
||||
path: '/function/tab',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-detail',
|
||||
path: '/function/tab-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-multi-detail',
|
||||
path: '/function/tab-multi-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Multi Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
multiTab: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '功能',
|
||||
icon: 'ri:function-line',
|
||||
order: 6
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
@ -743,7 +837,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 6
|
||||
order: 7
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -797,7 +891,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
meta: {
|
||||
title: '多级菜单',
|
||||
icon: 'carbon:menu',
|
||||
order: 7
|
||||
order: 8
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -809,7 +903,7 @@ export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
order: 9
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -16,7 +16,7 @@ export enum EnumStorageKey {
|
||||
/** 用户信息 */
|
||||
'user-info' = '__USER_INFO__',
|
||||
/** 多页签路由信息 */
|
||||
'tab-routes' = '__TAB_ROUTES__'
|
||||
'multi-tab-routes' = '__MULTI_TAB_ROUTES__'
|
||||
}
|
||||
|
||||
/** 数据类型 */
|
||||
|
@ -12,7 +12,7 @@
|
||||
@after-enter="handleAfterEnter"
|
||||
>
|
||||
<keep-alive :include="routeStore.cacheRoutes">
|
||||
<component :is="Component" v-if="app.reloadFlag" :key="route.path" />
|
||||
<component :is="Component" v-if="app.reloadFlag" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
|
@ -69,7 +69,7 @@ const options = computed<Option[]>(() => [
|
||||
{
|
||||
label: '关闭',
|
||||
key: 'close-current',
|
||||
disabled: props.currentPath === tab.homeTab.path,
|
||||
disabled: props.currentPath === tab.homeTab.fullPath,
|
||||
icon: iconifyRender('ant-design:close-outlined')
|
||||
},
|
||||
{
|
||||
|
@ -3,15 +3,15 @@
|
||||
<component
|
||||
:is="activeComponent"
|
||||
v-for="(item, index) in tab.tabs"
|
||||
:key="item.path"
|
||||
:is-active="tab.activeTab === item.path"
|
||||
:key="item.fullPath"
|
||||
:is-active="tab.activeTab === item.fullPath"
|
||||
:primary-color="theme.themeColor"
|
||||
:closable="item.path !== tab.homeTab.path"
|
||||
:closable="item.name !== tab.homeTab.name"
|
||||
:dark-mode="theme.darkMode"
|
||||
:class="{ '!mr-0': isChromeMode && index === tab.tabs.length - 1, 'mr-10px': !isChromeMode }"
|
||||
@click="tab.handleClickTab(item.path)"
|
||||
@close="tab.removeTab(item.path)"
|
||||
@contextmenu="handleContextMenu($event, item.path)"
|
||||
@click="tab.handleClickTab(item.fullPath)"
|
||||
@close="tab.removeTab(item.fullPath)"
|
||||
@contextmenu="handleContextMenu($event, item.fullPath)"
|
||||
>
|
||||
<Icon v-if="item.meta.icon" :icon="item.meta.icon" class="inline-block align-text-bottom mr-4px text-16px" />
|
||||
{{ item.meta.title }}
|
||||
@ -77,11 +77,11 @@ function setDropdown(x: number, y: number, currentPath: string) {
|
||||
}
|
||||
|
||||
/** 点击右键菜单 */
|
||||
async function handleContextMenu(e: MouseEvent, path: string) {
|
||||
async function handleContextMenu(e: MouseEvent, fullPath: string) {
|
||||
e.preventDefault();
|
||||
const { clientX, clientY } = e;
|
||||
hideDropdown();
|
||||
setDropdown(clientX, clientY, path);
|
||||
setDropdown(clientX, clientY, fullPath);
|
||||
await nextTick();
|
||||
showDropdown();
|
||||
}
|
||||
|
@ -45,10 +45,10 @@ function init() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
tab.addTab(route);
|
||||
tab.setActiveTab(route.path);
|
||||
tab.setActiveTab(route.fullPath);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -31,15 +31,10 @@ export async function createDynamicRouteGuard(
|
||||
|
||||
if (to.name === routeName('not-found-page')) {
|
||||
// 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由
|
||||
|
||||
// 若路由是从根路由重定向过来的,重新回到根路由
|
||||
const ROOT_ROUTE_NAME: AuthRoute.RouteKey = 'root';
|
||||
if (to.redirectedFrom?.name === ROOT_ROUTE_NAME) {
|
||||
next({ path: '/', replace: true, query: to.query });
|
||||
return false;
|
||||
}
|
||||
|
||||
next({ path: to.fullPath, replace: true, query: to.query });
|
||||
const path = to.redirectedFrom?.name === ROOT_ROUTE_NAME ? '/' : to.fullPath;
|
||||
next({ path, replace: true, query: to.query, hash: to.hash });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,13 @@ export const scrollBehavior: RouterScrollBehavior = (to, from) => {
|
||||
const tab = useTabStore();
|
||||
|
||||
if (to.hash) {
|
||||
resolve({
|
||||
el: to.hash,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
const el = document.querySelector(to.hash);
|
||||
if (el) {
|
||||
resolve({
|
||||
el,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { left, top } = tab.getTabScrollPosition(to.path);
|
||||
|
@ -8,7 +8,7 @@ const about: AuthRoute.Route = {
|
||||
singleLayout: 'basic',
|
||||
permissions: ['super', 'admin', 'user'],
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 8
|
||||
order: 9
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -37,7 +37,7 @@ const exception: AuthRoute.Route = {
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 6
|
||||
order: 7
|
||||
}
|
||||
};
|
||||
|
||||
|
49
src/router/modules/function.ts
Normal file
49
src/router/modules/function.ts
Normal file
@ -0,0 +1,49 @@
|
||||
const functionRoute: AuthRoute.Route = {
|
||||
name: 'function',
|
||||
path: '/function',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'function_tab',
|
||||
path: '/function/tab',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-detail',
|
||||
path: '/function/tab-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'function_tab-multi-detail',
|
||||
path: '/function/tab-multi-detail',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Tab Multi Detail',
|
||||
requiresAuth: true,
|
||||
hide: true,
|
||||
multiTab: true,
|
||||
activeMenu: 'function_tab',
|
||||
icon: 'ic:round-tab'
|
||||
}
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
title: '功能',
|
||||
icon: 'ri:function-line',
|
||||
order: 6
|
||||
}
|
||||
};
|
||||
|
||||
export default functionRoute;
|
@ -49,7 +49,7 @@ const multiMenu: AuthRoute.Route = {
|
||||
meta: {
|
||||
title: '多级菜单',
|
||||
icon: 'carbon:menu',
|
||||
order: 6
|
||||
order: 8
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
import type { RouteRecordNormalized, RouteLocationNormalizedLoaded } from 'vue-router';
|
||||
|
||||
/**
|
||||
* 根据vue路由获取tab路由
|
||||
* 根据vue路由获取tab路由
|
||||
* @param route
|
||||
*/
|
||||
export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocationNormalizedLoaded) {
|
||||
const fullPath = hasFullPath(route) ? route.fullPath : route.path;
|
||||
|
||||
const tabRoute: GlobalTabRoute = {
|
||||
name: route.name,
|
||||
path: route.path,
|
||||
fullPath,
|
||||
meta: route.meta,
|
||||
scrollPosition: {
|
||||
left: 0,
|
||||
@ -20,17 +22,36 @@ export function getTabRouteByVueRoute(route: RouteRecordNormalized | RouteLocati
|
||||
/**
|
||||
* 获取该页签在多页签数据中的索引
|
||||
* @param tabs - 多页签数据
|
||||
* @param path - 该页签的路径
|
||||
* @param fullPath - 该页签的路径
|
||||
*/
|
||||
export function getIndexInTabRoutes(tabs: GlobalTabRoute[], path: string) {
|
||||
return tabs.findIndex(tab => tab.path === path);
|
||||
export function getIndexInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
||||
return tabs.findIndex(tab => tab.fullPath === fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断该页签是否在多页签数据中
|
||||
* @param tabs - 多页签数据
|
||||
* @param path - 该页签的路径
|
||||
* @param fullPath - 该页签的路径
|
||||
*/
|
||||
export function isInTabRoutes(tabs: GlobalTabRoute[], path: string) {
|
||||
return getIndexInTabRoutes(tabs, path) > -1;
|
||||
export function isInTabRoutes(tabs: GlobalTabRoute[], fullPath: string) {
|
||||
return getIndexInTabRoutes(tabs, fullPath) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由名称获取该页签在多页签数据中的索引
|
||||
* @param tabs - 多页签数据
|
||||
* @param routeName - 路由名称
|
||||
*/
|
||||
export function getIndexInTabRoutesByRouteName(tabs: GlobalTabRoute[], routeName: string) {
|
||||
return tabs.findIndex(tab => tab.name === routeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断路由是否有fullPath属性
|
||||
* @param route 路由
|
||||
*/
|
||||
function hasFullPath(
|
||||
route: RouteRecordNormalized | RouteLocationNormalizedLoaded
|
||||
): route is RouteLocationNormalizedLoaded {
|
||||
return Boolean((route as RouteLocationNormalizedLoaded).fullPath);
|
||||
}
|
||||
|
@ -3,14 +3,14 @@ import { defineStore } from 'pinia';
|
||||
import { useRouterPush } from '@/composables';
|
||||
import { getTabRoutes, clearTabRoutes } from '@/utils';
|
||||
import { useThemeStore } from '../theme';
|
||||
import { getTabRouteByVueRoute, isInTabRoutes, getIndexInTabRoutes } from './helpers';
|
||||
import { getTabRouteByVueRoute, isInTabRoutes, getIndexInTabRoutes, getIndexInTabRoutesByRouteName } from './helpers';
|
||||
|
||||
interface TabState {
|
||||
/** 多页签数据 */
|
||||
tabs: GlobalTabRoute[];
|
||||
/** 多页签首页 */
|
||||
homeTab: GlobalTabRoute;
|
||||
/** 当前激活状态的页签(路由path) */
|
||||
/** 当前激活状态的页签(路由fullPath) */
|
||||
activeTab: string;
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export const useTabStore = defineStore('tab-store', {
|
||||
tabs: [],
|
||||
homeTab: {
|
||||
name: 'root',
|
||||
path: '/',
|
||||
fullPath: '/',
|
||||
meta: {
|
||||
title: 'Root'
|
||||
},
|
||||
@ -34,7 +34,7 @@ export const useTabStore = defineStore('tab-store', {
|
||||
/** 当前激活状态的页签索引 */
|
||||
activeTabIndex(state) {
|
||||
const { tabs, activeTab } = state;
|
||||
return tabs.findIndex(tab => tab.path === activeTab);
|
||||
return tabs.findIndex(tab => tab.fullPath === activeTab);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -45,10 +45,10 @@ export const useTabStore = defineStore('tab-store', {
|
||||
},
|
||||
/**
|
||||
* 设置当前路由对应的页签为激活状态
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
setActiveTab(path: string) {
|
||||
this.activeTab = path;
|
||||
setActiveTab(fullPath: string) {
|
||||
this.activeTab = fullPath;
|
||||
},
|
||||
/**
|
||||
* 初始化首页页签路由
|
||||
@ -68,23 +68,39 @@ export const useTabStore = defineStore('tab-store', {
|
||||
* @param route - 路由
|
||||
*/
|
||||
addTab(route: RouteLocationNormalizedLoaded) {
|
||||
if (!isInTabRoutes(this.tabs, route.path)) {
|
||||
const tab = getTabRouteByVueRoute(route);
|
||||
this.tabs.push(tab);
|
||||
const tab = getTabRouteByVueRoute(route);
|
||||
|
||||
if (isInTabRoutes(this.tabs, tab.fullPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = getIndexInTabRoutesByRouteName(this.tabs, route.name as string);
|
||||
|
||||
if (index === -1) {
|
||||
this.tabs.push(tab);
|
||||
return;
|
||||
}
|
||||
|
||||
const { multiTab = false } = route.meta;
|
||||
if (!multiTab) {
|
||||
this.tabs.splice(index, 1, tab);
|
||||
return;
|
||||
}
|
||||
|
||||
this.tabs.push(tab);
|
||||
},
|
||||
/**
|
||||
* 删除多页签
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
removeTab(path: string) {
|
||||
removeTab(fullPath: string) {
|
||||
const { routerPush } = useRouterPush(false);
|
||||
|
||||
const isActive = this.activeTab === path;
|
||||
const updateTabs = this.tabs.filter(tab => tab.path !== path);
|
||||
const isActive = this.activeTab === fullPath;
|
||||
const updateTabs = this.tabs.filter(tab => tab.fullPath !== fullPath);
|
||||
this.tabs = updateTabs;
|
||||
if (isActive && updateTabs.length) {
|
||||
const activePath = updateTabs[updateTabs.length - 1].path;
|
||||
const activePath = updateTabs[updateTabs.length - 1].fullPath;
|
||||
this.setActiveTab(activePath);
|
||||
routerPush(activePath);
|
||||
}
|
||||
@ -96,73 +112,73 @@ export const useTabStore = defineStore('tab-store', {
|
||||
clearTab(excludes: string[] = []) {
|
||||
const { routerPush } = useRouterPush(false);
|
||||
|
||||
const homePath = this.homeTab.path;
|
||||
const homePath = this.homeTab.fullPath;
|
||||
const remain = [homePath, ...excludes];
|
||||
const hasActive = remain.includes(this.activeTab);
|
||||
const updateTabs = this.tabs.filter(tab => remain.includes(tab.path));
|
||||
const updateTabs = this.tabs.filter(tab => remain.includes(tab.fullPath));
|
||||
this.tabs = updateTabs;
|
||||
if (!hasActive && updateTabs.length) {
|
||||
const activePath = updateTabs[updateTabs.length - 1].path;
|
||||
const activePath = updateTabs[updateTabs.length - 1].fullPath;
|
||||
this.setActiveTab(activePath);
|
||||
routerPush(activePath);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 清除左边多页签
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
clearLeftTab(path: string) {
|
||||
const index = getIndexInTabRoutes(this.tabs, path);
|
||||
clearLeftTab(fullPath: string) {
|
||||
const index = getIndexInTabRoutes(this.tabs, fullPath);
|
||||
if (index > -1) {
|
||||
const excludes = this.tabs.slice(index).map(item => item.path);
|
||||
const excludes = this.tabs.slice(index).map(item => item.fullPath);
|
||||
this.clearTab(excludes);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 清除右边多页签
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
clearRightTab(path: string) {
|
||||
const index = getIndexInTabRoutes(this.tabs, path);
|
||||
clearRightTab(fullPath: string) {
|
||||
const index = getIndexInTabRoutes(this.tabs, fullPath);
|
||||
if (index > -1) {
|
||||
const excludes = this.tabs.slice(0, index + 1).map(item => item.path);
|
||||
const excludes = this.tabs.slice(0, index + 1).map(item => item.fullPath);
|
||||
this.clearTab(excludes);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 点击单个tab
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
handleClickTab(path: string) {
|
||||
handleClickTab(fullPath: string) {
|
||||
const { routerPush } = useRouterPush(false);
|
||||
|
||||
const isActive = this.activeTab === path;
|
||||
const isActive = this.activeTab === fullPath;
|
||||
if (!isActive) {
|
||||
this.setActiveTab(path);
|
||||
routerPush(path);
|
||||
this.setActiveTab(fullPath);
|
||||
routerPush(fullPath);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 记录tab滚动位置
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
* @param position - tab当前页的滚动位置
|
||||
*/
|
||||
recordTabScrollPosition(path: string, position: { left: number; top: number }) {
|
||||
const index = getIndexInTabRoutes(this.tabs, path);
|
||||
recordTabScrollPosition(fullPath: string, position: { left: number; top: number }) {
|
||||
const index = getIndexInTabRoutes(this.tabs, fullPath);
|
||||
if (index > -1) {
|
||||
this.tabs[index].scrollPosition = position;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取tab滚动位置
|
||||
* @param path - 路由path
|
||||
* @param fullPath - 路由fullPath
|
||||
*/
|
||||
getTabScrollPosition(path: string) {
|
||||
getTabScrollPosition(fullPath: string) {
|
||||
const position = {
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
const index = getIndexInTabRoutes(this.tabs, path);
|
||||
const index = getIndexInTabRoutes(this.tabs, fullPath);
|
||||
if (index > -1) {
|
||||
Object.assign(position, this.tabs[index].scrollPosition);
|
||||
}
|
||||
@ -174,20 +190,27 @@ export const useTabStore = defineStore('tab-store', {
|
||||
|
||||
const tabs: GlobalTabRoute[] = theme.tab.isCache ? getTabRoutes() : [];
|
||||
|
||||
const hasHome = isInTabRoutes(tabs, this.homeTab.path);
|
||||
const hasHome = getIndexInTabRoutesByRouteName(tabs, this.homeTab.name as string) > -1;
|
||||
if (!hasHome && this.homeTab.name !== 'root') {
|
||||
tabs.unshift(this.homeTab);
|
||||
}
|
||||
|
||||
const isHome = currentRoute.path === this.homeTab.path;
|
||||
const hasCurrent = isInTabRoutes(tabs, currentRoute.path);
|
||||
if (!isHome && !hasCurrent) {
|
||||
const isHome = currentRoute.fullPath === this.homeTab.fullPath;
|
||||
const index = getIndexInTabRoutesByRouteName(tabs, currentRoute.name as string);
|
||||
if (!isHome) {
|
||||
const currentTab = getTabRouteByVueRoute(currentRoute);
|
||||
tabs.push(currentTab);
|
||||
if (!currentRoute.meta.multiTab) {
|
||||
tabs.splice(index, 1, currentTab);
|
||||
} else {
|
||||
const hasCurrent = isInTabRoutes(tabs, currentRoute.fullPath);
|
||||
if (!hasCurrent) {
|
||||
tabs.push(currentTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tabs = tabs;
|
||||
this.setActiveTab(currentRoute.path);
|
||||
this.setActiveTab(currentRoute.fullPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
10
src/typings/route.d.ts
vendored
10
src/typings/route.d.ts
vendored
@ -44,6 +44,10 @@ declare namespace AuthRoute {
|
||||
| 'auth-demo'
|
||||
| 'auth-demo_permission'
|
||||
| 'auth-demo_super'
|
||||
| 'function'
|
||||
| 'function_tab'
|
||||
| 'function_tab-detail'
|
||||
| 'function_tab-multi-detail'
|
||||
| 'exception'
|
||||
| 'exception_403'
|
||||
| 'exception_404'
|
||||
@ -94,12 +98,14 @@ declare namespace AuthRoute {
|
||||
hide?: boolean;
|
||||
/** 外链链接 */
|
||||
href?: string;
|
||||
/** 是否支持多个tab页签(默认一个,即相同name的路由会被替换) */
|
||||
multiTab?: boolean;
|
||||
/** 路由顺序,可用于菜单的排序 */
|
||||
order?: number;
|
||||
/** 表示是否是多级路由的中间级路由(用于转换路由数据时筛选多级路由的标识,定义路由时不用填写) */
|
||||
multi?: boolean;
|
||||
/** 当前路由需要选中的菜单项(用于跳转至不在左侧菜单显示的路由且需要高亮某个菜单的情况) */
|
||||
activeMenu?: RouteKey;
|
||||
/** 表示是否是多级路由的中间级路由(用于转换路由数据时筛选多级路由的标识,定义路由时不用填写) */
|
||||
multi?: boolean;
|
||||
};
|
||||
|
||||
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
|
||||
|
3
src/typings/system.d.ts
vendored
3
src/typings/system.d.ts
vendored
@ -276,7 +276,8 @@ type GlobalBreadcrumb = import('naive-ui').DropdownOption & {
|
||||
};
|
||||
|
||||
/** 多页签Tab的路由 */
|
||||
interface GlobalTabRoute extends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'path' | 'meta'> {
|
||||
interface GlobalTabRoute
|
||||
extends Pick<import('vue-router').RouteLocationNormalizedLoaded, 'name' | 'fullPath' | 'meta'> {
|
||||
/** 滚动的位置 */
|
||||
scrollPosition: {
|
||||
left: number;
|
||||
|
@ -3,13 +3,13 @@ import { setLocal, getLocal } from '../storage';
|
||||
|
||||
/** 缓存多页签数据 */
|
||||
export function setTabRoutes(data: GlobalTabRoute[]) {
|
||||
setLocal(EnumStorageKey['tab-routes'], data);
|
||||
setLocal(EnumStorageKey['multi-tab-routes'], data);
|
||||
}
|
||||
|
||||
/** 获取缓存的多页签数据 */
|
||||
export function getTabRoutes() {
|
||||
const routes: GlobalTabRoute[] = [];
|
||||
const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['tab-routes']);
|
||||
const data = getLocal<GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']);
|
||||
if (data) {
|
||||
const defaultTabRoutes = data.map(item => ({
|
||||
...item,
|
||||
|
27
src/views/function/tab-detail/index.vue
Normal file
27
src/views/function/tab-detail/index.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<n-space :vertical="true" :size="16">
|
||||
<n-card title="Tab Detail" :bordered="false" size="small" class="shadow-sm rounded-16px">
|
||||
<n-space :vertical="true" :size="12">
|
||||
<div>当前路由的描述数据(meta):</div>
|
||||
<div>{{ route.meta }}</div>
|
||||
<n-button @click="handleToTab">返回Tab</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { routeName } from '@/router';
|
||||
import { useRouterPush } from '@/composables';
|
||||
|
||||
const { routerPush } = useRouterPush();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
function handleToTab() {
|
||||
routerPush({ name: routeName('function_tab') });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
28
src/views/function/tab-multi-detail/index.vue
Normal file
28
src/views/function/tab-multi-detail/index.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<n-space :vertical="true" :size="16">
|
||||
<n-card title="Tab Detail" :bordered="false" size="small" class="shadow-sm rounded-16px">
|
||||
<n-space :vertical="true" :size="12">
|
||||
<div>当前路由的描述数据(meta):</div>
|
||||
<div>{{ route.meta }}</div>
|
||||
<div>当前路由的查询数据(query):</div>
|
||||
<div>{{ route.query }}</div>
|
||||
<n-button @click="handleToTab">返回Tab</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { routeName } from '@/router';
|
||||
import { useRouterPush } from '@/composables';
|
||||
|
||||
const route = useRoute();
|
||||
const { routerPush } = useRouterPush();
|
||||
|
||||
function handleToTab() {
|
||||
routerPush({ name: routeName('function_tab') });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
28
src/views/function/tab/index.vue
Normal file
28
src/views/function/tab/index.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<n-space :vertical="true" :size="16">
|
||||
<n-card title="Tab Home" :bordered="false" size="small" class="shadow-sm rounded-16px">
|
||||
<n-space :vertical="true" :size="12">
|
||||
<n-button @click="handleToTabDetail">跳转Tab Detail</n-button>
|
||||
<n-button @click="handleToTabMultiDetail(1)">跳转Tab Multi Detail 1</n-button>
|
||||
<n-button @click="handleToTabMultiDetail(2)">跳转Tab Multi Detail 2</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { routeName } from '@/router';
|
||||
import { useRouterPush } from '@/composables';
|
||||
|
||||
const { routerPush } = useRouterPush();
|
||||
|
||||
function handleToTabDetail() {
|
||||
routerPush({ name: routeName('function_tab-detail'), query: { name: 'abc' }, hash: '#DEMO_HASH' });
|
||||
}
|
||||
|
||||
function handleToTabMultiDetail(num: number) {
|
||||
routerPush({ name: routeName('function_tab-multi-detail'), query: { name: 'abc', num }, hash: '#DEMO_HASH' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user