/** * 物流管理页面 * 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md * 章节:四、物流管理 * * 统一合作伙伴界面规范: * - 基本信息:公司名称、地址、联系方式、报价描述 * - 联系人:支持多个联系人,标记主联系人 * - 收款信息:支持多个银行账户,标记默认账户 * - 业务台账:订单列表、运费总额、已付/未付金额 */ import React, { useState, useEffect, useCallback } from 'react' import { Table, Button, Modal, Form, Input, Select, message, Space, Tag, Card, Row, Col, Popconfirm, Tabs, Descriptions, Upload, Image, Checkbox } from 'antd' import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, PhoneOutlined, BankOutlined, FileTextOutlined, CarOutlined, DollarOutlined } from '@ant-design/icons' import type { ColumnsType } from 'antd/es/table' import dayjs from 'dayjs' import BusinessLedgerTab from '../components/BusinessLedgerTab' import useFormDraft from '../hooks/useFormDraft' interface LogisticsCompany { id: number name: string address: string phone: string quotation_description: string remark: string created_at: string contacts: Contact[] payment_infos: PaymentInfo[] orders: OrderRecord[] total_primary_freight: number total_secondary_freight: number paid_primary_freight: number paid_secondary_freight: number ledger?: { summary: { item_count: number total_primary_freight: number total_secondary_freight: number total_freight: number paid_primary_freight: number paid_secondary_freight: number paid_amount: number unpaid_amount: number } items: any[] } } interface Contact { id: number name: string phone: string position: string is_primary: number } interface PaymentInfo { id: number account_name: string account_number: string bank_name: string qr_code: string is_default: number } interface OrderRecord { id: number code: string order_code: string ship_date: string status: string primary_freight: number primary_freight_currency: string primary_freight_status: string secondary_freight: number secondary_freight_currency: string secondary_freight_status: string } const LogisticsCompaniesPage: React.FC = () => { const [companies, setCompanies] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) const [detailModalVisible, setDetailModalVisible] = useState(false) const [editingCompany, setEditingCompany] = useState(null) const [currentCompany, setCurrentCompany] = useState(null) const [activeDetailTab, setActiveDetailTab] = useState('basic') const [contactModalVisible, setContactModalVisible] = useState(false) const [editingContact, setEditingContact] = useState(null) const [contactForm] = Form.useForm() const [paymentModalVisible, setPaymentModalVisible] = useState(false) const [editingPayment, setEditingPayment] = useState(null) const [paymentForm] = Form.useForm() const [form] = Form.useForm() // 表单草稿保护 const { save: saveDraft, restore: restoreDraft, clear: clearDraft, hasDraft } = useFormDraft({ form, storageKey: 'logistics_company_create', }) const handleFormChange = useCallback(() => { saveDraft() }, [saveDraft]) const fetchCompanies = async () => { setLoading(true) try { const response = await fetch('/api/logistics-companies') const data = await response.json() if (data.success) { setCompanies(data.data) } else { message.error('获取物流公司列表失败') } } catch (error) { console.error('获取物流公司列表失败:', error) message.error('获取物流公司列表失败') } finally { setLoading(false) } } const fetchCompanyDetail = async (id: number) => { try { const response = await fetch(`/api/logistics-companies/${id}`) const data = await response.json() if (data.success) { setCurrentCompany(data.data) setDetailModalVisible(true) setActiveDetailTab('basic') } else { message.error('获取物流公司详情失败') } } catch (error) { console.error('获取物流公司详情失败:', error) message.error('获取物流公司详情失败') } } useEffect(() => { fetchCompanies() }, []) const handleCreate = () => { setEditingCompany(null) form.resetFields() setModalVisible(true) // 检查是否有草稿,提示用户是否恢复 setTimeout(() => { if (hasDraft()) { Modal.confirm({ title: '发现未完成的草稿', content: '检测到上次未提交的物流公司信息,是否恢复?', okText: '恢复草稿', cancelText: '重新填写', onOk: () => { restoreDraft() }, onCancel: () => { clearDraft() form.resetFields() }, }) } }, 0) } const handleEdit = (company: LogisticsCompany) => { setEditingCompany(company) form.setFieldsValue(company) setModalVisible(true) } const handleDelete = async (id: number) => { try { const response = await fetch(`/api/logistics-companies/${id}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { message.success('删除成功') fetchCompanies() } else { message.error(data.message || '删除失败') } } catch (error) { console.error('删除失败:', error) message.error('删除失败') } } const handleSave = async () => { try { const values = await form.validateFields() const url = editingCompany ? `/api/logistics-companies/${editingCompany.id}` : '/api/logistics-companies' const method = editingCompany ? 'PUT' : 'POST' const { contacts, ...companyData } = values const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(companyData) }) const data = await response.json() if (data.success) { if (!editingCompany && contacts && contacts.length > 0) { const companyId = data.data.id for (const contact of contacts) { if (contact.name) { await fetch(`/api/logistics-companies/${companyId}/contacts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(contact) }) } } } message.success(editingCompany ? '更新成功' : '创建成功') clearDraft() setModalVisible(false) fetchCompanies() } else { message.error('保存失败') } } catch (error) { console.error('保存失败:', error) } } const handleAddContact = () => { setEditingContact(null) contactForm.resetFields() setContactModalVisible(true) } const handleEditContact = (contact: Contact) => { setEditingContact(contact) contactForm.setFieldsValue(contact) setContactModalVisible(true) } const handleSaveContact = async () => { try { const values = await contactForm.validateFields() const url = editingContact ? `/api/logistics-companies/${currentCompany?.id}/contacts/${editingContact.id}` : `/api/logistics-companies/${currentCompany?.id}/contacts` const method = editingContact ? 'PUT' : 'POST' const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) }) const data = await response.json() if (data.success) { message.success(editingContact ? '联系人更新成功' : '联系人添加成功') setContactModalVisible(false) fetchCompanyDetail(currentCompany!.id) } else { message.error('操作失败') } } catch (error) { console.error('保存联系人失败:', error) } } const handleDeleteContact = async (contactId: number) => { try { const response = await fetch(`/api/logistics-companies/${currentCompany?.id}/contacts/${contactId}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { message.success('联系人删除成功') fetchCompanyDetail(currentCompany!.id) } else { message.error('删除失败') } } catch (error) { console.error('删除联系人失败:', error) } } const handleAddPayment = () => { setEditingPayment(null) paymentForm.resetFields() setPaymentModalVisible(true) } const handleEditPayment = (payment: PaymentInfo) => { setEditingPayment(payment) paymentForm.setFieldsValue(payment) setPaymentModalVisible(true) } const handleSavePayment = async () => { try { const values = await paymentForm.validateFields() const url = editingPayment ? `/api/logistics-companies/${currentCompany?.id}/payment-infos/${editingPayment.id}` : `/api/logistics-companies/${currentCompany?.id}/payment-infos` const method = editingPayment ? 'PUT' : 'POST' const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values) }) const data = await response.json() if (data.success) { message.success(editingPayment ? '收款信息更新成功' : '收款信息添加成功') setPaymentModalVisible(false) fetchCompanyDetail(currentCompany!.id) } else { message.error('操作失败') } } catch (error) { console.error('保存收款信息失败:', error) } } const handleDeletePayment = async (paymentId: number) => { try { const response = await fetch(`/api/logistics-companies/${currentCompany?.id}/payment-infos/${paymentId}`, { method: 'DELETE' }) const data = await response.json() if (data.success) { message.success('收款信息删除成功') fetchCompanyDetail(currentCompany!.id) } else { message.error('删除失败') } } catch (error) { console.error('删除收款信息失败:', error) } } const getFreightStatusTag = (status: string) => { const statusMap: Record = { pending: { color: 'default', text: '待付款' }, requested: { color: 'blue', text: '已申请' }, paid: { color: 'green', text: '已支付' } } const info = statusMap[status] || { color: 'default', text: status } return {info.text} } const columns: ColumnsType = [ { title: '公司名称', dataIndex: 'name', key: 'name', width: 180, render: (v: string, r: LogisticsCompany) => ( fetchCompanyDetail(r.id)} style={{ fontWeight: 500 }}>{v} ) }, { title: '联系电话', dataIndex: 'phone', key: 'phone', width: 120 }, { title: '报价描述', dataIndex: 'quotation_description', key: 'quotation_description', width: 200, ellipsis: true, render: (v: string) => v || '-' }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', width: 100, render: (v: string) => v ? dayjs(v).format('MM-DD') : '-' }, { title: '操作', key: 'actions', width: 150, fixed: 'right', render: (_, record) => ( }> {/* 编辑/新建弹窗 */} { if (form.isFieldsTouched()) { Modal.confirm({ title: '确认关闭', content: '表单数据尚未保存,关闭后可通过草稿恢复。确定关闭吗?', okText: '关闭', cancelText: '继续编辑', onOk: () => { saveDraft() setModalVisible(false) }, }) } else { setModalVisible(false) } }} width={700} maskClosable={false} >
{!editingCompany && ( {(fields, { add, remove }) => ( <>
联系人
{fields.map(({ key, name, ...restField }) => (
主联系人
{/* TAB3: 收款信息 */} 收款信息} key="payment">
{/* TAB4: 业务台账 */} 业务台账} key="orders"> )} {/* 联系人编辑弹窗 */} setContactModalVisible(false)} width={500} >
{/* 收款信息编辑弹窗 */} setPaymentModalVisible(false)} width={500} >
) } export default LogisticsCompaniesPage