feat(web): DataTable groupBy with collapsible counted group headers
Groups keep first-seen order (or a custom order), headers show counts and toggle with aria-expanded; collapsed groups persist per storageKey in localStorage (read after mount, failures ignored). The trash list groups by registered directory when items span more than one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
abc69886ea
commit
312e70d0bc
4 files changed
+276
-52
No files matched your search
@@ -94,7 +94,7 @@ export default function FilesTrashPage() {
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
if (r.failed.length) toast.error(`${r.failed.length} 项没有还原,原因见列表`);
|
||||
if (r.failed.length) toast.error(`${r.failed.length} 项没有还原,原因见列表`);
|
||||
},
|
||||
onError: (e) => toast.error(filesErrorText(e, "还原失败")),
|
||||
},
|
||||
@@ -219,7 +219,7 @@ export default function FilesTrashPage() {
|
||||
if (!snap?.title) return <span className="text-muted-foreground">—</span>;
|
||||
const label = `${snap.title}${snap.contentUnit ? ` ${snap.contentUnit}` : ""}`;
|
||||
return (
|
||||
<span className="block max-w-48 text-meta" title="删除时已退役;还原不会自动恢复资产">
|
||||
<span className="block max-w-48 text-meta" title="删除时已退役;还原不会自动恢复资产">
|
||||
{snap.mediaEntityId ? (
|
||||
<Link className="underline underline-offset-2" to={`/console/library/${snap.mediaEntityId}`}>
|
||||
{label}
|
||||
@@ -274,7 +274,7 @@ export default function FilesTrashPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="回收站"
|
||||
description="到期后自动清除,到期前可以还原。还原不会恢复资产状态,需要时重新整理。"
|
||||
description="到期后自动清除,到期前可以还原。还原不会恢复资产状态,需要时重新整理。"
|
||||
actions={
|
||||
<>
|
||||
{filesMockEnabled() ? <StatusBadge tone="warning">演示数据</StatusBadge> : null}
|
||||
@@ -308,8 +308,8 @@ export default function FilesTrashPage() {
|
||||
清除已到期 {expired.length || ""}
|
||||
</Button>
|
||||
}
|
||||
title="清除已到期的项?"
|
||||
description={`${expired.length} 项已过保留期,清除后不可恢复。`}
|
||||
title="清除已到期的项?"
|
||||
description={`${expired.length} 项已过保留期,清除后不可恢复。`}
|
||||
confirmLabel="清除"
|
||||
destructive
|
||||
onConfirm={() => doPurge(undefined)}
|
||||
@@ -340,12 +340,21 @@ export default function FilesTrashPage() {
|
||||
onLoadMore={() => void trash.fetchNextPage()}
|
||||
loadingMore={trash.isFetchingNextPage}
|
||||
empty={{ icon: Trash2, title: "回收站是空的", description: "在文件管理里删除的项会出现在这里。" }}
|
||||
groupBy={
|
||||
new Set(rows?.map((r) => r.rootId)).size > 1
|
||||
? {
|
||||
key: (r) => r.rootId,
|
||||
label: (k) => rootName.get(k)?.name ?? k,
|
||||
storageKey: "files-trash-by-root",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
open={!!purgeIds}
|
||||
onOpenChange={(o) => !o && setPurgeIds(null)}
|
||||
title={`立即清除 ${purgeIds?.length ?? 0} 项?`}
|
||||
title={`立即清除 ${purgeIds?.length ?? 0} 项?`}
|
||||
description="清除后不可恢复。"
|
||||
confirmLabel="清除"
|
||||
destructive
|
||||
@@ -359,14 +368,14 @@ export default function FilesTrashPage() {
|
||||
setPurgeAll(o);
|
||||
if (!o) setConfirmText("");
|
||||
}}
|
||||
title="清空回收站?"
|
||||
description={`全部 ${rows?.length ?? 0} 项(含未到期)将被清除,不可恢复。`}
|
||||
title="清空回收站?"
|
||||
description={`全部 ${rows?.length ?? 0} 项(含未到期)将被清除,不可恢复。`}
|
||||
confirmLabel="清空"
|
||||
confirmDisabled={confirmText.trim() !== "清空"}
|
||||
destructive
|
||||
onConfirm={() => {
|
||||
if (confirmText.trim() !== "清空") {
|
||||
toast.error("输入“清空”以确认");
|
||||
toast.error("输入「清空」以确认");
|
||||
return Promise.reject(new Error("unconfirmed"));
|
||||
}
|
||||
return doPurge(rows?.map((r) => r.id)).then(() => setConfirmText(""));
|
||||
|
||||
Reference in new issue
Block a user