From 5f2adf15f699db6a989a4a848961e60a06923dad Mon Sep 17 00:00:00 2001 From: matevip Date: Wed, 27 May 2026 15:09:11 +0800 Subject: [PATCH] polish(approval-grants-ui): show granter name, fix note cell rendering, tighten layout --- .../controller/ApprovalGrantController.java | 36 +++++++++++++++++- .../approval/grant/entity/ApprovalGrant.java | 9 +++++ mateclaw-ui/src/types/index.ts | 2 + .../Security/AutoApproveGrants/index.vue | 37 ++++++++++++++++--- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/approval/grant/controller/ApprovalGrantController.java b/mateclaw-server/src/main/java/vip/mate/approval/grant/controller/ApprovalGrantController.java index b852a9b5..6074c80b 100644 --- a/mateclaw-server/src/main/java/vip/mate/approval/grant/controller/ApprovalGrantController.java +++ b/mateclaw-server/src/main/java/vip/mate/approval/grant/controller/ApprovalGrantController.java @@ -15,6 +15,7 @@ import vip.mate.approval.grant.repository.ApprovalGrantMapper; import vip.mate.approval.grant.repository.ApprovalResolutionLogMapper; import vip.mate.approval.grant.service.ApprovalGrantService; import vip.mate.auth.model.UserEntity; +import vip.mate.auth.repository.UserMapper; import vip.mate.auth.service.AuthService; import vip.mate.common.result.R; import vip.mate.exception.MateClawException; @@ -57,6 +58,7 @@ public class ApprovalGrantController { private final ApprovalGrantMapper grantMapper; private final ApprovalResolutionLogMapper resolutionMapper; private final AuthService authService; + private final UserMapper userMapper; private final WorkspaceService workspaceService; // ─── Create ───────────────────────────────────────────────────────── @@ -139,7 +141,39 @@ public class ApprovalGrantController { wrapper.eq(ApprovalGrant::getGrantedBy, actorId); } Page pageObj = new Page<>(boundedPage, boundedSize); - return R.ok(grantMapper.selectPage(pageObj, wrapper)); + IPage result = grantMapper.selectPage(pageObj, wrapper); + fillGranterNames(result.getRecords()); + return R.ok(result); + } + + /** + * Batch-loads the display name (nickname → username fallback) for every + * unique {@code grantedBy} id on the page and writes it into the entity's + * transient {@code grantedByName} field. One round-trip via + * {@code selectBatchIds} rather than N queries; the field stays null when + * the source user has since been deleted. + */ + private void fillGranterNames(java.util.List records) { + if (records == null || records.isEmpty()) { + return; + } + java.util.Set userIds = new java.util.HashSet<>(); + for (ApprovalGrant g : records) { + if (g.getGrantedBy() != null) userIds.add(g.getGrantedBy()); + } + if (userIds.isEmpty()) return; + java.util.Map idToName = userMapper.selectBatchIds(userIds).stream() + .collect(java.util.stream.Collectors.toMap( + UserEntity::getId, + u -> u.getNickname() != null && !u.getNickname().isEmpty() + ? u.getNickname() + : u.getUsername(), + (a, b) -> a)); + for (ApprovalGrant g : records) { + if (g.getGrantedBy() != null) { + g.setGrantedByName(idToName.get(g.getGrantedBy())); + } + } } // ─── Active summary (chip "(N)") ──────────────────────────────────── diff --git a/mateclaw-server/src/main/java/vip/mate/approval/grant/entity/ApprovalGrant.java b/mateclaw-server/src/main/java/vip/mate/approval/grant/entity/ApprovalGrant.java index a8a01197..2749ba7a 100644 --- a/mateclaw-server/src/main/java/vip/mate/approval/grant/entity/ApprovalGrant.java +++ b/mateclaw-server/src/main/java/vip/mate/approval/grant/entity/ApprovalGrant.java @@ -52,6 +52,15 @@ public class ApprovalGrant { private Long grantedBy; + /** + * Display name of the granter (nickname → username). Not persisted; the + * controller fills it in after {@code selectPage} by batch-loading the + * touched user ids so the UI doesn't need a separate /users call for a + * snowflake → name lookup. Null when the user no longer exists. + */ + @TableField(exist = false) + private String grantedByName; + private LocalDateTime grantedAt; private Integer revoked; diff --git a/mateclaw-ui/src/types/index.ts b/mateclaw-ui/src/types/index.ts index c41b5286..9bc3eb38 100644 --- a/mateclaw-ui/src/types/index.ts +++ b/mateclaw-ui/src/types/index.ts @@ -1043,6 +1043,8 @@ export interface ApprovalGrant { grantKind: GrantKind expireAt: string | null grantedBy: string + /** Display name (nickname → username) of the granter; null if the user was deleted. */ + grantedByName?: string | null grantedAt: string revoked: number revokedBy: string | null diff --git a/mateclaw-ui/src/views/Security/AutoApproveGrants/index.vue b/mateclaw-ui/src/views/Security/AutoApproveGrants/index.vue index f0f507b0..0b423075 100644 --- a/mateclaw-ui/src/views/Security/AutoApproveGrants/index.vue +++ b/mateclaw-ui/src/views/Security/AutoApproveGrants/index.vue @@ -104,22 +104,35 @@ {{ t(`approval.grant.kind.${kindI18nKey(row.grantKind)}`) }} {{ formatDate(row.expireAt) }} - {{ row.grantedBy }} - {{ row.note }} + {{ row.grantedByName }} + #{{ row.grantedBy }} + + + {{ row.note }} + + - {{ t('common.revoked') }} + + + + + + {{ t('common.revoked') }} + @@ -610,7 +623,19 @@ onMounted(loadGrants) overflow-x: auto; } .rules-table { - min-width: 900px; + min-width: 1100px; /* enough headroom so the kind / granter / date columns + don't collapse into vertical-text mode on a narrow viewport */ +} +/* Prevent narrow columns from wrapping into stacked Chinese glyphs. */ +.rules-table th, +.rules-table td { + white-space: nowrap; +} +/* Note cell is the one cell where wrapping is fine — it's already + ellipsis-truncated by .note-cell. Keep this explicit so adding nowrap to + the wider scope above doesn't suppress the ellipsis. */ +.rules-table td .note-cell { + white-space: nowrap; } /* Pagination row — right-aligned beneath the table. McPagination provides