diff --git a/hub/filelib-web/src/lib/GrantsPanel.svelte b/hub/filelib-web/src/lib/GrantsPanel.svelte new file mode 100644 index 0000000..3199707 --- /dev/null +++ b/hub/filelib-web/src/lib/GrantsPanel.svelte @@ -0,0 +1,191 @@ + + +
+ {#if error !== null} +
{error}
+ {:else if grants === null} +
加载中…
+ {:else} + + + + + + {#if grants.length === 0} + + {:else} + {#each grants as g (g.id)} + + + + + + {/each} + {/if} + +
主体级别
暂无显式授权
+ + + {g.principalId} + {#if g.isCreatorGrant}(创建者){/if} + + {g.role} + + {#if !g.isCreatorGrant && canManage} + + {/if} +
+ {/if} + + {#if canManage} +
+
新增授权
+
+ + + {#if principalType === "USER"} + + {:else if groupOptions === null} + 加载 Group 列表… + {:else if groupOptions.length === 0} + 暂无可选 Group · 先到「Group 管理」建一个 + {:else} + + {/if} + + + +
+
MANAGE 仅创建者可授;创建者授权不可动(契约 8.1)
+ {/if} + +
diff --git a/hub/filelib-web/src/lib/NodeDetailPanel.svelte b/hub/filelib-web/src/lib/NodeDetailPanel.svelte index 13209d3..fe437c2 100644 --- a/hub/filelib-web/src/lib/NodeDetailPanel.svelte +++ b/hub/filelib-web/src/lib/NodeDetailPanel.svelte @@ -4,9 +4,12 @@ import { toastOk, toastErr } from "./stores.js"; import OverviewPanel from "./OverviewPanel.svelte"; import FilesPanel from "./FilesPanel.svelte"; + import GrantsPanel from "./GrantsPanel.svelte"; import Modal from "./Modal.svelte"; + import Icon from "./Icon.svelte"; - let tab = $state<"detail" | "files">("detail"); + type Tab = "detail" | "files" | "grants"; + let tab = $state("detail"); let showCreateChild = $state(false); let newName = $state(""); let newKind = $state<"FOLDER" | "PROJECT">("FOLDER"); @@ -17,6 +20,15 @@ const canManage = $derived(node?.role === "MANAGE"); const canEdit = $derived(canManage || node?.role === "EDIT"); + // 与旧 libraryBrowser 的 tab 组装一致:概览恒有;文件仅 PROJECT;授权仅 MANAGE + // (FOLDER 也有授权 —— 它虽是透明组织节点,授权仍挂在节点上,ADR-0021)。 + const tabs = $derived.by((): ReadonlyArray => { + const out: Array = [["detail", "概览"]]; + if (node?.kind === "PROJECT") out.push(["files", "文件"]); + if (canManage) out.push(["grants", "授权"]); + return out; + }); + $effect(() => { void node?.id; tab = "detail"; @@ -84,36 +96,43 @@
-
+
{node.name} - {node.kind === "PROJECT" ? "项目" : "文件夹"} + {node.kind === "PROJECT" ? "项目" : "文件夹"} + {node.role}
-
+
{#if canEdit && node.kind === "FOLDER"} - + {/if} {#if canManage} - - + + {/if}
- {#if node.kind === "FOLDER"} -
点击左侧树展开以浏览子内容
+
+ {#each tabs as [id, label] (id)} + + {/each} +
+ + {#if tab === "grants"} + + {:else if tab === "files" && node.kind === "PROJECT"} + {:else} -
- {#each [["detail", "概览"], ["files", "文件"]] as [id, label] (id)} - - {/each} -
- {#if tab === "detail"} - - {:else} - + + {#if node.kind === "FOLDER"} +
文件夹是透明组织节点,点左侧树展开以浏览子内容。
{/if} {/if}
@@ -121,24 +140,24 @@ {#if showCreateChild && node} (showCreateChild = false)}> -
- - +
+ +
-
- -
-
- - +
+ +
- - + +
{/if} diff --git a/hub/filelib-web/src/lib/OverviewPanel.svelte b/hub/filelib-web/src/lib/OverviewPanel.svelte index cf5a6f7..3c3b31d 100644 --- a/hub/filelib-web/src/lib/OverviewPanel.svelte +++ b/hub/filelib-web/src/lib/OverviewPanel.svelte @@ -12,8 +12,25 @@ let exportJob = $state(null); const canEdit = $derived(node.role === "MANAGE" || node.role === "EDIT"); + const canManage = $derived(node.role === "MANAGE"); const roleLabel = $derived(node.role === "MANAGE" ? "可管理" : node.role === "EDIT" ? "可编辑" : "只读"); + /** 独立权限开关(仅 PROJECT;关闭时只继承父级权限,创建者除外)。 */ + async function toggleIndependent(): Promise { + try { + await api(`/database/api/projects/${node.id}/independent-permission`, { + method: "PUT", + body: { enabled: !node.independentPermission }, + }); + toastOk("已切换"); + currentNode.update((n) => + n !== null && n.id === node.id ? { ...n, independentPermission: !node.independentPermission } : n, + ); + } catch (e) { + toastErr(e instanceof Error ? e.message : String(e)); + } + } + $effect(() => { void node.id; exportJob = null; @@ -67,7 +84,7 @@ } -
+
简介
@@ -77,7 +94,7 @@ 暂无简介 {/if} {#if canEdit} - + {/if}
@@ -85,39 +102,54 @@
+
类型 {node.kind === "PROJECT" ? "项目" : "文件夹"}
我的角色 {roleLabel}
-
创建时间 {new Date(node.createdAt).toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" })}
-
最近修改 {new Date(node.updatedAt).toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" })}
+
创建时间 {new Date(node.createdAt).toLocaleString("zh-CN")}
+
更新时间 {new Date(node.updatedAt).toLocaleString("zh-CN")}
-
+ + {#if node.kind === "PROJECT"} +
+
+ 独立权限 + {node.independentPermission ? "开启" : "关闭"} + {#if canManage} + + {/if} + 关闭时仅继承父级权限(创建者除外) +
-
导出
-
- - {#if exportJob} - - {#if exportJob.status === "DONE"} - 完成 · 下载 - {:else if exportJob.status === "FAILED"} - 失败:{exportJob.error ?? ""} - {:else} - {exportJob.status}… - {/if} - - {/if} -
+
+ +
导出
+
+ + + {#if exportJob} + + {#if exportJob.status === "DONE"} + 完成 · 下载 + {:else if exportJob.status === "FAILED"} + 失败:{exportJob.error ?? ""} + {:else} + {exportJob.status}… + {/if} + + {/if} +
+ {/if}
{#if showEditDesc} (showEditDesc = false)}> -
- - +
+ +
- - + +
{/if} diff --git a/hub/src/database/routes/filelibRoutes.ts b/hub/src/database/routes/filelibRoutes.ts index 3ce1bbc..39cd339 100644 --- a/hub/src/database/routes/filelibRoutes.ts +++ b/hub/src/database/routes/filelibRoutes.ts @@ -44,10 +44,22 @@ export async function registerFileLibRoutes( /* ------------------------------------------------------------ 身份 */ // 前端判断能力面用:是否网站管理员(root 创建按钮显隐)。 + // displayName/avatarUrl 供侧栏身份区显示 —— 页面不再服务端渲染(ADR-0029), + // 旧 renderDashboard 在 handler 里查 Prisma 拿到的名字,现在必须由这里带出去, + // 否则前端只有 userId 可显示。 app.get("/database/api/me", async (request, reply) => { const actor = await actorOrNull(request, reply, deps); if (actor === null) return reply; - return { userId: actor.userId, isWebsiteAdmin: actor.isWebsiteAdmin }; + const user = await deps.prisma.user.findUnique({ + where: { id: actor.userId }, + select: { displayName: true, avatarUrl: true }, + }); + return { + userId: actor.userId, + isWebsiteAdmin: actor.isWebsiteAdmin, + displayName: user?.displayName ?? actor.userId, + avatarUrl: user?.avatarUrl ?? null, + }; }); /* ------------------------------------------------------------ 树节点 */