This commit is contained in:
2026-07-01 08:27:26 +08:00
parent 9f4577d3d8
commit 25f0d8e97b
56 changed files with 5298 additions and 2 deletions
+119
View File
@@ -0,0 +1,119 @@
import { useEffect, useState } from 'react';
import {
Button, Drawer, Form, Input, Modal, Select, Space, Table, Tag, Typography, message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { request, type HqProfile } from '../lib/api';
import { ACCOUNT_STATUS_LABELS, fmtTime } from '../lib/constants';
import { useAdminList } from '../lib/useAdminList';
type Row = {
id: string; phone: string; name: string; adminRole: string; status: string; lastLoginAt: string | null; createdAt: string;
};
const ROLE_LABELS: Record<string, string> = {
SUPER_ADMIN: '超级管理员',
OPS: '运营',
FINANCE: '财务',
CUSTOMER_SERVICE: '客服',
};
export default function HqAccountsPage() {
const [profile, setProfile] = useState<HqProfile | null>(null);
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [createForm] = Form.useForm();
const [filters, setFilters] = useState<Record<string, string>>({});
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
'/admin/hq-accounts',
() => {
const qs = new URLSearchParams();
if (filters.phone) qs.set('phone', filters.phone);
if (filters.adminRole) qs.set('adminRole', filters.adminRole);
if (filters.status) qs.set('status', filters.status);
return qs;
},
[filters],
);
const [detail, setDetail] = useState<Row | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN';
useEffect(() => {
request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
}, []);
const columns: ColumnsType<Row> = [
{ title: '姓名', dataIndex: 'name' },
{ title: '手机', dataIndex: 'phone', width: 130 },
{ title: '角色', dataIndex: 'adminRole', width: 110, render: (r) => ROLE_LABELS[r] || r },
{ title: '状态', dataIndex: 'status', width: 90, render: (s) => <Tag>{ACCOUNT_STATUS_LABELS[s] || s}</Tag> },
{ title: '最后登录', dataIndex: 'lastLoginAt', width: 160, render: fmtTime },
{
title: '操作', width: 80,
render: (_, row) => (
<Button type="link" size="small" disabled={!isSuperAdmin} onClick={() => {
setDetail(row);
editForm.setFieldsValue(row);
setDrawerOpen(true);
}}></Button>
),
},
];
return (
<div>
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
<Typography.Title level={4} style={{ margin: 0 }}>HQ </Typography.Title>
{isSuperAdmin && <Button type="primary" onClick={() => setCreateOpen(true)}></Button>}
</Space>
<Form form={form} layout="inline" style={{ marginBottom: 16 }} onFinish={(v) => { setFilters(v); setPage(1); }}>
<Form.Item name="phone" label="手机"><Input allowClear /></Form.Item>
<Form.Item name="adminRole" label="角色">
<Select allowClear style={{ width: 120 }} options={Object.entries(ROLE_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>
<Form.Item><Button type="primary" htmlType="submit"></Button></Form.Item>
</Form>
<Table rowKey="id" loading={loading} columns={columns} dataSource={data?.items ?? []}
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
<Drawer title="编辑 HQ 账户" width={420} open={drawerOpen} onClose={() => setDrawerOpen(false)}
extra={
<Button type="primary" onClick={async () => {
if (!detail) return;
const v = await editForm.validateFields();
await request(`/admin/hq-accounts/${detail.id}`, { method: 'PUT', body: JSON.stringify(v) });
message.success('已保存');
setDrawerOpen(false);
void reload();
}}></Button>
}>
<Form form={editForm} layout="vertical">
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
<Form.Item name="adminRole" label="角色">
<Select options={Object.entries(ROLE_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>
<Form.Item name="status" label="状态">
<Select options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>
</Form>
</Drawer>
<Modal title="新建 HQ 账户" open={createOpen} onCancel={() => setCreateOpen(false)} onOk={async () => {
const v = await createForm.validateFields();
await request('/admin/hq-accounts', { method: 'POST', body: JSON.stringify(v) });
message.success('已创建');
setCreateOpen(false);
createForm.resetFields();
void reload();
}}>
<Form form={createForm} layout="vertical">
<Form.Item name="phone" label="手机" rules={[{ required: true }]}><Input /></Form.Item>
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
<Form.Item name="adminRole" label="角色" initialValue="OPS">
<Select options={Object.entries(ROLE_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>
</Form>
</Modal>
</div>
);
}