diff --git a/apps/web-antd/src/apis/modules/user.ts b/apps/web-antd/src/apis/modules/user.ts
index c39090d1..75d515b4 100644
--- a/apps/web-antd/src/apis/modules/user.ts
+++ b/apps/web-antd/src/apis/modules/user.ts
@@ -1,6 +1,7 @@
-import type { UserApiType } from '@/apis/types';
import type { UserInfo } from '@vben/types';
+import type { UserApiType } from '@/apis/types';
+
import { request } from '@/forward';
/**
diff --git a/apps/web-antd/src/app.vue b/apps/web-antd/src/app.vue
index 692545e0..bce3c2aa 100644
--- a/apps/web-antd/src/app.vue
+++ b/apps/web-antd/src/app.vue
@@ -1,13 +1,14 @@
+
+
+
+
diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json
index 8706d4c5..877c7f75 100644
--- a/internal/lint-configs/eslint-config/package.json
+++ b/internal/lint-configs/eslint-config/package.json
@@ -41,9 +41,9 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-i": "^2.29.1",
- "eslint-plugin-jsdoc": "^48.2.7",
+ "eslint-plugin-jsdoc": "^48.2.8",
"eslint-plugin-jsonc": "^2.16.0",
- "eslint-plugin-n": "^17.7.0",
+ "eslint-plugin-n": "^17.8.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-perfectionist": "^2.10.0",
"eslint-plugin-prettier": "^5.1.3",
diff --git a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts
index 1c45d329..86814fa4 100644
--- a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts
+++ b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts
@@ -22,31 +22,38 @@ export async function perfectionist(): Promise {
{
'custom-groups': {
type: {
- vben: 'vue',
- vue: ['vue', 'vue-*', '@vue*'],
+ vben: 'vben',
+ vue: 'vue',
},
value: {
- vben: 'vben',
- vue: ['@vben-*', '@vben-core/*'],
+ vben: ['@vben*', '@vben/*', '@vben-core/*'],
+ vue: ['vue', 'vue-*', '@vue*'],
},
},
groups: [
- 'side-effect',
- 'type',
- 'vue',
+ ['external-type', 'builtin-type', 'type'],
+ ['parent-type', 'sibling-type', 'index-type'],
+ ['internal-type'],
'builtin',
+ 'vue',
'vben',
'external',
- 'internal-type',
'internal',
['parent', 'sibling', 'index'],
+ 'side-effect',
+ 'side-effect-style',
'style',
'object',
'unknown',
- 'type',
- ['parent-type', 'sibling-type', 'index-type'],
],
- 'internal-pattern': ['@/layouts/**', '@/router/**', '@/views/**'],
+ 'internal-pattern': [
+ '@/layouts/**',
+ '@/apis/**',
+ '@/forward/**',
+ '@/router/**',
+ '@/views/**',
+ '#/**',
+ ],
'newlines-between': 'always',
order: 'asc',
type: 'natural',
diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json
index 61fdb67c..9c21f576 100644
--- a/internal/tailwind-config/package.json
+++ b/internal/tailwind-config/package.json
@@ -54,12 +54,12 @@
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.19",
- "cssnano": "^7.0.1",
+ "cssnano": "^7.0.2",
"postcss": "^8.4.38",
"postcss-antd-fixes": "^0.2.0",
"postcss-import": "^16.1.0",
"postcss-preset-env": "^9.5.14",
- "tailwindcss": "^3.4.3",
+ "tailwindcss": "^3.4.4",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts
index 0c2e85dd..ef2ee70d 100644
--- a/internal/tailwind-config/src/index.ts
+++ b/internal/tailwind-config/src/index.ts
@@ -2,10 +2,11 @@ import type { Config } from 'tailwindcss';
import path from 'node:path';
+import { fs, getPackagesSync } from '@vben/node-utils';
+
import { addDynamicIconSelectors } from '@iconify/tailwind';
import formsPlugin from '@tailwindcss/forms';
import typographyPlugin from '@tailwindcss/typography';
-import { fs, getPackagesSync } from '@vben/node-utils';
import animate from 'tailwindcss-animate';
import { enterAnimationPlugin } from './plugins/entry';
diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts
index 770a8370..52254196 100644
--- a/internal/vite-config/src/config/application.ts
+++ b/internal/vite-config/src/config/application.ts
@@ -1,5 +1,7 @@
import type { UserConfig } from 'vite';
+import type { DefineApplicationOptions } from '../typing';
+
import { resolve } from 'node:path';
import { defineConfig, loadEnv, mergeConfig } from 'vite';
@@ -7,8 +9,6 @@ import { defineConfig, loadEnv, mergeConfig } from 'vite';
import { getApplicationConditionPlugins } from '../plugins';
import { getCommonConfig } from './common';
-import type { DefineApplicationOptions } from '../typing';
-
function defineApplicationConfig(options: DefineApplicationOptions = {}) {
return defineConfig(async ({ command, mode }) => {
const { application = {}, vite = {} } = options;
diff --git a/internal/vite-config/src/config/index.ts b/internal/vite-config/src/config/index.ts
index 547ceffa..6cded559 100644
--- a/internal/vite-config/src/config/index.ts
+++ b/internal/vite-config/src/config/index.ts
@@ -1,11 +1,11 @@
+import type { DefineConfig } from '../typing';
+
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { defineApplicationConfig } from './application';
import { defineLibraryConfig } from './library';
-import type { DefineConfig } from '../typing';
-
export * from './application';
export * from './library';
diff --git a/internal/vite-config/src/config/library.ts b/internal/vite-config/src/config/library.ts
index 18e74d01..fca61d14 100644
--- a/internal/vite-config/src/config/library.ts
+++ b/internal/vite-config/src/config/library.ts
@@ -1,13 +1,14 @@
import type { UserConfig } from 'vite';
+import type { DefineLibraryOptions } from '../typing';
+
import { readPackageJSON } from '@vben/node-utils';
+
import { defineConfig, mergeConfig } from 'vite';
import { getLibraryConditionPlugins } from '../plugins';
import { getCommonConfig } from './common';
-import type { DefineLibraryOptions } from '../typing';
-
function defineLibraryConfig(options: DefineLibraryOptions = {}) {
return defineConfig(async ({ command, mode }) => {
const root = process.cwd();
diff --git a/internal/vite-config/src/plugins/extra-app-config.ts b/internal/vite-config/src/plugins/extra-app-config.ts
index acea1464..13d98ec9 100644
--- a/internal/vite-config/src/plugins/extra-app-config.ts
+++ b/internal/vite-config/src/plugins/extra-app-config.ts
@@ -3,6 +3,7 @@ import {
generatorContentHash,
readPackageJSON,
} from '@vben/node-utils';
+
import { type PluginOption } from 'vite';
import { getEnvConfig } from '../utils/env';
diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts
index 553f7262..49495ae0 100644
--- a/internal/vite-config/src/plugins/index.ts
+++ b/internal/vite-config/src/plugins/index.ts
@@ -1,9 +1,17 @@
import type { PluginOption } from 'vite';
+import type {
+ ApplicationPluginOptions,
+ CommonPluginOptions,
+ ConditionPlugin,
+ LibraryPluginOptions,
+} from '../typing';
+
import { join } from 'node:path';
-import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import { getPackage } from '@vben/node-utils';
+
+import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import viteVue from '@vitejs/plugin-vue';
import viteVueJsx from '@vitejs/plugin-vue-jsx';
import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
@@ -19,13 +27,6 @@ import { viteExtraAppConfigPlugin } from './extra-app-config';
import { viteImportMapPlugin } from './importmap';
import { viteInjectAppLoadingPlugin } from './inject-app-loading';
-import type {
- ApplicationPluginOptions,
- CommonPluginOptions,
- ConditionPlugin,
- LibraryPluginOptions,
-} from '../typing';
-
/**
* 获取条件成立的 vite 插件
* @param conditionPlugins
diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts
index f5d8f252..7aedc3e9 100644
--- a/internal/vite-config/src/plugins/inject-app-loading/index.ts
+++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts
@@ -2,6 +2,7 @@ import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { fs } from '@vben/node-utils';
+
import { type PluginOption } from 'vite';
/**
diff --git a/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html b/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html
index cf7e9676..44bdd91a 100644
--- a/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html
+++ b/internal/vite-config/src/plugins/inject-app-loading/loading-antd.html
@@ -29,7 +29,7 @@
.loading.hidden {
visibility: hidden;
opacity: 0;
- transition: all 1s ease-out;
+ transition: all 0.6s ease-out;
}
.loading .dots {
diff --git a/internal/vite-config/src/plugins/inject-app-loading/loading.html b/internal/vite-config/src/plugins/inject-app-loading/loading.html
index a947cd82..8833fb3c 100644
--- a/internal/vite-config/src/plugins/inject-app-loading/loading.html
+++ b/internal/vite-config/src/plugins/inject-app-loading/loading.html
@@ -23,7 +23,7 @@
.loading.hidden {
visibility: hidden;
opacity: 0;
- transition: all 1s ease-out;
+ transition: all 0.6s ease-out;
}
.dark .loading {
diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts
index c08c3cfc..ee5adc24 100644
--- a/internal/vite-config/src/utils/env.ts
+++ b/internal/vite-config/src/utils/env.ts
@@ -1,6 +1,7 @@
import { join } from 'node:path';
import { fs } from '@vben/node-utils';
+
import dotenv from 'dotenv';
/**
diff --git a/package.json b/package.json
index 62456c28..49fdc979 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"@changesets/cli": "^2.27.5",
"@ls-lint/ls-lint": "^2.2.3",
"@types/jsdom": "^21.1.7",
- "@types/node": "^20.14.1",
+ "@types/node": "^20.14.2",
"@vben/commitlint-config": "workspace:*",
"@vben/eslint-config": "workspace:*",
"@vben/lint-staged-config": "workspace:*",
@@ -76,7 +76,7 @@
"node": ">=18.7.0",
"pnpm": ">=8.5.0"
},
- "packageManager": "pnpm@9.1.4",
+ "packageManager": "pnpm@9.2.0",
"pnpm": {
"overrides": {
"@ctrl/tinycolor": "4.1.0",
diff --git a/packages/@vben-core/forward/helpers/src/generator-menus.ts b/packages/@vben-core/forward/helpers/src/generator-menus.ts
index 1a590175..ae0d2ca6 100644
--- a/packages/@vben-core/forward/helpers/src/generator-menus.ts
+++ b/packages/@vben-core/forward/helpers/src/generator-menus.ts
@@ -1,7 +1,7 @@
import type { ExRouteRecordRaw, MenuRecordRaw } from '@vben-core/typings';
+import type { RouteRecordRaw, Router } from 'vue-router';
import { mapTree } from '@vben-core/toolkit';
-import type { RouteRecordRaw, Router } from 'vue-router';
/**
* 根据 routes 生成菜单列表
diff --git a/packages/@vben-core/forward/helpers/src/generator-routes.ts b/packages/@vben-core/forward/helpers/src/generator-routes.ts
index c1e2674b..5e787566 100644
--- a/packages/@vben-core/forward/helpers/src/generator-routes.ts
+++ b/packages/@vben-core/forward/helpers/src/generator-routes.ts
@@ -1,16 +1,30 @@
-import { filterTree } from '@vben-core/toolkit';
import type { RouteRecordRaw } from 'vue-router';
+
+import { filterTree, mapTree } from '@vben-core/toolkit';
/**
* 动态生成路由
*/
async function generatorRoutes(
routes: RouteRecordRaw[],
roles: string[],
+ forbiddenPage?: RouteRecordRaw['component'],
): Promise {
// 根据角色标识过滤路由表,判断当前用户是否拥有指定权限
- return filterTree(routes, (route) => {
+ const finalRoutes = filterTree(routes, (route) => {
return hasVisible(route) && hasAuthority(route, roles);
});
+
+ if (!forbiddenPage) {
+ return finalRoutes;
+ }
+
+ // 如果有禁止访问的页面,将禁止访问的页面替换为403页面
+ return mapTree(finalRoutes, (route) => {
+ if (menuHasVisibleWithForbidden(route)) {
+ route.component = forbiddenPage;
+ }
+ return route;
+ });
}
/**
@@ -24,9 +38,10 @@ function hasAuthority(route: RouteRecordRaw, access: string[]) {
if (!authority) {
return true;
}
- return access.some((value) => {
- return authority.includes(value);
- });
+ return (
+ access.some((value) => authority.includes(value)) ||
+ menuHasVisibleWithForbidden(route)
+ );
}
/**
@@ -37,4 +52,12 @@ function hasVisible(route?: RouteRecordRaw) {
return !route?.meta?.hideInMenu;
}
+/**
+ * 判断路由是否在菜单中显示,但是访问会被重定向到403
+ * @param route
+ */
+function menuHasVisibleWithForbidden(route: RouteRecordRaw) {
+ return !!route.meta?.menuVisibleWithForbidden;
+}
+
export { generatorRoutes, hasAuthority, hasVisible };
diff --git a/packages/@vben-core/forward/helpers/src/merge-route-modules.test.ts b/packages/@vben-core/forward/helpers/src/merge-route-modules.test.ts
index 591ffd26..40d9243c 100644
--- a/packages/@vben-core/forward/helpers/src/merge-route-modules.test.ts
+++ b/packages/@vben-core/forward/helpers/src/merge-route-modules.test.ts
@@ -1,11 +1,11 @@
import type { RouteRecordRaw } from 'vue-router';
+import type { RouteModuleType } from './merge-route-modules';
+
import { describe, expect, it } from 'vitest';
import { mergeRouteModules } from './merge-route-modules';
-import type { RouteModuleType } from './merge-route-modules';
-
describe('mergeRouteModules', () => {
it('should merge route modules correctly', () => {
const routeModules: Record = {
diff --git a/packages/@vben-core/forward/preferences/src/index.ts b/packages/@vben-core/forward/preferences/src/index.ts
index 75305555..011b814e 100644
--- a/packages/@vben-core/forward/preferences/src/index.ts
+++ b/packages/@vben-core/forward/preferences/src/index.ts
@@ -1,9 +1,9 @@
import type { Flatten } from '@vben-core/typings';
-import { preferencesManager } from './preferences';
-
import type { Preferences } from './types';
+import { preferencesManager } from './preferences';
+
// 偏好设置(带有层级关系)
const preferences: Preferences = preferencesManager.getPreferences();
diff --git a/packages/@vben-core/forward/preferences/src/preferences.ts b/packages/@vben-core/forward/preferences/src/preferences.ts
index b835bee7..19b27bda 100644
--- a/packages/@vben-core/forward/preferences/src/preferences.ts
+++ b/packages/@vben-core/forward/preferences/src/preferences.ts
@@ -4,6 +4,10 @@ import type {
FlattenObjectKeys,
} from '@vben-core/typings';
+import type { Preferences } from './types';
+
+import { markRaw, reactive, watch } from 'vue';
+
import { StorageManager } from '@vben-core/cache';
import { flattenObject, nestedObject } from '@vben-core/helpers';
import { convertToHslCssVar, merge } from '@vben-core/toolkit';
@@ -14,12 +18,9 @@ import {
useCssVar,
useDebounceFn,
} from '@vueuse/core';
-import { markRaw, reactive, watch } from 'vue';
import { defaultPreferences } from './config';
-import type { Preferences } from './types';
-
const STORAGE_KEY = 'preferences';
const STORAGE_KEY_LOCALE = `${STORAGE_KEY}-locale`;
const STORAGE_KEY_THEME = `${STORAGE_KEY}-theme`;
diff --git a/packages/@vben-core/forward/preferences/src/use-preferences.ts b/packages/@vben-core/forward/preferences/src/use-preferences.ts
index 5b4ff90b..21fad0ad 100644
--- a/packages/@vben-core/forward/preferences/src/use-preferences.ts
+++ b/packages/@vben-core/forward/preferences/src/use-preferences.ts
@@ -1,7 +1,7 @@
-import { diff } from '@vben-core/toolkit';
-
import { computed } from 'vue';
+import { diff } from '@vben-core/toolkit';
+
import { isDarkTheme, preferencesManager } from './preferences';
function usePreferences() {
diff --git a/packages/@vben-core/forward/request/src/request-client/request-client.ts b/packages/@vben-core/forward/request/src/request-client/request-client.ts
index 3e8554bf..3d85c957 100644
--- a/packages/@vben-core/forward/request/src/request-client/request-client.ts
+++ b/packages/@vben-core/forward/request/src/request-client/request-client.ts
@@ -6,6 +6,8 @@ import type {
InternalAxiosRequestConfig,
} from 'axios';
+import type { MakeAuthorizationFn, RequestClientOptions } from './types';
+
import { merge } from '@vben-core/toolkit';
import axios from 'axios';
@@ -15,8 +17,6 @@ import { FileDownloader } from './modules/downloader';
import { InterceptorManager } from './modules/interceptor';
import { FileUploader } from './modules/uploader';
-import type { MakeAuthorizationFn, RequestClientOptions } from './types';
-
class RequestClient {
private instance: AxiosInstance;
private makeAuthorization: MakeAuthorizationFn | undefined;
diff --git a/packages/@vben-core/forward/stores/src/modules/access.ts b/packages/@vben-core/forward/stores/src/modules/access.ts
index 34fe3571..ff1a0ca5 100644
--- a/packages/@vben-core/forward/stores/src/modules/access.ts
+++ b/packages/@vben-core/forward/stores/src/modules/access.ts
@@ -1,5 +1,4 @@
import type { MenuRecordRaw } from '@vben-core/typings';
-
import type { RouteRecordRaw } from 'vue-router';
import { acceptHMRUpdate, defineStore } from 'pinia';
diff --git a/packages/@vben-core/forward/stores/src/modules/tabs.test.ts b/packages/@vben-core/forward/stores/src/modules/tabs.test.ts
index 255c1115..9cb17ce6 100644
--- a/packages/@vben-core/forward/stores/src/modules/tabs.test.ts
+++ b/packages/@vben-core/forward/stores/src/modules/tabs.test.ts
@@ -1,6 +1,7 @@
+import { createRouter, createWebHistory } from 'vue-router';
+
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { createRouter, createWebHistory } from 'vue-router';
import { useTabsStore } from './tabs';
diff --git a/packages/@vben-core/forward/stores/src/modules/tabs.ts b/packages/@vben-core/forward/stores/src/modules/tabs.ts
index 01ad1884..d9e962f8 100644
--- a/packages/@vben-core/forward/stores/src/modules/tabs.ts
+++ b/packages/@vben-core/forward/stores/src/modules/tabs.ts
@@ -1,10 +1,12 @@
-import { startProgress, stopProgress } from '@vben-core/toolkit';
-import { TabItem } from '@vben-core/typings';
import type { RouteRecordNormalized, Router } from 'vue-router';
-import { acceptHMRUpdate, defineStore } from 'pinia';
import { toRaw } from 'vue';
+import { startProgress, stopProgress } from '@vben-core/toolkit';
+import { TabItem } from '@vben-core/typings';
+
+import { acceptHMRUpdate, defineStore } from 'pinia';
+
/**
* @zh_CN 克隆路由,防止路由被修改
* @param route
diff --git a/packages/@vben-core/shared/iconify/src/factory.ts b/packages/@vben-core/shared/iconify/src/factory.ts
index 09d3ba46..b4323cd3 100644
--- a/packages/@vben-core/shared/iconify/src/factory.ts
+++ b/packages/@vben-core/shared/iconify/src/factory.ts
@@ -1,6 +1,7 @@
-import { Icon } from '@iconify/vue';
import { defineComponent, h } from 'vue';
+import { Icon } from '@iconify/vue';
+
function createIcon(name: string) {
return defineComponent({
setup(props, { attrs }) {
diff --git a/packages/@vben-core/shared/typings/src/vue-router.d.ts b/packages/@vben-core/shared/typings/src/vue-router.d.ts
index 47787eb7..e1d4b8f2 100644
--- a/packages/@vben-core/shared/typings/src/vue-router.d.ts
+++ b/packages/@vben-core/shared/typings/src/vue-router.d.ts
@@ -72,6 +72,10 @@ interface RouteMeta {
* 路由是否已经加载过
*/
loaded?: boolean;
+ /**
+ * 菜单可以看到,但是访问会被重定向到403
+ */
+ menuVisibleWithForbidden?: boolean;
/**
* 用于路由->菜单排序
*/
@@ -80,6 +84,7 @@ interface RouteMeta {
* 外链-跳转路径
*/
target?: string;
+
/**
* 标题名称
*/
diff --git a/packages/@vben-core/shared/typings/vue-router.d.ts b/packages/@vben-core/shared/typings/vue-router.d.ts
index abe9f926..239f315e 100644
--- a/packages/@vben-core/shared/typings/vue-router.d.ts
+++ b/packages/@vben-core/shared/typings/vue-router.d.ts
@@ -1,7 +1,7 @@
-import 'vue-router';
-
import type { RouteMeta as IRouteMeta } from '@vben-core/typings';
+import 'vue-router';
+
declare module 'vue-router' {
interface RouteMeta extends IRouteMeta {}
}
diff --git a/packages/@vben-core/uikit/layout-ui/src/components/layout-content.vue b/packages/@vben-core/uikit/layout-ui/src/components/layout-content.vue
index 96b93b76..7d3aef76 100644
--- a/packages/@vben-core/uikit/layout-ui/src/components/layout-content.vue
+++ b/packages/@vben-core/uikit/layout-ui/src/components/layout-content.vue
@@ -2,7 +2,6 @@
import type { ContentCompactType } from '@vben-core/typings';
import type { CSSProperties } from 'vue';
-
import { computed } from 'vue';
interface Props {
diff --git a/packages/@vben-core/uikit/layout-ui/src/components/layout-footer.vue b/packages/@vben-core/uikit/layout-ui/src/components/layout-footer.vue
index b4d68616..07a69017 100644
--- a/packages/@vben-core/uikit/layout-ui/src/components/layout-footer.vue
+++ b/packages/@vben-core/uikit/layout-ui/src/components/layout-footer.vue
@@ -1,10 +1,9 @@