feat(projects): 多页签绑定路由
This commit is contained in:
parent
eec0b36f59
commit
f29bc05dd9
@ -5,6 +5,7 @@ export enum EnumRoutePath {
|
||||
'not-found' = '/404',
|
||||
'no-permission' = '/403',
|
||||
'service-error' = '/500',
|
||||
'reload' = '/reload',
|
||||
// 自定义路由
|
||||
'dashboard' = '/dashboard',
|
||||
'dashboard-analysis' = '/dashboard/analysis',
|
||||
@ -22,6 +23,8 @@ export enum EnumRouteTitle {
|
||||
'not-found' = '未找到',
|
||||
'no-permission' = '无权限',
|
||||
'service-error' = '服务器错误',
|
||||
'reload' = '重载',
|
||||
// 自定义路由
|
||||
'dashboard' = '仪表盘',
|
||||
'dashboard-analysis' = '分析页',
|
||||
'dashboard-workbench' = '工作台',
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
import { EnumRoutePath } from '@/enum';
|
||||
import { router as globalRouter, RouteNameMap } from '@/router';
|
||||
import type { LoginModuleType } from '@/interface';
|
||||
|
||||
@ -61,9 +62,14 @@ export default function useRouterChange(inSetup: boolean = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function toReload(redirectUrl: string) {
|
||||
router.push({ path: EnumRoutePath.reload, query: { redirectUrl } });
|
||||
}
|
||||
|
||||
return {
|
||||
toHome,
|
||||
toLogin,
|
||||
toCurrentLogin
|
||||
toCurrentLogin,
|
||||
toReload
|
||||
};
|
||||
}
|
||||
|
@ -15,13 +15,13 @@ export interface ThemeSettings {
|
||||
menuStyle: MenuStyle;
|
||||
/** 头部样式 */
|
||||
headerStyle: HeaderStyle;
|
||||
/** 多标签样式 */
|
||||
/** 多页签样式 */
|
||||
multiTabStyle: MultiTabStyle;
|
||||
/** 面包屑样式 */
|
||||
crumbsStyle: CrumbsStyle;
|
||||
/** 页面样式 */
|
||||
pageStyle: PageStyle;
|
||||
/** 固定头部和多标签 */
|
||||
/** 固定头部和多页签 */
|
||||
fixedHeaderAndTab: boolean;
|
||||
/** 显示重载按钮 */
|
||||
showReload: boolean;
|
||||
@ -70,9 +70,9 @@ interface MenuStyle {
|
||||
}
|
||||
|
||||
interface MultiTabStyle {
|
||||
/** 多标签高度 */
|
||||
/** 多页签高度 */
|
||||
height: number;
|
||||
/** 多标签可见 */
|
||||
/** 多页签可见 */
|
||||
visible: boolean;
|
||||
/** 背景颜色 */
|
||||
bgColor: string;
|
||||
|
@ -1,25 +1,33 @@
|
||||
<template>
|
||||
<div v-if="fixedHeaderAndTab && theme.navStyle.mode !== 'horizontal-mix'" class="multi-tab-height w-full"></div>
|
||||
<div
|
||||
class="multi-tab-height flex-y-center w-full px-10px"
|
||||
class="multi-tab-height flex-y-center justify-between w-full px-10px"
|
||||
:class="{ 'multi-tab-top absolute': fixedHeaderAndTab, 'bg-[#f5f7f9]': !theme.darkMode }"
|
||||
:style="{ zIndex }"
|
||||
>
|
||||
<n-space :align="'center'">
|
||||
<n-tag>爱在西元前</n-tag>
|
||||
<n-tag type="success">不该</n-tag>
|
||||
<n-tag type="warning">超人不会飞</n-tag>
|
||||
<n-tag type="error">手写的从前</n-tag>
|
||||
<n-tag type="info">哪里都是你</n-tag>
|
||||
<n-gradient-text size="24">这是MultiTab组件</n-gradient-text>
|
||||
<n-tag
|
||||
v-for="item in app.multiTab.routes"
|
||||
:key="item.path"
|
||||
:type="app.multiTab.activeRoute === item.fullPath ? 'primary' : 'default'"
|
||||
class="cursor-pointer"
|
||||
@click="handleClickTab(item.fullPath)"
|
||||
>
|
||||
{{ item.meta?.title }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
<div class="flex-center w-32px h-32px bg-white cursor-pointer" @click="handleReload">
|
||||
<icon-mdi-refresh class="text-16px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { NSpace, NTag, NGradientText } from 'naive-ui';
|
||||
import { useThemeStore } from '@/store';
|
||||
import { computed, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { NSpace, NTag } from 'naive-ui';
|
||||
import { useThemeStore, useAppStore } from '@/store';
|
||||
import { useRouterChange } from '@/hooks';
|
||||
|
||||
defineProps({
|
||||
zIndex: {
|
||||
@ -28,7 +36,11 @@ defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
const { initMultiTab, addMultiTab, setActiveMultiTab, handleClickTab } = useAppStore();
|
||||
const { toReload } = useRouterChange();
|
||||
|
||||
const fixedHeaderAndTab = computed(() => theme.fixedHeaderAndTab || theme.navStyle.mode === 'horizontal-mix');
|
||||
const multiTabHeight = computed(() => {
|
||||
@ -39,6 +51,25 @@ const headerHeight = computed(() => {
|
||||
const { height } = theme.headerStyle;
|
||||
return `${height}px`;
|
||||
});
|
||||
|
||||
function handleReload() {
|
||||
toReload(route.fullPath);
|
||||
}
|
||||
|
||||
function init() {
|
||||
initMultiTab();
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
newValue => {
|
||||
addMultiTab(route);
|
||||
setActiveMultiTab(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
// 初始化
|
||||
init();
|
||||
</script>
|
||||
<style scoped>
|
||||
.multi-tab-height {
|
||||
|
@ -4,7 +4,7 @@
|
||||
<setting-menu-item label="分割菜单">
|
||||
<n-switch :value="theme.menuStyle.splitMenu" @update:value="handleSplitMenu" />
|
||||
</setting-menu-item>
|
||||
<setting-menu-item label="固定头部和多标签">
|
||||
<setting-menu-item label="固定头部和多页签">
|
||||
<n-switch :value="splitMenu" :disabled="disabledSplitMenu" @update:value="handleFixedHeaderAndTab" />
|
||||
</setting-menu-item>
|
||||
<setting-menu-item label="头部高度">
|
||||
@ -16,7 +16,7 @@
|
||||
@update:value="handleHeaderHeight"
|
||||
/>
|
||||
</setting-menu-item>
|
||||
<setting-menu-item label="多标签高度">
|
||||
<setting-menu-item label="多页签高度">
|
||||
<n-input-number
|
||||
class="w-120px"
|
||||
size="small"
|
||||
|
@ -7,7 +7,7 @@
|
||||
<setting-menu-item label="面包屑图标">
|
||||
<n-switch :value="theme.crumbsStyle.showIcon" @update:value="handleCrumbsIconVisible" />
|
||||
</setting-menu-item>
|
||||
<setting-menu-item label="多标签">
|
||||
<setting-menu-item label="多页签">
|
||||
<n-switch :value="theme.multiTabStyle.visible" @update:value="handleMultiTabVisible" />
|
||||
</setting-menu-item>
|
||||
<setting-menu-item label="页面切换动画">
|
||||
|
@ -1,24 +1,3 @@
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||
import type { App } from 'vue';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { constantRoutes, customRoutes, RouteNameMap } from './routes';
|
||||
import createRouterGuide from './permission';
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [...customRoutes, ...constantRoutes];
|
||||
|
||||
/** 用于部署vercel托管服务 */
|
||||
const isVercel = import.meta.env.VITE_HTTP_ENV === 'VERCEL';
|
||||
|
||||
export const router = createRouter({
|
||||
history: isVercel ? createWebHashHistory() : createWebHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
export async function setupRouter(app: App) {
|
||||
app.use(router);
|
||||
createRouterGuide(router);
|
||||
await router.isReady();
|
||||
}
|
||||
|
||||
export { RouteNameMap };
|
||||
export { router, setupRouter } from './setup';
|
||||
export { RouteNameMap, ROUTE_HOME, customRoutes } from './routes';
|
||||
export { menus } from './menus';
|
||||
|
@ -17,7 +17,7 @@ const loginModuleRegExp = getLoginModuleRegExp();
|
||||
* 固定不变的路由
|
||||
* @description !最后一项重定向未找到的路由须放置路由的最后一项
|
||||
*/
|
||||
export const constantRoutes: RouteRecordRaw[] = [
|
||||
const constantRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
name: RouteNameMap.get('system'),
|
||||
path: EnumRoutePath.system,
|
||||
@ -82,6 +82,17 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
}
|
||||
];
|
||||
|
||||
/** 路由首页 */
|
||||
export const ROUTE_HOME: CustomRoute = {
|
||||
name: RouteNameMap.get('dashboard-analysis'),
|
||||
path: EnumRoutePath['dashboard-analysis'],
|
||||
component: () => import('@/views/dashboard/analysis/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: EnumRouteTitle['dashboard-analysis']
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 自定义路由
|
||||
*/
|
||||
@ -89,10 +100,24 @@ export const customRoutes: CustomRoute[] = [
|
||||
{
|
||||
name: RouteNameMap.get('root'),
|
||||
path: EnumRoutePath.root,
|
||||
redirect: { name: RouteNameMap.get('dashboard-analysis') },
|
||||
component: BasicLayout,
|
||||
redirect: { name: ROUTE_HOME.name },
|
||||
meta: {
|
||||
isNotMenu: true
|
||||
}
|
||||
},
|
||||
children: [
|
||||
// 重载
|
||||
{
|
||||
name: RouteNameMap.get('reload'),
|
||||
path: EnumRoutePath.reload,
|
||||
component: () => import('@/views/system/reload/index.vue'),
|
||||
meta: {
|
||||
title: EnumRouteTitle.reload,
|
||||
isNotMenu: true,
|
||||
fullPage: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: RouteNameMap.get('dashboard'),
|
||||
@ -105,15 +130,7 @@ export const customRoutes: CustomRoute[] = [
|
||||
icon: Dashboard
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: RouteNameMap.get('dashboard-analysis'),
|
||||
path: EnumRoutePath['dashboard-analysis'],
|
||||
component: () => import('@/views/dashboard/analysis/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: EnumRouteTitle['dashboard-analysis']
|
||||
}
|
||||
},
|
||||
ROUTE_HOME,
|
||||
{
|
||||
name: RouteNameMap.get('dashboard-workbench'),
|
||||
path: EnumRoutePath['dashboard-workbench'],
|
||||
@ -169,10 +186,5 @@ export const customRoutes: CustomRoute[] = [
|
||||
}
|
||||
];
|
||||
|
||||
/** 路由白名单(不需要登录) */
|
||||
export const whitelistRoutes: string[] = [
|
||||
RouteNameMap.get('login')!,
|
||||
RouteNameMap.get('exception-403')!,
|
||||
RouteNameMap.get('exception-404')!,
|
||||
RouteNameMap.get('exception-500')!
|
||||
];
|
||||
/** 所有路由 */
|
||||
export const routes: RouteRecordRaw[] = [...customRoutes, ...constantRoutes];
|
||||
|
18
src/router/setup.ts
Normal file
18
src/router/setup.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';
|
||||
import type { App } from 'vue';
|
||||
import { routes } from './routes';
|
||||
import createRouterGuide from './permission';
|
||||
|
||||
/** 用于部署vercel托管服务 */
|
||||
const isVercel = import.meta.env.VITE_HTTP_ENV === 'VERCEL';
|
||||
|
||||
export const router = createRouter({
|
||||
history: isVercel ? createWebHashHistory() : createWebHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
export async function setupRouter(app: App) {
|
||||
app.use(router);
|
||||
createRouterGuide(router);
|
||||
await router.isReady();
|
||||
}
|
@ -48,7 +48,7 @@ const themeSettings: ThemeSettings = {
|
||||
bgColor: '#fff'
|
||||
},
|
||||
multiTabStyle: {
|
||||
height: 48,
|
||||
height: 44,
|
||||
visible: true,
|
||||
bgColor: '#fff'
|
||||
},
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { nextTick } from 'vue';
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||
import { store } from '@/store';
|
||||
import { router, ROUTE_HOME } from '@/router';
|
||||
|
||||
/** app状态 */
|
||||
interface AppState {
|
||||
menu: MenuState;
|
||||
multiTab: MultiTab;
|
||||
/** 重新加载标记 */
|
||||
reloadFlag: boolean;
|
||||
settingDrawer: SettingDrawer;
|
||||
}
|
||||
|
||||
@ -15,8 +19,15 @@ interface MenuState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
type MultiTabRoute = Partial<RouteLocationNormalizedLoaded> & {
|
||||
path: string;
|
||||
fullPath: string;
|
||||
};
|
||||
|
||||
/** 多页签 */
|
||||
interface MultiTab {
|
||||
routes: RouteLocationNormalizedLoaded[];
|
||||
routes: MultiTabRoute[];
|
||||
activeRoute: string;
|
||||
}
|
||||
|
||||
/** 项目配置抽屉的状态 */
|
||||
@ -32,8 +43,10 @@ const appStore = defineStore({
|
||||
collapsed: false
|
||||
},
|
||||
multiTab: {
|
||||
routes: []
|
||||
routes: [],
|
||||
activeRoute: ''
|
||||
},
|
||||
reloadFlag: true,
|
||||
settingDrawer: {
|
||||
visible: false
|
||||
}
|
||||
@ -47,9 +60,59 @@ const appStore = defineStore({
|
||||
toggleMenu() {
|
||||
this.menu.collapsed = !this.menu.collapsed;
|
||||
},
|
||||
/** 判断tab路由是否存在某个路由 */
|
||||
getIndexInTabRoutes(fullPath: string) {
|
||||
return this.multiTab.routes.findIndex(item => item.fullPath === fullPath);
|
||||
},
|
||||
/** 添加多tab的数据 */
|
||||
addMultiTab(route: RouteLocationNormalizedLoaded) {
|
||||
this.multiTab.routes.push(route);
|
||||
const { fullPath } = route;
|
||||
const isExist = this.getIndexInTabRoutes(fullPath) > -1;
|
||||
if (!isExist) {
|
||||
this.multiTab.routes.push({ ...route });
|
||||
}
|
||||
},
|
||||
handleClickTab(fullPath: string) {
|
||||
if (this.multiTab.activeRoute !== fullPath) {
|
||||
router.push(fullPath);
|
||||
this.setActiveMultiTab(fullPath);
|
||||
}
|
||||
},
|
||||
/** 设置当前路由对应的tab为激活状态 */
|
||||
setActiveMultiTab(fullPath: string) {
|
||||
this.multiTab.activeRoute = fullPath;
|
||||
},
|
||||
/** 获取路由首页信息 */
|
||||
getHomeTabRoute(route: RouteLocationNormalizedLoaded, isHome: boolean) {
|
||||
const { name, path, meta } = ROUTE_HOME;
|
||||
const home: MultiTabRoute = {
|
||||
name,
|
||||
path,
|
||||
fullPath: path,
|
||||
meta
|
||||
};
|
||||
if (isHome) {
|
||||
Object.assign(home, route);
|
||||
}
|
||||
return home;
|
||||
},
|
||||
/** 初始化多页签数据 */
|
||||
initMultiTab() {
|
||||
const { currentRoute } = router;
|
||||
const isHome = currentRoute.value.name === ROUTE_HOME.name;
|
||||
const home = this.getHomeTabRoute(currentRoute.value, isHome);
|
||||
const routes = [home];
|
||||
if (!isHome) {
|
||||
routes.push(currentRoute.value);
|
||||
}
|
||||
this.multiTab.routes = routes;
|
||||
this.setActiveMultiTab(currentRoute.value.fullPath);
|
||||
},
|
||||
/** 重新加载页面 */
|
||||
async handleReload() {
|
||||
this.reloadFlag = false;
|
||||
await nextTick();
|
||||
this.reloadFlag = true;
|
||||
},
|
||||
/** 打开配置抽屉 */
|
||||
openSettingDrawer() {
|
||||
|
@ -97,7 +97,7 @@ const themeStore = defineStore({
|
||||
this.multiTabStyle.height = height;
|
||||
}
|
||||
},
|
||||
/** 设置多标签的显示 */
|
||||
/** 设置多页签的显示 */
|
||||
handleMultiTabVisible(visible: boolean) {
|
||||
this.multiTabStyle.visible = visible;
|
||||
},
|
||||
|
18
src/views/system/reload/index.vue
Normal file
18
src/views/system/reload/index.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const { query } = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
function init() {
|
||||
const redirect = (query.redirectUrl as string) || '/';
|
||||
router.replace(redirect);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user