城市合伙人端的修改(后台)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Drawer, Form, Input, Modal, Popconfirm, Select, Space, Table, Tabs, Tag, Typography, message,
|
||||
Button, Checkbox, Drawer, Form, Input, Modal, Popconfirm, Select, Space, Table, Tabs, Tag, Typography, message,
|
||||
} from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { PARTNER_STAFF_ROLE_LABELS } from '@dukang/shared-types';
|
||||
import { PARTNER_PERMISSION_KEYS, PARTNER_PERMISSION_LABELS, PARTNER_STAFF_ROLE_LABELS, type PartnerPermissionKey } from '@dukang/shared-types';
|
||||
import { request, type Paginated } from '../lib/api';
|
||||
import { AdminCellLine } from '../components/AdminCellLine';
|
||||
import { ACCOUNT_STATUS_LABELS, ADMIN_OPTIONS_PAGE_SIZE, ORDER_STATUS_LABELS, PARTNER_BILL_STATUS_LABELS, fmtTime } from '../lib/constants';
|
||||
@@ -18,11 +18,12 @@ type AccountTreeRow = {
|
||||
status: string;
|
||||
isPrimary: number;
|
||||
staffRole?: string | null;
|
||||
permissions?: string[] | null;
|
||||
parentAccountId?: string | null;
|
||||
parent?: ParentAccount | null;
|
||||
createdAt: string;
|
||||
lastLoginAt?: string | null;
|
||||
partner?: { id: string; companyName: string };
|
||||
companyName?: string | null;
|
||||
children?: AccountTreeRow[];
|
||||
};
|
||||
|
||||
@@ -43,6 +44,11 @@ const STAFF_ROLE_OPTIONS = Object.entries(PARTNER_STAFF_ROLE_LABELS).map(([value
|
||||
label,
|
||||
}));
|
||||
|
||||
const PERMISSION_OPTIONS = PARTNER_PERMISSION_KEYS.map((key: PartnerPermissionKey) => ({
|
||||
value: key,
|
||||
label: PARTNER_PERMISSION_LABELS[key],
|
||||
}));
|
||||
|
||||
function filterTree(rows: AccountTreeRow[], phone?: string, status?: string): AccountTreeRow[] {
|
||||
const phoneQ = phone?.trim();
|
||||
const match = (row: AccountTreeRow) => {
|
||||
@@ -65,10 +71,6 @@ function filterTree(rows: AccountTreeRow[], phone?: string, status?: string): Ac
|
||||
return walk(rows);
|
||||
}
|
||||
|
||||
function accountTypeLabel(isPrimary: number) {
|
||||
return isPrimary === 1 ? '主账号' : '子账号';
|
||||
}
|
||||
|
||||
function staffRoleLabel(role?: string | null) {
|
||||
if (!role) return '-';
|
||||
return PARTNER_STAFF_ROLE_LABELS[role as keyof typeof PARTNER_STAFF_ROLE_LABELS] ?? role;
|
||||
@@ -77,7 +79,6 @@ function staffRoleLabel(role?: string | null) {
|
||||
export default function PartnerAccountsPage() {
|
||||
const [form] = Form.useForm();
|
||||
const [editForm] = Form.useForm();
|
||||
const [createForm] = Form.useForm();
|
||||
const [subForm] = Form.useForm();
|
||||
const [filters, setFilters] = useState<{ phone?: string; status?: string; partnerId?: string }>({});
|
||||
const [treeData, setTreeData] = useState<AccountTreeRow[]>([]);
|
||||
@@ -85,7 +86,6 @@ export default function PartnerAccountsPage() {
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [detail, setDetail] = useState<Detail | null>(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [subOpen, setSubOpen] = useState(false);
|
||||
const [subParent, setSubParent] = useState<AccountTreeRow | null>(null);
|
||||
const [partners, setPartners] = useState<PartnerOption[]>([]);
|
||||
@@ -117,6 +117,7 @@ export default function PartnerAccountsPage() {
|
||||
name: detail.name,
|
||||
phone: detail.phone,
|
||||
status: detail.status,
|
||||
permissions: detail.permissions ?? [],
|
||||
});
|
||||
}, [drawerOpen, detail, editForm]);
|
||||
|
||||
@@ -153,7 +154,7 @@ export default function PartnerAccountsPage() {
|
||||
function openAddSub(parent: AccountTreeRow) {
|
||||
setSubParent(parent);
|
||||
subForm.resetFields();
|
||||
subForm.setFieldsValue({ staffRole: 'INTERNAL' });
|
||||
subForm.setFieldsValue({ staffRole: 'INTERNAL', permissions: ['store:create'] });
|
||||
setSubOpen(true);
|
||||
}
|
||||
|
||||
@@ -179,9 +180,7 @@ export default function PartnerAccountsPage() {
|
||||
secondary={
|
||||
row.parentAccountId
|
||||
? `子账号 · ${staffRoleLabel(row.staffRole)}`
|
||||
: row.isPrimary === 1
|
||||
? '主账号'
|
||||
: '合伙人账号'
|
||||
: '主账号'
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -191,15 +190,14 @@ export default function PartnerAccountsPage() {
|
||||
title: '开城合伙人',
|
||||
width: 140,
|
||||
ellipsis: true,
|
||||
render: (_, row) => row.partner?.companyName || '—',
|
||||
render: (_, row) => row.companyName || row.parent?.name || '—',
|
||||
},
|
||||
{
|
||||
title: '账号类型',
|
||||
dataIndex: 'isPrimary',
|
||||
width: 90,
|
||||
render: (v, row) => (
|
||||
<Tag color={row.parentAccountId ? 'default' : v === 1 ? 'blue' : 'default'}>
|
||||
{row.parentAccountId ? '子账号' : accountTypeLabel(v)}
|
||||
render: (_, row) => (
|
||||
<Tag color={row.parentAccountId ? 'default' : 'blue'}>
|
||||
{row.parentAccountId ? '子账号' : '主账号'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
@@ -213,9 +211,9 @@ export default function PartnerAccountsPage() {
|
||||
title: '所属主账号',
|
||||
width: 140,
|
||||
render: (_, row) => (
|
||||
row.isPrimary === 1 || !row.parentAccountId
|
||||
? '-'
|
||||
: (row.parent ? `${row.parent.name} / ${row.parent.phone}` : '-')
|
||||
row.parentAccountId && row.parent
|
||||
? `${row.parent.name} / ${row.parent.phone}`
|
||||
: '-'
|
||||
),
|
||||
},
|
||||
{ title: '状态', dataIndex: 'status', width: 80, render: (s) => <Tag>{ACCOUNT_STATUS_LABELS[s] || s}</Tag> },
|
||||
@@ -226,7 +224,7 @@ export default function PartnerAccountsPage() {
|
||||
render: (_, row) => (
|
||||
<Space size={0} wrap onClick={(e) => e.stopPropagation()}>
|
||||
<Button type="link" size="small" onClick={() => void openAccount(row.id)}>详情</Button>
|
||||
{!row.parentAccountId && row.isPrimary === 1 ? (
|
||||
{!row.parentAccountId ? (
|
||||
<Button type="link" size="small" onClick={() => openAddSub(row)}>添加子账号</Button>
|
||||
) : null}
|
||||
{row.parentAccountId ? (
|
||||
@@ -260,10 +258,11 @@ export default function PartnerAccountsPage() {
|
||||
<div>
|
||||
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>开城合伙人账户</Typography.Title>
|
||||
<Typography.Text type="secondary">主账号下可展开/收起子账号,支持添加与删除子账号</Typography.Text>
|
||||
<Typography.Title level={4} style={{ margin: 0 }}>合伙人子账号</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
主账号在「开城合伙人」创建;此处仅管理子账号树与权限
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Button type="primary" onClick={() => { void loadPartners(); setCreateOpen(true); }}>新建账户</Button>
|
||||
</Space>
|
||||
<Form
|
||||
form={form}
|
||||
@@ -315,7 +314,7 @@ export default function PartnerAccountsPage() {
|
||||
}}
|
||||
/>
|
||||
<Drawer
|
||||
title={detail?.parentAccountId ? '合伙人子账号详情' : '开城合伙人账户详情'}
|
||||
title={detail?.parentAccountId ? '子账号详情' : '主账号详情(只读)'}
|
||||
width={720}
|
||||
open={drawerOpen}
|
||||
onClose={() => {
|
||||
@@ -323,7 +322,11 @@ export default function PartnerAccountsPage() {
|
||||
setDetail(null);
|
||||
}}
|
||||
destroyOnClose
|
||||
extra={<Button type="primary" onClick={() => void saveAccount()}>保存</Button>}
|
||||
extra={
|
||||
detail?.parentAccountId
|
||||
? <Button type="primary" onClick={() => void saveAccount()}>保存</Button>
|
||||
: null
|
||||
}
|
||||
>
|
||||
{detail && (
|
||||
<Tabs items={[
|
||||
@@ -339,32 +342,28 @@ export default function PartnerAccountsPage() {
|
||||
name: detail.name,
|
||||
phone: detail.phone,
|
||||
status: detail.status,
|
||||
permissions: detail.permissions ?? [],
|
||||
}}
|
||||
>
|
||||
<Form.Item label="开城合伙人">
|
||||
<Input value={detail.partner?.companyName} disabled />
|
||||
<Input value={detail.companyName || detail.parent?.name || '—'} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label="账号类型">
|
||||
<Input
|
||||
value={
|
||||
detail.parentAccountId
|
||||
? `子账号 · ${staffRoleLabel(detail.staffRole)}`
|
||||
: accountTypeLabel(detail.isPrimary)
|
||||
: '主账号'
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
</Form.Item>
|
||||
{detail.staffRole ? (
|
||||
<Form.Item label="角色">
|
||||
<Input value={staffRoleLabel(detail.staffRole)} disabled />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
{detail.parentAccountId && detail.parent ? (
|
||||
<Form.Item label="所属主账号">
|
||||
<Input value={`${detail.parent.name} / ${detail.parent.phone}`} disabled />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input disabled={!detail.parentAccountId} /></Form.Item>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="登录手机"
|
||||
@@ -373,14 +372,25 @@ export default function PartnerAccountsPage() {
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' },
|
||||
]}
|
||||
>
|
||||
<Input maxLength={11} />
|
||||
<Input maxLength={11} disabled={!detail.parentAccountId} />
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态" rules={[{ required: true }]}>
|
||||
<Select options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
|
||||
<Select disabled={!detail.parentAccountId} options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
|
||||
</Form.Item>
|
||||
<Typography.Text type="secondary">
|
||||
合伙人 H5 登录使用「登录手机」,与开城合伙人主体的「联系电话」可不同。
|
||||
</Typography.Text>
|
||||
{detail.parentAccountId ? (
|
||||
<Form.Item name="permissions" label="权限">
|
||||
<Checkbox.Group options={PERMISSION_OPTIONS} />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
{!detail.parentAccountId ? (
|
||||
<Typography.Text type="secondary">
|
||||
主账号请在「开城合伙人」页面编辑。
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
合伙人 H5 登录使用「登录手机」,与主账号联系电话可不同。
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
@@ -403,31 +413,6 @@ export default function PartnerAccountsPage() {
|
||||
]} />
|
||||
)}
|
||||
</Drawer>
|
||||
<Modal title="新建开城合伙人账户" open={createOpen} onCancel={() => setCreateOpen(false)} onOk={async () => {
|
||||
const v = await createForm.validateFields();
|
||||
await request('/admin/partner-accounts', { method: 'POST', body: JSON.stringify(v) });
|
||||
message.success('已创建');
|
||||
setCreateOpen(false);
|
||||
createForm.resetFields();
|
||||
void loadTree();
|
||||
}}>
|
||||
<Form form={createForm} layout="vertical">
|
||||
<Form.Item name="partnerId" label="开城合伙人" rules={[{ required: true }]}>
|
||||
<Select showSearch optionFilterProp="label" options={partners.map((p) => ({ value: p.id, label: p.companyName }))} />
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="登录手机"
|
||||
rules={[
|
||||
{ required: true },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' },
|
||||
]}
|
||||
>
|
||||
<Input maxLength={11} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Modal
|
||||
title={subParent ? `添加子账号 · ${subParent.name}` : '添加子账号'}
|
||||
open={subOpen}
|
||||
@@ -438,11 +423,11 @@ export default function PartnerAccountsPage() {
|
||||
await request('/admin/partner-accounts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
partnerId: subParent.partner?.id,
|
||||
parentAccountId: subParent.id,
|
||||
name: v.name,
|
||||
phone: v.phone,
|
||||
staffRole: v.staffRole,
|
||||
permissions: v.permissions,
|
||||
}),
|
||||
});
|
||||
message.success('子账号已创建');
|
||||
@@ -466,6 +451,9 @@ export default function PartnerAccountsPage() {
|
||||
<Form.Item name="staffRole" label="角色" rules={[{ required: true }]}>
|
||||
<Select options={STAFF_ROLE_OPTIONS.filter((o) => o.value !== 'PARTNER')} />
|
||||
</Form.Item>
|
||||
<Form.Item name="permissions" label="权限">
|
||||
<Checkbox.Group options={PERMISSION_OPTIONS} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user