@@ -13,9 +13,7 @@
{{ current?.name }}
-
- 描述:{{ current?.description }}
-
+
描述:{{ current?.description }}
@@ -26,7 +24,7 @@
-
-
-
- {
- "meta": {
- "sort": 30401,
- "title": "个人设置",
- "icon": "icon-park-outline-config"
- }
- }
-
\ No newline at end of file
diff --git a/src/pages/user/TabDemo.vue b/src/pages/user/TabDemo.vue
deleted file mode 100644
index e0a7bf5..0000000
--- a/src/pages/user/TabDemo.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/user/TabMail.vue b/src/pages/user/TabMail.vue
deleted file mode 100644
index a965326..0000000
--- a/src/pages/user/TabMail.vue
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-
-
- 启用
- 禁用
-
-
-
-
- :
-
-
- 示例: smtp.163.com:25。国内常见有
- 网易邮箱
- 阿里邮箱
- QQ邮箱等。
-
-
-
-
- 示例: example@mail.com。仅作为发送邮件时的发送人标识,与登陆无关。
-
-
-
- 是
- 否
-
-
-
-
- 示例: example@mail.com。企业邮箱请使用企业域名后缀。
-
-
-
- 示例:AATOLARFABJKYWUY。具体请在对应邮箱设置面板进行生成。
-
-
- 保存修改
-
-
-
-
-
-
配置测试
-
- 发送一封测试邮件,检测邮件设置是否能正常工作。
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "meta": {
- "sort": 30401,
- "title": "个人设置",
- "icon": "icon-park-outline-config"
- }
- }
-
\ No newline at end of file
diff --git a/src/pages/user/index.vue b/src/pages/user/index.vue
deleted file mode 100644
index 82f1c90..0000000
--- a/src/pages/user/index.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
功能列表
-
-
-
-
-
-
-
支付功能
-
通知管理员由企业互联的管理员来设置,拥有通知业务的最大权限。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{
- "meta": {
- "sort": 30401,
- "title": "个人设置",
- "icon": "icon-park-outline-config"
- }
-}
-
diff --git a/src/router/guards/auth.ts b/src/router/guards/auth.ts
index db3f902..a7f7fd8 100644
--- a/src/router/guards/auth.ts
+++ b/src/router/guards/auth.ts
@@ -88,20 +88,12 @@ export function useAuthGuard(router: Router) {
}
// 缓存处理
- const topNames: string[] = [];
- const appNames: string[] = [];
treeEach(routes, (item, level) => {
- const { keepAlive, name } = item.meta ?? {};
- if (keepAlive && name) {
- if (level === 1) {
- topNames.push(name);
- } else {
- appNames.push(name);
- }
+ const { cache, name } = item.meta ?? {};
+ if (cache && name) {
+ menuStore.caches.push(name);
}
});
- menuStore.setCacheTopNames(topNames);
- menuStore.setCacheAppNames(appNames);
// 首页处理
const home = treeFind(routes, i => i.path === menuStore.home);
diff --git a/src/router/guards/progress.ts b/src/router/guards/progress.ts
index 6a5d724..8da8e93 100644
--- a/src/router/guards/progress.ts
+++ b/src/router/guards/progress.ts
@@ -1,6 +1,6 @@
-import { NProgress } from "@/libs/nprogress";
-import { useAppStore } from "@/store";
-import { Router } from "vue-router";
+import { NProgress } from '@/libs/nprogress';
+import { useAppStore } from '@/store';
+import { Router } from 'vue-router';
const routeMap = new Map
();
@@ -10,6 +10,9 @@ export function useProgressGard(router: Router) {
if (routeMap.get(to.fullPath)) {
return true;
}
+ if (to.meta.loading === false) {
+ return true;
+ }
const appStore = useAppStore();
appStore.setPageLoading(true);
});
diff --git a/src/router/menus/index.ts b/src/router/menus/index.ts
index 210858b..8605768 100644
--- a/src/router/menus/index.ts
+++ b/src/router/menus/index.ts
@@ -14,7 +14,9 @@ export interface MenuItem {
external?: boolean;
name?: string;
only?: undefined | 'none' | 'dev';
- keepAlive: boolean;
+ cache?: boolean;
+ hide?: any;
+ link?: string;
children?: MenuItem[];
}
@@ -26,32 +28,15 @@ function routesToItems(routes: RouteRecordRaw[]): MenuItem[] {
const items: MenuItem[] = [];
for (const route of routes) {
- const { meta = {}, parentMeta, only, path } = route as any;
- const { title, sort, icon, keepAlive = false, name } = meta;
- let id = path;
- let paths = route.path.split('/');
- let parentId = paths.slice(0, -1).join('/');
- if (parentMeta) {
- const { title, icon, sort, only } = parentMeta;
- id = `${path}/index`;
- parentId = path;
- items.push({
- title,
- icon,
- sort,
- path,
- only,
- id: path,
- keepAlive: false,
- parentId: paths.slice(0, -1).join('/'),
- });
- } else {
- const p = paths.slice(0, -1).join('/');
- if (routes.some(i => i.path === p) && parentMeta) {
- parentId = p;
- }
+ const { path, meta = {} } = route;
+ if (meta.hide === true || meta.hide === 'menu') {
+ continue;
}
- items.push({ id, title, parentId, path, icon, sort, only, keepAlive, name });
+ let parentId = route.path.split('/').slice(0, -1).join('/');
+ if (!routes.some(i => i.path === parentId)) {
+ parentId = '';
+ }
+ items.push({ ...meta, id: path, parentId, path });
}
return items;
@@ -98,6 +83,21 @@ function sort(routes: T[], key
});
}
+function routeToMenus(routes: RouteRecordRaw[]) {
+ const items: MenuItem[] = [];
+
+ for (const route of routes) {
+ const { path, meta = {} } = route;
+ const item = { ...meta };
+ if (route.children) {
+ item.children = routeToMenus(route.children);
+ }
+ items.push({ ...item, id: path, parentId: (route as any).parentPath, path });
+ }
+
+ return items;
+}
+
/**
* 扁平化的菜单
*/
@@ -111,4 +111,4 @@ export const treeMenus = listToTree(flatMenus);
/**
* 排序过的树级菜单
*/
-export const menus = sort(treeMenus);
+export const menus = routeToMenus(appRoutes);
diff --git a/src/router/routes/page.ts b/src/router/routes/page.ts
index 3b8589e..3f8d2ac 100644
--- a/src/router/routes/page.ts
+++ b/src/router/routes/page.ts
@@ -2,11 +2,47 @@ import generatedRoutes from 'virtual:generated-pages';
import { RouteRecordRaw } from 'vue-router';
import { routes as vroutes } from 'vue-router/auto/routes';
-console.log({vroutes, generatedRoutes});
+console.log({ vroutes, generatedRoutes });
export const TOP_ROUTE_PREF = '_';
export const APP_ROUTE_NAME = '_layout';
+function treeRoutes(list: RouteRecordRaw[]) {
+ const map: Record = {};
+ const tree: RouteRecordRaw[] = [];
+
+ for (const item of list) {
+ map[item.path] = item;
+ }
+
+ for (const item of list) {
+ const parentPath = item.path.split('/').slice(0, -1).join('/');
+ const parent = map[parentPath];
+ if (parent) {
+ (item as any).parentPath = parentPath;
+ (parent.children || (parent.children = [])).push(item);
+ } else {
+ tree.push(item);
+ }
+ }
+
+ return tree;
+}
+
+function sortRoutes(routes: RouteRecordRaw[]) {
+ return routes.sort((prev, next) => {
+ if (prev.children) {
+ prev.children = sortRoutes(prev.children);
+ }
+ if (next.children) {
+ next.children = sortRoutes(next.children);
+ }
+ const x = prev.meta?.sort ?? 0;
+ const y = next.meta?.sort ?? 0;
+ return x - y;
+ });
+}
+
/**
* 转换路由
* @description 以 _ 开头的路由为顶级路由,其余为应用路由
@@ -27,7 +63,7 @@ const transformRoutes = (routes: RouteRecordRaw[]) => {
appRoutes.push(route);
}
- return [topRoutes, appRoutes];
+ return [topRoutes, sortRoutes(treeRoutes(appRoutes))];
};
export const [routes, appRoutes] = transformRoutes(generatedRoutes);
diff --git a/src/store/menu/index.ts b/src/store/menu/index.ts
index 9af8c0b..76e955e 100644
--- a/src/store/menu/index.ts
+++ b/src/store/menu/index.ts
@@ -1,14 +1,16 @@
-import { MenuItem } from "@/router";
-import { defineStore } from "pinia";
+import { MenuItem } from '@/router';
+import { defineStore } from 'pinia';
export const useMenuStore = defineStore({
- id: "menu",
+ id: 'menu',
state: (): MenuStore => {
return {
menus: [],
cacheAppNames: [],
cacheTopNames: [],
- home: "",
+ current: null,
+ caches: [],
+ home: '',
};
},
actions: {
@@ -42,6 +44,20 @@ export const useMenuStore = defineStore({
setCacheAppNames(names: string[]) {
this.cacheAppNames = names;
},
+
+ find(path: string, menus?: MenuItem[]): MenuItem | null {
+ let item: MenuItem | null = null;
+ for (const menu of menus ?? this.menus) {
+ if (menu.path === path) {
+ item = menu;
+ break;
+ }
+ if (menu.children) {
+ item = this.find(path, menu.children);
+ }
+ }
+ return item;
+ },
},
});
@@ -62,4 +78,6 @@ export interface MenuStore {
* 首页路径
*/
home: string;
+ current: MenuItem | null;
+ caches: string[];
}
diff --git a/src/types/auto-component.d.ts b/src/types/auto-component.d.ts
index 1085d0c..1b5c647 100644
--- a/src/types/auto-component.d.ts
+++ b/src/types/auto-component.d.ts
@@ -1,11 +1,11 @@
-// generated by unplugin-vue-components
-// We suggest you to commit this file into source control
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
-import '@vue/runtime-core'
-
export {}
-declare module '@vue/runtime-core' {
+declare module 'vue' {
export interface GlobalComponents {
AAutoComplete: typeof import('@arco-design/web-vue')['AutoComplete']
AAvatar: typeof import('@arco-design/web-vue')['Avatar']
@@ -40,6 +40,7 @@ declare module '@vue/runtime-core' {
AnAudio: typeof import('./../components/AnViewer/AnAudio.vue')['default']
AnEmpty: typeof import('./../components/AnEmpty/AnEmpty.vue')['default']
AnForbidden: typeof import('./../components/AnForbidden/AnForbidden.vue')['default']
+ AnPage: typeof import('./../components/AnPage/AnPage.vue')['default']
AnToast: typeof import('./../components/AnToast/AnToast.vue')['default']
AnViewer: typeof import('./../components/AnViewer/AnViewer.vue')['default']
APagination: typeof import('@arco-design/web-vue')['Pagination']
diff --git a/src/types/auto-import.d.ts b/src/types/auto-import.d.ts
index 00b0fed..f6471c3 100644
--- a/src/types/auto-import.d.ts
+++ b/src/types/auto-import.d.ts
@@ -1,4 +1,8 @@
-// Generated by 'unplugin-auto-import'
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
@@ -45,6 +49,7 @@ declare global {
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
+ const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
@@ -59,3 +64,9 @@ declare global {
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
+// for type re-export
+declare global {
+ // @ts-ignore
+ export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
+ import('vue')
+}
diff --git a/src/types/auto-router.d.ts b/src/types/auto-router.d.ts
index bf54a4f..3eeab3e 100644
--- a/src/types/auto-router.d.ts
+++ b/src/types/auto-router.d.ts
@@ -46,23 +46,26 @@ declare module 'vue-router/auto/routes' {
'/content/category/': RouteRecordInfo<'/content/category/', '/content/category', Record, Record>,
'/content/comment/': RouteRecordInfo<'/content/comment/', '/content/comment', Record, Record>,
'/content/material/': RouteRecordInfo<'/content/material/', '/content/material', Record, Record>,
+ '/content/material-category/': RouteRecordInfo<'/content/material-category/', '/content/material-category', Record, Record>,
'/content/post/': RouteRecordInfo<'/content/post/', '/content/post', Record, Record>,
'/dev/': RouteRecordInfo<'/dev/', '/dev', Record, Record>,
'/dev/editor/': RouteRecordInfo<'/dev/editor/', '/dev/editor', Record, Record>,
+ '/dev/nav/': RouteRecordInfo<'/dev/nav/', '/dev/nav', Record, Record>,
'/dev/openapi/': RouteRecordInfo<'/dev/openapi/', '/dev/openapi', Record, Record>,
'/home/': RouteRecordInfo<'/home/', '/home', Record, Record>,
'/log/': RouteRecordInfo<'/log/', '/log', Record, Record>,
'/log/login/': RouteRecordInfo<'/log/login/', '/log/login', Record, Record>,
'/log/operation/': RouteRecordInfo<'/log/operation/', '/log/operation', Record, Record>,
+ '/setting/': RouteRecordInfo<'/setting/', '/setting', Record, Record>,
+ '/setting/common/': RouteRecordInfo<'/setting/common/', '/setting/common', Record, Record>,
+ '/setting/function/': RouteRecordInfo<'/setting/function/', '/setting/function', Record, Record>,
+ '/setting/mail/': RouteRecordInfo<'/setting/mail/', '/setting/mail', Record, Record>,
'/system/': RouteRecordInfo<'/system/', '/system', Record, Record>,
+ '/system/department/': RouteRecordInfo<'/system/department/', '/system/department', Record, Record>,
'/system/dict/': RouteRecordInfo<'/system/dict/', '/system/dict', Record, Record>,
'/system/menu/': RouteRecordInfo<'/system/menu/', '/system/menu', Record, Record>,
'/system/role/': RouteRecordInfo<'/system/role/', '/system/role', Record, Record>,
'/system/user/': RouteRecordInfo<'/system/user/', '/system/user', Record, Record>,
- '/user/': RouteRecordInfo<'/user/', '/user', Record, Record>,
- '/user/TabCommon': RouteRecordInfo<'/user/TabCommon', '/user/TabCommon', Record, Record>,
- '/user/TabDemo': RouteRecordInfo<'/user/TabDemo', '/user/TabDemo', Record, Record>,
- '/user/TabMail': RouteRecordInfo<'/user/TabMail', '/user/TabMail', Record, Record>,
}
}
diff --git a/src/types/vue-router.d.ts b/src/types/vue-router.d.ts
index e47b574..34f461c 100644
--- a/src/types/vue-router.d.ts
+++ b/src/types/vue-router.d.ts
@@ -1,59 +1,68 @@
import "vue-router";
declare module "vue-router" {
- interface RouteRecordSingleView {
- parentMeta: {
- /**
- * 页面标题
- */
- title?: string;
- /**
- * 页面图标
- */
- icon?: string;
- /**
- * 页面排序,越小越靠前
- */
- sort?: number;
- };
- }
-
interface RouteMeta {
/**
* 页面标题
+ * @description
+ * 菜单和导航面包屑等地方会用到
*/
title?: string;
/**
* 页面图标
+ * @description
+ * 使用 icon-park-outline 图标集的图标类名
*/
icon?: string;
/**
- * 页面排序,越小越靠前
+ * 显示顺序
+ * @description
+ * 在菜单中的显示顺序,越小越靠前
*/
sort?: number;
/**
* 是否在菜单导航中隐藏
+ * @description
+ * - false // 不隐藏(默认)
+ * - true // 在路由和菜单中隐藏,即忽略且不打包
+ * - 'menu' // 在菜单中隐藏,通过其他方式访问
+ * - 'prod' // 在生产环境下隐藏
*/
- hide?: boolean;
+ hide?: boolean | 'menu' | 'prod';
/**
* 所需权限
+ * @example
+ * ```js
+ * ['system:user']
+ * ```
*/
auth?: string[];
/**
- * 是否在面包屑中隐藏
+ * 是否缓存
+ * @description
+ * 是否使用 keep-alive 缓存
*/
- breadcrumb?: boolean;
+ cache?: boolean;
/**
- * 是否缓存页面
- */
- keepAlive?: boolean;
- /**
- * 组件名字(keepAlive为true时必须)
+ * 组件名字
+ * @description
+ * 组件名字,当 cache为true 时必须
*/
name?: string;
/**
* 是否显示loading
+ * @description
+ * 可以自定义 loading 文本
*/
loading?: boolean | string;
+ /**
+ * 链接
+ * @description
+ * ```js
+ * 'https://juetan.cn'
+ * ```
+ */
+ link?: string;
+ parentPath?: string;
}
}
diff --git a/tsconfig.json b/tsconfig.json
index 4233024..1218102 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,6 +12,7 @@
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
+ "noImplicitAny": false,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
diff --git a/vite.config.ts b/vite.config.ts
index b01b37d..803c60e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,19 +1,19 @@
import Vue from '@vitejs/plugin-vue';
import VueJsx from '@vitejs/plugin-vue-jsx';
-import { resolve } from 'path';
-import { visualizer } from 'rollup-plugin-visualizer';
-import { presetIcons, presetUno } from 'unocss';
import Unocss from 'unocss/vite';
import AutoImport from 'unplugin-auto-import/vite';
-import { ArcoResolver } from 'unplugin-vue-components/resolvers';
import AutoComponent from 'unplugin-vue-components/vite';
import router from 'unplugin-vue-router/vite';
-import { defineConfig, loadEnv } from 'vite';
import Page from 'vite-plugin-pages';
-import { arcoToUnoColor } from './scripts/vite/color';
import iconFile from './scripts/vite/file.json';
import iconFmt from './scripts/vite/fmt.json';
import plugin from './scripts/vite/plugin';
+import { resolve } from 'path';
+import { visualizer } from 'rollup-plugin-visualizer';
+import { presetIcons, presetUno } from 'unocss';
+import { ArcoResolver } from 'unplugin-vue-components/resolvers';
+import { defineConfig, loadEnv } from 'vite';
+import { arcoToUnoColor } from './scripts/vite/color';
/**
* vite 配置
@@ -24,6 +24,7 @@ export default defineConfig(({ mode }) => {
const base = env.VITE_BASE ?? '/';
const host = env.VITE_HOST ?? '0.0.0.0';
const port = Number(env.VITE_PORT ?? 3020);
+
return {
base,
plugins: [
@@ -33,7 +34,7 @@ export default defineConfig(({ mode }) => {
*/
router({
dts: 'src/types/auto-router.d.ts',
- exclude: ['**/components/*'],
+ exclude: ['**/components/*', '**/*.*.*', '**/!(index).*'],
}),
/**
@@ -76,13 +77,26 @@ export default defineConfig(({ mode }) => {
* @see https://github.com/hannoeru/vite-plugin-pages
*/
Page({
- exclude: ['**/components/*', '**/*.*.*'],
+ exclude: ['**/components/*', '**/*.*.*', '**/!(index).*'],
importMode: 'async',
+ extensions: ['vue'],
onRoutesGenerated(routes) {
- if (mode === 'development') {
- return routes.filter(route => route.only !== 'none');
+ const isProd = mode !== 'development';
+ const result = [];
+ for (const route of routes) {
+ const { hide } = route.meta ?? {};
+ if (!route.meta) {
+ continue;
+ }
+ if (hide === true) {
+ continue;
+ }
+ if (isProd && hide === 'prod') {
+ continue;
+ }
+ result.push(route);
}
- return routes.filter(route => !['none', 'dev'].includes(route.only));
+ return result;
},
}),
@@ -155,5 +169,8 @@ export default defineConfig(({ mode }) => {
},
},
},
+ build: {
+ chunkSizeWarningLimit: 2000,
+ },
};
});