import { useCallback, useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { Button, Descriptions, Drawer, Form, Input, InputNumber, Modal, Select, Space, Table, Tabs, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { WAREHOUSE_MANAGER_LABELS, WarehouseManagerType } from '@dukang/shared-types'; import { request, type HqProfile, type Paginated } from '../lib/api'; import { parseProvinceCityCodes, type ParsedProvinceCity } from '../lib/china-region'; import { ADMIN_OPTIONS_PAGE_SIZE, CITY_STATUS_LABELS, fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; import CityPartnersPanel from '../components/CityPartnersPanel'; import ChinaProvinceCityCascader from '../components/ChinaProvinceCityCascader'; type WarehouseRow = { id: string; name: string; address: string; contactName: string; contactPhone: string; managerType: WarehouseManagerType; partnerAccountId: string | null; partnerCompanyName?: string | null; status: string; }; type Row = { id: string; code: string; name: string; province: string; status: string; storeCount: number; orderCount: number; partnerBindingCount?: number; createdAt: string; }; type PartnerOption = { id: string; companyName: string; cityId?: string | null }; type CityDeletePreview = { city: { id: string; code: string; name: string; province: string; status: string }; canDelete: boolean; blockers: string[]; warnings: string[]; summary: { primaryPartnerCount: number; staffCount: number; storeCount: number; warehouseCount: number; orderCount: number; redeemCount: number; partnerBillCount: number; }; partners: Array<{ id: string; phone: string; name: string; companyName?: string | null; status: string; staff: Array<{ id: string; phone: string; name: string; staffRole?: string | null }>; }>; orphanStaff: Array<{ id: string; phone: string; name: string }>; stores: Array<{ id: string; name: string; phone: string; status: string; address: string; partnerAccount?: { companyName?: string | null; phone?: string } | null; }>; warehouses: Array<{ id: string; name: string; status: string; address: string }>; }; const MANAGER_OPTIONS = Object.entries(WAREHOUSE_MANAGER_LABELS).map(([value, label]) => ({ value, label })); export default function CitiesPage() { const [form] = Form.useForm(); const [editForm] = Form.useForm(); const [createForm] = Form.useForm(); const [warehouseForm] = Form.useForm(); const [warehouseEditForm] = Form.useForm(); const [filters, setFilters] = useState>({}); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/cities', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.code) qs.set('code', filters.code); if (filters.status) qs.set('status', filters.status); return qs; }, [filters], ); const [detail, setDetail] = useState | null>(null); const [warehouses, setWarehouses] = useState([]); const [drawerOpen, setDrawerOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [warehouseOpen, setWarehouseOpen] = useState(false); const [warehouseEditOpen, setWarehouseEditOpen] = useState(false); const [warehouseEditId, setWarehouseEditId] = useState(null); const [partners, setPartners] = useState([]); const [createRegionPreview, setCreateRegionPreview] = useState(null); const createRegionCodes = Form.useWatch('regionCodes', createForm); const [warehouseManagerType, setWarehouseManagerType] = useState(WarehouseManagerType.HQ); const [editWarehouseManagerType, setEditWarehouseManagerType] = useState(WarehouseManagerType.HQ); const [deleteOpen, setDeleteOpen] = useState(false); const [deleteLoading, setDeleteLoading] = useState(false); const [deleteSubmitting, setDeleteSubmitting] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); const [deletePreview, setDeletePreview] = useState(null); const [confirmName, setConfirmName] = useState(''); const [profile, setProfile] = useState(null); const canDeleteCities = (profile?.permissionKeys ?? []).includes('cities_delete'); useEffect(() => { request('/admin/auth/me').then(setProfile).catch(() => {}); }, []); const loadPartners = useCallback(async (cityId: string) => { const res = await request>(`/admin/partners?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}&cityId=${cityId}`); setPartners(res.items); }, []); const loadWarehouses = useCallback(async (cityId: string) => { const wh = await request(`/admin/cities/${cityId}/warehouses`); setWarehouses(wh); }, []); useEffect(() => { if (!createRegionCodes?.length) { setCreateRegionPreview(null); return; } const parsed = parseProvinceCityCodes(createRegionCodes as string[]); createForm.setFieldsValue({ code: parsed?.cityCode, name: parsed?.city, province: parsed?.province, }); setCreateRegionPreview(parsed); }, [createRegionCodes, createForm]); function openCreateModal() { createForm.resetFields(); createForm.setFieldsValue({ status: 'PENDING' }); setCreateRegionPreview(null); setCreateOpen(true); } const openDetail = async (row: Row) => { const d = await request>(`/admin/cities/${row.id}`); setDetail(d); editForm.setFieldsValue({ ...d, maxPartnerCommissionPercent: d.maxPartnerCommissionRate != null ? Number(d.maxPartnerCommissionRate) * 100 : 5, }); await loadPartners(row.id); await loadWarehouses(row.id); setDrawerOpen(true); }; const openDelete = async (row: Row) => { setDeleteTarget(row); setDeletePreview(null); setConfirmName(''); setDeleteOpen(true); setDeleteLoading(true); try { const preview = await request(`/admin/cities/${row.id}/delete-preview`); setDeletePreview(preview); } catch (e) { message.error(e instanceof Error ? e.message : '加载删除预览失败'); setDeleteOpen(false); } finally { setDeleteLoading(false); } }; const confirmDelete = async () => { if (!deleteTarget || !deletePreview) return; if (!deletePreview.canDelete) { message.error(deletePreview.blockers.join(';') || '当前城市不可删除'); return; } if (confirmName.trim() !== deletePreview.city.name) { message.warning(`请输入城市名称「${deletePreview.city.name}」确认删除`); return; } setDeleteSubmitting(true); try { await request(`/admin/cities/${deleteTarget.id}`, { method: 'DELETE', body: JSON.stringify({ confirmName: confirmName.trim() }), }); message.success(`已删除城市「${deletePreview.city.name}」`); setDeleteOpen(false); setDeleteTarget(null); setDeletePreview(null); setConfirmName(''); if (detail?.id === deleteTarget.id) setDrawerOpen(false); void reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } finally { setDeleteSubmitting(false); } }; const columns: ColumnsType = [ { title: '编码', dataIndex: 'code', width: 90 }, { title: '城市', dataIndex: 'name', width: 100 }, { title: '省份', dataIndex: 'province', width: 90 }, { title: '状态', dataIndex: 'status', width: 90, render: (s) => {CITY_STATUS_LABELS[s] || s} }, { title: '合伙人', dataIndex: 'partnerBindingCount', width: 90 }, { title: '门店', dataIndex: 'storeCount', width: 70, render: (n: number, row) => ( {n ?? 0} ), }, { title: '订单', dataIndex: 'orderCount', width: 70 }, { title: '创建', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 140, render: (_, row) => ( {canDeleteCities ? ( ) : null} ), }, ]; const warehouseColumns: ColumnsType = [ { title: '仓库名', dataIndex: 'name' }, { title: '地址', dataIndex: 'address', ellipsis: true }, { title: '联系人', dataIndex: 'contactName', width: 90 }, { title: '电话', dataIndex: 'contactPhone', width: 120 }, { title: '管仓', dataIndex: 'managerType', width: 100, render: (v: WarehouseManagerType) => WAREHOUSE_MANAGER_LABELS[v] || v, }, { title: '合伙人', dataIndex: 'partnerCompanyName', ellipsis: true, render: (v) => v || '—' }, { title: '状态', dataIndex: 'status', width: 80, render: (s) => {s} }, { title: '操作', width: 120, render: (_, row) => ( ), }, ]; return (
城市
{ setFilters(v); setPage(1); }}> ({ value: p.id, label: p.companyName }))} /> )} ({ value: p.id, label: p.companyName }))} /> )} setConfirmName(e.target.value)} disabled={deleteSubmitting} /> ) : ( 存在阻断项,无法删除。请先处理订单等关联数据。 )} )}
); }