@@ -3,9 +3,12 @@ import {
|
||||
Button, Drawer, Form, Input, Modal, Radio, 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 { HQ_ADMIN_ROLES } from '@dukang/shared-types';
|
||||
import { request, type HqProfile, type Paginated } from '../lib/api';
|
||||
import { ACCOUNT_STATUS_LABELS, ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
|
||||
import { useAdminList } from '../lib/useAdminList';
|
||||
import { useAdminListColumns } from '../lib/useAdminListColumns';
|
||||
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
@@ -17,14 +20,13 @@ type Row = {
|
||||
status: string;
|
||||
lastLoginAt: string | null;
|
||||
createdAt: string;
|
||||
cityIds?: string[];
|
||||
};
|
||||
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
SUPER_ADMIN: '超级管理员',
|
||||
OPS: '运营',
|
||||
FINANCE: '财务',
|
||||
CUSTOMER_SERVICE: '客服',
|
||||
};
|
||||
type CityOption = { id: string; name: string };
|
||||
|
||||
const ROLE_OPTIONS = HQ_ADMIN_ROLES.map((r) => ({ value: r.value, label: r.label }));
|
||||
const ROLE_LABELS = Object.fromEntries(HQ_ADMIN_ROLES.map((r) => [r.value, r.label]));
|
||||
|
||||
export default function HqAccountsPage() {
|
||||
const [profile, setProfile] = useState<HqProfile | null>(null);
|
||||
@@ -46,14 +48,27 @@ export default function HqAccountsPage() {
|
||||
const [detail, setDetail] = useState<Row | null>(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [cities, setCities] = useState<CityOption[]>([]);
|
||||
const credentialType = Form.useWatch('credentialType', createForm) ?? 'phone';
|
||||
const editRole = Form.useWatch('adminRole', editForm);
|
||||
const createRole = Form.useWatch('adminRole', createForm);
|
||||
const isSuperAdmin = profile?.adminRole === 'SUPER_ADMIN';
|
||||
|
||||
useEffect(() => {
|
||||
request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const columns: ColumnsType<Row> = [
|
||||
useEffect(() => {
|
||||
if (!isSuperAdmin) return;
|
||||
request<Paginated<CityOption>>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
|
||||
.then((res) => setCities(res.items))
|
||||
.catch(() => {});
|
||||
}, [isSuperAdmin]);
|
||||
|
||||
const cityOptions = cities.map((c) => ({ value: c.id, label: c.name }));
|
||||
|
||||
const baseColumns: ColumnsType<Row> = [
|
||||
{ title: '姓名', dataIndex: 'name' },
|
||||
{ title: '用户名', dataIndex: 'loginName', width: 120, render: (v) => v || '—' },
|
||||
{ title: '手机', dataIndex: 'phone', width: 130 },
|
||||
@@ -67,7 +82,18 @@ export default function HqAccountsPage() {
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{ title: '角色', dataIndex: 'adminRole', width: 110, render: (r) => ROLE_LABELS[r] || r },
|
||||
{ title: '角色', dataIndex: 'adminRole', width: 130, render: (r) => ROLE_LABELS[r] || r },
|
||||
{
|
||||
title: '城市',
|
||||
width: 160,
|
||||
render: (_, r) => {
|
||||
if (!r.cityIds?.length) return <Typography.Text type="secondary">全国</Typography.Text>;
|
||||
const names = r.cityIds
|
||||
.map((id) => cities.find((c) => c.id === id)?.name || id)
|
||||
.join('、');
|
||||
return names;
|
||||
},
|
||||
},
|
||||
{ title: '状态', dataIndex: 'status', width: 90, render: (s) => <Tag>{ACCOUNT_STATUS_LABELS[s] || s}</Tag> },
|
||||
{ title: '最后登录', dataIndex: 'lastLoginAt', width: 160, render: fmtTime },
|
||||
{
|
||||
@@ -81,6 +107,7 @@ export default function HqAccountsPage() {
|
||||
loginName: row.loginName,
|
||||
adminRole: row.adminRole,
|
||||
status: row.status,
|
||||
cityIds: row.cityIds ?? [],
|
||||
});
|
||||
setDrawerOpen(true);
|
||||
}}>编辑</Button>
|
||||
@@ -88,16 +115,20 @@ export default function HqAccountsPage() {
|
||||
},
|
||||
];
|
||||
|
||||
const { columns, settingsButton, settingsModal } = useAdminListColumns('hq-accounts', baseColumns, { page, pageSize });
|
||||
|
||||
return (
|
||||
<div>
|
||||
{settingsModal}
|
||||
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>HQ 账户</Typography.Title>
|
||||
{settingsButton}
|
||||
{isSuperAdmin && (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
createForm.resetFields();
|
||||
createForm.setFieldsValue({ credentialType: 'phone', adminRole: 'OPS' });
|
||||
createForm.setFieldsValue({ credentialType: 'phone', adminRole: 'OPS', cityIds: [] });
|
||||
setCreateOpen(true);
|
||||
}}
|
||||
>
|
||||
@@ -108,23 +139,31 @@ export default function HqAccountsPage() {
|
||||
<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 }))} />
|
||||
<Select allowClear style={{ width: 160 }} options={ROLE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item><Button type="primary" htmlType="submit">查询</Button></Form.Item>
|
||||
</Form>
|
||||
<Table rowKey="id" loading={loading} columns={columns} dataSource={data?.items ?? []}
|
||||
<Table scroll={{ x: 'max-content' }} 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)}
|
||||
<Drawer title="编辑 HQ 账户" width={460} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
||||
extra={
|
||||
<Button type="primary" onClick={async () => {
|
||||
<Button type="primary" loading={saving} onClick={async () => {
|
||||
if (!detail) return;
|
||||
const v = await editForm.validateFields();
|
||||
const payload = { ...v };
|
||||
if (!payload.password) delete payload.password;
|
||||
await request(`/admin/hq-accounts/${detail.id}`, { method: 'PUT', body: JSON.stringify(payload) });
|
||||
message.success('已保存');
|
||||
setDrawerOpen(false);
|
||||
void reload();
|
||||
try {
|
||||
const v = await editForm.validateFields();
|
||||
const payload: Record<string, unknown> = { ...v };
|
||||
if (!payload.password) delete payload.password;
|
||||
if (!String(payload.loginName ?? '').trim()) delete payload.loginName;
|
||||
setSaving(true);
|
||||
await request(`/admin/hq-accounts/${detail.id}`, { method: 'PUT', body: JSON.stringify(payload) });
|
||||
message.success('已保存');
|
||||
setDrawerOpen(false);
|
||||
void reload();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}}>保存</Button>
|
||||
}>
|
||||
<Form form={editForm} layout="vertical">
|
||||
@@ -144,7 +183,15 @@ export default function HqAccountsPage() {
|
||||
<Input.Password placeholder="至少 6 位" />
|
||||
</Form.Item>
|
||||
<Form.Item name="adminRole" label="角色">
|
||||
<Select options={Object.entries(ROLE_LABELS).map(([value, label]) => ({ value, label }))} />
|
||||
<Select options={ROLE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="cityIds"
|
||||
label="负责城市"
|
||||
extra={editRole === 'CITY_STORE_SERVICE' ? '城市门店服务必须至少选一个城市' : '不选表示全国可见'}
|
||||
rules={editRole === 'CITY_STORE_SERVICE' ? [{ required: true, type: 'array', min: 1, message: '请至少勾选一个城市' }] : []}
|
||||
>
|
||||
<Select mode="multiple" allowClear showSearch optionFilterProp="label" placeholder="不选=全国" options={cityOptions} />
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
|
||||
@@ -156,15 +203,19 @@ export default function HqAccountsPage() {
|
||||
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();
|
||||
try {
|
||||
const v = await createForm.validateFields();
|
||||
await request('/admin/hq-accounts', { method: 'POST', body: JSON.stringify(v) });
|
||||
message.success('已创建');
|
||||
setCreateOpen(false);
|
||||
createForm.resetFields();
|
||||
void reload();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message) message.error(e.message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form form={createForm} layout="vertical" initialValues={{ credentialType: 'phone', adminRole: 'OPS' }}>
|
||||
<Form form={createForm} layout="vertical" initialValues={{ credentialType: 'phone', adminRole: 'OPS', cityIds: [] }}>
|
||||
<Form.Item name="credentialType" label="创建方式">
|
||||
<Radio.Group>
|
||||
<Radio value="phone">手机号账户(短信登录)</Radio>
|
||||
@@ -173,7 +224,15 @@ export default function HqAccountsPage() {
|
||||
</Form.Item>
|
||||
<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 }))} />
|
||||
<Select options={ROLE_OPTIONS} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="cityIds"
|
||||
label="负责城市"
|
||||
extra={createRole === 'CITY_STORE_SERVICE' ? '城市门店服务必须至少选一个城市' : '不选表示全国可见'}
|
||||
rules={createRole === 'CITY_STORE_SERVICE' ? [{ required: true, type: 'array', min: 1, message: '请至少勾选一个城市' }] : []}
|
||||
>
|
||||
<Select mode="multiple" allowClear showSearch optionFilterProp="label" placeholder="不选=全国" options={cityOptions} />
|
||||
</Form.Item>
|
||||
{credentialType === 'phone' ? (
|
||||
<Form.Item name="phone" label="手机号" rules={[{ required: true, message: '请输入手机号' }]}>
|
||||
|
||||
Reference in New Issue
Block a user