feat(admin): 管理后台 - 统一桌面端信息架构与视觉规范
This commit is contained in:
@@ -43,26 +43,27 @@ export function createUserColumns({
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ key: "email", title: "邮箱", dataIndex: "email", render: (email) => email || <span className="text-foreground/40">未填写</span> },
|
||||
{ key: "email", title: "邮箱", dataIndex: "email", align: "center", render: (email) => email || <span className="text-foreground/40">未填写</span> },
|
||||
{
|
||||
key: "credits",
|
||||
title: "当前积分",
|
||||
dataIndex: "availableMicrocredits",
|
||||
width: 130,
|
||||
align: "right",
|
||||
align: "center",
|
||||
render: (value, user) => <span className="tabular-nums" title={`冻结积分:${formatCredits(user.reservedMicrocredits)}`}>{formatCredits(value)}</span>,
|
||||
},
|
||||
{ key: "role", title: "角色", dataIndex: "role", width: 110, render: (role) => <AdminStatusBadge label={role === "admin" ? "管理员" : "普通用户"} tone={role === "admin" ? "info" : "neutral"} /> },
|
||||
{ key: "status", title: "状态", dataIndex: "status", width: 110, render: (status) => <AdminStatusBadge label={status === "active" ? "已启用" : "已停用"} tone={status === "active" ? "success" : "neutral"} /> },
|
||||
{ key: "createdAt", title: "注册时间", dataIndex: "createdAt", width: 180, render: formatTime },
|
||||
{ key: "role", title: "角色", dataIndex: "role", width: 110, align: "center", render: (role) => <AdminStatusBadge label={role === "admin" ? "管理员" : "普通用户"} tone={role === "admin" ? "info" : "neutral"} /> },
|
||||
{ key: "status", title: "状态", dataIndex: "status", width: 110, align: "center", render: (status) => <AdminStatusBadge label={status === "active" ? "已启用" : "已停用"} tone={status === "active" ? "success" : "neutral"} /> },
|
||||
{ key: "createdAt", title: "注册时间", dataIndex: "createdAt", width: 180, align: "center", render: formatTime },
|
||||
{
|
||||
key: "actions",
|
||||
title: "操作",
|
||||
width: 180,
|
||||
align: "right",
|
||||
width: 280,
|
||||
align: "center",
|
||||
render: (_, user) => (
|
||||
<AdminRowActions
|
||||
primary={{ label: "详情", icon: <Eye className="size-3.5" />, onClick: () => onView(user) }}
|
||||
visibleActionCount={2}
|
||||
actions={[
|
||||
{ key: "edit", label: "编辑用户", icon: <Pencil className="size-3.5" />, onClick: () => onEdit(user) },
|
||||
{
|
||||
|
||||
@@ -74,9 +74,9 @@ export function AdminUserEditDrawer({
|
||||
<Drawer
|
||||
title={user ? `编辑用户 · ${user.displayName || user.username}` : "编辑用户"}
|
||||
open={Boolean(user)}
|
||||
width="min(520px, 100vw)"
|
||||
size="min(520px, 100vw)"
|
||||
onClose={close}
|
||||
maskClosable={!saving}
|
||||
mask={{ closable: !saving }}
|
||||
destroyOnHidden
|
||||
extra={<Button type="primary" loading={saving} onClick={() => void save()}>保存</Button>}
|
||||
>
|
||||
@@ -172,9 +172,9 @@ export function AdminUserCreateDrawer({
|
||||
<Drawer
|
||||
title={"\u6dfb\u52a0\u7528\u6237"}
|
||||
open={open}
|
||||
width="min(520px, 100vw)"
|
||||
size="min(520px, 100vw)"
|
||||
onClose={close}
|
||||
maskClosable={!saving}
|
||||
mask={{ closable: !saving }}
|
||||
destroyOnHidden
|
||||
extra={<Button type="primary" loading={saving} onClick={() => void save()}>{"\u4fdd\u5b58"}</Button>}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { App, Button, Checkbox, Dropdown, Input } from "antd";
|
||||
import { Ban, ChevronDown, Search, Settings2, UserPlus } from "lucide-react";
|
||||
import { App, Button, Checkbox, Dropdown, Input, Select } from "antd";
|
||||
import { Ban, Search, Settings2, UserPlus } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { PaginationBar } from "@/components/layout/workspace-page";
|
||||
import { useDebouncedValue } from "@/hooks/use-debounced-value";
|
||||
import { bulkDisableAdminUsers, deleteAdminUser, listAdminUsers, updateAdminUser, type AdminUser, type LocalUser } from "@/services/api/auth";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
import { AdminBatchBar, AdminDataTable, AdminFilterChip, AdminTableEmpty } from "../components/admin-ui";
|
||||
import { AdminBatchBar, AdminDataTable, AdminTableEmpty } from "../components/admin-ui";
|
||||
import { useTableUrlState } from "../lib/use-table-url-state";
|
||||
import { AdminUserDetailDrawer } from "../components/admin-user-detail-drawer";
|
||||
import { createUserColumns, userColumnOptions, type UserColumnKey } from "./users-columns";
|
||||
@@ -144,30 +144,26 @@ export default function UsersPanel({ onUserChanged }: { onUserChanged?: (user: L
|
||||
className="app-list-search"
|
||||
prefix={<Search className="size-4 text-foreground/40" />}
|
||||
value={state.filter}
|
||||
aria-label="搜索用户"
|
||||
placeholder="搜索用户名、名称或邮箱"
|
||||
onChange={(event) => update({ filter: event.target.value, page: 1 }, true)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
toolbarActiveFilters={(
|
||||
<>
|
||||
{state.filter ? <AdminFilterChip label={`搜索:${state.filter}`} onRemove={() => update({ filter: "", page: 1 })} /> : null}
|
||||
{state.role !== "all" ? <AdminFilterChip label={`角色:${state.role === "admin" ? "管理员" : "普通用户"}`} onRemove={() => update({ role: "all", page: 1 })} /> : null}
|
||||
{state.status !== "all" ? <AdminFilterChip label={`状态:${state.status === "active" ? "已启用" : "已停用"}`} onRemove={() => update({ status: "all", page: 1 })} /> : null}
|
||||
</>
|
||||
)}
|
||||
toolbarActive={hasFilters}
|
||||
onReset={resetFilters}
|
||||
toolbarFilters={
|
||||
<>
|
||||
<FilterMenu
|
||||
label="角色"
|
||||
<Select
|
||||
aria-label="筛选用户角色"
|
||||
className="w-32"
|
||||
value={state.role}
|
||||
options={[{ value: "all", label: "全部角色" }, { value: "admin", label: "管理员" }, { value: "user", label: "普通用户" }]}
|
||||
onChange={(role) => update({ role, page: 1 })}
|
||||
/>
|
||||
<FilterMenu
|
||||
label="状态"
|
||||
<Select
|
||||
aria-label="筛选用户状态"
|
||||
className="w-32"
|
||||
value={state.status}
|
||||
options={[{ value: "all", label: "全部状态" }, { value: "active", label: "已启用" }, { value: "disabled", label: "已停用" }]}
|
||||
onChange={(status) => update({ status, page: 1 })}
|
||||
@@ -179,7 +175,7 @@ export default function UsersPanel({ onUserChanged }: { onUserChanged?: (user: L
|
||||
<Button icon={<UserPlus className="size-4" />} onClick={() => { setEditingUser(null); setCreateUserOpen(true); }}>{"\u6dfb\u52a0\u7528\u6237"}</Button>
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
dropdownRender={() => (
|
||||
popupRender={() => (
|
||||
<div className="w-48 rounded-md border border-border bg-popover p-2 shadow-lg">
|
||||
<div className="px-2 pb-2 text-xs font-medium text-foreground/55">显示列</div>
|
||||
<div className="space-y-0.5">
|
||||
@@ -234,20 +230,3 @@ export default function UsersPanel({ onUserChanged }: { onUserChanged?: (user: L
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FilterMenu({ label, value, options, onChange }: { label: string; value: string; options: Array<{ value: string; label: string }>; onChange: (value: string) => void }) {
|
||||
const selected = options.find((option) => option.value === value)?.label || label;
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
menu={{
|
||||
selectable: true,
|
||||
selectedKeys: [value],
|
||||
items: options.map((option) => ({ key: option.value, label: option.label })),
|
||||
onClick: ({ key }) => onChange(key),
|
||||
}}
|
||||
>
|
||||
<Button>{value === "all" ? label : selected}<ChevronDown className="ml-1 size-3.5" /></Button>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user