feat(web): table-style 库管理, admin entries under avatar 系统管理, shelf-view upload button
This commit is contained in:
@@ -58,6 +58,22 @@ export function AppShell() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>{user?.username}{isAdmin ? " · admin" : ""}</DropdownMenuLabel>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>系统管理</DropdownMenuLabel>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/admin/libraries">
|
||||
<FolderCog /> 库管理
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/admin/users">
|
||||
<Users /> 用户管理
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={logout}>
|
||||
<LogOut /> 退出登录
|
||||
@@ -73,12 +89,6 @@ export function AppShell() {
|
||||
<Brand />
|
||||
<nav aria-label="主导航" className="flex flex-col gap-1">
|
||||
<NavItem to="/" icon={<BookOpen className="size-4 shrink-0" />} label="书架" active={loc.pathname === "/" && !libParam} />
|
||||
{isAdmin && (
|
||||
<>
|
||||
<NavItem to="/admin/libraries" icon={<FolderCog className="size-4 shrink-0" />} label="库管理" active={loc.pathname.startsWith("/admin/libraries")} />
|
||||
<NavItem to="/admin/users" icon={<Users className="size-4 shrink-0" />} label="用户管理" active={loc.pathname.startsWith("/admin/users")} />
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
{libs.length > 0 && (
|
||||
<div className="mt-3 hidden min-h-0 flex-1 flex-col lg:flex">
|
||||
@@ -112,8 +122,6 @@ export function AppShell() {
|
||||
{/* 移动端底 Tab */}
|
||||
<nav aria-label="底部导航" className="flex h-14 shrink-0 items-stretch border-t bg-background/85 backdrop-blur md:hidden">
|
||||
<TabItem to="/" icon={<BookOpen className="size-5" />} label="书架" />
|
||||
{isAdmin && <TabItem to="/admin/libraries" icon={<FolderCog className="size-5" />} label="库管理" />}
|
||||
{isAdmin && <TabItem to="/admin/users" icon={<Users className="size-5" />} label="用户" />}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||
import { useRef, useState, type ComponentProps } from "react";
|
||||
import { Upload } from "lucide-react";
|
||||
import { api } from "@/api/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "./Toaster";
|
||||
|
||||
export async function uploadToLib(qc: QueryClient, libId: number, list: FileList | File[]): Promise<boolean> {
|
||||
const files = Array.from(list); // FileList 活引用,先快照
|
||||
if (!files.length) return false;
|
||||
try {
|
||||
for (const f of files) await api.uploadBook(libId, f);
|
||||
toast("ok", `已上传 ${files.length} 个文件,等待 scanner 收编`);
|
||||
void qc.invalidateQueries({ queryKey: ["books"] });
|
||||
setTimeout(() => void qc.invalidateQueries({ queryKey: ["books"] }), 5000);
|
||||
return true;
|
||||
} catch (e) {
|
||||
toast("err", e instanceof Error ? e.message : "上传失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 上传按钮(隐藏 file input),白名单:cbz/zip/pdf/epub/txt/md. */
|
||||
export function UploadButton({
|
||||
libId,
|
||||
variant,
|
||||
size = "sm",
|
||||
label = "上传",
|
||||
}: {
|
||||
libId: number;
|
||||
variant?: ComponentProps<typeof Button>["variant"];
|
||||
size?: ComponentProps<typeof Button>["size"];
|
||||
label?: string;
|
||||
}) {
|
||||
const qc = useQueryClient();
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
ref={ref}
|
||||
type="file"
|
||||
multiple
|
||||
accept=".cbz,.zip,.pdf,.epub,.txt,.md"
|
||||
className="hidden"
|
||||
onChange={async (e) => {
|
||||
const files = Array.from(e.target.files ?? []);
|
||||
e.target.value = "";
|
||||
setBusy(true);
|
||||
await uploadToLib(qc, libId, files);
|
||||
setBusy(false);
|
||||
}}
|
||||
/>
|
||||
<Button variant={variant} size={size} disabled={busy} onClick={() => ref.current?.click()}>
|
||||
<Upload /> {busy ? "上传中…" : label}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user