import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useRef, useState, type FormEvent } from "react"; import { api } from "../../api/client"; import { TopBar } from "../../components/TopBar"; import { toast } from "../../components/Toaster"; import { btn, btnPrimary, card, input } from "../../components/ui"; export default function AdminLibraries() { const qc = useQueryClient(); const libsQ = useQuery({ queryKey: ["libraries"], queryFn: api.listLibraries }); const [name, setName] = useState(""); const fileRefs = useRef>({}); const create = useMutation({ mutationFn: () => api.createLibrary(name.trim()), onSuccess: (lib) => { toast("ok", `库「${lib.name}」已创建`); setName(""); void qc.invalidateQueries({ queryKey: ["libraries"] }); }, onError: (e) => toast("err", e instanceof Error ? e.message : "建库失败"), }); const scan = useMutation({ mutationFn: (id: number) => api.scanLibrary(id), onSuccess: () => { toast("ok", "扫描已触发"); setTimeout(() => void qc.invalidateQueries({ queryKey: ["books"] }), 3000); }, onError: (e) => toast("err", e instanceof Error ? e.message : "扫描失败"), }); async function onFiles(libId: number, list: FileList | null) { if (!list?.length) return; try { for (const f of Array.from(list)) await api.uploadBook(libId, f); // 串行,单个失败即止并报错 toast("ok", `已上传 ${list.length} 个文件,等待 scanner 收编`); void qc.invalidateQueries({ queryKey: ["books"] }); setTimeout(() => void qc.invalidateQueries({ queryKey: ["books"] }), 5000); } catch (e) { toast("err", e instanceof Error ? e.message : "上传失败"); } } function submit(e: FormEvent) { e.preventDefault(); create.mutate(); } return (

库管理

setName(e.target.value)} />
{(libsQ.data ?? []).map((l) => (
{l.name} #{l.id}

{l.root_path}

{ fileRefs.current[l.id] = el; }} type="file" multiple accept=".cbz,.zip,.pdf,.epub,.txt,.md" className="hidden" onChange={(e) => { void onFiles(l.id, e.target.files); e.target.value = ""; }} /> 白名单:cbz/zip/pdf/epub/txt/md
))} {(libsQ.data ?? []).length === 0 && !libsQ.isPending && (

还没有库,用上方表单创建。

)}
); }