diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx deleted file mode 100644 index 9f8dea3..0000000 --- a/frontend/src/components/TopBar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { ReactNode } from "react"; -import { Link, NavLink } from "react-router-dom"; -import { useAuth } from "../auth/AuthContext"; -import { IconBooks, IconFolder, IconLogout, IconUser } from "./icons"; -import { btnGhost } from "./ui"; - -export function TopBar({ right }: { right?: ReactNode }) { - const { user, isAdmin, logout } = useAuth(); - return ( -
- - - - - - BookLib - - - {isAdmin && ( - - )} -
- {right} - - - {Array.from(user?.username ?? "?")[0]?.toUpperCase()} - - {user?.username} - {isAdmin && admin} - - -
-
- ); -} - -function TabLink({ to, children }: { to: string; children: ReactNode }) { - return ( - - `flex items-center gap-1.5 rounded-full px-3 py-1.5 text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500 ${ - isActive ? "bg-white/[0.09] text-stone-100 ring-1 ring-white/10" : "text-stone-400 hover:bg-white/[0.06] hover:text-stone-100" - }` - } - > - {children} - - ); -} diff --git a/frontend/src/components/icons.tsx b/frontend/src/components/icons.tsx index a5a678c..f8e5467 100644 --- a/frontend/src/components/icons.tsx +++ b/frontend/src/components/icons.tsx @@ -1,116 +1,15 @@ import type { SVGProps } from "react"; -// 统一 1.7px 描边的线性图标(Phosphor 风格),尺寸由外部 className 控制 function Svg({ children, ...p }: SVGProps) { 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 -
-
+
+

库管理

+
+ setName(e.target.value)} /> + +
+ {libsQ.isPending && ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + ))} - {(libsQ.data ?? []).length === 0 && !libsQ.isPending && ( -

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

- )}
+ )} +
+ {libs.map((l) => ( + e.preventDefault()} onDrop={(e) => onDrop(e, l.id)}> + + + {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 · 也可直接拖文件进卡片 + +
+ ))} + {libs.length === 0 && !libsQ.isPending &&

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

}
); diff --git a/frontend/src/pages/admin/Users.tsx b/frontend/src/pages/admin/Users.tsx index ae48636..03bc73e 100644 --- a/frontend/src/pages/admin/Users.tsx +++ b/frontend/src/pages/admin/Users.tsx @@ -2,9 +2,16 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useState, type FormEvent } from "react"; import { api } from "../../api/client"; import { useAuth } from "../../auth/AuthContext"; -import { TopBar } from "../../components/TopBar"; import { toast } from "../../components/Toaster"; -import { btn, btnPrimary, card, input } from "../../components/ui"; +import { + AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, + AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { Skeleton } from "@/components/ui/skeleton"; export default function AdminUsers() { const qc = useQueryClient(); @@ -39,55 +46,68 @@ export default function AdminUsers() { } return ( -
- -
-

用户管理

-
- setU(e.target.value)} /> - setP(e.target.value)} /> - - -
- - - - - - - - - - +
+

用户管理

+
+ setU(e.target.value)} /> + setP(e.target.value)} /> + + + + {usersQ.isPending ? ( + + ) : ( +
ID用户名角色创建时间 -
+ + + ID + 用户名 + 角色 + 创建时间 + + + + {(usersQ.data ?? []).map((x) => ( - - - - - - - + + {x.id} + {x.username} + {x.role} + {x.created_at.slice(0, 19).replace("T", " ")} + + + + + + + + 删除用户 {x.username}? + 该用户的阅读进度与书签一并删除。 + + + 取消 + del.mutate(x.id)}> + 删除 + + + + + + ))} - -
{x.id}{x.username}{x.role}{x.created_at.slice(0, 19).replace("T", " ")} - -
-
+ + + )}
); }