diff --git a/frontend/src/lib/shelfMeta.ts b/frontend/src/lib/shelfMeta.ts
new file mode 100644
index 0000000..567a676
--- /dev/null
+++ b/frontend/src/lib/shelfMeta.ts
@@ -0,0 +1,8 @@
+import type { GroupMode, SortKey } from "./shelf";
+
+export const SORTS: SortKey[] = ["added", "title", "progress"];
+export const GROUPS: { v: GroupMode; label: string }[] = [
+ { v: "flat", label: "平铺" },
+ { v: "dir", label: "按目录" },
+];
+export const LIB0 = 0;
diff --git a/frontend/src/pages/Shelf.tsx b/frontend/src/pages/Shelf.tsx
index c28fd21..764db60 100644
--- a/frontend/src/pages/Shelf.tsx
+++ b/frontend/src/pages/Shelf.tsx
@@ -1,88 +1,84 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo, useState } from "react";
-import { useNavigate } from "react-router-dom";
-import { api } from "../api/client";
-import type { Book } from "../api/types";
-import { useAuth } from "../auth/AuthContext";
-import { Cover } from "../components/Cover";
-import { IconAlert, IconBookOpen, IconClock, IconFolder, IconRefresh, IconScan, IconSearch, IconX } from "../components/icons";
-import { TopBar } from "../components/TopBar";
-import { toast } from "../components/Toaster";
-import { btn, card, formatBadge, formatSize, input, pill, pillActive } from "../components/ui";
-import { groupByDir } from "../lib/group";
+import { useNavigate, useSearchParams } from "react-router-dom";
+import { ArrowDownUp, Ellipsis, FolderOpen, LibraryBig, RefreshCw, ScanLine, Search, TriangleAlert } from "lucide-react";
+import { api } from "@/api/client";
+import type { Book } from "@/api/types";
+import { useAuth } from "@/auth/AuthContext";
+import { Cover } from "@/components/Cover";
+import { toast } from "@/components/Toaster";
+import { formatBadge } from "@/components/ui";
+import {
+ AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
+ AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+import { Button } from "@/components/ui/button";
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
+import { Input } from "@/components/ui/input";
+import { Skeleton } from "@/components/ui/skeleton";
+import { formatSize } from "@/lib/format";
+import { sections, SORT_LABEL, type GroupMode, type SortKey } from "@/lib/shelf";
+import { GROUPS, LIB0, SORTS } from "@/lib/shelfMeta";
-function BookCard({
- book,
- index,
- onOpen,
- onDelete,
-}: {
- book: Book;
- index: number;
- onOpen: () => void;
- onDelete: () => void;
-}) {
+const GRID = "grid grid-cols-[repeat(auto-fill,minmax(7.5rem,1fr))] gap-x-4 gap-y-6 sm:grid-cols-[repeat(auto-fill,minmax(9.5rem,1fr))]";
+
+function BookCard({ book, index, onOpen, onDelete }: { book: Book; index: number; onOpen: () => void; onDelete: () => void }) {
const { isAdmin } = useAuth();
const pct = Math.round(book.percent * 100);
return (
-
-