refactor(projects): 路由声明重构,添加composables,BaseLayout进行中,文件夹规范

This commit is contained in:
Soybean 2021-11-19 01:33:36 +08:00
parent 1c5fdca596
commit 1e84d13d54
75 changed files with 668 additions and 565 deletions

View File

@ -62,6 +62,7 @@
"material-icon-theme.folders.associations": { "material-icon-theme.folders.associations": {
"enum": "typescript", "enum": "typescript",
"store": "context", "store": "context",
"composables": "hook",
"business": "core", "business": "core",
} }
} }

View File

@ -0,0 +1,4 @@
export * from './route';
export * from './router';
export * from './system';
export * from './layout';

View File

@ -0,0 +1,49 @@
import { computed } from 'vue';
import { useThemeStore, useAppStore } from '@/store';
export function useLayoutConfig() {
const theme = useThemeStore();
const app = useAppStore();
/** 反转sider */
const siderInverted = computed(() => theme.navStyle.theme !== 'light');
/** 侧边菜单宽度 */
const siderMenuWidth = computed(() => {
const { collapsed } = app.menu;
const { collapsedWidth, width } = theme.menuStyle;
return collapsed ? collapsedWidth : width;
});
/** 反转header */
const headerInverted = computed(() => (theme.navStyle.theme !== 'dark' ? siderInverted.value : !siderInverted.value));
/** 头部定位 */
const headerPosition = computed(() => (theme.fixedHeaderAndTab ? 'absolute' : 'static'));
/** 全局头部的高度(px) */
const headerHeight = computed(() => `${theme.headerStyle.height}px`);
/** 多页签Tab的高度(px) */
const multiTabHeight = computed(() => `${theme.multiTabStyle.height}px`);
/** 全局头部和多页签的总高度 */
const headerAndMultiTabHeight = computed(() => {
const {
multiTabStyle: { visible, height: tH },
headerStyle: { height: hH }
} = theme;
const height = visible ? tH + hH : hH;
return `${height}px`;
});
return {
siderInverted,
siderMenuWidth,
headerInverted,
headerPosition,
headerHeight,
multiTabHeight,
headerAndMultiTabHeight
};
}

View File

@ -0,0 +1,63 @@
import { computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { routeName } from '@/router';
import type { RouteKey } from '@/interface';
/**
*
* @description - setup里面调用
*/
export function useRouteProps() {
const route = useRoute();
const props = computed(() => {
/** 路由名称 */
const name = route.name as string;
/** 缓存页面 */
const keepAlive = Boolean(route.meta?.keepAlive);
/** 视高100% */
const fullPage = Boolean(route.meta?.fullPage);
return {
name,
keepAlive,
fullPage
};
});
return props;
}
/**
*
* @description - setup里面调用
*/
export function useRouteQuery() {
const route = useRoute();
/** 登录跳转链接 */
const loginRedirectUrl = computed(() => {
let url: string | undefined;
if (route.name === routeName('login')) {
url = (route.query?.redirectUrl as string) || '';
}
return url;
});
return {
loginRedirectUrl
};
}
/**
*
* @param callback
*/
export function routeNameWatcher(callback: (name: RouteKey) => void) {
const route = useRoute();
watch(
() => route.name,
newValue => {
callback(newValue as RouteKey);
}
);
}

View File

@ -1,20 +1,13 @@
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router'; import type { RouteLocationRaw } from 'vue-router';
import { EnumRoutePath } from '@/enum'; import { router as globalRouter, routePath } from '@/router';
import { router as globalRouter } from '@/router';
import type { LoginModuleType } from '@/interface'; import type { LoginModuleType } from '@/interface';
/**
*
* - current: 取当前的path作为重定向地址
*/
type LoginRedirect = 'current' | EnumRoutePath;
/** /**
* *
* @param inSetup - vue页面/setup里面调用 * @param inSetup - vue页面/setup里面调用
*/ */
export default function useRouterChange(inSetup: boolean = true) { export function useRouterPush(inSetup: boolean = true) {
const router = inSetup ? useRouter() : globalRouter; const router = inSetup ? useRouter() : globalRouter;
const route = inSetup ? useRoute() : null; const route = inSetup ? useRoute() : null;
@ -23,6 +16,11 @@ export default function useRouterChange(inSetup: boolean = true) {
router.push('/'); router.push('/');
} }
/**
*
* - current: 取当前的path作为重定向地址
*/
type LoginRedirect = 'current' | string;
/** /**
* (vue路由) * (vue路由)
* @param module - * @param module -
@ -30,13 +28,13 @@ export default function useRouterChange(inSetup: boolean = true) {
*/ */
function toLogin(module: LoginModuleType = 'pwd-login', redirectUrl: LoginRedirect = 'current') { function toLogin(module: LoginModuleType = 'pwd-login', redirectUrl: LoginRedirect = 'current') {
const routeLocation: RouteLocationRaw = { const routeLocation: RouteLocationRaw = {
path: EnumRoutePath.login, path: routePath('login'),
query: { module } query: { module }
}; };
if (redirectUrl) { if (redirectUrl) {
let url = redirectUrl; let url = redirectUrl;
if (redirectUrl === 'current') { if (redirectUrl === 'current') {
url = router.currentRoute.value.fullPath as EnumRoutePath; url = router.currentRoute.value.fullPath;
} }
routeLocation.query!.redirectUrl = url; routeLocation.query!.redirectUrl = url;
} }
@ -51,12 +49,12 @@ export default function useRouterChange(inSetup: boolean = true) {
function toCurrentLogin(module: LoginModuleType) { function toCurrentLogin(module: LoginModuleType) {
if (route) { if (route) {
const { query } = route; const { query } = route;
router.push({ path: EnumRoutePath.login, query: { ...query, module } }); router.push({ path: routePath('login'), query: { ...query, module } });
} }
} }
/** 登录后跳转重定向的地址 */ /** 登录后跳转重定向的地址 */
function toLoginRedirectUrl(path: EnumRoutePath) { function toLoginRedirectUrl(path: string) {
router.push(path); router.push(path);
} }

View File

@ -1,11 +1,13 @@
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core'; import { useBreakpoints, breakpointsTailwind } from '@vueuse/core';
/** 项目名称 */
export function useAppTitle() {
return import.meta.env.VITE_APP_TITLE as string;
}
/** 是否是移动端 */ /** 是否是移动端 */
export default function useIsMobile() { export function useIsMobile() {
const breakpoints = useBreakpoints(breakpointsTailwind); const breakpoints = useBreakpoints(breakpointsTailwind);
const isMobile = breakpoints.smaller('lg'); const isMobile = breakpoints.smaller('lg');
return isMobile;
return {
isMobile
};
} }

1
src/composables/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './common';

5
src/enum/common/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './system';
export * from './theme';
export * from './animate';
export * from './typeof';
export * from './storage';

View File

@ -5,21 +5,6 @@ export enum ContentType {
formData = 'multipart/form-data' formData = 'multipart/form-data'
} }
/** 数据类型 */
export enum EnumDataType {
number = '[object Number]',
string = '[object String]',
boolean = '[object Boolean]',
null = '[object Null]',
undefined = '[object Undefined]',
object = '[object Object]',
array = '[object Array]',
date = '[object Date]',
regexp = '[object RegExp]',
set = '[object Set]',
map = '[object Map]'
}
/** 登录模块 */ /** 登录模块 */
export enum EnumLoginModule { export enum EnumLoginModule {
'pwd-login' = '账密登录', 'pwd-login' = '账密登录',

14
src/enum/common/typeof.ts Normal file
View File

@ -0,0 +1,14 @@
/** 数据类型 */
export enum EnumDataType {
number = '[object Number]',
string = '[object String]',
boolean = '[object Boolean]',
null = '[object Null]',
undefined = '[object Undefined]',
object = '[object Object]',
array = '[object Array]',
date = '[object Date]',
regexp = '[object RegExp]',
set = '[object Set]',
map = '[object Map]'
}

View File

@ -1,5 +1 @@
export * from './common'; export * from './common';
export * from './animate';
export * from './theme';
export * from './route';
export * from './storage';

View File

@ -1,71 +0,0 @@
export enum EnumRoutePath {
'root' = '/',
'login' = '/login',
'not-found' = '/404',
'no-permission' = '/403',
'service-error' = '/500',
'reload' = '/reload',
// 自定义路由
'dashboard' = '/dashboard',
'dashboard_analysis' = '/dashboard/analysis',
'dashboard_workbench' = '/dashboard/workbench',
'document' = '/document',
'document_vue' = '/document/vue',
'document_vite' = '/document/vite',
'document_naive' = '/document/naive',
'component' = '/component',
'component_map' = '/component/map',
'component_video' = '/component/video',
'component_editor' = '/component/editor',
'component_editor_quill' = '/component/editor/quill',
'component_editor_markdown' = '/component/editor/markdown',
'component_swiper' = '/component/swiper',
'feat' = '/feat',
'feat_copy' = '/feat/copy',
'feat_icon' = '/feat/icon',
'feat_print' = '/feat/print',
'multi-menu' = '/multi-menu',
'multi-menu_first' = '/multi-menu/first',
'multi-menu_first_second' = '/multi-menu/first/second',
'exception' = '/exception',
'exception_403' = '/exception/403',
'exception_404' = '/exception/404',
'exception_500' = '/exception/500',
'about' = '/about'
}
export enum EnumRouteTitle {
'root' = 'root',
'login' = '登录',
'not-found' = '未找到',
'no-permission' = '无权限',
'service-error' = '服务器错误',
'reload' = '重载',
// 自定义路由
'dashboard' = '仪表盘',
'dashboard_analysis' = '分析页',
'dashboard_workbench' = '工作台',
'document' = '文档',
'document_vue' = 'vue文档',
'document_vite' = 'vite文档',
'document_naive' = 'naive文档',
'component' = '组件插件',
'component_map' = '地图插件',
'component_video' = '视频插件',
'component_editor' = '编辑器',
'component_editor_quill' = '富文本编辑器',
'component_editor_markdown' = 'markdown编辑器',
'component_swiper' = 'Swiper插件',
'feat' = '功能示例',
'feat_copy' = '剪贴板',
'feat_icon' = '图标',
'feat_print' = '打印',
'multi-menu' = '多级菜单',
'multi-menu_first' = '一级菜单',
'multi-menu_first_second' = '二级菜单',
'exception' = '异常页',
'exception_403' = '异常页-403',
'exception_404' = '异常页-404',
'exception_500' = '异常页-500',
'about' = '关于'
}

View File

@ -1,5 +1,5 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import useBoolean from '../common/useBoolean'; import { useBoolean } from '../common';
/** /**
* *
@ -9,10 +9,10 @@ export default function useCountDown(second: number) {
if (second <= 0 && second % 1 !== 0) { if (second <= 0 && second % 1 !== 0) {
throw Error('倒计时的时间应该为一个正整数!'); throw Error('倒计时的时间应该为一个正整数!');
} }
const { bool: isComplete, setTrue, setFalse } = useBoolean(false);
const counts = ref(0); const counts = ref(0);
const isCounting = computed(() => Boolean(counts.value)); const isCounting = computed(() => Boolean(counts.value));
// 完成一轮倒计时
const { bool: isComplete, setTrue, setFalse } = useBoolean(false);
let intervalId: any; let intervalId: any;

View File

@ -1,23 +1,5 @@
import useAppTitle from './useAppTitle';
import useContext from './useContext'; import useContext from './useContext';
import useRouterChange from './useRouterChange';
import useRouteParam from './useRouteParam';
import useRouteQuery from './useRouteQuery';
import useRouteProps from './useRouteProps';
import useBoolean from './useBoolean'; import useBoolean from './useBoolean';
import useLoading from './useLoading'; import useLoading from './useLoading';
import useScrollBehavior from './useScrollBehavior';
import useIsMobile from './useIsMobile';
export { export { useContext, useBoolean, useLoading };
useAppTitle,
useContext,
useRouterChange,
useRouteParam,
useRouteQuery,
useRouteProps,
useBoolean,
useLoading,
useScrollBehavior,
useIsMobile
};

View File

@ -1,6 +0,0 @@
/** 项目名称 */
export default function useAppTitle() {
const title = import.meta.env.VITE_APP_TITLE as string;
return title;
}

View File

@ -1,7 +0,0 @@
// import { computed } from 'vue';
// import { useRoute } from 'vue-router';
// import { ROUTE_NAME_MAP } from '@/utils';
export default function useRouteParam() {
// const route = useRoute();
}

View File

@ -1,22 +0,0 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';
export default function useRouteProps() {
const route = useRoute();
const props = computed(() => {
/** 路由名称 */
const name = route.name as string;
/** 混存页面 */
const keepAlive = Boolean(route.meta?.keepAlive);
/** 视高100% */
const fullPage = Boolean(route.meta?.fullPage);
return {
name,
keepAlive,
fullPage
};
});
return props;
}

View File

@ -1,21 +0,0 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { EnumRoutePath } from '@/enum';
import { ROUTE_NAME_MAP } from '@/utils';
export default function useRouteQuery() {
const route = useRoute();
/** 登录跳转链接 */
const loginRedirectUrl = computed(() => {
let url: EnumRoutePath | undefined;
if (route.name === ROUTE_NAME_MAP.get('login')) {
url = (route.query?.redirectUrl as EnumRoutePath) || '';
}
return url;
});
return {
loginRedirectUrl
};
}

View File

@ -1,16 +0,0 @@
import { ref } from 'vue';
/** 滚动行为 */
export default function useScrollBehavior() {
const scrollbar = ref<HTMLElement | null>(null);
/** 重置滚动条行为 */
function resetScrollBehavior() {
scrollbar.value?.scrollTo({ left: 0, top: 0 });
}
return {
scrollbar,
resetScrollBehavior
};
}

View File

@ -0,0 +1 @@
export * from './auth';

View File

@ -1,40 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import type { MenuOption } from 'naive-ui';
import { EnumRoutePath, EnumLoginModule } from '@/enum';
/** 路由描述 */
interface RouteMeta {
/** 路由名称 */
title?: string;
/** 缓存页面 */
keepAlive?: boolean;
/** 页面100%视高 */
fullPage?: boolean;
/** 不作为菜单 */
isNotMenu?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: string;
/** 路由作为菜单时的排序 */
order?: number;
}
/** 路由配置 */
export type CustomRoute = RouteRecordRaw & { meta: RouteMeta };
/** 路由路径 */
export type RoutePathKey = keyof typeof EnumRoutePath;
/** 菜单项配置 */
export type GlobalMenuOption = MenuOption & {
routeName: string;
routePath: string;
};
/** 登录模块 */
export type LoginModuleType = keyof typeof EnumLoginModule;
/** npm依赖包版本信息 */
export interface VersionInfo {
name: string;
version: string;
}

View File

@ -0,0 +1,2 @@
export * from './system';
export * from './route';

View File

@ -0,0 +1,55 @@
import type { RouteRecordRaw } from 'vue-router';
/** 路由描述 */
interface RouteMeta {
/** 路由名称 */
title?: string;
/** 缓存页面 */
keepAlive?: boolean;
/** 页面100%视高 */
fullPage?: boolean;
/** 不作为菜单 */
isNotMenu?: boolean;
/** 菜单和面包屑对应的图标 */
icon?: string;
/** 路由作为菜单时的排序 */
order?: number;
}
/** 路由配置 */
export type CustomRoute = RouteRecordRaw & { meta: RouteMeta };
/** 路由声明的key */
export type RouteKey =
| 'root'
| 'login'
| 'not-found'
| 'no-permission'
| 'service-error'
// 自定义路由
| 'dashboard'
| 'dashboard_analysis'
| 'dashboard_workbench'
| 'document'
| 'document_vue'
| 'document_vite'
| 'document_naive'
| 'component'
| 'component_map'
| 'component_video'
| 'component_editor'
| 'component_editor_quill'
| 'component_editor_markdown'
| 'component_swiper'
| 'feat'
| 'feat_copy'
| 'feat_icon'
| 'feat_print'
| 'multi-menu'
| 'multi-menu_first'
| 'multi-menu_first_second'
| 'exception'
| 'exception_403'
| 'exception_404'
| 'exception_500'
| 'about';

View File

@ -0,0 +1,17 @@
import type { MenuOption } from 'naive-ui';
import { EnumLoginModule } from '@/enum';
/** 菜单项配置 */
export type GlobalMenuOption = MenuOption & {
routeName: string;
routePath: string;
};
/** 登录模块 */
export type LoginModuleType = keyof typeof EnumLoginModule;
/** npm依赖包版本信息 */
export interface VersionInfo {
name: string;
version: string;
}

View File

@ -1,3 +1,3 @@
export * from './common';
export * from './business'; export * from './business';
export * from './theme'; export * from './theme';
export * from './common';

View File

@ -1,11 +1,34 @@
<template> <template>
<div> <n-layout class="h-full">
<div class="flex"> <n-layout-content
<h3>horizontal</h3> :native-scrollbar="false"
</div> :content-style="{ height: routeProps.fullPage ? '100%' : 'auto' }"
<router-view /> class="bg-[#f6f9f8] dark:bg-deep-dark"
</div> >
<n-layout-header :inverted="headerInverted" :position="headerPosition" class="z-11">
<global-header :show-logo="true" :show-menu-collape="false" :show-menu="true" class="relative z-2" />
<global-tab v-if="theme.multiTabStyle.visible" />
</n-layout-header>
<header-placeholder />
<global-content />
<global-footer />
</n-layout-content>
</n-layout>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
<style scoped></style> import { NLayout, NLayoutContent, NLayoutHeader } from 'naive-ui';
import { useThemeStore } from '@/store';
import { useRouteProps, useLayoutConfig } from '@/composables';
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, HeaderPlaceholder } from '../common';
const theme = useThemeStore();
const routeProps = useRouteProps();
const { headerInverted, headerPosition } = useLayoutConfig();
</script>
<style scoped>
.global-sider {
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
}
</style>

View File

@ -1,10 +1,5 @@
<template> <template>
<div> <div></div>
<div class="flex">
<h3>horizontal-mix</h3>
</div>
<router-view />
</div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts"></script>

View File

@ -1,18 +1,18 @@
<template> <template>
<n-layout :has-sider="true" class="h-full"> <n-layout :has-sider="true" class="h-full">
<n-layout-sider <n-layout-sider
class="global-sider z-12" class="global-sider z-12 transition-all duration-200 ease-in-out"
:inverted="inverted" :inverted="siderInverted"
collapse-mode="width" collapse-mode="width"
:collapsed="app.menu.collapsed" :collapsed="app.menu.collapsed"
:collapsed-width="theme.menuStyle.collapsedWidth" :collapsed-width="theme.menuStyle.collapsedWidth"
:width="menuWidth" :width="siderMenuWidth"
:native-scrollbar="false" :native-scrollbar="false"
@collapse="handleMenuCollapse(true)" @collapse="handleMenuCollapse(true)"
@expand="handleMenuCollapse(false)" @expand="handleMenuCollapse(false)"
> >
<global-logo :show-title="!app.menu.collapsed" class="header-height absolute-lt w-full z-2" /> <global-logo :show-title="!app.menu.collapsed" class="absolute-lt z-2" />
<global-menu class="header-padding" /> <global-menu :style="{ paddingTop: headerHeight }" />
</n-layout-sider> </n-layout-sider>
<n-layout-content <n-layout-content
:native-scrollbar="false" :native-scrollbar="false"
@ -20,15 +20,10 @@
class="bg-[#f6f9f8] dark:bg-deep-dark" class="bg-[#f6f9f8] dark:bg-deep-dark"
> >
<n-layout-header :inverted="headerInverted" :position="headerPosition" class="z-11"> <n-layout-header :inverted="headerInverted" :position="headerPosition" class="z-11">
<global-header <global-header :show-logo="false" :show-menu-collape="true" :show-menu="false" class="relative z-2" />
:show-logo="false" <global-tab v-if="theme.multiTabStyle.visible" />
:show-menu-collape="true"
:show-menu="false"
class="header-height relative z-2"
/>
<global-tab v-if="theme.multiTabStyle.visible" class="tab-height" />
</n-layout-header> </n-layout-header>
<div v-if="theme.fixedHeaderAndTab" class="header-tab_height"></div> <header-placeholder />
<global-content /> <global-content />
<global-footer /> <global-footer />
</n-layout-content> </n-layout-content>
@ -36,66 +31,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { NLayout, NLayoutSider, NLayoutContent, NLayoutHeader } from 'naive-ui'; import { NLayout, NLayoutSider, NLayoutContent, NLayoutHeader } from 'naive-ui';
import { useThemeStore, useAppStore } from '@/store'; import { useThemeStore, useAppStore } from '@/store';
import { useRouteProps } from '@/hooks'; import { useRouteProps, useLayoutConfig } from '@/composables';
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, GlobalLogo, GlobalMenu } from '../common'; import {
GlobalHeader,
GlobalContent,
GlobalFooter,
GlobalTab,
HeaderPlaceholder,
GlobalLogo,
GlobalMenu
} from '../common';
const theme = useThemeStore(); const theme = useThemeStore();
const app = useAppStore(); const app = useAppStore();
const routeProps = useRouteProps();
const { handleMenuCollapse } = useAppStore(); const { handleMenuCollapse } = useAppStore();
const routeProps = useRouteProps();
const inverted = computed(() => { const { siderInverted, siderMenuWidth, headerInverted, headerPosition, headerHeight } = useLayoutConfig();
return theme.navStyle.theme !== 'light';
});
const menuWidth = computed(() => {
const { collapsed } = app.menu;
const { collapsedWidth, width } = theme.menuStyle;
return collapsed ? collapsedWidth : width;
});
const headerInverted = computed(() => {
return theme.navStyle.theme !== 'dark' ? inverted.value : !inverted.value;
});
const headerPosition = computed(() => (theme.fixedHeaderAndTab ? 'absolute' : 'static'));
const headerHeight = computed(() => {
const { height } = theme.headerStyle;
return `${height}px`;
});
const tabHeight = computed(() => {
const { height } = theme.multiTabStyle;
return `${height}px`;
});
const headerAndTabHeight = computed(() => {
const {
multiTabStyle: { visible, height: tH },
headerStyle: { height: hH }
} = theme;
const height = visible ? tH + hH : hH;
return `${height}px`;
});
</script> </script>
<style scoped> <style scoped>
.global-sider { .global-sider {
transition: all 0.2s ease-in-out;
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%); box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
} }
.header-height {
height: v-bind(headerHeight);
}
.header-padding {
padding-top: v-bind(headerHeight);
}
.tab-height {
height: v-bind(tabHeight);
}
.header-tab_height {
height: v-bind(headerAndTabHeight);
}
</style> </style>

View File

@ -1,11 +1,50 @@
<template> <template>
<div> <n-layout :has-sider="true" class="h-full">
<div class="flex"> <n-layout-sider
<h3>vertical-mix</h3> class="global-sider z-12 transition-all duration-200 ease-in-out"
</div> :inverted="siderInverted"
<router-view /> collapse-mode="width"
</div> :collapsed="app.menu.collapsed"
:collapsed-width="theme.menuStyle.collapsedWidth"
:width="siderMenuWidth"
:native-scrollbar="false"
@collapse="handleMenuCollapse(true)"
@expand="handleMenuCollapse(false)"
>
<global-logo :show-title="!app.menu.collapsed" class="absolute-lt z-2" />
<global-menu :style="{ paddingTop: headerHeight }" />
</n-layout-sider>
<n-layout-content
:native-scrollbar="false"
:content-style="{ height: routeProps.fullPage ? '100%' : 'auto' }"
class="bg-[#f6f9f8] dark:bg-deep-dark"
>
<n-layout-header :inverted="headerInverted" :position="headerPosition" class="z-11">
<global-header :show-logo="false" :show-menu-collape="true" :show-menu="false" class="relative z-2" />
<global-tab v-if="theme.multiTabStyle.visible" />
</n-layout-header>
<div v-if="theme.fixedHeaderAndTab" :style="{ height: headerAndMultiTabHeight }"></div>
<global-content />
<global-footer />
</n-layout-content>
</n-layout>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
<style scoped></style> import { NLayout, NLayoutSider, NLayoutContent, NLayoutHeader } from 'naive-ui';
import { useThemeStore, useAppStore } from '@/store';
import { useRouteProps, useLayoutConfig } from '@/composables';
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, GlobalLogo, GlobalMenu } from '../common';
const theme = useThemeStore();
const app = useAppStore();
const { handleMenuCollapse } = useAppStore();
const routeProps = useRouteProps();
const { siderInverted, siderMenuWidth, headerInverted, headerPosition, headerHeight, headerAndMultiTabHeight } =
useLayoutConfig();
</script>
<style scoped>
.global-sider {
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
}
</style>

View File

@ -15,7 +15,7 @@ import { computed } from 'vue';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useReloadInject } from '@/context'; import { useReloadInject } from '@/context';
import { cacheRoutes } from '@/router'; import { cacheRoutes } from '@/router';
import { useRouteProps } from '@/hooks'; import { useRouteProps } from '@/composables';
const theme = useThemeStore(); const theme = useThemeStore();
const { reload } = useReloadInject(); const { reload } = useReloadInject();

View File

@ -32,15 +32,15 @@ import type { RouteLocationMatched } from 'vue-router';
import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui'; import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui';
import type { DropdownOption } from 'naive-ui'; import type { DropdownOption } from 'naive-ui';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { EnumRoutePath } from '@/enum'; import { routePath } from '@/router';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import type { RoutePathKey } from '@/interface'; import type { RouteKey } from '@/interface';
type Breadcrumb = DropdownOption & { type Breadcrumb = DropdownOption & {
key: string; key: string;
label: string; label: string;
disabled: boolean; disabled: boolean;
routeName: RoutePathKey; routeName: RouteKey;
hasChildren: boolean; hasChildren: boolean;
iconName?: string; iconName?: string;
children?: Breadcrumb[]; children?: Breadcrumb[];
@ -62,11 +62,11 @@ function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
const list: Breadcrumb[] = []; const list: Breadcrumb[] = [];
routeMatched.forEach(item => { routeMatched.forEach(item => {
if (!item.meta?.isNotMenu) { if (!item.meta?.isNotMenu) {
const routeName = item.name as RoutePathKey; const routeName = item.name as RouteKey;
const breadcrumItem: Breadcrumb = { const breadcrumItem: Breadcrumb = {
key: routeName, key: routeName,
label: (item.meta?.title as string) || '', label: (item.meta?.title as string) || '',
disabled: item.path === EnumRoutePath.root, disabled: item.path === routePath('root'),
routeName, routeName,
hasChildren: false hasChildren: false
}; };
@ -84,7 +84,7 @@ function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
} }
function dropdownSelect(optionKey: string) { function dropdownSelect(optionKey: string) {
const key = optionKey as RoutePathKey; const key = optionKey as RouteKey;
router.push({ name: key }); router.push({ name: key });
} }
</script> </script>

View File

@ -10,13 +10,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { NDropdown, useDialog } from 'naive-ui'; import { NDropdown, useDialog } from 'naive-ui';
import { HoverContainer } from '@/components'; import { HoverContainer } from '@/components';
import { useRouterChange } from '@/hooks'; import { useRouterPush } from '@/composables';
import { iconifyRender, resetAuthStorage } from '@/utils'; import { iconifyRender, resetAuthStorage } from '@/utils';
import avatar from '@/assets/svg/avatar/avatar01.svg'; import avatar from '@/assets/svg/avatar/avatar01.svg';
type DropdownKey = 'user-center' | 'logout'; type DropdownKey = 'user-center' | 'logout';
const { toLogin } = useRouterChange(); const { toLogin } = useRouterPush();
const dialog = useDialog(); const dialog = useDialog();
const options = [ const options = [

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="global-header flex-y-center w-full"> <div class="global-header flex-y-center w-full" :style="{ height: headerHeight }">
<global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.menuStyle.width + 'px' }" /> <global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.menuStyle.width + 'px' }" />
<div class="flex-1-hidden flex-y-center h-full"> <div v-if="!showMenu" class="flex-1-hidden flex-y-center h-full">
<menu-collapse v-if="showMenuCollape" /> <menu-collapse v-if="showMenuCollape" />
<global-breadcrumb v-if="theme.crumbsStyle.visible" /> <global-breadcrumb v-if="theme.crumbsStyle.visible" />
</div> </div>
<div <div
v-if="showMenu" v-else
class="flex-1-hidden flex-y-center h-full" class="flex-1-hidden flex-y-center h-full"
:style="{ justifyContent: theme.menuStyle.horizontalPosition }" :style="{ justifyContent: theme.menuStyle.horizontalPosition }"
> >
@ -24,6 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useLayoutConfig } from '@/composables';
import { import {
HeaderMenu, HeaderMenu,
GlobalBreadcrumb, GlobalBreadcrumb,
@ -48,6 +49,7 @@ interface Props {
defineProps<Props>(); defineProps<Props>();
const theme = useThemeStore(); const theme = useThemeStore();
const { headerHeight } = useLayoutConfig();
const showSettingButton = import.meta.env.DEV || import.meta.env.VITE_HTTP_ENV === 'STAGING'; const showSettingButton = import.meta.env.DEV || import.meta.env.VITE_HTTP_ENV === 'STAGING';
</script> </script>

View File

@ -3,6 +3,7 @@
href="/" href="/"
class=" class="
flex-center flex-center
w-full
nowrap-hidden nowrap-hidden
bg-light bg-light
dark:bg-dark dark:bg-dark
@ -11,6 +12,7 @@
ease-in-out ease-in-out
cursor-pointer cursor-pointer
" "
:style="{ height: headerHeight }"
> >
<system-logo class="w-32px h-32px" :color="theme.themeColor" /> <system-logo class="w-32px h-32px" :color="theme.themeColor" />
<h2 v-show="showTitle" class="text-primary pl-8px text-16px font-bold">{{ title }}</h2> <h2 v-show="showTitle" class="text-primary pl-8px text-16px font-bold">{{ title }}</h2>
@ -20,7 +22,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { SystemLogo } from '@/components'; import { SystemLogo } from '@/components';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useAppTitle } from '@/hooks'; import { useAppTitle, useLayoutConfig } from '@/composables';
interface Props { interface Props {
/** 显示名字 */ /** 显示名字 */
@ -31,5 +33,6 @@ defineProps<Props>();
const theme = useThemeStore(); const theme = useThemeStore();
const title = useAppTitle(); const title = useAppTitle();
const { headerHeight } = useLayoutConfig();
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="multi-tab flex-center w-full pl-16px"> <div class="multi-tab flex-center w-full pl-16px" :style="{ height: multiTabHeight }">
<div class="flex-1-hidden h-full"> <div class="flex-1-hidden h-full">
<better-scroll :options="{ scrollX: true, scrollY: false, click: true }"> <better-scroll :options="{ scrollX: true, scrollY: false, click: true }">
<multi-tab /> <multi-tab />
@ -13,11 +13,13 @@
import { watch } from 'vue'; import { watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { useLayoutConfig } from '@/composables';
import { BetterScroll } from '@/components'; import { BetterScroll } from '@/components';
import { MultiTab, ReloadButton } from './components'; import { MultiTab, ReloadButton } from './components';
const route = useRoute(); const route = useRoute();
const { initMultiTab, addMultiTab, setActiveMultiTab } = useAppStore(); const { initMultiTab, addMultiTab, setActiveMultiTab } = useAppStore();
const { multiTabHeight } = useLayoutConfig();
function init() { function init() {
initMultiTab(); initMultiTab();

View File

@ -0,0 +1,12 @@
<template>
<div v-if="theme.fixedHeaderAndTab" :style="{ height: headerAndMultiTabHeight }"></div>
</template>
<script setup lang="ts">
import { useThemeStore } from '@/store';
import { useLayoutConfig } from '@/composables';
const theme = useThemeStore();
const { headerAndMultiTabHeight } = useLayoutConfig();
</script>
<style scoped></style>

View File

@ -4,6 +4,16 @@ import GlobalFooter from './GlobalFooter/index.vue';
import GlobalLogo from './GlobalLogo/index.vue'; import GlobalLogo from './GlobalLogo/index.vue';
import GlobalMenu from './GlobalMenu/index.vue'; import GlobalMenu from './GlobalMenu/index.vue';
import GlobalTab from './GlobalTab/index.vue'; import GlobalTab from './GlobalTab/index.vue';
import HeaderPlaceholder from './HeaderPlaceholder/index.vue';
import SettingDrawer from './SettingDrawer/index.vue'; import SettingDrawer from './SettingDrawer/index.vue';
export { GlobalHeader, GlobalContent, GlobalFooter, GlobalLogo, GlobalMenu, GlobalTab, SettingDrawer }; export {
GlobalHeader,
GlobalContent,
GlobalFooter,
GlobalLogo,
GlobalMenu,
GlobalTab,
HeaderPlaceholder,
SettingDrawer
};

View File

@ -1,6 +1,14 @@
<template> <template>
<div <div
class="flex-1 flex-col-stretch p-16px bg-[#F6F9F8] dark:bg-black" class="
flex-1 flex-col-stretch
p-16px
bg-[#f6f9f8]
dark:bg-deep-dark
transition-backgorund-color
duration-300
ease-in-out
"
:class="{ 'overflow-hidden': routeProps.fullPage }" :class="{ 'overflow-hidden': routeProps.fullPage }"
> >
<router-view v-slot="{ Component, route }"> <router-view v-slot="{ Component, route }">
@ -17,7 +25,7 @@
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useReloadInject } from '@/context'; import { useReloadInject } from '@/context';
import { cacheRoutes } from '@/router'; import { cacheRoutes } from '@/router';
import { useRouteProps } from '@/hooks'; import { useRouteProps } from '@/composables';
const theme = useThemeStore(); const theme = useThemeStore();
const { reload } = useReloadInject(); const { reload } = useReloadInject();

View File

@ -32,15 +32,15 @@ import type { RouteLocationMatched } from 'vue-router';
import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui'; import { NBreadcrumb, NBreadcrumbItem, NDropdown } from 'naive-ui';
import type { DropdownOption } from 'naive-ui'; import type { DropdownOption } from 'naive-ui';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { EnumRoutePath } from '@/enum'; import { routePath } from '@/router';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import type { RoutePathKey } from '@/interface'; import type { RouteKey } from '@/interface';
type Breadcrumb = DropdownOption & { type Breadcrumb = DropdownOption & {
key: string; key: string;
label: string; label: string;
disabled: boolean; disabled: boolean;
routeName: RoutePathKey; routeName: RouteKey;
hasChildren: boolean; hasChildren: boolean;
iconName?: string; iconName?: string;
children?: Breadcrumb[]; children?: Breadcrumb[];
@ -62,11 +62,11 @@ function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
const list: Breadcrumb[] = []; const list: Breadcrumb[] = [];
routeMatched.forEach(item => { routeMatched.forEach(item => {
if (!item.meta?.isNotMenu) { if (!item.meta?.isNotMenu) {
const routeName = item.name as RoutePathKey; const routeName = item.name as RouteKey;
const breadcrumItem: Breadcrumb = { const breadcrumItem: Breadcrumb = {
key: routeName, key: routeName,
label: (item.meta?.title as string) || '', label: (item.meta?.title as string) || '',
disabled: item.path === EnumRoutePath.root, disabled: item.path === routePath('root'),
routeName, routeName,
hasChildren: false hasChildren: false
}; };
@ -84,7 +84,7 @@ function recursionBreadcrumb(routeMatched: RouteLocationMatched[]) {
} }
function dropdownSelect(optionKey: string) { function dropdownSelect(optionKey: string) {
const key = optionKey as RoutePathKey; const key = optionKey as RouteKey;
router.push({ name: key }); router.push({ name: key });
} }
</script> </script>

View File

@ -10,13 +10,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { NDropdown, useDialog } from 'naive-ui'; import { NDropdown, useDialog } from 'naive-ui';
import { HoverContainer } from '@/components'; import { HoverContainer } from '@/components';
import { useRouterChange } from '@/hooks'; import { useRouterPush } from '@/composables';
import { iconifyRender, resetAuthStorage } from '@/utils'; import { iconifyRender, resetAuthStorage } from '@/utils';
import avatar from '@/assets/svg/avatar/avatar01.svg'; import avatar from '@/assets/svg/avatar/avatar01.svg';
type DropdownKey = 'user-center' | 'logout'; type DropdownKey = 'user-center' | 'logout';
const { toLogin } = useRouterChange(); const { toLogin } = useRouterPush();
const dialog = useDialog(); const dialog = useDialog();
const options = [ const options = [

View File

@ -35,7 +35,7 @@ import { useRouter, useRoute } from 'vue-router';
import { NScrollbar, NMenu } from 'naive-ui'; import { NScrollbar, NMenu } from 'naive-ui';
import type { MenuOption } from 'naive-ui'; import type { MenuOption } from 'naive-ui';
import { useThemeStore, useAppStore } from '@/store'; import { useThemeStore, useAppStore } from '@/store';
import { useAppTitle } from '@/hooks'; import { useAppTitle } from '@/composables';
import { menus } from '@/router'; import { menus } from '@/router';
import type { GlobalMenuOption } from '@/interface'; import type { GlobalMenuOption } from '@/interface';

View File

@ -1,8 +1,19 @@
<template> <template>
<div v-if="fixedHeaderAndTab && theme.navStyle.mode !== 'horizontal-mix'" class="multi-tab-height w-full"></div> <div v-if="fixedHeaderAndTab && theme.navStyle.mode !== 'horizontal-mix'" class="multi-tab-height w-full"></div>
<div <div
class="multi-tab flex-center justify-between w-full pl-10px" class="
:class="[theme.darkMode ? 'bg-[#18181c]' : 'bg-white', { 'multi-tab-top absolute': fixedHeaderAndTab }]" multi-tab
flex-center
justify-between
w-full
pl-10px
bg-light
dark:bg-dark
transition-backgorund-color
duration-300
ease-in-out
"
:class="{ 'multi-tab-top absolute': fixedHeaderAndTab }"
:style="{ zIndex }" :style="{ zIndex }"
:align="'center'" :align="'center'"
justify="space-between" justify="space-between"

View File

@ -9,7 +9,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { SystemLogo } from '@/components'; import { SystemLogo } from '@/components';
import { useAppStore, useThemeStore } from '@/store'; import { useAppStore, useThemeStore } from '@/store';
import { useAppTitle } from '@/hooks'; import { useAppTitle } from '@/composables';
const app = useAppStore(); const app = useAppStore();
const theme = useThemeStore(); const theme = useThemeStore();

View File

@ -4,7 +4,7 @@
<global-header v-if="isHorizontalMix" :z-index="14" /> <global-header v-if="isHorizontalMix" :z-index="14" />
<div class="flex-1-hidden flex h-full"> <div class="flex-1-hidden flex h-full">
<global-sider v-if="isHorizontalMix" :z-index="13" /> <global-sider v-if="isHorizontalMix" :z-index="13" />
<n-scrollbar ref="scrollbar" class="h-full" :content-class="routeProps.fullPage ? 'h-full' : ''"> <n-scrollbar class="h-full" :content-class="routeProps.fullPage ? 'h-full' : ''">
<div <div
class="inline-flex-col-stretch w-full" class="inline-flex-col-stretch w-full"
:class="[{ 'content-padding': isHorizontalMix }, routeProps.fullPage ? 'h-full' : 'min-h-100vh']" :class="[{ 'content-padding': isHorizontalMix }, routeProps.fullPage ? 'h-full' : 'min-h-100vh']"
@ -21,16 +21,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { NLayout, NScrollbar } from 'naive-ui'; import { NLayout, NScrollbar } from 'naive-ui';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useRouteProps, useScrollBehavior } from '@/hooks'; import { useRouteProps } from '@/composables';
import { GlobalSider, GlobalHeader, GlobalTab, GlobalContent, GlobalFooter, SettingDrawer } from './components'; import { GlobalSider, GlobalHeader, GlobalTab, GlobalContent, GlobalFooter, SettingDrawer } from './components';
const route = useRoute();
const theme = useThemeStore(); const theme = useThemeStore();
const { scrollbar, resetScrollBehavior } = useScrollBehavior();
const routeProps = useRouteProps(); const routeProps = useRouteProps();
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix'); const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
@ -42,13 +39,6 @@ const headerAndMultiTabHeight = computed(() => {
} = theme; } = theme;
return `${hHeight + mHeight}px`; return `${hHeight + mHeight}px`;
}); });
watch(
() => route.name,
() => {
resetScrollBehavior();
}
);
</script> </script>
<style scoped> <style scoped>
:deep(.n-scrollbar-rail) { :deep(.n-scrollbar-rail) {

View File

@ -18,26 +18,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from 'vue';
import { useRoute } from 'vue-router';
import { NScrollbar } from 'naive-ui'; import { NScrollbar } from 'naive-ui';
import { useRouteProps, useScrollBehavior } from '@/hooks';
import { useThemeStore } from '@/store'; import { useThemeStore } from '@/store';
import { useReloadInject } from '@/context'; import { useReloadInject } from '@/context';
import { cacheRoutes } from '@/router'; import { cacheRoutes } from '@/router';
import { useRouteProps } from '@/composables';
const theme = useThemeStore(); const theme = useThemeStore();
const { reload } = useReloadInject(); const { reload } = useReloadInject();
const route = useRoute();
const { scrollbar, resetScrollBehavior } = useScrollBehavior();
const routeProps = useRouteProps(); const routeProps = useRouteProps();
watch(
() => route.name,
() => {
resetScrollBehavior();
}
);
</script> </script>
<style scoped></style> <style scoped></style>

68
src/router/const/index.ts Normal file
View File

@ -0,0 +1,68 @@
import type { RouteKey } from '@/interface';
interface RouteConst {
/** 路由名称 */
name: RouteKey;
/** 路由路径 */
path: string;
/** 路由标题 */
title: string;
}
/** 声明的路由名称、路径和标题 */
const routeConstMap = new Map<RouteKey, RouteConst>([
['root', { name: 'root', path: '/', title: 'Root' }],
['login', { name: 'login', path: '/login', title: '登录' }],
['no-permission', { name: 'no-permission', path: '/403', title: '无权限' }],
['not-found', { name: 'not-found', path: '/404', title: '未找到' }],
['service-error', { name: 'service-error', path: '/500', title: '服务器错误' }],
['dashboard', { name: 'dashboard', path: '/dashboard', title: '仪表盘' }],
['dashboard_analysis', { name: 'dashboard_analysis', path: '/dashboard/analysis', title: '分析页' }],
['dashboard_workbench', { name: 'dashboard_workbench', path: '/dashboard/workbench', title: '工作台' }],
['document', { name: 'document', path: '/document', title: '文档' }],
['document_vue', { name: 'document_vue', path: '/document/vue', title: 'vue文档' }],
['document_vite', { name: 'document_vite', path: '/document/vite', title: 'vite文档' }],
['document_naive', { name: 'document_naive', path: '/document/naive', title: 'naive文档' }],
['component', { name: 'component', path: '/component', title: '组件插件' }],
['component_map', { name: 'component_map', path: '/component/map', title: '地图插件' }],
['component_video', { name: 'component_video', path: '/component/video', title: '视频插件' }],
['component_editor', { name: 'component_editor', path: '/component/editor', title: '编辑器' }],
[
'component_editor_quill',
{ name: 'component_editor_quill', path: '/component/editor/quill', title: '富文本编辑器' }
],
[
'component_editor_markdown',
{ name: 'component_editor_markdown', path: '/component/editor/markdown', title: 'markdown编辑器' }
],
['component_swiper', { name: 'component_swiper', path: '/component/swiper', title: 'Swiper插件' }],
['feat', { name: 'feat', path: '/feat', title: '功能示例' }],
['feat_copy', { name: 'feat_copy', path: '/feat/copy', title: '剪贴板' }],
['feat_icon', { name: 'feat_icon', path: '/feat/icon', title: '图标' }],
['feat_print', { name: 'feat_print', path: '/feat/print', title: '打印' }],
['multi-menu', { name: 'multi-menu', path: '/multi-menu', title: '多级菜单' }],
['multi-menu_first', { name: 'multi-menu_first', path: '/multi-menu/first', title: '一级菜单' }],
['multi-menu_first_second', { name: 'multi-menu_first_second', path: '/multi-menu/first/second', title: '二级菜单' }],
['exception', { name: 'exception', path: '/exception', title: '异常页' }],
['exception_403', { name: 'exception_403', path: '/exception/403', title: '异常页-403' }],
['exception_404', { name: 'exception_404', path: '/exception/404', title: '异常页-404' }],
['exception_500', { name: 'exception_500', path: '/exception/500', title: '异常页-500' }],
['about', { name: 'about', path: '/about', title: '关于' }]
]);
/** 获取路由的声明(name、path、title) */
export function getRouteConst(key: RouteKey) {
return routeConstMap.get(key)!;
}
/** 路由名称 */
export function routeName(key: RouteKey) {
return routeConstMap.get(key)!.name;
}
/** 路由路径 */
export function routePath(key: RouteKey) {
return routeConstMap.get(key)!.path;
}
/** 路由标题 */
export function routeTitle(key: RouteKey) {
return routeConstMap.get(key)!.title;
}

View File

@ -0,0 +1,5 @@
import { getCacheRoutes, transformRouteToMenu } from '@/utils';
import { customRoutes, routes } from '../routes';
export const cacheRoutes = getCacheRoutes(routes);
export const menus = transformRouteToMenu(customRoutes);

View File

@ -1,8 +1,4 @@
import { getCacheRoutes, transformRouteToMenu } from '@/utils'; export * from './const';
import { customRoutes, routes, ROUTE_HOME } from './routes'; export * from './routes';
import { router, setupRouter } from './setup'; export * from './setup';
export * from './export';
const cacheRoutes = getCacheRoutes(routes);
const menus = transformRouteToMenu(customRoutes);
export { customRoutes, routes, ROUTE_HOME, router, setupRouter, cacheRoutes, menus };

View File

@ -1,18 +1,19 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum'; import { setRouterCacheName, setSingleRoute } from '@/utils';
import { ROUTE_NAME_MAP, setRouterCacheName, setSingleRoute } from '@/utils';
import { BasicLayout } from '@/layouts'; import { BasicLayout } from '@/layouts';
import About from '@/views/about/index.vue'; import About from '@/views/about/index.vue';
import { getRouteConst } from '../const';
setRouterCacheName(About, ROUTE_NAME_MAP.get('about')); const { name, path, title } = getRouteConst('about');
setRouterCacheName(About, name);
const ABOUT: CustomRoute = setSingleRoute(BasicLayout, { const ABOUT: CustomRoute = setSingleRoute(BasicLayout, {
name: ROUTE_NAME_MAP.get('about'), name,
path: EnumRoutePath.about, path,
component: About, component: About,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.about, title,
icon: 'fluent:book-information-24-regular' icon: 'fluent:book-information-24-regular'
} }
}); });

View File

@ -1,90 +1,90 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BasicLayout, RouterViewLayout } from '@/layouts'; import { BasicLayout, RouterViewLayout } from '@/layouts';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import { setRouterCacheName } from '@/utils';
import ComponentMap from '@/views/component/map/index.vue'; import ComponentMap from '@/views/component/map/index.vue';
import ComponentVideo from '@/views/component/video/index.vue'; import ComponentVideo from '@/views/component/video/index.vue';
import EditorQuill from '@/views/component/editor/quill/index.vue'; import EditorQuill from '@/views/component/editor/quill/index.vue';
import EditorMarkdown from '@/views/component/editor/markdown/index.vue'; import EditorMarkdown from '@/views/component/editor/markdown/index.vue';
import ComponentSwiper from '@/views/component/swiper/index.vue'; import ComponentSwiper from '@/views/component/swiper/index.vue';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(ComponentMap, ROUTE_NAME_MAP.get('component_map')); setRouterCacheName(ComponentMap, routeName('component_map'));
setRouterCacheName(ComponentVideo, ROUTE_NAME_MAP.get('component_video')); setRouterCacheName(ComponentVideo, routeName('component_video'));
setRouterCacheName(EditorQuill, ROUTE_NAME_MAP.get('component_editor_quill')); setRouterCacheName(EditorQuill, routeName('component_editor_quill'));
setRouterCacheName(EditorMarkdown, ROUTE_NAME_MAP.get('component_editor_markdown')); setRouterCacheName(EditorMarkdown, routeName('component_editor_markdown'));
setRouterCacheName(ComponentSwiper, ROUTE_NAME_MAP.get('component_swiper')); setRouterCacheName(ComponentSwiper, routeName('component_swiper'));
const COMPONENT: CustomRoute = { const COMPONENT: CustomRoute = {
name: ROUTE_NAME_MAP.get('component'), name: routeName('component'),
path: EnumRoutePath.component, path: routePath('component'),
component: BasicLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('component_map') }, redirect: { name: routeName('component_map') },
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component, title: routeTitle('component'),
icon: 'fluent:app-store-24-regular' icon: 'fluent:app-store-24-regular'
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('component_map'), name: routeName('component_map'),
path: EnumRoutePath.component_map, path: routePath('component_map'),
component: ComponentMap, component: ComponentMap,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_map, title: routeTitle('component_map'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('component_video'), name: routeName('component_video'),
path: EnumRoutePath.component_video, path: routePath('component_video'),
component: ComponentVideo, component: ComponentVideo,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_video, title: routeTitle('component_video'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('component_editor'), name: routeName('component_editor'),
path: EnumRoutePath.component_editor, path: routePath('component_editor'),
component: RouterViewLayout, component: RouterViewLayout,
redirect: { name: ROUTE_NAME_MAP.get('component_editor_quill') }, redirect: { name: routeName('component_editor_quill') },
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_editor, title: routeTitle('component_editor'),
fullPage: true fullPage: true
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('component_editor_quill'), name: routeName('component_editor_quill'),
path: EnumRoutePath.component_editor_quill, path: routePath('component_editor_quill'),
component: EditorQuill, component: EditorQuill,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_editor_quill, title: routeTitle('component_editor_quill'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('component_editor_markdown'), name: routeName('component_editor_markdown'),
path: EnumRoutePath.component_editor_markdown, path: routePath('component_editor_markdown'),
component: EditorMarkdown, component: EditorMarkdown,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_editor_markdown, title: routeTitle('component_editor_markdown'),
fullPage: true fullPage: true
} }
} }
] ]
}, },
{ {
name: ROUTE_NAME_MAP.get('component_swiper'), name: routeName('component_swiper'),
path: EnumRoutePath.component_swiper, path: routePath('component_swiper'),
component: ComponentSwiper, component: ComponentSwiper,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.component_swiper title: routeTitle('component_swiper')
} }
} }
] ]

View File

@ -1,31 +1,31 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum'; import { BasicLayout } from '@/layouts';
import { BaseLayout } from '@/layouts'; import { setRouterCacheName } from '@/utils';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils';
import { ROUTE_HOME } from '../routes';
import DashboardWorkbench from '@/views/dashboard/workbench/index.vue'; import DashboardWorkbench from '@/views/dashboard/workbench/index.vue';
import { ROUTE_HOME } from '../routes';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(DashboardWorkbench, ROUTE_NAME_MAP.get('dashboard_workbench')); setRouterCacheName(DashboardWorkbench, routeName('dashboard_workbench'));
const DASHBOARD: CustomRoute = { const DASHBOARD: CustomRoute = {
name: ROUTE_NAME_MAP.get('dashboard'), name: routeName('dashboard'),
path: EnumRoutePath.dashboard, path: routePath('dashboard'),
component: BaseLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('dashboard_analysis') }, redirect: { name: routeName('dashboard_analysis') },
meta: { meta: {
title: EnumRouteTitle.dashboard, title: routeTitle('dashboard_analysis'),
icon: 'carbon:dashboard' icon: 'carbon:dashboard'
}, },
children: [ children: [
ROUTE_HOME, ROUTE_HOME,
{ {
name: ROUTE_NAME_MAP.get('dashboard_workbench'), name: routeName('dashboard_workbench'),
path: EnumRoutePath.dashboard_workbench, path: routePath('dashboard_workbench'),
component: DashboardWorkbench, component: DashboardWorkbench,
meta: { meta: {
keepAlive: true, keepAlive: true,
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.dashboard_workbench title: routeTitle('dashboard_workbench')
} }
} }
] ]

View File

@ -1,54 +1,54 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BasicLayout } from '@/layouts'; import { BasicLayout } from '@/layouts';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import { setRouterCacheName } from '@/utils';
import DocumentVue from '@/views/document/vue/index.vue'; import DocumentVue from '@/views/document/vue/index.vue';
import DocumentVite from '@/views/document/vite/index.vue'; import DocumentVite from '@/views/document/vite/index.vue';
import DocumentNaive from '@/views/document/naive/index.vue'; import DocumentNaive from '@/views/document/naive/index.vue';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(DocumentVue, ROUTE_NAME_MAP.get('document_vue')); setRouterCacheName(DocumentVue, routeName('document_vue'));
setRouterCacheName(DocumentVite, ROUTE_NAME_MAP.get('document_vite')); setRouterCacheName(DocumentVite, routeName('document_vite'));
setRouterCacheName(DocumentNaive, ROUTE_NAME_MAP.get('document_naive')); setRouterCacheName(DocumentNaive, routeName('document_naive'));
const DOCUMENT: CustomRoute = { const DOCUMENT: CustomRoute = {
name: ROUTE_NAME_MAP.get('document'), name: routeName('document'),
path: EnumRoutePath.document, path: routePath('document'),
component: BasicLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('document_vue') }, redirect: { name: routeName('document_vue') },
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.document, title: routeTitle('document'),
icon: 'carbon:document' icon: 'carbon:document'
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('document_vue'), name: routeName('document_vue'),
path: EnumRoutePath.document_vue, path: routePath('document_vue'),
component: DocumentVue, component: DocumentVue,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.document_vue, title: routeTitle('document_vue'),
fullPage: true, fullPage: true,
keepAlive: true keepAlive: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('document_vite'), name: routeName('document_vite'),
path: EnumRoutePath.document_vite, path: routePath('document_vite'),
component: DocumentVite, component: DocumentVite,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.document_vite, title: routeTitle('document_vite'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('document_naive'), name: routeName('document_naive'),
path: EnumRoutePath.document_naive, path: routePath('document_naive'),
component: DocumentNaive, component: DocumentNaive,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.document_naive, title: routeTitle('document_naive'),
fullPage: true fullPage: true
} }
} }

View File

@ -1,53 +1,53 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BasicLayout } from '@/layouts'; import { BasicLayout } from '@/layouts';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import { setRouterCacheName } from '@/utils';
import Exception403 from '@/views/system/exception/403.vue'; import Exception403 from '@/views/system/exception/403.vue';
import Exception404 from '@/views/system/exception/404.vue'; import Exception404 from '@/views/system/exception/404.vue';
import Exception500 from '@/views/system/exception/500.vue'; import Exception500 from '@/views/system/exception/500.vue';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(Exception404, ROUTE_NAME_MAP.get('exception_404')); setRouterCacheName(Exception404, routeName('exception_404'));
setRouterCacheName(Exception403, ROUTE_NAME_MAP.get('exception_403')); setRouterCacheName(Exception403, routeName('exception_403'));
setRouterCacheName(Exception500, ROUTE_NAME_MAP.get('exception_500')); setRouterCacheName(Exception500, routeName('exception_500'));
const EXCEPTION: CustomRoute = { const EXCEPTION: CustomRoute = {
name: ROUTE_NAME_MAP.get('exception'), name: routeName('exception'),
path: EnumRoutePath.exception, path: routePath('exception'),
component: BasicLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('exception_403') }, redirect: { name: routeName('exception_403') },
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.exception, title: routeTitle('exception'),
icon: 'ant-design:exception-outlined' icon: 'ant-design:exception-outlined'
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('exception_403'), name: routeName('exception_403'),
path: EnumRoutePath.exception_403, path: routePath('exception_403'),
component: Exception403, component: Exception403,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.exception_403, title: routeTitle('exception_403'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('exception_404'), name: routeName('exception_404'),
path: EnumRoutePath.exception_404, path: routePath('exception_404'),
component: Exception404, component: Exception404,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.exception_404, title: routeTitle('exception_404'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('exception_500'), name: routeName('exception_500'),
path: EnumRoutePath.exception_500, path: routePath('exception_500'),
component: Exception500, component: Exception500,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.exception_500, title: routeTitle('exception_500'),
fullPage: true fullPage: true
} }
} }

View File

@ -1,52 +1,52 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BasicLayout } from '@/layouts'; import { BasicLayout } from '@/layouts';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import { setRouterCacheName } from '@/utils';
import FeatCopy from '@/views/feat/copy/index.vue'; import FeatCopy from '@/views/feat/copy/index.vue';
import FeatIcon from '@/views/feat/icon/index.vue'; import FeatIcon from '@/views/feat/icon/index.vue';
import FeatPrint from '@/views/feat/print/index.vue'; import FeatPrint from '@/views/feat/print/index.vue';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(FeatCopy, ROUTE_NAME_MAP.get('feat_copy')); setRouterCacheName(FeatCopy, routeName('feat_copy'));
setRouterCacheName(FeatIcon, ROUTE_NAME_MAP.get('feat_icon')); setRouterCacheName(FeatIcon, routeName('feat_icon'));
setRouterCacheName(FeatPrint, ROUTE_NAME_MAP.get('feat_print')); setRouterCacheName(FeatPrint, routeName('feat_print'));
const FEAT: CustomRoute = { const FEAT: CustomRoute = {
name: ROUTE_NAME_MAP.get('feat'), name: routeName('feat'),
path: EnumRoutePath.feat, path: routePath('feat'),
component: BasicLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('feat_copy') }, redirect: { name: routeName('feat_copy') },
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.feat, title: routeTitle('feat'),
icon: 'ic:round-repeat' icon: 'ic:round-repeat'
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('feat_copy'), name: routeName('feat_copy'),
path: EnumRoutePath.feat_copy, path: routePath('feat_copy'),
component: FeatCopy, component: FeatCopy,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.feat_copy, title: routeTitle('feat_copy'),
fullPage: true fullPage: true
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('feat_icon'), name: routeName('feat_icon'),
path: EnumRoutePath.feat_icon, path: routePath('feat_icon'),
component: FeatIcon, component: FeatIcon,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.feat_icon title: routeTitle('feat_icon')
} }
}, },
{ {
name: ROUTE_NAME_MAP.get('feat_print'), name: routeName('feat_print'),
path: EnumRoutePath.feat_print, path: routePath('feat_print'),
component: FeatPrint, component: FeatPrint,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.feat_print title: routeTitle('feat_print')
} }
} }
] ]

View File

@ -1,40 +1,41 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BasicLayout, RouterViewLayout } from '@/layouts'; import { BasicLayout, RouterViewLayout } from '@/layouts';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils'; import { setRouterCacheName } from '@/utils';
import MultiMenuFirstSecond from '@/views/multi-menu/first/second/index.vue'; import MultiMenuFirstSecond from '@/views/multi-menu/first/second/index.vue';
import { routeName, routePath, routeTitle } from '../const';
setRouterCacheName(MultiMenuFirstSecond, ROUTE_NAME_MAP.get('multi-menu_first_second')); setRouterCacheName(MultiMenuFirstSecond, routeName('multi-menu_first_second'));
const MULTI_MENU: CustomRoute = { const MULTI_MENU: CustomRoute = {
name: ROUTE_NAME_MAP.get('multi-menu'), name: routeName('multi-menu'),
path: EnumRoutePath['multi-menu'], path: routePath('multi-menu'),
component: BasicLayout, component: BasicLayout,
redirect: { name: ROUTE_NAME_MAP.get('multi-menu_first') }, redirect: { name: routeName('multi-menu_first') },
meta: { meta: {
title: EnumRouteTitle['multi-menu'], title: routeTitle('multi-menu'),
icon: 'carbon:menu' icon: 'carbon:menu'
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('multi-menu_first'), name: routeName('multi-menu_first'),
path: EnumRoutePath['multi-menu_first'], path: routePath('multi-menu_first'),
component: RouterViewLayout, component: RouterViewLayout,
redirect: { name: ROUTE_NAME_MAP.get('multi-menu_first_second') }, redirect: { name: routeName('multi-menu_first_second') },
meta: { meta: {
keepAlive: true, keepAlive: true,
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle['multi-menu_first'] title: routeTitle('multi-menu_first_second')
}, },
children: [ children: [
{ {
name: ROUTE_NAME_MAP.get('multi-menu_first_second'), name: routeName('multi-menu_first_second'),
path: EnumRoutePath['multi-menu_first_second'], path: routePath('multi-menu_first_second'),
component: MultiMenuFirstSecond, component: MultiMenuFirstSecond,
meta: { meta: {
keepAlive: true, keepAlive: true,
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle['multi-menu_first_second'] title: routeTitle('multi-menu_first_second'),
fullPage: true
} }
} }
] ]

View File

@ -1,11 +1,10 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath } from '@/enum'; import { routeName, routePath } from '../const';
import { ROUTE_NAME_MAP } from '@/utils';
import { ROUTE_HOME } from '../routes'; import { ROUTE_HOME } from '../routes';
const ROOT: CustomRoute = { const ROOT: CustomRoute = {
name: ROUTE_NAME_MAP.get('root'), name: routeName('root'),
path: EnumRoutePath.root, path: routePath('root'),
redirect: { name: ROUTE_HOME.name }, redirect: { name: ROUTE_HOME.name },
meta: { meta: {
isNotMenu: true isNotMenu: true

View File

@ -1,6 +1,7 @@
import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router'; import type { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { useTitle } from '@vueuse/core'; import { useTitle } from '@vueuse/core';
import { getToken, getLoginRedirectUrl, ROUTE_NAME_MAP } from '@/utils'; import { routeName } from '@/router';
import { getToken, getLoginRedirectUrl } from '@/utils';
/** /**
* *
@ -29,9 +30,9 @@ function handleRouterAction(to: RouteLocationNormalized, from: RouteLocationNorm
const routerAction: [boolean, () => void][] = [ const routerAction: [boolean, () => void][] = [
// 已登录状态跳转登录页,跳转至首页 // 已登录状态跳转登录页,跳转至首页
[ [
isLogin && to.name === ROUTE_NAME_MAP.get('login'), isLogin && to.name === routeName('login'),
() => { () => {
next({ name: ROUTE_NAME_MAP.get('root') }); next({ name: routeName('root') });
} }
], ],
// 不需要登录权限的页面直接通行 // 不需要登录权限的页面直接通行
@ -46,7 +47,7 @@ function handleRouterAction(to: RouteLocationNormalized, from: RouteLocationNorm
!isLogin && needLogin, !isLogin && needLogin,
() => { () => {
const redirectUrl = getLoginRedirectUrl(); const redirectUrl = getLoginRedirectUrl();
next({ name: ROUTE_NAME_MAP.get('login'), query: { redirectUrl } }); next({ name: routeName('login'), query: { redirectUrl } });
} }
], ],
// 登录状态进入需要登录权限的页面,直接通行 // 登录状态进入需要登录权限的页面,直接通行

View File

@ -1,12 +1,11 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { EnumRoutePath, EnumRouteTitle } from '@/enum';
import { BlankLayout } from '@/layouts'; import { BlankLayout } from '@/layouts';
import type { LoginModuleType } from '@/interface'; import type { LoginModuleType } from '@/interface';
import { ROUTE_NAME_MAP } from '@/utils';
import Login from '@/views/system/login/index.vue'; import Login from '@/views/system/login/index.vue';
import NoPermission from '@/views/system/exception/403.vue'; import NoPermission from '@/views/system/exception/403.vue';
import NotFound from '@/views/system/exception/404.vue'; import NotFound from '@/views/system/exception/404.vue';
import ServiceError from '@/views/system/exception/500.vue'; import ServiceError from '@/views/system/exception/500.vue';
import { routeName, routePath, routeTitle } from '../const';
/** /**
* *
@ -17,15 +16,15 @@ const constantRoutes: RouteRecordRaw[] = [
name: 'single_', name: 'single_',
path: '/single_', path: '/single_',
component: BlankLayout, component: BlankLayout,
redirect: { name: ROUTE_NAME_MAP.get('not-found') }, redirect: { name: routeName('not-found') },
meta: { meta: {
keepAlive: true keepAlive: true
}, },
children: [ children: [
// 登录 // 登录
{ {
name: ROUTE_NAME_MAP.get('login'), name: routeName('login'),
path: EnumRoutePath.login, path: routePath('login'),
component: Login, component: Login,
props: route => { props: route => {
const moduleType: LoginModuleType = (route.query?.module as LoginModuleType) || 'pwd-login'; const moduleType: LoginModuleType = (route.query?.module as LoginModuleType) || 'pwd-login';
@ -34,37 +33,37 @@ const constantRoutes: RouteRecordRaw[] = [
}; };
}, },
meta: { meta: {
title: EnumRouteTitle.login, title: routeTitle('login'),
fullPage: true fullPage: true
} }
}, },
// 403 // 403
{ {
name: ROUTE_NAME_MAP.get('no-permission'), name: routeName('no-permission'),
path: EnumRoutePath['no-permission'], path: routePath('no-permission'),
component: NoPermission, component: NoPermission,
meta: { meta: {
title: EnumRouteTitle['no-permission'], title: routeTitle('no-permission'),
fullPage: true fullPage: true
} }
}, },
// 404 // 404
{ {
name: ROUTE_NAME_MAP.get('not-found'), name: routeName('not-found'),
path: EnumRoutePath['not-found'], path: routePath('not-found'),
component: NotFound, component: NotFound,
meta: { meta: {
title: EnumRouteTitle['not-found'], title: routeTitle('not-found'),
fullPage: true fullPage: true
} }
}, },
// 500 // 500
{ {
name: ROUTE_NAME_MAP.get('service-error'), name: routeName('service-error'),
path: EnumRoutePath['service-error'], path: routePath('service-error'),
component: ServiceError, component: ServiceError,
meta: { meta: {
title: EnumRouteTitle['service-error'], title: routeTitle('service-error'),
fullPage: true fullPage: true
} }
} }
@ -73,7 +72,7 @@ const constantRoutes: RouteRecordRaw[] = [
// 匹配无效的路径重定向404 // 匹配无效的路径重定向404
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
redirect: { name: 'not-found' } redirect: { name: routeName('not-found') }
} }
]; ];

View File

@ -1,18 +1,20 @@
import type { CustomRoute } from '@/interface'; import type { CustomRoute } from '@/interface';
import { EnumRoutePath, EnumRouteTitle } from '@/enum'; import { setRouterCacheName } from '@/utils';
import { ROUTE_NAME_MAP, setRouterCacheName } from '@/utils';
import DashboardAnalysis from '@/views/dashboard/analysis/index.vue'; import DashboardAnalysis from '@/views/dashboard/analysis/index.vue';
import { getRouteConst } from '../const';
setRouterCacheName(DashboardAnalysis, ROUTE_NAME_MAP.get('dashboard_analysis')); const { name, path, title } = getRouteConst('dashboard_analysis');
setRouterCacheName(DashboardAnalysis, name);
/** 路由首页 */ /** 路由首页 */
const ROUTE_HOME: CustomRoute = { const ROUTE_HOME: CustomRoute = {
name: ROUTE_NAME_MAP.get('dashboard_analysis'), name,
path: EnumRoutePath.dashboard_analysis, path,
component: DashboardAnalysis, component: DashboardAnalysis,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
title: EnumRouteTitle.dashboard_analysis title
} }
}; };

View File

@ -7,7 +7,7 @@ type ResponseFail = [any, null];
/** /**
* *
* @author Soybean() 2021-03-15 * @author Soybean<honghuangdc@gmail.com> 2021-03-15
* @class Request * @class Request
*/ */
class Request { class Request {

View File

@ -1,2 +1,2 @@
export * from './theme'; export * from './theme';
export * from './constant'; export * from './sdk';

View File

@ -1,16 +1,7 @@
import type { Component } from 'vue'; import type { Component } from 'vue';
import { EnumRoutePath } from '@/enum'; import type { CustomRoute } from '@/interface';
import type { RoutePathKey, CustomRoute } from '@/interface';
import { router } from '@/router'; import { router } from '@/router';
/** 获取路由name map */
function getRouteNameMap() {
return new Map<RoutePathKey, RoutePathKey>((Object.keys(EnumRoutePath) as RoutePathKey[]).map(v => [v, v]));
}
/** 路由名称 */
export const ROUTE_NAME_MAP = getRouteNameMap();
/** 给需要缓存的页面组件设置名称 */ /** 给需要缓存的页面组件设置名称 */
export function setRouterCacheName(component: Component, name?: string) { export function setRouterCacheName(component: Component, name?: string) {
if (name) { if (name) {
@ -20,8 +11,8 @@ export function setRouterCacheName(component: Component, name?: string) {
/** 获取登录后的重定向地址 */ /** 获取登录后的重定向地址 */
export function getLoginRedirectUrl() { export function getLoginRedirectUrl() {
const path = router.currentRoute.value.fullPath as EnumRoutePath; const path = router.currentRoute.value.fullPath as string;
const redirectUrl = path === EnumRoutePath.root ? undefined : path; const redirectUrl = path === '/' ? undefined : path;
return redirectUrl; return redirectUrl;
} }
@ -31,12 +22,12 @@ export function getLoginRedirectUrl() {
* @param notFoundName - 404 * @param notFoundName - 404
* @param container - * @param container -
*/ */
export function setSingleRoute(container: Component, route: CustomRoute) { export function setSingleRoute(container: Component, route: CustomRoute, notFoundName = 'not-found') {
const routeItem: CustomRoute = { const routeItem: CustomRoute = {
name: `${route.name as string}_`, name: `${route.name as string}_`,
path: `${route.path}_`, path: `${route.path}_`,
component: container, component: container,
redirect: { name: ROUTE_NAME_MAP.get('not-found') }, redirect: { name: notFoundName },
meta: { meta: {
isNotMenu: true isNotMenu: true
}, },

View File

@ -1,6 +1,10 @@
<template> <template>
<div>多级菜单-二级菜单</div> <div>
<n-card title="多级菜单-二级菜单" class="h-full shadow-sm rounded-16px"></n-card>
</div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import { NCard } from 'naive-ui';
</script>
<style scoped></style> <style scoped></style>

View File

@ -23,10 +23,11 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui'; import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui';
import type { FormInst } from 'naive-ui'; import type { FormInst } from 'naive-ui';
import { useRouterChange, useSmsCode } from '@/hooks'; import { useRouterPush } from '@/composables';
import { useSmsCode } from '@/hooks';
const message = useMessage(); const message = useMessage();
const { toCurrentLogin } = useRouterChange(); const { toCurrentLogin } = useRouterPush();
const { label, isCounting, start } = useSmsCode(); const { label, isCounting, start } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null); const formRef = ref<(HTMLElement & FormInst) | null>(null);

View File

@ -36,13 +36,14 @@ import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useNotification }
import type { FormInst, FormRules } from 'naive-ui'; import type { FormInst, FormRules } from 'naive-ui';
import { EnumLoginModule } from '@/enum'; import { EnumLoginModule } from '@/enum';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
import { useRouterChange, useRouteQuery, useLoading } from '@/hooks'; import { useRouterPush, useRouteQuery } from '@/composables';
import { useLoading } from '@/hooks';
import { setToken } from '@/utils'; import { setToken } from '@/utils';
import { OtherLogin } from './components'; import { OtherLogin } from './components';
const notification = useNotification(); const notification = useNotification();
const auth = useAuthStore(); const auth = useAuthStore();
const { toHome, toCurrentLogin, toLoginRedirectUrl } = useRouterChange(); const { toHome, toCurrentLogin, toLoginRedirectUrl } = useRouterPush();
const { loginRedirectUrl } = useRouteQuery(); const { loginRedirectUrl } = useRouteQuery();
const { loading, startLoading, endLoading } = useLoading(); const { loading, startLoading, endLoading } = useLoading();

View File

@ -30,10 +30,11 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useMessage } from 'naive-ui'; import { NForm, NFormItem, NInput, NSpace, NCheckbox, NButton, useMessage } from 'naive-ui';
import type { FormInst } from 'naive-ui'; import type { FormInst } from 'naive-ui';
import { useRouterChange, useSmsCode } from '@/hooks'; import { useRouterPush } from '@/composables';
import { useSmsCode } from '@/hooks';
const message = useMessage(); const message = useMessage();
const { toCurrentLogin } = useRouterChange(); const { toCurrentLogin } = useRouterPush();
const { label, isCounting, start } = useSmsCode(); const { label, isCounting, start } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null); const formRef = ref<(HTMLElement & FormInst) | null>(null);

View File

@ -29,10 +29,11 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui'; import { NForm, NFormItem, NInput, NSpace, NButton, useMessage } from 'naive-ui';
import type { FormInst } from 'naive-ui'; import type { FormInst } from 'naive-ui';
import { useRouterChange, useSmsCode } from '@/hooks'; import { useRouterPush } from '@/composables';
import { useSmsCode } from '@/hooks';
const message = useMessage(); const message = useMessage();
const { toCurrentLogin } = useRouterChange(); const { toCurrentLogin } = useRouterPush();
const { label, isCounting, start } = useSmsCode(); const { label, isCounting, start } = useSmsCode();
const formRef = ref<(HTMLElement & FormInst) | null>(null); const formRef = ref<(HTMLElement & FormInst) | null>(null);

View File

@ -25,7 +25,7 @@ import { computed } from 'vue';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { NCard, NGradientText } from 'naive-ui'; import { NCard, NGradientText } from 'naive-ui';
import { SystemLogo, LoginBg } from '@/components'; import { SystemLogo, LoginBg } from '@/components';
import { useAppTitle } from '@/hooks'; import { useAppTitle } from '@/composables';
import { EnumLoginModule } from '@/enum'; import { EnumLoginModule } from '@/enum';
import { mixColor } from '@/utils'; import { mixColor } from '@/utils';
import type { LoginModuleType } from '@/interface'; import type { LoginModuleType } from '@/interface';