feat(projects): 新增静态路由
This commit is contained in:
parent
bbfdcc8276
commit
ca2dfa6185
4
.env
4
.env
@ -8,5 +8,5 @@ VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
|
||||
|
||||
VITE_SERVER_PORT=3200
|
||||
|
||||
VITE_HTTP_PROXY=true
|
||||
|
||||
# 权限路由模式: static | dynamic
|
||||
VITE_AUTH_ROUTE_MODE=dynamic
|
||||
|
@ -9,7 +9,7 @@ type ServiceEnv = Record<
|
||||
}
|
||||
>;
|
||||
|
||||
/** 请求的环境 */
|
||||
/** 环境配置 */
|
||||
const serviceEnvConfig: ServiceEnv = {
|
||||
dev: {
|
||||
url: 'http://localhost:8080',
|
||||
|
@ -1,2 +1,3 @@
|
||||
# 是否开启打包文件大小结果分析
|
||||
VITE_HTTP_PROXY=true
|
||||
|
||||
VITE_VISUALIZER=false
|
||||
|
@ -50,14 +50,15 @@ Soybean Admin 是一个基于 Vue3、Vite、TypeScript、Naive UI 的免费中
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [ ] 添加前端静态路由
|
||||
- [x] 添加前端静态路由
|
||||
- [ ] 用户角色切换示例、按钮级别权限指令
|
||||
- [ ] 最近功能的有关文档更新
|
||||
- [ ] 引入ECharts替换AntV G2Plot
|
||||
- [ ] 性能优化(优化递归函数)
|
||||
- [ ] 精简版(新分支thin)
|
||||
- [ ] 集成unocss替换windicss(新分支unocss)
|
||||
- [ ] 表单、表格示例
|
||||
- [ ] 添加锁屏组件、全局Iframe组件
|
||||
- [ ] 用户角色切换示例、按钮级别权限指令
|
||||
- [ ] 示例页面完善
|
||||
- [ ] 其他UI版本
|
||||
- [ ] element-plus版本
|
||||
@ -133,7 +134,7 @@ pnpm i -g commitizen
|
||||
|
||||
- 微信交流群:
|
||||
<div style="text-align:left">
|
||||
<img src="https://s2.loli.net/2022/03/23/pNiaVoP6yIvtS5C.jpg" style="width:200px" />
|
||||
<img src="https://s2.loli.net/2022/03/30/VpmnTMsgXJH72B9.jpg" style="width:200px" />
|
||||
</div>
|
||||
|
||||
- QQ交流群 `711301266`
|
||||
|
@ -3,7 +3,7 @@ import { getEnvConfig } from '../../.env-config';
|
||||
|
||||
/**
|
||||
* 设置网络代理
|
||||
* @param viteEnv
|
||||
* @param viteEnv - vite环境描述
|
||||
*/
|
||||
export function createViteProxy(viteEnv: ImportMetaEnv) {
|
||||
const isOpenProxy = viteEnv.VITE_HTTP_PROXY === 'true';
|
||||
|
26
components.d.ts
vendored
26
components.d.ts
vendored
@ -10,33 +10,7 @@ declare module 'vue' {
|
||||
DarkModeSwitch: typeof import('./src/components/common/DarkModeSwitch.vue')['default']
|
||||
GithubLink: typeof import('./src/components/custom/GithubLink.vue')['default']
|
||||
HoverContainer: typeof import('./src/components/common/HoverContainer.vue')['default']
|
||||
IconAntDesignCloseOutlined: typeof import('~icons/ant-design/close-outlined')['default']
|
||||
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
|
||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
|
||||
IconCustomAvatar: typeof import('~icons/custom/avatar')['default']
|
||||
IconCustomLogo: typeof import('~icons/custom/logo')['default']
|
||||
IconCustomLogoFill: typeof import('~icons/custom/logo-fill')['default']
|
||||
IconCustomNoPermission: typeof import('~icons/custom/no-permission')['default']
|
||||
IconCustomNotFound: typeof import('~icons/custom/not-found')['default']
|
||||
IconCustomServiceError: typeof import('~icons/custom/service-error')['default']
|
||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
|
||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
|
||||
IconIcOutlineCheck: typeof import('~icons/ic/outline-check')['default']
|
||||
IconLineMdMenuFoldLeft: typeof import('~icons/line-md/menu-fold-left')['default']
|
||||
IconLineMdMenuUnfoldLeft: typeof import('~icons/line-md/menu-unfold-left')['default']
|
||||
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
|
||||
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
|
||||
IconMdiClose: typeof import('~icons/mdi/close')['default']
|
||||
IconMdiGithub: typeof import('~icons/mdi/github')['default']
|
||||
IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default']
|
||||
IconMdiPin: typeof import('~icons/mdi/pin')['default']
|
||||
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']
|
||||
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
|
||||
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
|
||||
IconPhCaretDoubleLeftBold: typeof import('~icons/ph/caret-double-left-bold')['default']
|
||||
IconPhCaretDoubleRightBold: typeof import('~icons/ph/caret-double-right-bold')['default']
|
||||
IconSelect: typeof import('./src/components/custom/IconSelect.vue')['default']
|
||||
IconUilSearch: typeof import('~icons/uil/search')['default']
|
||||
ImageVerify: typeof import('./src/components/custom/ImageVerify.vue')['default']
|
||||
LoadingEmptyWrapper: typeof import('./src/components/business/LoadingEmptyWrapper.vue')['default']
|
||||
LoginAgreement: typeof import('./src/components/business/LoginAgreement.vue')['default']
|
||||
|
@ -69,16 +69,6 @@ const routes: AuthRoute.Route[] = [
|
||||
icon: 'simple-icons:vite',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'document_naive',
|
||||
path: '/document/naive',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'naive文档',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:alpha-n-box-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'document_project',
|
||||
path: '/document/project',
|
||||
|
40
package.json
40
package.json
@ -26,18 +26,18 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g2plot": "^2.4.10",
|
||||
"@antv/g2plot": "^2.4.13",
|
||||
"@better-scroll/core": "^2.4.2",
|
||||
"@vueuse/core": "^8.0.0",
|
||||
"@vueuse/core": "^8.2.0",
|
||||
"axios": "^0.26.1",
|
||||
"clipboard": "^2.0.10",
|
||||
"colord": "^2.9.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.10.8",
|
||||
"dayjs": "^1.11.0",
|
||||
"form-data": "^4.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.26.4",
|
||||
"pinia": "^2.0.11",
|
||||
"naive-ui": "^2.27.0",
|
||||
"pinia": "^2.0.12",
|
||||
"print-js": "^1.6.0",
|
||||
"qs": "^6.10.3",
|
||||
"soybean-admin-layout": "^1.0.4",
|
||||
@ -52,17 +52,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@amap/amap-jsapi-types": "^0.0.8",
|
||||
"@commitlint/cli": "^16.2.1",
|
||||
"@commitlint/cli": "^16.2.3",
|
||||
"@commitlint/config-conventional": "^16.2.1",
|
||||
"@iconify/json": "^2.1.14",
|
||||
"@iconify/vue": "^3.1.4",
|
||||
"@iconify/json": "^2.1.21",
|
||||
"@iconify/vue": "^3.2.0",
|
||||
"@types/bmapgl": "^0.0.5",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
||||
"@typescript-eslint/parser": "^5.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.17.0",
|
||||
"@typescript-eslint/parser": "^5.17.0",
|
||||
"@vitejs/plugin-vue": "^2.2.4",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^10.0.0",
|
||||
@ -71,30 +71,30 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"cz-customizable": "^6.3.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.5.0",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.5",
|
||||
"lint-staged": "^12.3.7",
|
||||
"mockjs": "^1.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier": "^2.6.1",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "^1.49.9",
|
||||
"typescript": "~4.6.2",
|
||||
"unplugin-icons": "^0.13.3",
|
||||
"unplugin-vue-components": "^0.18.0",
|
||||
"typescript": "^4.6.3",
|
||||
"unplugin-icons": "^0.14.1",
|
||||
"unplugin-vue-components": "^0.18.5",
|
||||
"vite": "2.8.6",
|
||||
"vite-plugin-html": "^3.1.0",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-html-template": "^1.1.2",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-windicss": "^1.8.3",
|
||||
"vue-tsc": "^0.32.1",
|
||||
"vueuc": "^0.4.27",
|
||||
"vue-tsc": "^0.33.9",
|
||||
"vueuc": "^0.4.28",
|
||||
"windicss": "^3.5.1"
|
||||
}
|
||||
}
|
||||
|
2805
pnpm-lock.yaml
2805
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
16
src/directives/permission.ts
Normal file
16
src/directives/permission.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
import { useAuthStore } from '@/store';
|
||||
|
||||
export default function setupLoginDirective(app: App) {
|
||||
const auth = useAuthStore();
|
||||
|
||||
const loginDirective: Directive<HTMLElement, Auth.RoleType | undefined> = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
if (binding.value !== auth.userInfo.userRole) {
|
||||
el.remove();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
app.directive('login', loginDirective);
|
||||
}
|
@ -15,9 +15,9 @@ export async function createDynamicRouteGuard(
|
||||
const route = useRouteStore();
|
||||
const isLogin = Boolean(getToken());
|
||||
|
||||
// 初始化动态路由
|
||||
if (!route.isAddedDynamicRoute) {
|
||||
// 未登录情况下直接回到登录页,登录成功后再加载动态路由
|
||||
// 初始化权限路由
|
||||
if (!route.isInitedAuthRoute) {
|
||||
// 未登录情况下直接回到登录页,登录成功后再加载权限路由
|
||||
if (!isLogin) {
|
||||
if (to.name === routeName('login')) {
|
||||
next();
|
||||
@ -28,16 +28,16 @@ export async function createDynamicRouteGuard(
|
||||
return false;
|
||||
}
|
||||
|
||||
await route.initDynamicRoute(router);
|
||||
await route.initAuthRoute(router);
|
||||
|
||||
if (to.name === routeName('not-found-page')) {
|
||||
// 动态路由没有加载导致被not-found-page路由捕获,等待动态路由加载好了,回到之前的路由
|
||||
// 动态路由没有加载导致被not-found-page路由捕获,等待权限路由加载好了,回到之前的路由
|
||||
next({ path: to.fullPath, replace: true, query: to.query });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 动态路由已经加载,仍然未找到,重定向到not-found
|
||||
// 权限路由已经加载,仍然未找到,重定向到not-found
|
||||
if (to.name === routeName('not-found-page')) {
|
||||
next({ name: routeName('not-found'), replace: true });
|
||||
return false;
|
||||
|
@ -21,3 +21,4 @@ export async function setupRouter(app: App) {
|
||||
}
|
||||
|
||||
export * from './routes';
|
||||
export * from './modules';
|
||||
|
15
src/router/modules/about.ts
Normal file
15
src/router/modules/about.ts
Normal file
@ -0,0 +1,15 @@
|
||||
const about: AuthRoute.Route = {
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '关于',
|
||||
requiresAuth: true,
|
||||
singleLayout: 'basic',
|
||||
permissions: ['super', 'admin', 'test'],
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 7,
|
||||
},
|
||||
};
|
||||
|
||||
export default about;
|
44
src/router/modules/component.ts
Normal file
44
src/router/modules/component.ts
Normal file
@ -0,0 +1,44 @@
|
||||
const component: AuthRoute.Route = {
|
||||
name: 'component',
|
||||
path: '/component',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'component_button',
|
||||
path: '/component/button',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '按钮',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-radio-button-checked',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'component_card',
|
||||
path: '/component/card',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '卡片',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:card-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'component_table',
|
||||
path: '/component/table',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '表格',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:table-large',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '组件示例',
|
||||
icon: 'fluent:app-store-24-regular',
|
||||
order: 3,
|
||||
},
|
||||
};
|
||||
|
||||
export default component;
|
35
src/router/modules/dashboard.ts
Normal file
35
src/router/modules/dashboard.ts
Normal file
@ -0,0 +1,35 @@
|
||||
const dashboard: AuthRoute.Route = {
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'dashboard_analysis',
|
||||
path: '/dashboard/analysis',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '分析页',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:analysis',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dashboard_workbench',
|
||||
path: '/dashboard/workbench',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '工作台',
|
||||
requiresAuth: true,
|
||||
permissions: ['super', 'admin'],
|
||||
icon: 'icon-park-outline:workbench',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '仪表盘',
|
||||
icon: 'carbon:dashboard',
|
||||
order: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export default dashboard;
|
54
src/router/modules/document.ts
Normal file
54
src/router/modules/document.ts
Normal file
@ -0,0 +1,54 @@
|
||||
const document: AuthRoute.Route = {
|
||||
name: 'document',
|
||||
path: '/document',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'document_vue',
|
||||
path: '/document/vue',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'vue文档',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:vuejs',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'document_vue-new',
|
||||
path: '/document/vue-new',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'vue文档(新版)',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:vuejs',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'document_vite',
|
||||
path: '/document/vite',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'vite文档',
|
||||
requiresAuth: true,
|
||||
icon: 'simple-icons:vite',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'document_project',
|
||||
path: '/document/project',
|
||||
meta: {
|
||||
title: '项目文档(外链)',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:file-link-outline',
|
||||
href: 'https://docs.soybean.pro/',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '文档',
|
||||
icon: 'carbon:document',
|
||||
order: 2,
|
||||
},
|
||||
};
|
||||
|
||||
export default document;
|
44
src/router/modules/exception.ts
Normal file
44
src/router/modules/exception.ts
Normal file
@ -0,0 +1,44 @@
|
||||
const exception: AuthRoute.Route = {
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'exception_403',
|
||||
path: '/exception/403',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '异常页403',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-block',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'exception_404',
|
||||
path: '/exception/404',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '异常页404',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-web-asset-off',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'exception_500',
|
||||
path: '/exception/500',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '异常页500',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-wifi-off',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '异常页',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 5,
|
||||
},
|
||||
};
|
||||
|
||||
export default exception;
|
5
src/router/modules/index.ts
Normal file
5
src/router/modules/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { handleModuleRoutes } from '@/utils';
|
||||
|
||||
const modules = import.meta.globEager('./**/*.ts') as AuthRoute.RouteModule;
|
||||
|
||||
export const routes = handleModuleRoutes(modules);
|
56
src/router/modules/multi-menu.ts
Normal file
56
src/router/modules/multi-menu.ts
Normal file
@ -0,0 +1,56 @@
|
||||
const multiMenu: AuthRoute.Route = {
|
||||
name: 'multi-menu',
|
||||
path: '/multi-menu',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'multi-menu_first',
|
||||
path: '/multi-menu/first',
|
||||
component: 'multi',
|
||||
children: [
|
||||
{
|
||||
name: 'multi-menu_first_second',
|
||||
path: '/multi-menu/first/second',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '二级菜单',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:outline-menu',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'multi-menu_first_second-new',
|
||||
path: '/multi-menu/first/second-new',
|
||||
component: 'multi',
|
||||
children: [
|
||||
{
|
||||
name: 'multi-menu_first_second-new_third',
|
||||
path: '/multi-menu/first/second-new/third',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '三级菜单',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:outline-menu',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '二级菜单(有子菜单)',
|
||||
icon: 'ic:outline-menu',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '一级菜单',
|
||||
icon: 'ic:outline-menu',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '多级菜单',
|
||||
icon: 'carbon:menu',
|
||||
order: 6,
|
||||
},
|
||||
};
|
||||
|
||||
export default multiMenu;
|
105
src/router/modules/plugin.ts
Normal file
105
src/router/modules/plugin.ts
Normal file
@ -0,0 +1,105 @@
|
||||
const plugin: AuthRoute.Route = {
|
||||
name: 'plugin',
|
||||
path: '/plugin',
|
||||
component: 'basic',
|
||||
children: [
|
||||
{
|
||||
name: 'plugin_map',
|
||||
path: '/plugin/map',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '地图',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:map',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_video',
|
||||
path: '/plugin/video',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '视频',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:video',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_editor',
|
||||
path: '/plugin/editor',
|
||||
component: 'multi',
|
||||
children: [
|
||||
{
|
||||
name: 'plugin_editor_quill',
|
||||
path: '/plugin/editor/quill',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '富文本编辑器',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:file-document-edit-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_editor_markdown',
|
||||
path: '/plugin/editor/markdown',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'markdown编辑器',
|
||||
requiresAuth: true,
|
||||
icon: 'ri:markdown-line',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '编辑器',
|
||||
icon: 'icon-park-outline:editor',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_swiper',
|
||||
path: '/plugin/swiper',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: 'Swiper插件',
|
||||
requiresAuth: true,
|
||||
icon: 'simple-icons:swiper',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_copy',
|
||||
path: '/plugin/copy',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '剪贴板',
|
||||
requiresAuth: true,
|
||||
icon: 'mdi:clipboard-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_icon',
|
||||
path: '/plugin/icon',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '图标',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-insert-emoticon',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'plugin_print',
|
||||
path: '/plugin/print',
|
||||
component: 'self',
|
||||
meta: {
|
||||
title: '打印',
|
||||
requiresAuth: true,
|
||||
icon: 'ic:baseline-local-printshop',
|
||||
},
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
title: '插件示例',
|
||||
icon: 'clarity:plugin-line',
|
||||
order: 4,
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
@ -1,5 +1,6 @@
|
||||
import type { Router } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
import { routes as staticRoutes } from '@/router';
|
||||
import { fetchUserRoutes } from '@/service';
|
||||
import {
|
||||
getUserInfo,
|
||||
@ -11,9 +12,15 @@ import {
|
||||
import { useTabStore } from '../tab';
|
||||
|
||||
interface RouteState {
|
||||
/** 是否添加过动态路由 */
|
||||
isAddedDynamicRoute: boolean;
|
||||
/** 路由首页name */
|
||||
/**
|
||||
* 权限路由模式:
|
||||
* - static - 前端声明的静态
|
||||
* - dynamic - 后端返回的动态
|
||||
*/
|
||||
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
|
||||
/** 是否初始化了权限路由 */
|
||||
isInitedAuthRoute: boolean;
|
||||
/** 路由首页name(前端静态路由时生效,后端动态路由该值会被后端返回的值覆盖) */
|
||||
routeHomeName: AuthRoute.RouteKey;
|
||||
/** 菜单 */
|
||||
menus: GlobalMenuOption[];
|
||||
@ -25,38 +32,69 @@ interface RouteState {
|
||||
|
||||
export const useRouteStore = defineStore('route-store', {
|
||||
state: (): RouteState => ({
|
||||
isAddedDynamicRoute: false,
|
||||
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
|
||||
isInitedAuthRoute: false,
|
||||
routeHomeName: 'dashboard_analysis',
|
||||
menus: [],
|
||||
searchMenus: [],
|
||||
cacheRoutes: [],
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
* 处理权限路由
|
||||
* @param routes - 权限路由
|
||||
* @param router - 路由实例
|
||||
*/
|
||||
handleAuthRoutes(routes: AuthRoute.Route[], router: Router) {
|
||||
this.menus = transformAuthRouteToMenu(routes);
|
||||
this.searchMenus = transformAuthRoutesToSearchMenus(routes);
|
||||
|
||||
const vueRoutes = transformAuthRoutesToVueRoutes(routes);
|
||||
vueRoutes.forEach((route) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
this.cacheRoutes = getCacheRoutes(vueRoutes);
|
||||
},
|
||||
/**
|
||||
* 初始化动态路由
|
||||
* @param router - 路由实例
|
||||
*/
|
||||
async initDynamicRoute(router: Router) {
|
||||
const { initHomeTab } = useTabStore();
|
||||
|
||||
const { userId } = getUserInfo();
|
||||
if (!userId) return;
|
||||
const { data } = await fetchUserRoutes(userId);
|
||||
if (data) {
|
||||
this.routeHomeName = data.home;
|
||||
this.menus = transformAuthRouteToMenu(data.routes);
|
||||
this.searchMenus = transformAuthRoutesToSearchMenus(data.routes);
|
||||
|
||||
const vueRoutes = transformAuthRoutesToVueRoutes(data.routes);
|
||||
vueRoutes.forEach((route) => {
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
this.cacheRoutes = getCacheRoutes(vueRoutes);
|
||||
|
||||
initHomeTab(data.home, router);
|
||||
this.isAddedDynamicRoute = true;
|
||||
this.handleAuthRoutes(data.routes, router);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化静态路由
|
||||
* @param router - 路由实例
|
||||
*/
|
||||
async initStaticRoute(router: Router) {
|
||||
// 先根据用户权限过滤一下staticRoutes
|
||||
|
||||
this.handleAuthRoutes(staticRoutes, router);
|
||||
},
|
||||
/**
|
||||
* 初始化权限路由
|
||||
* @param router - 路由实例
|
||||
*/
|
||||
async initAuthRoute(router: Router) {
|
||||
const { initHomeTab } = useTabStore();
|
||||
const { userId } = getUserInfo();
|
||||
if (!userId) return;
|
||||
|
||||
const isDynamicRoute = this.authRouteMode === 'dynamic';
|
||||
if (isDynamicRoute) {
|
||||
await this.initDynamicRoute(router);
|
||||
} else {
|
||||
await this.initStaticRoute(router);
|
||||
}
|
||||
|
||||
initHomeTab(this.routeHomeName, router);
|
||||
this.isInitedAuthRoute = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
8
src/typings/business.d.ts
vendored
8
src/typings/business.d.ts
vendored
@ -1,13 +1,13 @@
|
||||
/** 用户相关模块 */
|
||||
declare namespace Auth {
|
||||
/**
|
||||
* 用户角色类型
|
||||
* - super: 超级管理员
|
||||
* 用户角色类型(前端静态路由用角色类型进行路由权限的控制)
|
||||
* - super: 超级管理员(该权限具有所有路由数据)
|
||||
* - admin: 管理员
|
||||
* - test: 测试
|
||||
* - visitor: 游客
|
||||
* - normal: 普通用户
|
||||
*/
|
||||
type RoleType = 'super' | 'admin' | 'test' | 'visitor';
|
||||
type RoleType = 'super' | 'admin' | 'test' | 'normal';
|
||||
|
||||
/** 用户信息 */
|
||||
interface UserInfo {
|
||||
|
12
src/typings/env.d.ts
vendored
12
src/typings/env.d.ts
vendored
@ -7,7 +7,12 @@ declare module '*.vue' {
|
||||
export default component;
|
||||
}
|
||||
|
||||
/** env环境类型 */
|
||||
/**
|
||||
* env环境类型
|
||||
* - dev: 后台开发环境
|
||||
* - test: 后台测试环境
|
||||
* - prod: 后台生产环境
|
||||
*/
|
||||
type EnvType = 'dev' | 'test' | 'prod';
|
||||
|
||||
interface ImportMetaEnv {
|
||||
@ -21,6 +26,11 @@ interface ImportMetaEnv {
|
||||
readonly VITE_APP_DESC: string;
|
||||
/** 开发启动的服务端口号 */
|
||||
readonly VITE_SERVER_PORT: string;
|
||||
/**
|
||||
* 权限路由模式:
|
||||
* - static - 前端声明的静态
|
||||
* - dynamic - 后端返回的动态 */
|
||||
readonly VITE_AUTH_ROUTE_MODE: 'static' | 'dynamic';
|
||||
/** vite环境类型 */
|
||||
readonly VITE_ENV_TYPE?: EnvType;
|
||||
/** 开启请求代理 */
|
||||
|
10
src/typings/route.d.ts
vendored
10
src/typings/route.d.ts
vendored
@ -73,7 +73,10 @@ declare namespace AuthRoute {
|
||||
singleLayout?: Extract<RouteComponent, 'basic' | 'blank'>;
|
||||
/** 需要登录权限 */
|
||||
requiresAuth?: boolean;
|
||||
/** 哪些类型的用户有权限才能访问的路由(空的话则表示不需要权限) */
|
||||
/**
|
||||
* 哪些类型的用户有权限才能访问的路由(空的话则表示不需要权限)
|
||||
* @description 后端动态路由数据不需要该属性,直接由后端根据用户角色返回对应权限的路由数据
|
||||
*/
|
||||
permissions?: Auth.RoleType[];
|
||||
/** 缓存页面 */
|
||||
keepAlive?: boolean;
|
||||
@ -89,7 +92,7 @@ declare namespace AuthRoute {
|
||||
multi?: boolean;
|
||||
};
|
||||
|
||||
/** 单个路由的类型结构(后端返回此类型结构的路由) */
|
||||
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
|
||||
interface Route {
|
||||
/** 路由名称(路由唯一标识) */
|
||||
name: RouteKey;
|
||||
@ -113,6 +116,9 @@ declare namespace AuthRoute {
|
||||
props?: boolean | Record<string, any> | ((to: any) => Record<string, any>);
|
||||
}
|
||||
|
||||
/** 前端导入的路由模块 */
|
||||
type RouteModule = Record<string, { default: AuthRoute.Route }>;
|
||||
|
||||
/** 单独一级路由的key (单独路由需要添加一个父级路由用于应用布局组件) */
|
||||
type SingleRouteKey = Exclude<
|
||||
GetSingleRouteKey<RouteKey>,
|
||||
|
@ -37,7 +37,7 @@ export function getUserInfo() {
|
||||
userId: '',
|
||||
userName: '',
|
||||
userPhone: '',
|
||||
userRole: 'visitor',
|
||||
userRole: 'test',
|
||||
};
|
||||
const userInfo: Auth.UserInfo = getLocal<Auth.UserInfo>(EnumStorageKey['user-info']) || emptyInfo;
|
||||
return userInfo;
|
||||
|
@ -27,7 +27,7 @@ export function transformToTimeCountDown(seconds: number) {
|
||||
* @param start - 开始范围
|
||||
* @param end - 结束范围
|
||||
*/
|
||||
export function getRandomInterger(end: number, start = 0) {
|
||||
export function getRandomInteger(end: number, start = 0) {
|
||||
const range = end - start;
|
||||
const random = Math.floor(Math.random() * range + start);
|
||||
return random;
|
||||
|
@ -5,7 +5,6 @@ const CryptoSecret = '__CryptoJS_Secret__';
|
||||
/**
|
||||
* 加密数据
|
||||
* @param data - 数据
|
||||
* @param secret - 密钥
|
||||
*/
|
||||
export function encrypto(data: any) {
|
||||
const newData = JSON.stringify(data);
|
||||
@ -14,11 +13,10 @@ export function encrypto(data: any) {
|
||||
|
||||
/**
|
||||
* 解密数据
|
||||
* @param ciphertext - 密文
|
||||
* @param secret - 密钥
|
||||
* @param cipherText - 密文
|
||||
*/
|
||||
export function decrypto(ciphertext: string) {
|
||||
const bytes = CryptoJS.AES.decrypt(ciphertext, CryptoSecret);
|
||||
export function decrypto(cipherText: string) {
|
||||
const bytes = CryptoJS.AES.decrypt(cipherText, CryptoSecret);
|
||||
const originalText = bytes.toString(CryptoJS.enc.Utf8);
|
||||
if (originalText) {
|
||||
return JSON.parse(originalText);
|
||||
|
23
src/utils/router/auth.ts
Normal file
23
src/utils/router/auth.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/**
|
||||
* 根据用户权限过滤路由
|
||||
* @param routes - 权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], permission: Auth.RoleType) {
|
||||
const filters: AuthRoute.Route[] = [];
|
||||
|
||||
routes.forEach((route) => {
|
||||
filterAuthRouteByUserPermission(route, permission);
|
||||
});
|
||||
return filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户权限过滤单个路由
|
||||
* @param route - 单个权限路由
|
||||
* @param permission - 权限
|
||||
*/
|
||||
function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
|
||||
return [];
|
||||
}
|
@ -33,7 +33,7 @@ export function transformAuthRoutesToSearchMenus(routes: AuthRoute.Route[], tree
|
||||
|
||||
/**
|
||||
* 将单个权限路由转换成vue路由
|
||||
* @param route - 权限路由
|
||||
* @param item - 单个权限路由
|
||||
*/
|
||||
function transformAuthRouteToVueRoute(item: AuthRoute.Route) {
|
||||
const resultRoute: RouteRecordRaw[] = [];
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './module';
|
||||
export * from './helpers';
|
||||
export * from './cache';
|
||||
export * from './menu';
|
||||
|
28
src/utils/router/module.ts
Normal file
28
src/utils/router/module.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { consoleError } from '../common';
|
||||
|
||||
/**
|
||||
* 权限路由排序
|
||||
* @param routes - 权限路由
|
||||
*/
|
||||
function sortRoutes(routes: AuthRoute.Route[]) {
|
||||
return routes.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理全部导入的路由模块
|
||||
* @param modules - 路由模块
|
||||
*/
|
||||
export function handleModuleRoutes(modules: AuthRoute.RouteModule) {
|
||||
const routes: AuthRoute.Route[] = [];
|
||||
|
||||
Object.keys(modules).forEach((key) => {
|
||||
const item = modules[key].default;
|
||||
if (item) {
|
||||
routes.push(item);
|
||||
} else {
|
||||
consoleError(`路由模块解析出错: key = ${key}`);
|
||||
}
|
||||
});
|
||||
|
||||
return sortRoutes(routes);
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
import { ref, onMounted } from 'vue';
|
||||
import type { DataTableColumn } from 'naive-ui';
|
||||
import { useLoadingEmpty } from '@/hooks';
|
||||
import { getRandomInterger } from '@/utils';
|
||||
import { getRandomInteger } from '@/utils';
|
||||
|
||||
interface DataSource {
|
||||
name: string;
|
||||
@ -52,7 +52,7 @@ function createDataSource(): DataSource[] {
|
||||
.map((_item, index) => {
|
||||
return {
|
||||
name: `Name${index}`,
|
||||
age: getRandomInterger(30, 20),
|
||||
age: getRandomInteger(30, 20),
|
||||
address: '中国',
|
||||
};
|
||||
});
|
||||
|
@ -5,6 +5,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const src = 'https://www.naiveui.com';
|
||||
const src = 'https://www.naiveui.com/zh-CN/os-theme/docs/introduction';
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@ -5,8 +5,8 @@
|
||||
"src/**/*",
|
||||
"src/**/*.vue",
|
||||
"vite.config.*",
|
||||
"mock/**/*.ts",
|
||||
"build/**/*.ts",
|
||||
"mock/**/*.ts",
|
||||
".env-config.ts",
|
||||
"components.d.ts"
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user