feat(projects): 多页签绑定路由

This commit is contained in:
Soybean 2021-09-17 19:50:24 +08:00
parent eec0b36f59
commit f29bc05dd9
13 changed files with 195 additions and 65 deletions

View File

@ -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' = '工作台',

View File

@ -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
};
}

View File

@ -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;

View File

@ -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 {

View File

@ -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"

View File

@ -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="页面切换动画">

View File

@ -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';

View File

@ -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
View 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();
}

View File

@ -48,7 +48,7 @@ const themeSettings: ThemeSettings = {
bgColor: '#fff'
},
multiTabStyle: {
height: 48,
height: 44,
visible: true,
bgColor: '#fff'
},

View File

@ -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() {

View File

@ -97,7 +97,7 @@ const themeStore = defineStore({
this.multiTabStyle.height = height;
}
},
/** 设置多签的显示 */
/** 设置多签的显示 */
handleMultiTabVisible(visible: boolean) {
this.multiTabStyle.visible = visible;
},

View 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>