feat(filelib): 老师端左栏导航:回收站 + 最近打开(ADR-0031)

- 回收站:listBin(祖先全活跃的已删顶点;管理员/直连 MANAGE 可见)、
  restore(与 D15 对称只清本节点,落审计)、purge(仅管理员,pathIds 枚举
  子树按深度降序分批硬删,绕过 self-FK RESTRICT)
- 最近打开:FileLibRecentVisit 表(filePath='' 兜底 PG 唯一索引),客户端
  成功打开后上报(VIEW 门禁,upsert 刷新),列表 20 条,D8/D15 可见性过滤
- 前端:/app 左栏(文件库/最近打开/回收站);RecentView/BinView;
  GridLibraryView 埋点 + navTarget 跳转(breadcrumb 建栈,role 已捎带)
- 测试:filelib-nav 集成 4 例;全套 79 例绿
This commit is contained in:
ymy
2026-07-31 13:27:04 +08:00
parent 04fa383286
commit d072e9ec1e
17 changed files with 998 additions and 17 deletions
+43 -4
View File
@@ -1,12 +1,30 @@
<script lang="ts">
/** 老师端。未登录显示登录卡片;登录后是网盘式文件库浏览器。 */
/** 老师端。未登录显示登录卡片;登录后是带左栏导航的文件库(ADR-0031)。 */
import { onMount } from "svelte";
import { me, authChecked } from "$lib/stores.js";
import { loadSession } from "$lib/session.js";
import LoginView from "$lib/LoginView.svelte";
import GridLibraryView from "$lib/GridLibraryView.svelte";
import GridLibraryView, { type NavTarget } from "$lib/GridLibraryView.svelte";
import RecentView from "$lib/RecentView.svelte";
import BinView from "$lib/BinView.svelte";
import Icon from "$lib/Icon.svelte";
onMount(loadSession);
type View = "library" | "recent" | "bin";
let view = $state<View>("library");
let navTarget = $state<NavTarget | null>(null);
function openFromRecent(target: NavTarget): void {
navTarget = target;
view = "library";
}
const tabs: ReadonlyArray<readonly [View, string, "layers" | "clock" | "trash"]> = [
["library", "文件库", "layers"],
["recent", "最近打开", "clock"],
["bin", "回收站", "trash"],
];
</script>
<svelte:head><title>文件库</title></svelte:head>
@@ -14,8 +32,29 @@
{#if !$authChecked}
<div class="flex h-full items-center justify-center text-ink-3">加载中…</div>
{:else if $me}
<div class="flex h-full flex-col">
<GridLibraryView />
<div class="flex h-full">
<!-- 左栏导航(ADR-0031) -->
<nav class="flex w-[168px] shrink-0 flex-col gap-0.5 border-r border-line-soft bg-sidebar px-2.5 py-3.5">
{#each tabs as [id, label, icon] (id)}
<button
class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[13px] transition {view === id
? 'bg-selected font-semibold text-ink'
: 'text-ink-2 hover:bg-hover'}"
onclick={() => (view = id)}
>
<span class="text-ink-3"><Icon name={icon} size={15} /></span>
{label}
</button>
{/each}
</nav>
{#if view === "library"}
<GridLibraryView {navTarget} onnavigated={() => (navTarget = null)} />
{:else if view === "recent"}
<RecentView onopen={openFromRecent} />
{:else}
<BinView />
{/if}
</div>
{:else}
<LoginView />