Browse Source

feat: Improve the front-end and back-end permission mode and useAccess

vben 8 months ago
parent
commit
37a4f971c8
27 changed files with 522 additions and 79 deletions
  1. 1 1
      apps/backend-mock/package.json
  2. 10 0
      apps/backend-mock/src/modules/auth/auth.controller.ts
  3. 24 0
      apps/backend-mock/src/modules/auth/auth.service.ts
  4. 98 3
      apps/backend-mock/src/modules/menu/menu.controller.ts
  5. 8 1
      apps/web-antd/src/apis/modules/user.ts
  6. 3 13
      apps/web-antd/src/router/routes/modules/demos.ts
  7. 8 2
      apps/web-antd/src/store/modules/access.ts
  8. 13 0
      apps/web-antd/src/views/demos/access/backend/access-test-1.vue
  9. 13 0
      apps/web-antd/src/views/demos/access/backend/access-test-2.vue
  10. 13 0
      apps/web-antd/src/views/demos/access/backend/access-test-3.vue
  11. 112 3
      apps/web-antd/src/views/demos/access/backend/button-control.vue
  12. 95 3
      apps/web-antd/src/views/demos/access/backend/index.vue
  13. 1 1
      apps/web-antd/src/views/demos/access/frontend/access-test-1.vue
  14. 1 1
      apps/web-antd/src/views/demos/access/frontend/access-test-2.vue
  15. 1 1
      apps/web-antd/src/views/demos/access/frontend/access-test-3.vue
  16. 48 14
      apps/web-antd/src/views/demos/access/frontend/button-control.vue
  17. 0 6
      apps/web-antd/src/views/demos/access/frontend/index.vue
  18. 2 2
      package.json
  19. 3 3
      packages/@core/forward/preferences/src/types.ts
  20. 15 1
      packages/@core/forward/stores/src/modules/access.ts
  21. 5 5
      packages/business/access/src/code-authority.vue
  22. 1 0
      packages/business/access/src/generate-menu-and-routes/generate-routes-backend.ts
  23. 7 5
      packages/business/access/src/generate-menu-and-routes/index.ts
  24. 5 5
      packages/business/access/src/role-authority.vue
  25. 29 3
      packages/business/access/src/use-access.ts
  26. 3 3
      packages/locales/src/langs/en-US.yaml
  27. 3 3
      packages/locales/src/langs/zh-CN.yaml

+ 1 - 1
apps/backend-mock/package.json

@@ -36,7 +36,7 @@
     "@nestjs/cli": "^10.4.2",
     "@nestjs/schematics": "^10.1.2",
     "@types/express": "^4.17.21",
-    "@types/node": "^20.14.9",
+    "@types/node": "^20.14.10",
     "nodemon": "^3.1.4",
     "ts-node": "^10.9.2",
     "typescript": "^5.5.3"

+ 10 - 0
apps/backend-mock/src/modules/auth/auth.controller.ts

@@ -19,6 +19,16 @@ import { AuthService } from './auth.service';
 export class AuthController {
   constructor(private authService: AuthService) {}
 
+  /**
+   * 获取用户权限码
+   * @param req
+   */
+  @Get('getAccessCodes')
+  @HttpCode(HttpStatus.OK)
+  async getAccessCodes(@Request() req: Request) {
+    return await this.authService.getAccessCodes(req.user.username);
+  }
+
   /**
    * 获取用户信息
    * @param req

+ 24 - 0
apps/backend-mock/src/modules/auth/auth.service.ts

@@ -19,6 +19,30 @@ export class AuthService {
    * get user info
    * @param username
    */
+  async getAccessCodes(username: string): Promise<string[]> {
+    const user = await this.usersService.findOne(username);
+
+    const mockCodes = [
+      // super
+      {
+        codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
+        userId: 0,
+      },
+      {
+        // admin
+        codes: ['AC_100010', 'AC_100020', 'AC_100030'],
+        userId: 1,
+      },
+      {
+        // user
+        codes: ['AC_1000001', 'AC_1000002'],
+        userId: 2,
+      },
+    ];
+
+    return mockCodes.find((item) => item.userId === user.id)?.codes ?? [];
+  }
+
   async getUserInfo(username: string): Promise<Omit<UserEntity, 'password'>> {
     const user = await this.usersService.findOne(username);
     const { password: _pass, ...userInfo } = user;

+ 98 - 3
apps/backend-mock/src/modules/menu/menu.controller.ts

@@ -10,7 +10,7 @@ export class MenuController {
   @HttpCode(HttpStatus.OK)
   async getAll(@Request() req: Request) {
     // 模拟请求延迟
-    await sleep(1000);
+    await sleep(500);
     // 请求用户的id
     const userId = req.user.id;
 
@@ -46,15 +46,110 @@ export class MenuController {
         ],
       },
     ];
+
+    const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
+      const roleWithMenus = {
+        admin: {
+          component: '/demos/access/backend/access-test-2',
+          meta: {
+            icon: 'mdi:button-cursor',
+            title: 'page.demos.access.access-test-2',
+          },
+          name: 'AccessBackendTest2',
+          path: 'access-test-2',
+        },
+        super: {
+          component: '/demos/access/backend/access-test-1',
+          meta: {
+            icon: 'mdi:button-cursor',
+            title: 'page.demos.access.access-test-1',
+          },
+          name: 'AccessBackendTest1',
+          path: 'access-test-1',
+        },
+        user: {
+          component: '/demos/access/backend/access-test-3',
+          meta: {
+            icon: 'mdi:button-cursor',
+            title: 'page.demos.access.access-test-3',
+          },
+          name: 'AccessBackendTest3',
+          path: 'access-test-3',
+        },
+      };
+
+      return [
+        {
+          component: 'BasicLayout',
+          meta: {
+            icon: 'ic:baseline-view-in-ar',
+            keepAlive: true,
+            order: 1000,
+            title: 'page.demos.title',
+          },
+          name: 'Demos',
+          path: '/demos',
+          redirect: '/demos/access',
+          children: [
+            {
+              meta: {
+                icon: 'mdi:shield-key-outline',
+                title: 'page.demos.access.title',
+              },
+              name: 'Access',
+              path: 'access',
+              redirect: '/demos/access/backend',
+              children: [
+                {
+                  name: 'AccessBackend',
+                  path: 'backend',
+                  meta: {
+                    icon: 'mdi:cloud-key-outline',
+                    title: 'page.demos.access.backend-control',
+                  },
+                  redirect: '/demos/access/backend/page-control',
+                  children: [
+                    {
+                      name: 'AccessBackendPageControl',
+                      path: 'page-control',
+                      component: '/demos/access/backend/index',
+                      meta: {
+                        icon: 'mdi:page-previous-outline',
+                        title: 'page.demos.access.page',
+                      },
+                    },
+                    {
+                      name: 'AccessBackendButtonControl',
+                      path: 'button-control',
+                      component: '/demos/access/backend/button-control',
+                      meta: {
+                        icon: 'mdi:button-cursor',
+                        title: 'page.demos.access.button',
+                      },
+                    },
+                    roleWithMenus[role],
+                  ],
+                },
+              ],
+            },
+          ],
+        },
+      ];
+    };
+
     const MOCK_MENUS = [
       {
-        menus: [...dashboardMenus],
+        menus: [...dashboardMenus, ...createDemosMenus('super')],
         userId: 0,
       },
       {
-        menus: [...dashboardMenus],
+        menus: [...dashboardMenus, ...createDemosMenus('admin')],
         userId: 1,
       },
+      {
+        menus: [...dashboardMenus, ...createDemosMenus('user')],
+        userId: 2,
+      },
     ];
 
     return MOCK_MENUS.find((item) => item.userId === userId)?.menus ?? [];

+ 8 - 1
apps/web-antd/src/apis/modules/user.ts

@@ -18,4 +18,11 @@ async function getUserInfo() {
   return requestClient.get<UserInfo>('/auth/getUserInfo');
 }
 
-export { getUserInfo, userLogin };
+/**
+ * 获取用户权限码
+ */
+async function getAccessCodes() {
+  return requestClient.get<string[]>('/auth/getAccessCodes');
+}
+
+export { getAccessCodes, getUserInfo, userLogin };

+ 3 - 13
apps/web-antd/src/router/routes/modules/demos.ts

@@ -61,7 +61,7 @@ const routes: RouteRecordRaw[] = [
                 component: () =>
                   import('#/views/demos/access/frontend/access-test-1.vue'),
                 meta: {
-                  authority: ['admin'],
+                  authority: ['super'],
                   icon: 'mdi:button-cursor',
                   title: $t('page.demos.access.access-test-1'),
                 },
@@ -72,7 +72,7 @@ const routes: RouteRecordRaw[] = [
                 component: () =>
                   import('#/views/demos/access/frontend/access-test-2.vue'),
                 meta: {
-                  authority: ['user'],
+                  authority: ['admin'],
                   icon: 'mdi:button-cursor',
                   title: $t('page.demos.access.access-test-2'),
                 },
@@ -83,7 +83,7 @@ const routes: RouteRecordRaw[] = [
                 component: () =>
                   import('#/views/demos/access/frontend/access-test-3.vue'),
                 meta: {
-                  authority: ['super'],
+                  authority: ['user'],
                   icon: 'mdi:button-cursor',
                   title: $t('page.demos.access.access-test-3'),
                 },
@@ -109,16 +109,6 @@ const routes: RouteRecordRaw[] = [
                   title: $t('page.demos.access.page'),
                 },
               },
-              {
-                name: 'AccessBackendButtonControl',
-                path: 'button-control',
-                component: () =>
-                  import('#/views/demos/access/frontend/button-control.vue'),
-                meta: {
-                  icon: 'mdi:button-cursor',
-                  title: $t('page.demos.access.button'),
-                },
-              },
             ],
           },
         ],

+ 8 - 2
apps/web-antd/src/store/modules/access.ts

@@ -10,7 +10,7 @@ import { useCoreAccessStore } from '@vben-core/stores';
 
 import { defineStore } from 'pinia';
 
-import { getUserInfo, userLogin } from '#/apis';
+import { getAccessCodes, getUserInfo, userLogin } from '#/apis';
 
 export const useAccessStore = defineStore('access', () => {
   const coreStoreAccess = useCoreAccessStore();
@@ -55,9 +55,15 @@ export const useAccessStore = defineStore('access', () => {
 
         // 获取用户信息并存储到 accessStore 中
         // Get user information and store it in accessStore
-        userInfo = await fetchUserInfo();
+        const [fetchUserInfoResult, accessCodes] = await Promise.all([
+          fetchUserInfo(),
+          getAccessCodes(),
+        ]);
+
+        userInfo = fetchUserInfoResult;
 
         coreStoreAccess.setUserInfo(userInfo);
+        coreStoreAccess.setAccessCodes(accessCodes);
 
         onSuccess
           ? await onSuccess?.()

+ 13 - 0
apps/web-antd/src/views/demos/access/backend/access-test-1.vue

@@ -0,0 +1,13 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/universal-ui';
+
+defineOptions({ name: 'AccessFrontendAccessTest1' });
+</script>
+
+<template>
+  <Fallback
+    description="当前页面仅 Super 账号可见"
+    status="comming-soon"
+    title="页面访问测试"
+  />
+</template>

+ 13 - 0
apps/web-antd/src/views/demos/access/backend/access-test-2.vue

@@ -0,0 +1,13 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/universal-ui';
+
+defineOptions({ name: 'AccessFrontendAccessTest2' });
+</script>
+
+<template>
+  <Fallback
+    description="当前页面仅 Admin 账号可见"
+    status="comming-soon"
+    title="页面访问测试"
+  />
+</template>

+ 13 - 0
apps/web-antd/src/views/demos/access/backend/access-test-3.vue

@@ -0,0 +1,13 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/universal-ui';
+
+defineOptions({ name: 'AccessFrontendAccessTest1' });
+</script>
+
+<template>
+  <Fallback
+    description="当前页面仅 User 可见"
+    status="comming-soon"
+    title="页面访问测试"
+  />
+</template>

+ 112 - 3
apps/web-antd/src/views/demos/access/backend/button-control.vue

@@ -1,9 +1,118 @@
 <script lang="ts" setup>
-import { Fallback } from '@vben/universal-ui';
+import type { LoginAndRegisterParams } from '@vben/universal-ui';
 
-defineOptions({ name: 'AccessBackendButtonControl' });
+import { useRouter } from 'vue-router';
+
+import { CodeAuthority, useAccess } from '@vben/access';
+
+import { Button } from 'ant-design-vue';
+
+import { useAccessStore, useAppStore } from '#/store';
+
+defineOptions({ name: 'AccessBackend' });
+
+const accounts: Record<string, LoginAndRegisterParams> = {
+  admin: {
+    password: '123456',
+    username: 'admin',
+  },
+  super: {
+    password: '123456',
+    username: 'vben',
+  },
+  user: {
+    password: '123456',
+    username: 'jack',
+  },
+};
+
+const { accessMode, hasAuthByCodes } = useAccess();
+const accessStore = useAccessStore();
+const appStore = useAppStore();
+const router = useRouter();
+
+function roleButtonType(role: string) {
+  return accessStore.userRoles.includes(role) ? 'primary' : 'default';
+}
+
+async function changeAccount(role: string) {
+  if (accessStore.userRoles.includes(role)) {
+    return;
+  }
+
+  const account = accounts[role];
+  await appStore.resetAppState();
+  await accessStore.authLogin(account, async () => {
+    router.go(0);
+  });
+}
 </script>
 
 <template>
-  <Fallback status="comming-soon" />
+  <div class="p-5">
+    <div class="card-box p-5">
+      <h1 class="text-xl font-semibold">后端页面访问权限演示</h1>
+      <div class="text-foreground/80 mt-2">切换不同的账号,观察按钮变化。</div>
+    </div>
+
+    <template v-if="accessMode === 'backend'">
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3">
+          <span class="text-lg">当前账号:</span>
+          <span class="text-primary mx-4">
+            {{ accessStore.userRoles }}
+          </span>
+        </div>
+
+        <Button :type="roleButtonType('super')" @click="changeAccount('super')">
+          切换为 Super 账号
+        </Button>
+
+        <Button
+          :type="roleButtonType('admin')"
+          class="mx-4"
+          @click="changeAccount('admin')"
+        >
+          切换为 Admin 账号
+        </Button>
+        <Button :type="roleButtonType('user')" @click="changeAccount('user')">
+          切换为 User 账号
+        </Button>
+      </div>
+
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3 text-lg">组件形式控制</div>
+        <CodeAuthority :value="['AC_100100']">
+          <Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_100030']">
+          <Button class="mr-4"> Admin 账号可见 ["AC_100010"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_1000001']">
+          <Button class="mr-4"> User 账号可见 ["AC_1000001"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_100100', 'AC_100010']">
+          <Button class="mr-4">
+            Super & Admin 账号可见 ["AC_100100","AC_1000001"]
+          </Button>
+        </CodeAuthority>
+      </div>
+
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3 text-lg">函数形式控制</div>
+        <Button v-if="hasAuthByCodes(['AC_100100'])" class="mr-4">
+          Super 账号可见 ["AC_1000001"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_100030'])" class="mr-4">
+          Admin 账号可见 ["AC_100010"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_1000001'])" class="mr-4">
+          User 账号可见 ["AC_1000001"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_100100', 'AC_1000001'])" class="mr-4">
+          Super & Admin 账号可见 ["AC_100100","AC_1000001"]
+        </Button>
+      </div>
+    </template>
+  </div>
 </template>

+ 95 - 3
apps/web-antd/src/views/demos/access/backend/index.vue

@@ -1,9 +1,101 @@
 <script lang="ts" setup>
-import { Fallback } from '@vben/universal-ui';
+import type { LoginAndRegisterParams } from '@vben/universal-ui';
 
-defineOptions({ name: 'AccessFrontend' });
+import { useRouter } from 'vue-router';
+
+import { useAccess } from '@vben/access';
+
+import { Button } from 'ant-design-vue';
+
+import { useAccessStore, useAppStore } from '#/store';
+
+defineOptions({ name: 'AccessBackend' });
+
+const accounts: Record<string, LoginAndRegisterParams> = {
+  admin: {
+    password: '123456',
+    username: 'admin',
+  },
+  super: {
+    password: '123456',
+    username: 'vben',
+  },
+  user: {
+    password: '123456',
+    username: 'jack',
+  },
+};
+
+const { accessMode, toggleAccessMode } = useAccess();
+const accessStore = useAccessStore();
+const appStore = useAppStore();
+const router = useRouter();
+
+function roleButtonType(role: string) {
+  return accessStore.userRoles.includes(role) ? 'primary' : 'default';
+}
+
+async function changeAccount(role: string) {
+  if (accessStore.userRoles.includes(role)) {
+    return;
+  }
+
+  const account = accounts[role];
+  await appStore.resetAppState();
+  await accessStore.authLogin(account, async () => {
+    router.go(0);
+  });
+}
+
+async function handleToggleAccessMode() {
+  await toggleAccessMode();
+  await appStore.resetAppState();
+  await accessStore.authLogin(accounts.super, async () => {
+    router.go(0);
+  });
+}
 </script>
 
 <template>
-  <Fallback status="comming-soon" />
+  <div class="p-5">
+    <div class="card-box p-5">
+      <h1 class="text-xl font-semibold">后端页面访问权限演示</h1>
+      <div class="text-foreground/80 mt-2">
+        切换不同的账号,观察左侧菜单变化。
+      </div>
+    </div>
+
+    <div class="card-box mt-5 p-5 font-semibold">
+      <span class="text-lg">当前权限模式:</span>
+      <span class="text-primary mx-4">{{ accessMode }}</span>
+      <Button type="primary" @click="handleToggleAccessMode">
+        切换为{{ accessMode === 'frontend' ? '后端' : '前端' }}权限模式
+      </Button>
+    </div>
+    <template v-if="accessMode === 'backend'">
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3">
+          <span class="text-lg">当前账号:</span>
+          <span class="text-primary mx-4">
+            {{ accessStore.userRoles }}
+          </span>
+        </div>
+
+        <Button :type="roleButtonType('super')" @click="changeAccount('super')">
+          切换为 Super 账号
+        </Button>
+
+        <Button
+          :type="roleButtonType('admin')"
+          class="mx-4"
+          @click="changeAccount('admin')"
+        >
+          切换为 Admin 账号
+        </Button>
+        <Button :type="roleButtonType('user')" @click="changeAccount('user')">
+          切换为 User 账号
+        </Button>
+      </div>
+    </template>
+  </div>
 </template>

+ 1 - 1
apps/web-antd/src/views/demos/access/frontend/access-test-1.vue

@@ -6,7 +6,7 @@ defineOptions({ name: 'AccessFrontendAccessTest1' });
 
 <template>
   <Fallback
-    description="当前页面仅 Admin 角色可见"
+    description="当前页面仅 Super 角色可见"
     status="comming-soon"
     title="页面访问测试"
   />

+ 1 - 1
apps/web-antd/src/views/demos/access/frontend/access-test-2.vue

@@ -6,7 +6,7 @@ defineOptions({ name: 'AccessFrontendAccessTest2' });
 
 <template>
   <Fallback
-    description="当前页面仅 User 角色可见"
+    description="当前页面仅 Admin 角色可见"
     status="comming-soon"
     title="页面访问测试"
   />

+ 1 - 1
apps/web-antd/src/views/demos/access/frontend/access-test-3.vue

@@ -6,7 +6,7 @@ defineOptions({ name: 'AccessFrontendAccessTest1' });
 
 <template>
   <Fallback
-    description="当前页面仅 Super 角色可见"
+    description="当前页面仅 User 角色可见"
     status="comming-soon"
     title="页面访问测试"
   />

+ 48 - 14
apps/web-antd/src/views/demos/access/frontend/button-control.vue

@@ -3,7 +3,7 @@ import type { LoginAndRegisterParams } from '@vben/universal-ui';
 
 import { useRouter } from 'vue-router';
 
-import { RoleAuthority, useAccess } from '@vben/access';
+import { CodeAuthority, RoleAuthority, useAccess } from '@vben/access';
 
 import { Button } from 'ant-design-vue';
 
@@ -11,7 +11,7 @@ import { useAccessStore, useAppStore } from '#/store';
 
 defineOptions({ name: 'AccessFrontendButtonControl' });
 
-const { accessMode, hasAuthByRole } = useAccess();
+const { accessMode, hasAuthByCodes, hasAuthByRoles } = useAccess();
 const accessStore = useAccessStore();
 const appStore = useAppStore();
 const router = useRouter();
@@ -80,34 +80,68 @@ async function changeAccount(role: string) {
         </Button>
       </div>
       <div class="card-box mt-5 p-5 font-semibold">
-        <div class="mb-3 text-lg">组件形式控制</div>
-        <RoleAuthority :roles="['super']">
+        <div class="mb-3 text-lg">角色 - 组件形式控制</div>
+        <RoleAuthority :value="['super']">
           <Button class="mr-4"> Super 角色可见 </Button>
         </RoleAuthority>
-        <RoleAuthority :roles="['admin']">
+        <RoleAuthority :value="['admin']">
           <Button class="mr-4"> Admin 角色可见 </Button>
         </RoleAuthority>
-        <RoleAuthority :roles="['user']">
+        <RoleAuthority :value="['user']">
           <Button class="mr-4"> User 角色可见 </Button>
         </RoleAuthority>
-        <RoleAuthority :roles="['super', 'admin']">
-          <Button class="mr-4"> Super  Admin 角色都可见 </Button>
+        <RoleAuthority :value="['super', 'admin']">
+          <Button class="mr-4"> Super & Admin 角色都可见 </Button>
         </RoleAuthority>
       </div>
 
       <div class="card-box mt-5 p-5 font-semibold">
-        <div class="mb-3 text-lg">函数形式控制</div>
-        <Button v-if="hasAuthByRole(['super'])" class="mr-4">
+        <div class="mb-3 text-lg">角色 - 函数形式控制</div>
+        <Button v-if="hasAuthByRoles(['super'])" class="mr-4">
           Super 角色可见
         </Button>
-        <Button v-if="hasAuthByRole(['admin'])" class="mr-4">
+        <Button v-if="hasAuthByRoles(['admin'])" class="mr-4">
           Admin 角色可见
         </Button>
-        <Button v-if="hasAuthByRole(['user'])" class="mr-4">
+        <Button v-if="hasAuthByRoles(['user'])" class="mr-4">
           User 角色可见
         </Button>
-        <Button v-if="hasAuthByRole(['super', 'admin'])" class="mr-4">
-          Super 和 Admin 角色都可见
+        <Button v-if="hasAuthByRoles(['super', 'admin'])" class="mr-4">
+          Super & Admin 角色都可见
+        </Button>
+      </div>
+
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3 text-lg">权限码 - 组件形式控制</div>
+        <CodeAuthority :value="['AC_100100']">
+          <Button class="mr-4"> Super 账号可见 ["AC_1000001"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_100030']">
+          <Button class="mr-4"> Admin 账号可见 ["AC_100010"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_1000001']">
+          <Button class="mr-4"> User 账号可见 ["AC_1000001"] </Button>
+        </CodeAuthority>
+        <CodeAuthority :value="['AC_100100', 'AC_100010']">
+          <Button class="mr-4">
+            Super & Admin 账号可见 ["AC_100100","AC_1000001"]
+          </Button>
+        </CodeAuthority>
+      </div>
+
+      <div class="card-box mt-5 p-5 font-semibold">
+        <div class="mb-3 text-lg">权限码 - 函数形式控制</div>
+        <Button v-if="hasAuthByCodes(['AC_100100'])" class="mr-4">
+          Super 账号可见 ["AC_1000001"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_100030'])" class="mr-4">
+          Admin 账号可见 ["AC_100010"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_1000001'])" class="mr-4">
+          User 账号可见 ["AC_1000001"]
+        </Button>
+        <Button v-if="hasAuthByCodes(['AC_100100', 'AC_1000001'])" class="mr-4">
+          Super & Admin 账号可见 ["AC_100100","AC_1000001"]
         </Button>
       </div>
     </template>

+ 0 - 6
apps/web-antd/src/views/demos/access/frontend/index.vue

@@ -56,12 +56,6 @@ async function changeAccount(role: string) {
     </div>
 
     <template v-if="accessMode === 'frontend'">
-      <div class="card-box mt-5 p-5 font-semibold">
-        <span class="text-lg">当前权限模式:</span>
-        <span class="text-primary mx-4">{{ accessMode }}</span>
-        <Button type="primary">切换权限模式</Button>
-      </div>
-
       <div class="card-box mt-5 p-5 font-semibold">
         <div class="mb-3">
           <span class="text-lg">当前账号:</span>

+ 2 - 2
package.json

@@ -51,7 +51,7 @@
     "@changesets/cli": "^2.27.7",
     "@ls-lint/ls-lint": "^2.2.3",
     "@types/jsdom": "^21.1.7",
-    "@types/node": "^20.14.9",
+    "@types/node": "^20.14.10",
     "@vben/commitlint-config": "workspace:*",
     "@vben/eslint-config": "workspace:*",
     "@vben/lint-staged-config": "workspace:*",
@@ -63,7 +63,7 @@
     "@vben/vsh": "workspace:*",
     "@vue/test-utils": "^2.4.6",
     "cross-env": "^7.0.3",
-    "cspell": "^8.10.2",
+    "cspell": "^8.10.4",
     "husky": "^9.0.11",
     "is-ci": "^3.0.1",
     "jsdom": "^24.1.0",

+ 3 - 3
packages/@core/forward/preferences/src/types.ts

@@ -9,7 +9,7 @@ import type {
 
 type BreadcrumbStyleType = 'background' | 'normal';
 
-type accessModeType = 'allow-all' | 'backend' | 'frontend';
+type AccessModeType = 'allow-all' | 'backend' | 'frontend';
 
 type NavigationStyleType = 'plain' | 'rounded';
 
@@ -19,7 +19,7 @@ type AuthPageLayoutType = 'panel-center' | 'panel-left' | 'panel-right';
 
 interface AppPreferences {
   /** 权限模式 */
-  accessMode: accessModeType;
+  accessMode: AccessModeType;
   /** 是否开启vben助手 */
   aiAssistant: boolean;
   /** 登录注册页面布局 */
@@ -190,6 +190,7 @@ interface Preferences {
 type PreferencesKeys = keyof Preferences;
 
 export type {
+  AccessModeType,
   AppPreferences,
   AuthPageLayoutType,
   BreadcrumbPreferences,
@@ -212,5 +213,4 @@ export type {
   ThemeModeType,
   ThemePreferences,
   TransitionPreferences,
-  accessModeType,
 };

+ 15 - 1
packages/@core/forward/stores/src/modules/access.ts

@@ -29,6 +29,10 @@ interface BasicUserInfo {
 }
 
 interface AccessState {
+  /**
+   * 权限码
+   */
+  accessCodes: string[];
   /**
    * 可访问的菜单列表
    */
@@ -60,6 +64,9 @@ interface AccessState {
  */
 const useCoreAccessStore = defineStore('core-access', {
   actions: {
+    setAccessCodes(codes: string[]) {
+      this.accessCodes = codes;
+    },
     setAccessMenus(menus: MenuRecordRaw[]) {
       this.accessMenus = menus;
     },
@@ -84,6 +91,9 @@ const useCoreAccessStore = defineStore('core-access', {
     },
   },
   getters: {
+    getAccessCodes(): string[] {
+      return this.accessCodes;
+    },
     getAccessMenus(): MenuRecordRaw[] {
       return this.accessMenus;
     },
@@ -102,12 +112,16 @@ const useCoreAccessStore = defineStore('core-access', {
     getUserRoles(): string[] {
       return this.userRoles;
     },
+    string(): string[] {
+      return this.accessCodes;
+    },
   },
   persist: {
     // 持久化
-    paths: ['accessToken', 'refreshToken'],
+    paths: ['accessToken', 'refreshToken', 'accessCodes'],
   },
   state: (): AccessState => ({
+    accessCodes: [],
     accessMenus: [],
     accessRoutes: [],
     accessToken: null,

+ 5 - 5
packages/business/access/src/code-authority.vue

@@ -9,7 +9,7 @@ interface Props {
    * Specified codes is visible
    * @default []
    */
-  codes?: string[];
+  value?: string[];
 }
 
 defineOptions({
@@ -17,13 +17,13 @@ defineOptions({
 });
 
 withDefaults(defineProps<Props>(), {
-  codes: () => [],
+  value: () => [],
 });
 
-const { hasAuthByRole } = useAccess();
+const { hasAuthByCodes } = useAccess();
 </script>
 
 <template>
-  <slot v-if="!codes"></slot>
-  <slot v-else-if="hasAuthByRole(codes)"></slot>
+  <slot v-if="!value"></slot>
+  <slot v-else-if="hasAuthByCodes(value)"></slot>
 </template>

+ 1 - 0
packages/business/access/src/generate-menu-and-routes/generate-routes-backend.ts

@@ -30,6 +30,7 @@ async function generateRoutesByBackend(
     }
 
     const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap);
+
     return routes;
   } catch (error) {
     console.error(error);

+ 7 - 5
packages/business/access/src/generate-menu-and-routes/index.ts

@@ -1,4 +1,4 @@
-import type { accessModeType } from '@vben-core/preferences';
+import type { AccessModeType } from '@vben-core/preferences';
 import type { RouteRecordRaw } from 'vue-router';
 
 import type { GeneratorMenuAndRoutesOptions } from '../types';
@@ -10,7 +10,7 @@ import { generateRoutesByBackend } from './generate-routes-backend';
 import { generateRoutesByFrontend } from './generate-routes-frontend';
 
 async function generateMenusAndRoutes(
-  mode: accessModeType,
+  mode: AccessModeType,
   options: GeneratorMenuAndRoutesOptions,
 ) {
   const { router } = options;
@@ -20,7 +20,9 @@ async function generateMenusAndRoutes(
   const accessibleRoutes = await generateRoutes(mode, options);
 
   // 动态添加到router实例内
-  accessibleRoutes.forEach((route) => router.addRoute(route));
+  accessibleRoutes.forEach((route) => {
+    router.addRoute(route);
+  });
 
   // 生成菜单
   const accessibleMenus = await generateMenus1(mode, accessibleRoutes, options);
@@ -33,7 +35,7 @@ async function generateMenusAndRoutes(
  * @param mode
  */
 async function generateRoutes(
-  mode: accessModeType,
+  mode: AccessModeType,
   options: GeneratorMenuAndRoutesOptions,
 ) {
   const { forbiddenComponent, roles, routes } = options;
@@ -60,7 +62,7 @@ async function generateRoutes(
 }
 
 async function generateMenus1(
-  mode: accessModeType,
+  mode: AccessModeType,
   routes: RouteRecordRaw[],
   options: GeneratorMenuAndRoutesOptions,
 ) {

+ 5 - 5
packages/business/access/src/role-authority.vue

@@ -9,7 +9,7 @@ interface Props {
    * Specified role is visible
    * @default []
    */
-  roles?: string[];
+  value?: string[];
 }
 
 defineOptions({
@@ -17,13 +17,13 @@ defineOptions({
 });
 
 withDefaults(defineProps<Props>(), {
-  roles: undefined,
+  value: undefined,
 });
 
-const { hasAuthByRole } = useAccess();
+const { hasAuthByRoles } = useAccess();
 </script>
 
 <template>
-  <slot v-if="!roles"></slot>
-  <slot v-else-if="hasAuthByRole(roles)"></slot>
+  <slot v-if="!value"></slot>
+  <slot v-else-if="hasAuthByRoles(value)"></slot>
 </template>

+ 29 - 3
packages/business/access/src/use-access.ts

@@ -1,6 +1,6 @@
 import { computed } from 'vue';
 
-import { preferences } from '@vben-core/preferences';
+import { preferences, updatePreferences } from '@vben-core/preferences';
 import { useCoreAccessStore } from '@vben-core/stores';
 
 function useAccess() {
@@ -14,13 +14,39 @@ function useAccess() {
    * @description: Determine whether there is permission,The role is judged by the user's role
    * @param roles
    */
-  function hasAuthByRole(roles: string[]) {
+  function hasAuthByRoles(roles: string[]) {
     const userRoleSet = new Set(coreAccessStore.getUserRoles);
     const intersection = roles.filter((item) => userRoleSet.has(item));
     return intersection.length > 0;
   }
 
-  return { accessMode, hasAuthByRole };
+  /**
+   * 基于权限码判断是否有权限
+   * @description: Determine whether there is permission,The permission code is judged by the user's permission code
+   * @param codes
+   */
+  function hasAuthByCodes(codes: string[]) {
+    const userCodesSet = new Set(coreAccessStore.getAccessCodes);
+
+    const intersection = codes.filter((item) => userCodesSet.has(item));
+    return intersection.length > 0;
+  }
+
+  async function toggleAccessMode() {
+    updatePreferences({
+      app: {
+        accessMode:
+          preferences.app.accessMode === 'frontend' ? 'backend' : 'frontend',
+      },
+    });
+  }
+
+  return {
+    accessMode,
+    hasAuthByCodes,
+    hasAuthByRoles,
+    toggleAccessMode,
+  };
 }
 
 export { useAccess };

+ 3 - 3
packages/locales/src/langs/en-US.yaml

@@ -9,9 +9,9 @@ page:
       page: Page visit
       button: Button control
       loading-menu: In the loading menu
-      access-test-1: Admin visit
-      access-test-2: User visit
-      access-test-3: Super visit
+      access-test-1: Super visit
+      access-test-2: Admin visit
+      access-test-3: User visit
     nested:
       title: Nested Menu
       menu1: Menu 1

+ 3 - 3
packages/locales/src/langs/zh-CN.yaml

@@ -8,9 +8,9 @@ page:
       backend-control: 后端控制
       page: 页面访问
       button: 按钮控制
-      access-test-1: Admin 角色可见
-      access-test-2: User 角色可见
-      access-test-3: Super 角色可见
+      access-test-1: Super 可见
+      access-test-2: Admin 可见
+      access-test-3: User 可见
 
     nested:
       title: 嵌套菜单