chore(filelib-web): 授权成员单元格只留头像+名称,userId 与飞书 ID 拆为独立两列

GrantDto 增加 principalOpenId(USER 主体的 feishuOpenId),GROUP 行两列显示 —;
搜索过滤同步覆盖 openId。
This commit is contained in:
ymy
2026-07-27 16:40:25 +08:00
parent ef02428bb6
commit cc4d9d907c
3 changed files with 24 additions and 11 deletions
+8 -8
View File
@@ -60,6 +60,7 @@
(g) =>
g.principalId.toLowerCase().includes(q) ||
(g.principalName ?? "").toLowerCase().includes(q) ||
(g.principalOpenId ?? "").toLowerCase().includes(q) ||
(g.principalType === "USER" ? "个人" : "group").includes(q),
);
});
@@ -241,12 +242,12 @@
{:else}
<table class="list">
<thead>
<tr><th>成员</th><th>类型</th><th>权限</th><th>加入时间</th><th></th></tr>
<tr><th>成员</th><th>userId</th><th>飞书 ID</th><th>类型</th><th>权限</th><th>加入时间</th><th></th></tr>
</thead>
<tbody>
{#if shown.length === 0}
<tr>
<td colspan="5" class="quiet !py-[18px] text-center">
<td colspan="7" class="quiet !py-[18px] text-center">
{searchText.trim() === "" ? "暂无授权" : `无匹配「${searchText.trim()}」的授权`}
</td>
</tr>
@@ -256,15 +257,14 @@
<td>
<a class="inline-flex items-center gap-2 text-ink hover:text-accent" href={principalHref(g)}>
<Avatar displayName={g.principalName} userId={g.principalId} size={26} />
<span>
<span class="flex items-center gap-1.5 text-[13px]">
{g.principalName ?? g.principalId}
{#if g.isCreatorGrant}<span class="quiet">(创建者)</span>{/if}
</span>
<span class="block font-mono text-[11px] text-ink-3">{g.principalId}</span>
<span class="flex items-center gap-1.5 text-[13px]">
{g.principalName ?? g.principalId}
{#if g.isCreatorGrant}<span class="quiet">(创建者)</span>{/if}
</span>
</a>
</td>
<td class="file-meta font-mono">{g.principalType === "USER" ? g.principalId : "—"}</td>
<td class="file-meta font-mono">{g.principalOpenId ?? "—"}</td>
<td><span class="tag">{g.principalType === "USER" ? "个人" : "Group"}</span></td>
<td>
<!-- 创建者授权不可动(契约 8.1);非 MANAGE 持有者只读。 -->
+2
View File
@@ -107,6 +107,8 @@ export interface Grant {
readonly principalId: string;
/** 主体显示名(用户 displayName / 组 name);主体已删为 null,展示回落 principalId。 */
readonly principalName: string | null;
/** USER 主体的飞书 openId;GROUP 或主体已删为 null。 */
readonly principalOpenId: string | null;
readonly role: Role;
/** 创建者授权不可收回、不可改(契约 8.1)。 */
readonly isCreatorGrant: boolean;
+14 -3
View File
@@ -27,6 +27,8 @@ export interface GrantDto {
readonly principalId: string;
/** 主体显示名(用户 displayName / 组 name);主体已删时为 null,前端回落 principalId。 */
readonly principalName: string | null;
/** USER 主体的飞书 openId;GROUP 或主体已删时为 null。 */
readonly principalOpenId: string | null;
readonly role: FileLibRole;
readonly isCreatorGrant: boolean;
readonly createdAt: Date;
@@ -38,13 +40,14 @@ function toDto(grant: FileLibGrant): GrantDto {
principalType: grant.principalType,
principalId: grant.principalId,
principalName: null,
principalOpenId: null,
role: grant.role,
isCreatorGrant: grant.isCreatorGrant,
createdAt: grant.createdAt,
};
}
/** 批量回填主体显示名(两次查询,不做 per-row 往返)。可在事务内调用。 */
/** 批量回填主体显示名与飞书 openId(两次查询,不做 per-row 往返)。可在事务内调用。 */
async function withPrincipalNames(
prisma: Pick<PrismaClient, "user" | "memberGroup">,
grants: readonly GrantDto[],
@@ -53,7 +56,10 @@ async function withPrincipalNames(
const groupIds = [...new Set(grants.filter((g) => g.principalType === "GROUP").map((g) => g.principalId))];
const users = userIds.length === 0
? []
: await prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, displayName: true } });
: await prisma.user.findMany({
where: { id: { in: userIds } },
select: { id: true, displayName: true, feishuOpenId: true },
});
const groups = groupIds.length === 0
? []
: await prisma.memberGroup.findMany({ where: { id: { in: groupIds } }, select: { id: true, name: true } });
@@ -61,7 +67,12 @@ async function withPrincipalNames(
...users.map((u) => [u.id, u.displayName] as const),
...groups.map((g) => [g.id, g.name] as const),
]);
return grants.map((g) => ({ ...g, principalName: nameById.get(g.principalId) ?? null }));
const openIdById = new Map<string, string>(users.map((u) => [u.id, u.feishuOpenId] as const));
return grants.map((g) => ({
...g,
principalName: nameById.get(g.principalId) ?? null,
principalOpenId: g.principalType === "USER" ? openIdById.get(g.principalId) ?? null : null,
}));
}
type Tx = Prisma.TransactionClient;