diff --git a/frontend/app/admin/users/UsersAdmin.tsx b/frontend/app/admin/users/UsersAdmin.tsx index e9ba07e..17a4398 100644 --- a/frontend/app/admin/users/UsersAdmin.tsx +++ b/frontend/app/admin/users/UsersAdmin.tsx @@ -38,6 +38,7 @@ import { toast } from "@/lib/toast"; import { realtime } from "@/lib/realtime"; import Avatar from "@/components/Avatar"; import RoleBadge from "@/components/RoleBadge"; +import Modal from "@/components/Modal"; export type UserFilter = "all" | "staff" | "banned"; @@ -848,15 +849,6 @@ function RoleAssignDialog({ } }, [target]); - useEffect(() => { - if (!target) return; - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape" && !acting) onClose(); - }; - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [target, acting, onClose]); - if (!target) return null; const toggleBoard = (id: number) => { @@ -904,217 +896,198 @@ function RoleAssignDialog({ const saveLabel = willBan ? "保存并封禁" : willUnban ? "保存并解封" : "保存"; return ( -
!acting && onClose()} - > -
e.stopPropagation()} - role="dialog" - aria-modal="true" - aria-label="角色与账号" - > -
-
-

- 角色与账号 -

-

- @{target.username}({target.nickname}) -

-
- -
- -

- 角色决定后台入口与前台管理能力;封禁立即强制下线。登录历史、站长内容档案仍走列表其他入口。 -

- -

- 管理角色 -

-
- {options.map((o) => { - const active = role === o.value; - return ( - - ); - })} -
- - {role === ROLES.BOARD_ADMIN && ( -
-

- 授权板块 * -

- {boards.length === 0 ? ( -

板块加载中…

- ) : ( -
- {boards.map((b) => { - const checked = boardIds.includes(b.id); - return ( - - ); - })} -
- )} - {err && ( -

- {err} -

- )} -
- )} - -
-

- 账号状态 -

-
- - -
- {willBan && ( -

- 封禁后立即强制下线,无法登录、发帖与回复;已发布内容保留。角色变更与封禁将一并保存。 -

- )} -
- -
+ + + @{target.username}({target.nickname}) + + + 角色决定后台入口与前台管理能力;封禁立即强制下线。登录历史、站长内容档案仍走列表其他入口。 + + + } + onClose={onClose} + busy={acting} + maxWidthClass="max-w-[500px]" + panelClassName="p-6 max-h-[88vh] overflow-y-auto" + ariaLabel="角色与账号" + footer={ + <> -
+ + } + > +

+ 管理角色 +

+
+ {options.map((o) => { + const active = role === o.value; + return ( + + ); + })}
-
+ + {role === ROLES.BOARD_ADMIN && ( +
+

+ 授权板块 * +

+ {boards.length === 0 ? ( +

板块加载中…

+ ) : ( +
+ {boards.map((b) => { + const checked = boardIds.includes(b.id); + return ( + + ); + })} +
+ )} + {err && ( +

+ {err} +

+ )} +
+ )} + +
+

+ 账号状态 +

+
+ + +
+ {willBan && ( +

+ 封禁后立即强制下线,无法登录、发帖与回复;已发布内容保留。角色变更与封禁将一并保存。 +

+ )} +
+ ); } @@ -1154,133 +1127,107 @@ function LoginLogsDialog({ target, onClose }: { target: AdminUser | null; onClos if (target) setPage(1); }, [target]); - useEffect(() => { - if (!target) return; - const onKey = (e: KeyboardEvent) => { - if (e.key === "Escape") onClose(); - }; - window.addEventListener("keydown", onKey); - return () => window.removeEventListener("keydown", onKey); - }, [target, onClose]); - if (!target) return null; const pageSize = 15; const totalPages = Math.max(1, Math.ceil(total / pageSize)); return ( -
-
e.stopPropagation()} - role="dialog" - aria-modal="true" - aria-label="登录历史" - > -
-
-

- - 登录历史 -

-

- @{target.username} · 共 {total} 条记录(含失败尝试) -

-
- -
- -
- {loading ? ( -
- 加载中… -
- ) : err ? ( -

{err}

- ) : logs.length === 0 ? ( -
- -

暂无登录记录

-
- ) : ( -
    - {logs.map((log) => ( -
  • - -
    -

    - {log.ip || "未知 IP"} - - {log.success ? "成功" : "失败"} - -

    -

    - {log.user_agent || "未知设备"} -

    -
    - - {formatDate(log.created_at)} {new Date(log.created_at).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })} - -
  • - ))} -
- )} -
- - {total > pageSize && ( -
- + + + 登录历史 + + } + description={`@${target.username} · 共 ${total} 条记录(含失败尝试)`} + onClose={onClose} + dismissOnOverlay + maxWidthClass="max-w-[520px]" + panelClassName="p-6 max-h-[88vh] flex flex-col" + ariaLabel="登录历史" + footer={ + total > pageSize ? ( + <> + 第 {page} / {totalPages} 页 -
- - -
+ + + + ) : undefined + } + > +
+ {loading ? ( +
+ 加载中…
+ ) : err ? ( +

{err}

+ ) : logs.length === 0 ? ( +
+ +

暂无登录记录

+
+ ) : ( +
    + {logs.map((log) => ( +
  • + +
    +

    + {log.ip || "未知 IP"} + + {log.success ? "成功" : "失败"} + +

    +

    + {log.user_agent || "未知设备"} +

    +
    + + {formatDate(log.created_at)}{" "} + {new Date(log.created_at).toLocaleTimeString("zh-CN", { + hour: "2-digit", + minute: "2-digit", + })} + +
  • + ))} +
)}
-
+ ); } diff --git a/frontend/app/chat/ChatShell.tsx b/frontend/app/chat/ChatShell.tsx index 4c20aac..ceaff56 100644 --- a/frontend/app/chat/ChatShell.tsx +++ b/frontend/app/chat/ChatShell.tsx @@ -26,6 +26,7 @@ import { onDocumentVisible } from "@/lib/visibilityReconcile"; import { toast } from "@/lib/toast"; import Avatar from "@/components/Avatar"; import ContextMenu, { type CtxMenuItem } from "@/components/ContextMenu"; +import Modal from "@/components/Modal"; function roomTitle(room: ChatRoomView) { if (room.room_type === "direct") { @@ -600,115 +601,99 @@ function CreateGroupModal({ }; return ( -
-
e.stopPropagation()} - > -
-

- 新建群聊 -

- + + + } + > +
+
+ + setName(e.target.value)} + maxLength={32} + placeholder="2-32 个字符" + className="w-full px-3.5 h-10 rounded-lg outline-none" + style={{ background: "var(--bg)", border: "1px solid var(--line)", color: "var(--ink)" }} + />
-
+
+ +