城市合伙人端的修改(后台)

This commit is contained in:
2026-07-12 10:19:39 +08:00
parent d0f0fa09af
commit 7f031cc4c2
74 changed files with 5430 additions and 975 deletions
@@ -0,0 +1,406 @@
import { useCallback, useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import {
Alert,
Button,
Descriptions,
Form,
Input,
Modal,
Popconfirm,
Select,
Space,
Table,
Tag,
Typography,
message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import {
WAREHOUSE_MANAGER_LABELS,
WAREHOUSE_STATUS_LABELS,
WarehouseManagerType,
WarehouseStatus,
} from '@dukang/shared-types';
import { request, type Paginated } from '../lib/api';
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
import { useAdminList } from '../lib/useAdminList';
type Row = {
id: string;
cityId: string;
cityName: string;
cityCode: string;
name: string;
address: string;
contactName: string;
contactPhone: string;
managerType: WarehouseManagerType;
partnerAccountId: string | null;
partnerCompanyName?: string | null;
status: string;
createdAt: string;
};
type CityOption = { id: string; name: string; code: string };
type PartnerOption = { id: string; companyName: string };
const MANAGER_OPTIONS = Object.entries(WAREHOUSE_MANAGER_LABELS).map(([value, label]) => ({ value, label }));
const STATUS_OPTIONS = Object.entries(WAREHOUSE_STATUS_LABELS).map(([value, label]) => ({ value, label }));
export default function CityWarehousesPage() {
const [filterForm] = Form.useForm();
const [createForm] = Form.useForm();
const [editForm] = Form.useForm();
const [filters, setFilters] = useState<Record<string, string>>({});
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
'/admin/city-warehouses',
() => {
const qs = new URLSearchParams();
if (filters.name) qs.set('name', filters.name);
if (filters.cityId) qs.set('cityId', filters.cityId);
if (filters.managerType) qs.set('managerType', filters.managerType);
if (filters.status) qs.set('status', filters.status);
return qs;
},
[filters],
);
const [cities, setCities] = useState<CityOption[]>([]);
const [partners, setPartners] = useState<PartnerOption[]>([]);
const [createOpen, setCreateOpen] = useState(false);
const [editOpen, setEditOpen] = useState(false);
const [editRow, setEditRow] = useState<Row | null>(null);
const [createManagerType, setCreateManagerType] = useState<WarehouseManagerType>(WarehouseManagerType.HQ);
const [editManagerType, setEditManagerType] = useState<WarehouseManagerType>(WarehouseManagerType.HQ);
const [createCityId, setCreateCityId] = useState<string | undefined>();
const loadCities = useCallback(async () => {
const res = await request<Paginated<CityOption>>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`);
setCities(res.items);
}, []);
const loadPartners = useCallback(async (cityId: string) => {
const res = await request<Paginated<PartnerOption>>(
`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}&cityId=${cityId}`,
);
setPartners(res.items);
}, []);
useEffect(() => {
void loadCities();
}, [loadCities]);
async function openEdit(row: Row) {
setEditRow(row);
setEditManagerType(row.managerType);
await loadPartners(row.cityId);
editForm.setFieldsValue({
name: row.name,
address: row.address,
contactName: row.contactName,
contactPhone: row.contactPhone,
managerType: row.managerType,
partnerAccountId: row.partnerAccountId,
status: row.status,
});
setEditOpen(true);
}
function onManagerTypeChange(
type: WarehouseManagerType,
form: typeof createForm | typeof editForm,
setType: (v: WarehouseManagerType) => void,
) {
setType(type);
if (type !== WarehouseManagerType.PARTNER) {
form.setFieldValue('partnerAccountId', undefined);
}
}
const columns: ColumnsType<Row> = [
{ title: '仓库名', dataIndex: 'name', ellipsis: true, width: 140 },
{
title: '城市',
width: 100,
render: (_, row) => (
<span title={row.cityCode}>
{row.cityName}
</span>
),
},
{ title: '地址', dataIndex: 'address', ellipsis: true },
{ title: '联系人', dataIndex: 'contactName', width: 90 },
{ title: '电话', dataIndex: 'contactPhone', width: 120 },
{
title: '管仓类型',
dataIndex: 'managerType',
width: 100,
render: (v) => WAREHOUSE_MANAGER_LABELS[v as WarehouseManagerType] || v,
},
{
title: '管仓合伙人',
dataIndex: 'partnerCompanyName',
width: 120,
ellipsis: true,
render: (v, row) =>
v && row.partnerAccountId ? (
<Link to="/city-partners">{v}</Link>
) : (
'—'
),
},
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (s) => (
<Tag color={s === WarehouseStatus.ACTIVE ? 'green' : 'default'}>
{WAREHOUSE_STATUS_LABELS[s as WarehouseStatus] || s}
</Tag>
),
},
{ title: '创建', dataIndex: 'createdAt', width: 150, render: fmtTime },
{
title: '操作',
width: 120,
fixed: 'right',
render: (_, row) => (
<Space size={0}>
<Button type="link" size="small" onClick={() => void openEdit(row)}>
</Button>
<Popconfirm
title="确定删除该仓库?"
description="删除后将解除关联合伙人的管仓绑定"
onConfirm={async () => {
await request(`/admin/city-warehouses/${row.id}`, { method: 'DELETE' });
message.success('已删除');
void reload();
}}
>
<Button type="link" size="small" danger>
</Button>
</Popconfirm>
</Space>
),
},
];
return (
<div>
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
<Typography.Title level={4} style={{ margin: 0 }}>
</Typography.Title>
<Button
type="primary"
onClick={() => {
createForm.resetFields();
createForm.setFieldsValue({ managerType: WarehouseManagerType.HQ, status: WarehouseStatus.ACTIVE });
setCreateManagerType(WarehouseManagerType.HQ);
setCreateCityId(undefined);
setPartners([]);
setCreateOpen(true);
}}
>
</Button>
</Space>
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="管仓合伙人仅可在此页面分配;城市合伙人详情中的管仓仓库为只读展示。"
/>
<Form
form={filterForm}
layout="inline"
style={{ marginBottom: 16 }}
onFinish={(v) => {
setFilters(v);
setPage(1);
}}
>
<Form.Item name="name" label="仓库名">
<Input allowClear placeholder="仓库名" />
</Form.Item>
<Form.Item name="cityId" label="城市">
<Select
allowClear
showSearch
optionFilterProp="label"
style={{ width: 140 }}
placeholder="全部城市"
options={cities.map((c) => ({ value: c.id, label: c.name }))}
/>
</Form.Item>
<Form.Item name="managerType" label="管仓类型">
<Select allowClear style={{ width: 120 }} placeholder="全部" options={MANAGER_OPTIONS} />
</Form.Item>
<Form.Item name="status" label="状态">
<Select allowClear style={{ width: 100 }} placeholder="全部" options={STATUS_OPTIONS} />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit">
</Button>
<Button
onClick={() => {
filterForm.resetFields();
setFilters({});
setPage(1);
}}
>
</Button>
</Space>
</Form.Item>
</Form>
<Table
rowKey="id"
className="admin-table-nowrap"
loading={loading}
columns={columns}
dataSource={data?.items ?? []}
scroll={{ x: 1100 }}
pagination={{
current: page,
pageSize,
total: data?.total ?? 0,
showSizeChanger: true,
showTotal: (t) => `${t}`,
onChange: (p, ps) => {
setPage(p);
setPageSize(ps);
},
}}
/>
<Modal
title="新建仓库"
open={createOpen}
width={520}
onCancel={() => setCreateOpen(false)}
onOk={async () => {
const v = await createForm.validateFields();
await request(`/admin/cities/${v.cityId}/warehouses`, {
method: 'POST',
body: JSON.stringify(v),
});
message.success('已创建');
setCreateOpen(false);
void reload();
}}
>
<Form form={createForm} layout="vertical">
<Form.Item name="cityId" label="开城城市" rules={[{ required: true }]}>
<Select
showSearch
optionFilterProp="label"
options={cities.map((c) => ({ value: c.id, label: `${c.name} (${c.code})` }))}
onChange={(id) => {
setCreateCityId(id);
createForm.setFieldValue('partnerAccountId', undefined);
void loadPartners(id);
}}
/>
</Form.Item>
<Form.Item name="name" label="仓库名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="address" label="地址" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="contactName" label="联系人" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="contactPhone" label="联系电话" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="managerType" label="管仓类型" rules={[{ required: true }]}>
<Select
options={MANAGER_OPTIONS}
onChange={(v) => onManagerTypeChange(v, createForm, setCreateManagerType)}
/>
</Form.Item>
{createManagerType === WarehouseManagerType.PARTNER && createCityId && (
<Form.Item name="partnerAccountId" label="管仓合伙人" rules={[{ required: true }]}>
<Select
showSearch
optionFilterProp="label"
placeholder={partners.length ? '选择合伙人' : '该城市暂无合伙人'}
options={partners.map((p) => ({ value: p.id, label: p.companyName }))}
/>
</Form.Item>
)}
<Form.Item name="status" label="状态" initialValue={WarehouseStatus.ACTIVE}>
<Select options={STATUS_OPTIONS} />
</Form.Item>
</Form>
</Modal>
<Modal
title="编辑仓库"
open={editOpen}
width={520}
onCancel={() => setEditOpen(false)}
onOk={async () => {
if (!editRow) return;
const v = await editForm.validateFields();
await request(`/admin/city-warehouses/${editRow.id}`, {
method: 'PUT',
body: JSON.stringify(v),
});
message.success('已更新');
setEditOpen(false);
void reload();
}}
>
{editRow && (
<Descriptions column={1} size="small" bordered style={{ marginBottom: 16 }}>
<Descriptions.Item label="所属城市">
{editRow.cityName}{editRow.cityCode}
</Descriptions.Item>
</Descriptions>
)}
<Form form={editForm} layout="vertical">
<Form.Item name="name" label="仓库名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="address" label="地址" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="contactName" label="联系人" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="contactPhone" label="联系电话" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="managerType" label="管仓类型" rules={[{ required: true }]}>
<Select
options={MANAGER_OPTIONS}
onChange={(v) => onManagerTypeChange(v, editForm, setEditManagerType)}
/>
</Form.Item>
{editManagerType === WarehouseManagerType.PARTNER && editRow && (
<Form.Item name="partnerAccountId" label="管仓合伙人" rules={[{ required: true }]}>
<Select
showSearch
optionFilterProp="label"
options={partners.map((p) => ({ value: p.id, label: p.companyName }))}
/>
</Form.Item>
)}
<Form.Item name="status" label="状态">
<Select options={STATUS_OPTIONS} />
</Form.Item>
</Form>
</Modal>
</div>
);
}