From 5cc74ac42c2b4558eaac5b900ee81d826afdefa4 Mon Sep 17 00:00:00 2001 From: hahwu <31872165+hahwu@users.noreply.github.com> Date: Thu, 7 May 2026 14:39:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86=E3=80=81ap?= =?UTF-8?q?k=E5=8C=85=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/api/core/admin.user.ts | 8 + apps/web-antd/src/api/core/apk.ts | 19 ++ apps/web-antd/src/api/core/permission.ts | 4 + .../src/locales/langs/en-US/page.json | 6 +- .../src/locales/langs/zh-CN/page.json | 6 +- apps/web-antd/src/model/admin.user.ts | 12 +- apps/web-antd/src/router/guard.ts | 18 +- .../src/router/routes/modules/admin.ts | 16 +- .../src/router/routes/modules/dashboard.ts | 49 +-- .../src/router/routes/modules/devops.ts | 66 ++++ .../src/router/routes/modules/experiment.ts | 4 +- .../src/router/routes/modules/language.ts | 2 + .../src/router/routes/modules/notification.ts | 4 +- .../src/router/routes/modules/operation.ts | 19 +- .../src/router/routes/modules/userlog.ts | 3 +- apps/web-antd/src/store/auth.ts | 17 +- .../src/views/admin/config/config-table.vue | 2 +- .../src/views/admin/log/log-table.vue | 2 +- .../src/views/admin/permission/permission.vue | 3 +- .../src/views/admin/permission/role.vue | 2 +- .../views/admin/permission/user-assign.vue | 40 ++- .../src/views/admin/permission/user-group.vue | 2 +- .../src/views/admin/user/user-table.vue | 291 +++++++++++++++--- .../dashboard/analytics/analytics-trends.vue | 19 +- .../dashboard/analytics/analytics-visits.vue | 15 +- .../src/views/dashboard/analytics/index.vue | 51 ++- .../operation/activity/activity-table.vue | 5 +- .../src/views/operation/apk/index.vue | 182 +++++++++++ .../src/views/userlog/userlist/user.vue | 22 +- packages/types/src/user.ts | 5 + 30 files changed, 709 insertions(+), 185 deletions(-) create mode 100644 apps/web-antd/src/api/core/apk.ts create mode 100644 apps/web-antd/src/router/routes/modules/devops.ts create mode 100644 apps/web-antd/src/views/operation/apk/index.vue diff --git a/apps/web-antd/src/api/core/admin.user.ts b/apps/web-antd/src/api/core/admin.user.ts index 4b40a85..7812d62 100644 --- a/apps/web-antd/src/api/core/admin.user.ts +++ b/apps/web-antd/src/api/core/admin.user.ts @@ -40,6 +40,14 @@ export async function addAdminApi(param: UserInfo) { return requestClient.post('/admin/add', param); } +export async function editAdminApi(param: UserInfo) { + return requestClient.post('/admin/edit', param); +} + +export async function deleteAdminApi(id: number) { + return requestClient.post('/admin/delete', { id }); +} + export async function getAdminConfigList(param:AdminConfigListParam) { return requestClient.post('/admin/config/list', param); } diff --git a/apps/web-antd/src/api/core/apk.ts b/apps/web-antd/src/api/core/apk.ts new file mode 100644 index 0000000..0a3fca9 --- /dev/null +++ b/apps/web-antd/src/api/core/apk.ts @@ -0,0 +1,19 @@ +import { requestClient } from '#/api/request'; + +export namespace ApkApi { + export type Environment = 'dev' | 'prod' | 'stable'; + + export interface PackageItem { + downloadPath: string; + env: Environment; + exists: boolean; + fileName: string; + size: number; + uploadedAt: string; + version: string; + } +} + +export async function getApkPackagesApi() { + return requestClient.get('/apk/packages'); +} \ No newline at end of file diff --git a/apps/web-antd/src/api/core/permission.ts b/apps/web-antd/src/api/core/permission.ts index 7ffba84..0cd85a8 100644 --- a/apps/web-antd/src/api/core/permission.ts +++ b/apps/web-antd/src/api/core/permission.ts @@ -209,3 +209,7 @@ export async function setUserPermissionsDirectApi(params: PermissionApi.UserPerm export async function getUserRolesApi(admin_id: number) { return requestClient.post('/admin/user/role/list', { admin_id }); } + +export async function getUserRolesBatchApi(admin_ids: number[]) { + return requestClient.post>('/admin/user/role/batch-list', { admin_ids }); +} diff --git a/apps/web-antd/src/locales/langs/en-US/page.json b/apps/web-antd/src/locales/langs/en-US/page.json index 17ec06e..cf7c980 100644 --- a/apps/web-antd/src/locales/langs/en-US/page.json +++ b/apps/web-antd/src/locales/langs/en-US/page.json @@ -41,7 +41,10 @@ }, "dashboard": { "title": "Dashboard", - "analytics": "Analytics", + "analytics": "Analytics" + }, + "devops": { + "title": "DevOps", "server-list": "Server List", "node-list": "Node List", "mysql-list": "MySQL List", @@ -65,6 +68,7 @@ }, "operation": { "title": "Operation", + "apk": "Client APK", "level": "Level", "mail": "Mail", "order": "Order", diff --git a/apps/web-antd/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json index db9e6b4..e57b9e1 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/page.json +++ b/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -40,8 +40,11 @@ } }, "dashboard": { + "title": "工作台", + "analytics": "分析台" + }, + "devops": { "title": "运维管理", - "analytics": "分析台", "server-list": "区服列表", "node-list": "节点列表", "mysql-list": "MySQL列表", @@ -65,6 +68,7 @@ }, "operation": { "title": "运营管理", + "apk": "客户端 APK 下载", "scripts": "自动化脚本", "mail": "邮件管理", "order": "订单管理", diff --git a/apps/web-antd/src/model/admin.user.ts b/apps/web-antd/src/model/admin.user.ts index 7b82c6b..4d1929c 100644 --- a/apps/web-antd/src/model/admin.user.ts +++ b/apps/web-antd/src/model/admin.user.ts @@ -2,12 +2,18 @@ export interface UserInfo { id?: number; username: string; password?: string; - phone: string; + real_name?: string; + nickname?: string; + phone?: string; email?: string; avatar?: string; - uid?: string; - group: string; + group?: string; role: number; + status?: number; + lastLoginTime?: number; + lastLoginIp?: string; + createTime?: number; + updateTime?: number; remark?: string; } diff --git a/apps/web-antd/src/router/guard.ts b/apps/web-antd/src/router/guard.ts index 969dd58..4d1b1ec 100644 --- a/apps/web-antd/src/router/guard.ts +++ b/apps/web-antd/src/router/guard.ts @@ -50,13 +50,24 @@ function setupAccessGuard(router: Router) { const userStore = useUserStore(); const authStore = useAuthStore(); + const getResolvedHomePath = async () => { + const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); + return userInfo.homePath || DEFAULT_HOME_PATH; + }; + // 基本路由,这些路由不需要进入权限拦截 if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { + const homePath = await getResolvedHomePath(); return decodeURIComponent( - (to.query?.redirect as string) || DEFAULT_HOME_PATH, + (to.query?.redirect as string) || homePath, ); } + + if (to.path === '/' && accessStore.accessToken) { + return await getResolvedHomePath(); + } + return true; } @@ -89,10 +100,13 @@ function setupAccessGuard(router: Router) { // 当前登录用户拥有的角色标识列表 const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); const userRoles = userInfo.roles ?? []; + // 将单点权限码合并进 roles,供路由 authority 字段做精细匹配 + const userPermissions: string[] = (userInfo as any).permissions ?? []; + const accessIdentifiers = [...new Set([...userRoles, ...userPermissions])]; // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ - roles: userRoles, + roles: accessIdentifiers, router, // 则会在菜单中显示,但是访问会被重定向到403 routes: accessRoutes, diff --git a/apps/web-antd/src/router/routes/modules/admin.ts b/apps/web-antd/src/router/routes/modules/admin.ts index 5bb1fec..2224cb4 100644 --- a/apps/web-antd/src/router/routes/modules/admin.ts +++ b/apps/web-antd/src/router/routes/modules/admin.ts @@ -10,7 +10,7 @@ const routes: RouteRecordRaw[] = [ icon: 'material-symbols:brightness-empty-outline', order: -1, title: $t('page.admin.title'), - authority:['super'], + authority: ['super', 'AC1001', 'AC1101', 'AC1107', 'AC1113', 'AC1117'], }, name: 'Admin', path: '/admin', @@ -20,7 +20,7 @@ const routes: RouteRecordRaw[] = [ path: '/user-management', component: () => import('#/views/admin/user/index.vue'), meta: { - authority: ['super', 'admin'], + authority: ['super', 'AC1001', 'AC1002'], affixTab: false, icon: 'majesticons:user-box-line', title: $t('page.admin.user'), @@ -31,7 +31,7 @@ const routes: RouteRecordRaw[] = [ path: '/user-management-log', component: () => import('#/views/admin/log/index.vue'), meta: { - authority: ['super', 'admin'], + authority: ['super', 'AC1003'], affixTab: false, icon: 'material-symbols:assignment-rounded', title: $t('page.admin.log'), @@ -42,7 +42,7 @@ const routes: RouteRecordRaw[] = [ path: '/user-management-config', component: () => import('#/views/admin/config/index.vue'), meta: { - authority: ['super', 'admin'], + authority: ['super', 'AC1004', 'AC1005', 'AC1006'], affixTab: false, icon: 'material-symbols:assignment-rounded', title: $t('page.admin.config'), @@ -53,7 +53,7 @@ const routes: RouteRecordRaw[] = [ path: '/permission/user-group', component: () => import('#/views/admin/permission/user-group.vue'), meta: { - authority: ['super'], + authority: ['super', 'AC1101', 'AC1102', 'AC1103', 'AC1104', 'AC1105', 'AC1106'], affixTab: false, icon: 'material-symbols:group', title: $t('page.admin.permission.userGroup'), @@ -64,7 +64,7 @@ const routes: RouteRecordRaw[] = [ path: '/permission/role', component: () => import('#/views/admin/permission/role.vue'), meta: { - authority: ['super'], + authority: ['super', 'AC1107', 'AC1108', 'AC1109', 'AC1110', 'AC1111', 'AC1112'], affixTab: false, icon: 'material-symbols:shield-person', title: $t('page.admin.permission.role'), @@ -75,7 +75,7 @@ const routes: RouteRecordRaw[] = [ path: '/permission/permission', component: () => import('#/views/admin/permission/permission.vue'), meta: { - authority: ['super'], + authority: ['super', 'AC1113', 'AC1114', 'AC1115', 'AC1116'], affixTab: false, icon: 'material-symbols:key', title: $t('page.admin.permission.permission'), @@ -86,7 +86,7 @@ const routes: RouteRecordRaw[] = [ path: '/permission/user-assign', component: () => import('#/views/admin/permission/user-assign.vue'), meta: { - authority: ['super'], + authority: ['super', 'AC1117', 'AC1118', 'AC1119', 'AC1120', 'AC1121'], affixTab: false, icon: 'material-symbols:manage-accounts', title: $t('page.admin.permission.userAssign'), diff --git a/apps/web-antd/src/router/routes/modules/dashboard.ts b/apps/web-antd/src/router/routes/modules/dashboard.ts index ba2623f..2baecd1 100644 --- a/apps/web-antd/src/router/routes/modules/dashboard.ts +++ b/apps/web-antd/src/router/routes/modules/dashboard.ts @@ -7,6 +7,7 @@ const routes: RouteRecordRaw[] = [ { component: BasicLayout, meta: { + authority: ['super', 'AC5003', 'AC5004'], icon: 'lucide:layout-dashboard', order: -1, title: $t('page.dashboard.title'), @@ -19,56 +20,14 @@ const routes: RouteRecordRaw[] = [ path: '/analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { - affixTab: false, + affixTab: true, + authority: ['super', 'AC5003', 'AC5004'], icon: 'lucide:area-chart', title: $t('page.dashboard.analytics'), }, }, - { - name: 'ServerList', - path: '/server-list', - component: () => import('#/views/dashboard/serverList/index.vue'), - meta: { - affixTab: false, - icon: 'lucide:gamepad', - title: $t('page.dashboard.server-list'), - authority: ['super', 'admin'], - }, - }, - { - name: 'AppList', - path: '/app-list', - component: () => import('#/views/dashboard/appList/index.vue'), - meta: { - affixTab: false, - icon: 'lucide:app-window', - title: $t('page.dashboard.app-list'), - authority: ['super'], - }, - }, - { - name: 'NodeList', - path: '/node-list', - component: () => import('#/views/dashboard/nodeList/index.vue'), - meta: { - affixTab: false, - icon: 'lucide:server', - title: $t('page.dashboard.node-list'), - authority: ['super'], - }, - }, - { - name: 'MysqlList', - path: '/mysql-list', - component: () => import('#/views/dashboard/mysqlList/index.vue'), - meta: { - affixTab: false, - icon: 'lucide:database', - title: $t('page.dashboard.mysql-list'), - authority: ['super'], - }, - }, ], + redirect: '/analytics', }, ]; diff --git a/apps/web-antd/src/router/routes/modules/devops.ts b/apps/web-antd/src/router/routes/modules/devops.ts new file mode 100644 index 0000000..87ec1aa --- /dev/null +++ b/apps/web-antd/src/router/routes/modules/devops.ts @@ -0,0 +1,66 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { BasicLayout } from '#/layouts'; +import { $t } from '#/locales'; + +const routes: RouteRecordRaw[] = [ + { + component: BasicLayout, + meta: { + icon: 'lucide:monitor-cog', + order: 0, + title: $t('page.devops.title'), + authority: ['super', 'AC4004', 'AC4006', 'AC4007', 'AC4008', 'AC4009', 'AC4010', 'AC4011'], + }, + name: 'Devops', + path: '/devops', + children: [ + { + name: 'ServerList', + path: '/server-list', + component: () => import('#/views/dashboard/serverList/index.vue'), + meta: { + affixTab: false, + icon: 'lucide:gamepad', + title: $t('page.devops.server-list'), + authority: ['super', 'AC4006', 'AC4010', 'AC4011'], + }, + }, + { + name: 'AppList', + path: '/app-list', + component: () => import('#/views/dashboard/appList/index.vue'), + meta: { + affixTab: false, + icon: 'lucide:app-window', + title: $t('page.devops.app-list'), + authority: ['super', 'AC4007', 'AC4008', 'AC4009'], + }, + }, + { + name: 'NodeList', + path: '/node-list', + component: () => import('#/views/dashboard/nodeList/index.vue'), + meta: { + affixTab: false, + icon: 'lucide:server', + title: $t('page.devops.node-list'), + authority: ['super', 'AC4004', 'AC4005'], + }, + }, + { + name: 'MysqlList', + path: '/mysql-list', + component: () => import('#/views/dashboard/mysqlList/index.vue'), + meta: { + affixTab: false, + icon: 'lucide:database', + title: $t('page.devops.mysql-list'), + authority: ['super'], + }, + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-antd/src/router/routes/modules/experiment.ts b/apps/web-antd/src/router/routes/modules/experiment.ts index f09fb1a..bd07873 100644 --- a/apps/web-antd/src/router/routes/modules/experiment.ts +++ b/apps/web-antd/src/router/routes/modules/experiment.ts @@ -10,7 +10,7 @@ const routes: RouteRecordRaw[] = [ icon: 'lucide:layout-dashboard', order: -1, title: $t('page.experiment.title'), - authority: ['super', 'admin'], + authority: ['super', 'AC9201', 'AC9202', 'AC9214'], }, name: 'Experiment', path: '/experiment', @@ -23,6 +23,7 @@ const routes: RouteRecordRaw[] = [ affixTab: false, icon: 'lucide:flask-conical', title: $t('page.experiment.abtest'), + authority: ['super', 'AC9201', 'AC9202', 'AC9203', 'AC9204', 'AC9205', 'AC9206', 'AC9207', 'AC9208', 'AC9209', 'AC9210', 'AC9211', 'AC9212', 'AC9213'], }, }, { @@ -33,6 +34,7 @@ const routes: RouteRecordRaw[] = [ affixTab: false, icon: 'lucide:users', title: $t('page.experiment.groupQuery'), + authority: ['super', 'AC9214'], }, }, ], diff --git a/apps/web-antd/src/router/routes/modules/language.ts b/apps/web-antd/src/router/routes/modules/language.ts index 50eaf7d..26be1ff 100644 --- a/apps/web-antd/src/router/routes/modules/language.ts +++ b/apps/web-antd/src/router/routes/modules/language.ts @@ -10,6 +10,7 @@ const routes: RouteRecordRaw[] = [ icon: 'solar:book-2-bold', order: 1001, title: $t('page.language.title'), + authority: ['super', 'AC9001', 'AC9002', 'AC9003', 'AC9004', 'AC9005'], }, name: 'Translate', path: '/translate', @@ -22,6 +23,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lets-icons:order', title: $t('page.language.translationList'), + authority: ['super', 'AC9001', 'AC9002', 'AC9003', 'AC9004', 'AC9005'], }, } diff --git a/apps/web-antd/src/router/routes/modules/notification.ts b/apps/web-antd/src/router/routes/modules/notification.ts index a97c7e0..46d3696 100644 --- a/apps/web-antd/src/router/routes/modules/notification.ts +++ b/apps/web-antd/src/router/routes/modules/notification.ts @@ -10,7 +10,7 @@ const routes: RouteRecordRaw[] = [ icon: 'lucide:bell-ring', order: 1002, title: $t('page.notification.title'), - authority: ['super', 'admin'], + authority: ['super', 'AC9101', 'AC9102'], }, name: 'Notification', path: '/notification', @@ -23,7 +23,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lucide:bell-plus', title: $t('page.notification.config'), - authority: ['super', 'admin'], + authority: ['super', 'AC9101', 'AC9102'], }, }, ], diff --git a/apps/web-antd/src/router/routes/modules/operation.ts b/apps/web-antd/src/router/routes/modules/operation.ts index b56cd7f..8534458 100644 --- a/apps/web-antd/src/router/routes/modules/operation.ts +++ b/apps/web-antd/src/router/routes/modules/operation.ts @@ -10,11 +10,22 @@ const routes: RouteRecordRaw[] = [ icon: 'lucide:file-clock', order: 1001, title: $t('page.operation.title'), - authority: ['super', 'admin'], + authority: ['super', 'AC6001', 'AC7001', 'AC8001', 'AC9301', 'AC5002','AC9401'], }, name: 'Operation', path: '/operation', children: [ + { + name: 'ApkDownload', + path: '/apk-download', + component: () => import('#/views/operation/apk/index.vue'), + meta: { + affixTab: true, + icon: 'material-symbols:cloud-download', + title: $t('page.operation.apk'), + authority: ['super', 'AC9401'], + }, + }, { name: 'Scripts', path: '/scripts', @@ -23,7 +34,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lucide:chart-no-axes-column-increasing', title: $t('page.operation.scripts'), - authority: ['super', 'admin'], + authority: ['super', 'AC9301', 'AC9302', 'AC9303'], }, }, { @@ -34,6 +45,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lucide:mail', title: $t('page.operation.mail'), + authority: ['super', 'AC7001', 'AC7002', 'AC7003'], }, }, { @@ -44,6 +56,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lucide:mail', title: $t('page.operation.copyUser'), + authority: ['super', 'AC8001'], }, }, { @@ -54,6 +67,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lets-icons:order', title: $t('page.operation.order'), + authority: ['super', 'AC5002'], }, }, { @@ -64,6 +78,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lets-icons:order', title: $t('page.operation.activity'), + authority: ['super', 'AC6001', 'AC6002', 'AC6003', 'AC6004', 'AC6005'], }, } ], diff --git a/apps/web-antd/src/router/routes/modules/userlog.ts b/apps/web-antd/src/router/routes/modules/userlog.ts index 301a336..2f65adb 100644 --- a/apps/web-antd/src/router/routes/modules/userlog.ts +++ b/apps/web-antd/src/router/routes/modules/userlog.ts @@ -10,7 +10,7 @@ const routes: RouteRecordRaw[] = [ icon: 'lucide:laugh', order: 1000, title: $t('page.userlog.title'), - authority: ['super', 'admin'], + authority: ['super', 'AC2001', 'AC2002', 'AC2003', 'AC2004', 'AC2005', 'AC3001', 'AC3003', 'AC3004'], }, name: 'Userlog', path: '/userlog', @@ -23,6 +23,7 @@ const routes: RouteRecordRaw[] = [ affixTab: true, icon: 'lucide:list', title: $t('page.userlog.userlist'), + authority: ['super', 'AC3001', 'AC3002', 'AC3003', 'AC3004'], }, } ], diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index 8b45477..bf55505 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -46,7 +46,10 @@ export const useAuthStore = defineStore('auth', () => { userInfo = fetchUserInfoResult; userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); + const mergedAccessCodes = [ + ...new Set([...(accessCodes || []), ...(((userInfo as any)?.permissions || []) as string[])]), + ]; + accessStore.setAccessCodes(mergedAccessCodes); if (accessStore.loginExpired) { accessStore.setLoginExpired(false); @@ -96,7 +99,10 @@ export const useAuthStore = defineStore('auth', () => { userInfo = fetchUserInfoResult; userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); + const mergedAccessCodes = [ + ...new Set([...(accessCodes || []), ...(((userInfo as any)?.permissions || []) as string[])]), + ]; + accessStore.setAccessCodes(mergedAccessCodes); if (accessStore.loginExpired) { accessStore.setLoginExpired(false); @@ -147,6 +153,13 @@ export const useAuthStore = defineStore('auth', () => { let userInfo: null | UserInfo = null; userInfo = await getUserInfoApi(); userStore.setUserInfo(userInfo); + const mergedAccessCodes = [ + ...new Set([ + ...(accessStore.accessCodes || []), + ...(((userInfo as any)?.permissions || []) as string[]), + ]), + ]; + accessStore.setAccessCodes(mergedAccessCodes); return userInfo; } diff --git a/apps/web-antd/src/views/admin/config/config-table.vue b/apps/web-antd/src/views/admin/config/config-table.vue index 6f2a71e..e1b4520 100644 --- a/apps/web-antd/src/views/admin/config/config-table.vue +++ b/apps/web-antd/src/views/admin/config/config-table.vue @@ -15,7 +15,7 @@ const gridOptions: VxeGridProps = { { field: 'remark', title: '备注', }, {title: '操作',width: 200,fixed: 'right',slots: {default:"operation"},}, ], - height: 'auto', + minHeight: 650, pagerConfig: {}, proxyConfig: { response: { diff --git a/apps/web-antd/src/views/admin/log/log-table.vue b/apps/web-antd/src/views/admin/log/log-table.vue index 2a21b08..ffc4066 100644 --- a/apps/web-antd/src/views/admin/log/log-table.vue +++ b/apps/web-antd/src/views/admin/log/log-table.vue @@ -44,7 +44,7 @@ const gridOptions: VxeGridProps = { field: 'createTime', title: '时间', formatter: ({ cellValue }) => formatUTC8Time(cellValue), slots: { header: 'time_header' } }, ], - height: 'auto', + minHeight: 650, pagerConfig: { pageSize: 100, }, diff --git a/apps/web-antd/src/views/admin/permission/permission.vue b/apps/web-antd/src/views/admin/permission/permission.vue index 665fe76..c3afaba 100644 --- a/apps/web-antd/src/views/admin/permission/permission.vue +++ b/apps/web-antd/src/views/admin/permission/permission.vue @@ -139,7 +139,8 @@ const gridOptions: VxeGridProps = { }, { fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 160 }, ], - height: 'auto', + minHeight: 650, + autoResize: true, pagerConfig: {}, proxyConfig: { response: { result: 'items', total: 'total' }, diff --git a/apps/web-antd/src/views/admin/permission/role.vue b/apps/web-antd/src/views/admin/permission/role.vue index 37e41dd..23da911 100644 --- a/apps/web-antd/src/views/admin/permission/role.vue +++ b/apps/web-antd/src/views/admin/permission/role.vue @@ -120,7 +120,7 @@ const gridOptions: VxeGridProps = { { field: 'remark', title: '备注', minWidth: 200 }, { fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 160 }, ], - height: 'auto', + minHeight: 650, pagerConfig: {}, proxyConfig: { response: { result: 'items', total: 'total' }, diff --git a/apps/web-antd/src/views/admin/permission/user-assign.vue b/apps/web-antd/src/views/admin/permission/user-assign.vue index 686af98..ef8173e 100644 --- a/apps/web-antd/src/views/admin/permission/user-assign.vue +++ b/apps/web-antd/src/views/admin/permission/user-assign.vue @@ -8,6 +8,7 @@ import { getUserGroupsApi, setUserGroupsApi, getUserRolesApi, + getUserRolesBatchApi, getPermissionListApi, getUserPermissionsDirectApi, setUserPermissionsDirectApi, @@ -18,9 +19,10 @@ import { Button, Card, Space, Tag, Spin, message, Modal, Tabs, TabPane, Transfer, } from 'ant-design-vue'; -import { ref, computed } from 'vue'; +import { computed, onActivated, ref } from 'vue'; // ─── 行级缓存(用户组、角色)───────────────────────────── +const currentUsers = ref([]); const userGroupsMap = ref>(new Map()); const userRolesMap = ref>(new Map()); @@ -28,18 +30,27 @@ async function loadRowDataForList(users: UserInfo[]) { const ids = users.map((u) => u.id).filter((id): id is number => id != null); const [groupResults, roleResults] = await Promise.all([ Promise.allSettled(ids.map((id) => getUserGroupsApi(id))), - Promise.allSettled(ids.map((id) => getUserRolesApi(id))), + getUserRolesBatchApi(ids), ]); const gMap = new Map(); const rMap = new Map(); ids.forEach((id, i) => { if (groupResults[i]!.status === 'fulfilled') gMap.set(id, (groupResults[i] as any).value || []); - if (roleResults[i]!.status === 'fulfilled') rMap.set(id, (roleResults[i] as any).value || []); + rMap.set(id, roleResults?.[String(id)] || []); }); userGroupsMap.value = gMap; userRolesMap.value = rMap; } +async function refreshUserRelationCache(adminId: number) { + const [groups, roles] = await Promise.all([ + getUserGroupsApi(adminId), + getUserRolesApi(adminId), + ]); + userGroupsMap.value = new Map(userGroupsMap.value).set(adminId, groups || []); + userRolesMap.value = new Map(userRolesMap.value).set(adminId, roles || []); +} + // ─── 分配弹窗 ───────────────────────────────────────────── const modalOpen = ref(false); const modalLoading = ref(false); @@ -82,15 +93,19 @@ async function openAssignModal(row: UserInfo) { modalLoading.value = true; modalOpen.value = true; try { - const [groupsRes, permsRes, userGroups, userDirectPerms] = await Promise.all([ + const [groupsRes, permsRes, userGroups, userDirectPerms, userRoles] = await Promise.all([ getUserGroupListApi({ page: 1, pageSize: 500 }), getPermissionListApi({ page: 1, pageSize: 500 }), getUserGroupsApi(row.id), getUserPermissionsDirectApi(row.id), + getUserRolesApi(row.id), ]); + console.log('userGroups', userGroups); allGroups.value = groupsRes.items || []; allPermissions.value = permsRes.items || []; selectedGroupIds.value = (userGroups || []).map((g) => String(g.id)); + userGroupsMap.value = new Map(userGroupsMap.value).set(row.id, userGroups || []); + userRolesMap.value = new Map(userRolesMap.value).set(row.id, userRoles || []); const directPerms = userDirectPerms || []; selectedAllowPermIds.value = directPerms.filter((p) => p.grant_type === 1).map((p) => String(p.permission_id)); selectedDenyPermIds.value = directPerms.filter((p) => p.grant_type === 2).map((p) => String(p.permission_id)); @@ -113,13 +128,7 @@ async function handleConfirm() { ]); message.success('权限分配成功'); modalOpen.value = false; - // 刷新当前行缓存 - const [groups, roles] = await Promise.all([ - getUserGroupsApi(currentAdminId.value), - getUserRolesApi(currentAdminId.value), - ]); - userGroupsMap.value = new Map(userGroupsMap.value).set(currentAdminId.value, groups || []); - userRolesMap.value = new Map(userRolesMap.value).set(currentAdminId.value, roles || []); + await refreshUserRelationCache(currentAdminId.value); } finally { modalLoading.value = false; } @@ -136,7 +145,7 @@ const gridOptions: VxeGridProps = { { field: 'userRoles', title: '权限组(角色)', minWidth: 200, slots: { default: 'userRolesSlot' } }, { fixed: 'right', slots: { default: 'actionSlot' }, title: '操作', width: 120 }, ], - height: '100%', + minHeight: 650, pagerConfig: {}, proxyConfig: { response: { total: 'total', result: 'data' }, @@ -144,6 +153,7 @@ const gridOptions: VxeGridProps = { query: async () => { const result = await getAdminListApi(); const list: UserInfo[] = Array.isArray(result) ? result : ((result as any)?.data ?? []); + currentUsers.value = list; void loadRowDataForList(list); return result; }, @@ -153,6 +163,12 @@ const gridOptions: VxeGridProps = { }; const [Grid, GridApi] = useVbenVxeGrid({ gridOptions }); + +onActivated(() => { + if (currentUsers.value.length > 0) { + void loadRowDataForList(currentUsers.value); + } +});