diff --git a/apps/backend-mock/.env b/apps/backend-mock/.env deleted file mode 100644 index b20c4a65..00000000 --- a/apps/backend-mock/.env +++ /dev/null @@ -1,3 +0,0 @@ -PORT=5320 -ACCESS_TOKEN_SECRET=access_token_secret -REFRESH_TOKEN_SECRET=refresh_token_secret diff --git a/apps/backend-mock/api/menu/all.ts b/apps/backend-mock/api/menu/all.ts deleted file mode 100644 index 580cee4f..00000000 --- a/apps/backend-mock/api/menu/all.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { verifyAccessToken } from '~/utils/jwt-utils'; -import { unAuthorizedResponse } from '~/utils/response'; - -export default eventHandler(async (event) => { - const userinfo = verifyAccessToken(event); - if (!userinfo) { - return unAuthorizedResponse(event); - } - - const menus = - MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? []; - return useResponseSuccess(menus); -}); diff --git a/apps/backend-mock/middleware/1.api.ts b/apps/backend-mock/middleware/1.api.ts deleted file mode 100644 index 84e2ce0e..00000000 --- a/apps/backend-mock/middleware/1.api.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default defineEventHandler((event) => { - if (event.method === 'OPTIONS') { - event.node.res.statusCode = 204; - event.node.res.statusMessage = 'No Content.'; - return 'OK'; - } -}); diff --git a/apps/backend-mock/routes/[...].ts b/apps/backend-mock/routes/[...].ts deleted file mode 100644 index 70c5f7c7..00000000 --- a/apps/backend-mock/routes/[...].ts +++ /dev/null @@ -1,12 +0,0 @@ -export default defineEventHandler(() => { - return ` -

Hello Vben Admin

-

Mock service is starting

- -`; -}); diff --git a/apps/web-antd/.env b/apps/web-antd/.env index c14a467f..e3653b82 100644 --- a/apps/web-antd/.env +++ b/apps/web-antd/.env @@ -1,5 +1,8 @@ # 应用标题 -VITE_APP_TITLE=Vben Admin Antd +VITE_APP_TITLE=萧康云医 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-antd +VITE_APP_NAMESPACE=xk-admin- + +# 应用版本 +XK_APP_VERSION=1.0.0 diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development index c138f482..1eae9e67 100644 --- a/apps/web-antd/.env.development +++ b/apps/web-antd/.env.development @@ -7,7 +7,7 @@ VITE_BASE=/ VITE_GLOB_API_URL=/api # 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true +VITE_NITRO_MOCK=false # 是否打开 devtools,true 为打开,false 为关闭 VITE_DEVTOOLS=false diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production index 5375847a..deeb8835 100644 --- a/apps/web-antd/.env.production +++ b/apps/web-antd/.env.production @@ -1,7 +1,7 @@ VITE_BASE=/ # 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api +VITE_GLOB_API_URL=/api/admin # 是否开启压缩,可以设置为 none, brotli, gzip VITE_COMPRESS=none @@ -10,7 +10,7 @@ VITE_COMPRESS=none VITE_PWA=false # vue-router 的模式 -VITE_ROUTER_HISTORY=hash +VITE_ROUTER_HISTORY=createWebHashHistory # 是否注入全局loading VITE_INJECT_APP_LOADING=true diff --git a/apps/web-ele/.env b/apps/web-ele/.env deleted file mode 100644 index 87cb3df1..00000000 --- a/apps/web-ele/.env +++ /dev/null @@ -1,5 +0,0 @@ -# 应用标题 -VITE_APP_TITLE=Vben Admin Ele - -# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-ele diff --git a/apps/web-ele/.env.analyze b/apps/web-ele/.env.analyze deleted file mode 100644 index ffafa8dd..00000000 --- a/apps/web-ele/.env.analyze +++ /dev/null @@ -1,7 +0,0 @@ -# public path -VITE_BASE=/ - -# Basic interface address SPA -VITE_GLOB_API_URL=/api - -VITE_VISUALIZER=true diff --git a/apps/web-ele/.env.development b/apps/web-ele/.env.development deleted file mode 100644 index 8bcb432e..00000000 --- a/apps/web-ele/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# 端口号 -VITE_PORT=5777 - -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - -# 是否打开 devtools,true 为打开,false 为关闭 -VITE_DEVTOOLS=false - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true diff --git a/apps/web-ele/.env.production b/apps/web-ele/.env.production deleted file mode 100644 index 5375847a..00000000 --- a/apps/web-ele/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none - -# 是否开启 PWA -VITE_PWA=false - -# vue-router 的模式 -VITE_ROUTER_HISTORY=hash - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true - -# 打包后是否生成dist.zip -VITE_ARCHIVER=true diff --git a/apps/web-ele/src/router/access.ts b/apps/web-ele/src/router/access.ts deleted file mode 100644 index 2d07c892..00000000 --- a/apps/web-ele/src/router/access.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { - ComponentRecordType, - GenerateMenuAndRoutesOptions, -} from '@vben/types'; - -import { generateAccessible } from '@vben/access'; -import { preferences } from '@vben/preferences'; - -import { ElMessage } from 'element-plus'; - -import { getAllMenusApi } from '#/api'; -import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); - -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); - - const layoutMap: ComponentRecordType = { - BasicLayout, - IFrameView, - }; - - return await generateAccessible(preferences.app.accessMode, { - ...options, - fetchMenuListAsync: async () => { - ElMessage({ - duration: 1500, - message: `${$t('common.loadingMenu')}...`, - }); - return await getAllMenusApi(); - }, - // 可以指定没有权限跳转403页面 - forbiddenComponent, - // 如果 route.meta.menuVisibleWithForbidden = true - layoutMap, - pageMap, - }); -} - -export { generateAccess }; diff --git a/apps/web-naive/.env b/apps/web-naive/.env deleted file mode 100644 index 350660c0..00000000 --- a/apps/web-naive/.env +++ /dev/null @@ -1,5 +0,0 @@ -# 应用标题 -VITE_APP_TITLE=Vben Admin Naive - -# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-naive diff --git a/apps/web-naive/.env.analyze b/apps/web-naive/.env.analyze deleted file mode 100644 index ffafa8dd..00000000 --- a/apps/web-naive/.env.analyze +++ /dev/null @@ -1,7 +0,0 @@ -# public path -VITE_BASE=/ - -# Basic interface address SPA -VITE_GLOB_API_URL=/api - -VITE_VISUALIZER=true diff --git a/apps/web-naive/.env.development b/apps/web-naive/.env.development deleted file mode 100644 index 11c5254a..00000000 --- a/apps/web-naive/.env.development +++ /dev/null @@ -1,16 +0,0 @@ -# 端口号 -VITE_PORT=5888 - -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - -# 是否打开 devtools,true 为打开,false 为关闭 -VITE_DEVTOOLS=false - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true diff --git a/apps/web-naive/.env.production b/apps/web-naive/.env.production deleted file mode 100644 index 5375847a..00000000 --- a/apps/web-naive/.env.production +++ /dev/null @@ -1,19 +0,0 @@ -VITE_BASE=/ - -# 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api - -# 是否开启压缩,可以设置为 none, brotli, gzip -VITE_COMPRESS=none - -# 是否开启 PWA -VITE_PWA=false - -# vue-router 的模式 -VITE_ROUTER_HISTORY=hash - -# 是否注入全局loading -VITE_INJECT_APP_LOADING=true - -# 打包后是否生成dist.zip -VITE_ARCHIVER=true diff --git a/apps/web-naive/src/router/access.ts b/apps/web-naive/src/router/access.ts deleted file mode 100644 index 7a80bac0..00000000 --- a/apps/web-naive/src/router/access.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { - ComponentRecordType, - GenerateMenuAndRoutesOptions, -} from '@vben/types'; - -import { generateAccessible } from '@vben/access'; -import { preferences } from '@vben/preferences'; - -import { message } from '#/adapter/naive'; -import { getAllMenusApi } from '#/api'; -import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; - -const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); - -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); - - const layoutMap: ComponentRecordType = { - BasicLayout, - IFrameView, - }; - - return await generateAccessible(preferences.app.accessMode, { - ...options, - fetchMenuListAsync: async () => { - message.loading(`${$t('common.loadingMenu')}...`, { - duration: 1.5, - }); - return await getAllMenusApi(); - }, - // 可以指定没有权限跳转403页面 - forbiddenComponent, - // 如果 route.meta.menuVisibleWithForbidden = true - layoutMap, - pageMap, - }); -} - -export { generateAccess }; diff --git a/docs/src/en/guide/in-depth/access.md b/docs/src/en/guide/in-depth/access.md deleted file mode 100644 index 05997d7d..00000000 --- a/docs/src/en/guide/in-depth/access.md +++ /dev/null @@ -1,318 +0,0 @@ ---- -outline: deep ---- - -# Access Control - -The framework has built-in two types of access control methods: - -- Determining whether a menu or button can be accessed based on user roles -- Determining whether a menu or button can be accessed through an API - -## Frontend Access Control - -**Implementation Principle**: The permissions for routes are hardcoded on the frontend, specifying which permissions are required to view certain routes. Only general routes are initialized, and routes that require permissions are not added to the route table. After logging in or obtaining user roles through other means, the roles are used to traverse the route table to generate a route table that the role can access. This table is then added to the router instance using `router.addRoute`, achieving permission filtering. - -**Disadvantage**: The permissions are relatively inflexible; if the backend changes roles, the frontend needs to be adjusted accordingly. This is suitable for systems with relatively fixed roles. - -### Steps - -- Ensure the current mode is set to frontend access control - -Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='frontend'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - // Default value, optional - accessMode: 'frontend', - }, -}); -``` - -- Configure route permissions - -#### If not configured, it is visible by default - -```ts {3} - { - meta: { - authority: ['super'], - }, -}, -``` - -- Ensure the roles returned by the interface match the permissions in the route table - -You can look under `src/store/auth` in the application to find the following code: - -```ts -// Set the login user information, ensuring that userInfo.roles is an array and contains permissions from the route table -// For example: userInfo.roles=['super', 'admin'] -authStore.setUserInfo(userInfo); -``` - -At this point, the configuration is complete. You need to ensure that the roles returned by the interface after login match the permissions in the route table; otherwise, access will not be possible. - -### Menu Visible but Access Forbidden - -Sometimes, we need the menu to be visible but access to it forbidden. This can be achieved by setting `menuVisibleWithForbidden` to `true`. In this case, the menu will be visible, but access will be forbidden, redirecting to a 403 page. - -```ts -{ - meta: { - menuVisibleWithForbidden: true, - }, -}, -``` - -## Backend Access Control - -**Implementation Principle**: It is achieved by dynamically generating a routing table through an API, which returns data following a certain structure. The frontend processes this data into a recognizable structure, then adds it to the routing instance using `router.addRoute`, realizing the dynamic generation of permissions. - -**Disadvantage**: The backend needs to provide a data structure that meets the standards, and the frontend needs to process this structure. This is suitable for systems with more complex permissions. - -### Steps - -- Ensure the current mode is set to backend access control - -Adjust `preferences.ts` in the corresponding application directory to ensure `accessMode='backend'`. - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - accessMode: 'backend', - }, -}); -``` - -- Ensure the structure of the menu data returned by the interface is correct - -You can look under `src/router/access.ts` in the application to find the following code: - -```ts -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - return await generateAccessible(preferences.app.accessMode, { - fetchMenuListAsync: async () => { - // This interface is for the menu data returned by the backend - return await getAllMenus(); - }, - }); -} -``` - -- Interface returns menu data, see comments for explanation - -::: details Example of Interface Returning Menu Data - -```ts -const dashboardMenus = [ - { - // Here, 'BasicLayout' is hardcoded and cannot be changed - component: 'BasicLayout', - meta: { - order: -1, - title: 'page.dashboard.title', - }, - name: 'Dashboard', - path: '/', - redirect: '/analytics', - children: [ - { - name: 'Analytics', - path: '/analytics', - // Here is the path of the page, need to remove 'views/' and '.vue' - component: '/dashboard/analytics/index', - meta: { - affixTab: true, - title: 'page.dashboard.analytics', - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - title: 'page.dashboard.workspace', - }, - }, - ], - }, -]; -``` - -::: - -At this point, the configuration is complete. You need to ensure that after logging in, the format of the menu returned by the interface is correct; otherwise, access will not be possible. - -## Fine-grained Control of Buttons - -In some cases, we need to control the display of buttons with fine granularity. We can control the display of buttons through interfaces or roles. - -### Permission Code - -The permission code is the code returned by the interface. The logic to determine whether a button is displayed is located under `src/store/auth`: - -```ts -const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodes(), -]); - -userInfo = fetchUserInfoResult; -authStore.setUserInfo(userInfo); -accessStore.setAccessCodes(accessCodes); -``` - -Locate the `getAccessCodes` corresponding interface, which can be adjusted according to business logic. - -The data structure returned by the permission code is an array of strings, for example: `['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']` - -With the permission codes, you can use the `AccessControl` component and API provided by `@vben/access` to show and hide buttons. - -#### Component Method - -```vue - - - -``` - -#### API Method - -```vue - - - -``` - -#### Directive Method - -> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array. - -```vue - -``` - -### Roles - -The method of determining roles does not require permission codes returned by the interface; it directly determines whether buttons are displayed based on roles. - -#### Component Method - -```vue - - - -``` - -#### API Method - -```vue - - - -``` - -#### Directive Method - -> The directive supports binding single or multiple permission codes. For a single one, you can pass a string or an array containing one permission code, and for multiple permission codes, you can pass an array. - -```vue - -``` diff --git a/docs/src/guide/in-depth/access.md b/docs/src/guide/in-depth/access.md deleted file mode 100644 index c0c4bc99..00000000 --- a/docs/src/guide/in-depth/access.md +++ /dev/null @@ -1,311 +0,0 @@ ---- -outline: deep ---- - -# 权限 - -框架内置了两种权限控制方式: - -- 通过用户角色来判断菜单或者按钮是否可以访问 -- 通过接口来判断菜单或者按钮是否可以访问 - -## 前端访问控制 - -**实现原理**: 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登录后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoute` 添加到路由实例,实现权限的过滤。 - -**缺点**: 权限相对不自由,如果后台改动角色,前台也需要跟着改动。适合角色较固定的系统 - -### 步骤 - -- 确保当前模式为前端访问控制模式 - -调整对应应用目录下的`preferences.ts`,确保`accessMode='frontend'`。 - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - // 默认值,可不填 - accessMode: 'frontend', - }, -}); -``` - -- 配置路由权限 - -**如果不配置,默认可见** - -```ts {3} - { - meta: { - authority: ['super'], - }, -}, -``` - -- 确保接口返回的角色和路由表的权限匹配 - -可查看应用下的 `src/store/auth`,找到下面代码, - -```ts -// 设置登录用户信息,需要确保 userInfo.roles 是一个数组,且包含路由表中的权限 -// 例如:userInfo.roles=['super', 'admin'] -authStore.setUserInfo(userInfo); -``` - -到这里,就已经配置完成,你需要确保登录后,接口返回的角色和路由表的权限匹配,否则无法访问。 - -### 菜单可见,但禁止访问 - -有时候,我们需要菜单可见,但是禁止访问,可以通过下面的方式实现,设置 `menuVisibleWithForbidden` 为 `true`,此时菜单可见,但是禁止访问,会跳转403页面。 - -```ts -{ - meta: { - menuVisibleWithForbidden: true, - }, -}, -``` - -## 后端访问控制 - -**实现原理**: 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 `router.addRoute` 添加到路由实例,实现权限的动态生成。 - -**缺点**: 后端需要提供符合规范的数据结构,前端需要处理数据结构,适合权限较为复杂的系统。 - -### 步骤 - -- 确保当前模式为后端访问控制模式 - -调整对应应用目录下的`preferences.ts`,确保`accessMode='backend'`。 - -```ts -import { defineOverridesPreferences } from '@vben/preferences'; - -export const overridesPreferences = defineOverridesPreferences({ - // overrides - app: { - accessMode: 'backend', - }, -}); -``` - -- 确保接口返回的菜单数据结构正确 - -可查看应用下的 `src/router/access.ts`,找到下面代码, - -```ts {5} -async function generateAccess(options: GenerateMenuAndRoutesOptions) { - return await generateAccessible(preferences.app.accessMode, { - fetchMenuListAsync: async () => { - // 这个接口为后端返回的菜单数据 - return await getAllMenus(); - }, - }); -} -``` - -- 接口返回菜单数据,可看注释说明 - -::: details 接口返回菜单数据示例 - -```ts -const dashboardMenus = [ - { - // 这里固定写死 BasicLayout,不可更改 - component: 'BasicLayout', - meta: { - order: -1, - title: 'page.dashboard.title', - }, - name: 'Dashboard', - path: '/', - redirect: '/analytics', - children: [ - { - name: 'Analytics', - path: '/analytics', - // 这里为页面的路径,需要去掉 views/ 和 .vue - component: '/dashboard/analytics/index', - meta: { - affixTab: true, - title: 'page.dashboard.analytics', - }, - }, - { - name: 'Workspace', - path: '/workspace', - component: '/dashboard/workspace/index', - meta: { - title: 'page.dashboard.workspace', - }, - }, - ], - }, -]; -``` - -::: - -到这里,就已经配置完成,你需要确保登录后,接口返回的菜单格式正确,否则无法访问。 - -## 按钮细粒度控制 - -在某些情况下,我们需要对按钮进行细粒度的控制,我们可以借助接口或者角色来控制按钮的显示。 - -### 权限码 - -权限码为接口返回的权限码,通过权限码来判断按钮是否显示,逻辑在`src/store/auth`下: - -```ts -const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodes(), -]); - -userInfo = fetchUserInfoResult; -authStore.setUserInfo(userInfo); -accessStore.setAccessCodes(accessCodes); -``` - -找到 `getAccessCodes` 对应的接口,可根据业务逻辑进行调整。 - -权限码返回的数据结构为字符串数组,例如:`['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010']` - -有了权限码,就可以使用 `@vben/access` 提供的`AccessControl`组件及API来进行按钮的显示与隐藏。 - -#### 组件方式 - -```vue - - - -``` - -#### API方式 - -```vue - - - -``` - -#### 指令方式 - -> 指令支持绑定单个或多个权限码。单个时可以直接传入字符串或数组中包含一个权限码,多个权限码则传入数组。 - -```vue - -``` - -### 角色 - -角色判断方式不需要接口返回的权限码,直接通过角色来判断按钮是否显示。 - -#### 组件方式 - -```vue - - - -``` - -#### API方式 - -```vue - - - -``` - -#### 指令方式 - -> 指令支持绑定单个或多个权限码。单个时可以直接传入字符串或数组中包含一个权限码,多个权限码则传入数组。 - -```vue - -``` diff --git a/vben-admin.code-workspace b/vben-admin.code-workspace index aa8205bd..b083daae 100644 --- a/vben-admin.code-workspace +++ b/vben-admin.code-workspace @@ -1,25 +1,9 @@ { "folders": [ - { - "name": "@vben/backend-mock", - "path": "apps/backend-mock", - }, { "name": "@vben/web-antd", "path": "apps/web-antd", }, - { - "name": "@vben/web-ele", - "path": "apps/web-ele", - }, - { - "name": "@vben/web-naive", - "path": "apps/web-naive", - }, - { - "name": "@vben/docs", - "path": "docs", - }, { "name": "@vben/commitlint-config", "path": "internal/lint-configs/commitlint-config",