备份:大改造前的完整版本 - 修复合同细节/付款节点/文件上传/施工管理/项目保存等BUG
This commit is contained in:
@@ -9,10 +9,10 @@
|
||||
* - 收款信息:支持多个银行账户,标记默认账户
|
||||
* - 业务台账:订单列表、运费总额、已付/未付金额
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react'
|
||||
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
|
||||
Row, Col, Popconfirm, Tabs, Descriptions, Upload, Image, Checkbox
|
||||
} from 'antd'
|
||||
import {
|
||||
PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined,
|
||||
@@ -21,15 +21,14 @@ import {
|
||||
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
|
||||
code: string
|
||||
name: string
|
||||
address: string
|
||||
phone: string
|
||||
quotation_description: string
|
||||
status: string
|
||||
remark: string
|
||||
created_at: string
|
||||
contacts: Contact[]
|
||||
@@ -88,7 +87,6 @@ interface OrderRecord {
|
||||
const LogisticsCompaniesPage: React.FC = () => {
|
||||
const [companies, setCompanies] = useState<LogisticsCompany[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedStatus, setSelectedStatus] = useState<string | null>(null)
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
const [detailModalVisible, setDetailModalVisible] = useState(false)
|
||||
@@ -106,13 +104,20 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
|
||||
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 params = new URLSearchParams()
|
||||
if (selectedStatus) params.append('status', selectedStatus)
|
||||
|
||||
const response = await fetch(`/api/logistics-companies?${params}`)
|
||||
const response = await fetch('/api/logistics-companies')
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
@@ -147,12 +152,30 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchCompanies()
|
||||
}, [selectedStatus])
|
||||
}, [])
|
||||
|
||||
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) => {
|
||||
@@ -185,15 +208,30 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
: '/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(values)
|
||||
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 {
|
||||
@@ -316,15 +354,6 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusTag = (status: string) => {
|
||||
const statusMap: Record<string, { color: string; text: string }> = {
|
||||
active: { color: 'green', text: '合作中' },
|
||||
inactive: { color: 'default', text: '已停用' }
|
||||
}
|
||||
const info = statusMap[status] || { color: 'default', text: status }
|
||||
return <Tag color={info.color}>{info.text}</Tag>
|
||||
}
|
||||
|
||||
const getFreightStatusTag = (status: string) => {
|
||||
const statusMap: Record<string, { color: string; text: string }> = {
|
||||
pending: { color: 'default', text: '待付款' },
|
||||
@@ -359,14 +388,6 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
ellipsis: true,
|
||||
render: (v: string) => v || '-'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: getStatusTag
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
@@ -395,7 +416,7 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name', width: 100 },
|
||||
{ title: '职位', dataIndex: 'position', key: 'position', width: 80 },
|
||||
{ title: '电话', dataIndex: 'phone', key: 'phone', width: 120 },
|
||||
{ title: '主联系人', dataIndex: 'is_primary', key: 'is_primary', width: 80, render: (v: number) => v ? <Tag color="blue">主联系人</Tag> : null },
|
||||
{ title: '主联系人', dataIndex: 'is_primary', key: 'is_primary', width: 80, render: (v: boolean | number) => v ? <Tag color="blue">主联系人</Tag> : null },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
@@ -415,7 +436,7 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
{ title: '收款户名', dataIndex: 'account_name', key: 'account_name', width: 120 },
|
||||
{ title: '银行账号', dataIndex: 'account_number', key: 'account_number', width: 150 },
|
||||
{ title: '开户银行', dataIndex: 'bank_name', key: 'bank_name', width: 120 },
|
||||
{ title: '默认', dataIndex: 'is_default', key: 'is_default', width: 60, render: (v: number) => v ? <Tag color="green">默认</Tag> : null },
|
||||
{ title: '默认', dataIndex: 'is_default', key: 'is_default', width: 60, render: (v: boolean | number) => v ? <Tag color="green">默认</Tag> : null },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
@@ -450,14 +471,6 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<Card extra={<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>新建物流公司</Button>}>
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
<Col span={6}>
|
||||
<Select placeholder="选择状态筛选" allowClear style={{ width: '100%' }} onChange={(v) => setSelectedStatus(v)}>
|
||||
<Select.Option value="active">合作中</Select.Option>
|
||||
<Select.Option value="inactive">已停用</Select.Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Table columns={columns} dataSource={companies} rowKey="id" loading={loading} pagination={{ pageSize: 20 }} size="small" scroll={{ x: 1100 }} />
|
||||
</Card>
|
||||
|
||||
@@ -466,48 +479,77 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
title={editingCompany ? '编辑物流公司' : '新建物流公司'}
|
||||
open={modalVisible}
|
||||
onOk={handleSave}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
width={600}
|
||||
onCancel={() => {
|
||||
if (form.isFieldsTouched()) {
|
||||
Modal.confirm({
|
||||
title: '确认关闭',
|
||||
content: '表单数据尚未保存,关闭后可通过草稿恢复。确定关闭吗?',
|
||||
okText: '关闭',
|
||||
cancelText: '继续编辑',
|
||||
onOk: () => {
|
||||
saveDraft()
|
||||
setModalVisible(false)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
setModalVisible(false)
|
||||
}
|
||||
}}
|
||||
width={700}
|
||||
maskClosable={false}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="name" label="公司名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入公司名称" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="code" label="公司编码">
|
||||
<Input placeholder="自动生成或手动输入" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="phone" label="联系电话">
|
||||
<Input placeholder="请输入联系电话" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form form={form} layout="vertical" onValuesChange={handleFormChange}>
|
||||
<Form.Item name="name" label="公司名称" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入公司名称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="address" label="地址">
|
||||
<Input placeholder="请输入地址" />
|
||||
</Form.Item>
|
||||
<Form.Item name="quotation_description" label="报价描述">
|
||||
<Input.TextArea rows={3} placeholder="请输入报价描述(如:中国-老挝陆运报价、时效等)" />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select placeholder="请选择状态">
|
||||
<Select.Option value="active">合作中</Select.Option>
|
||||
<Select.Option value="inactive">已停用</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="remark" label="备注">
|
||||
<Input.TextArea rows={2} placeholder="请输入备注" />
|
||||
</Form.Item>
|
||||
{!editingCompany && (
|
||||
<Form.List name="contacts">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
<div style={{ marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontWeight: 500 }}>联系人</span>
|
||||
<Button type="dashed" size="small" icon={<PlusOutlined />} onClick={() => add()}>添加联系人</Button>
|
||||
</div>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Row key={key} gutter={8} style={{ marginBottom: 8 }}>
|
||||
<Col span={6}>
|
||||
<Form.Item {...restField} name={[name, 'name']} rules={[{ required: true, message: '必填' }]}>
|
||||
<Input placeholder="姓名" size="small" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Form.Item {...restField} name={[name, 'phone']}>
|
||||
<Input placeholder="电话" size="small" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Form.Item {...restField} name={[name, 'position']}>
|
||||
<Input placeholder="职位" size="small" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Form.Item {...restField} name={[name, 'is_primary']} valuePropName="checked">
|
||||
<Checkbox>主联系人</Checkbox>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Button type="text" danger size="small" icon={<DeleteOutlined />} onClick={() => remove(name)} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -525,13 +567,11 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
<Tabs.TabPane tab={<span><FileTextOutlined /> 基本信息</span>} key="basic">
|
||||
<Descriptions bordered column={2}>
|
||||
<Descriptions.Item label="公司名称">{currentCompany.name}</Descriptions.Item>
|
||||
<Descriptions.Item label="公司编码">{currentCompany.code}</Descriptions.Item>
|
||||
<Descriptions.Item label="联系电话">{currentCompany.phone || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="邮箱">{currentCompany.email || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">{currentCompany.created_at}</Descriptions.Item>
|
||||
<Descriptions.Item label="地址" span={2}>{currentCompany.address || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="报价描述" span={2}>{currentCompany.quotation_description || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">{getStatusTag(currentCompany.status)}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">{currentCompany.created_at}</Descriptions.Item>
|
||||
{currentCompany.remark && <Descriptions.Item label="备注" span={2}>{currentCompany.remark}</Descriptions.Item>}
|
||||
</Descriptions>
|
||||
</Tabs.TabPane>
|
||||
@@ -588,10 +628,10 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="is_primary" label="主联系人" valuePropName="checked">
|
||||
<Form.Item name="is_primary" label="主联系人">
|
||||
<Select placeholder="是否为主联系人">
|
||||
<Select.Option value={1}>是</Select.Option>
|
||||
<Select.Option value={0}>否</Select.Option>
|
||||
<Select.Option value={true}>是</Select.Option>
|
||||
<Select.Option value={false}>否</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
@@ -626,8 +666,8 @@ const LogisticsCompaniesPage: React.FC = () => {
|
||||
</Form.Item>
|
||||
<Form.Item name="is_default" label="默认账户">
|
||||
<Select placeholder="是否为默认账户">
|
||||
<Select.Option value={1}>是</Select.Option>
|
||||
<Select.Option value={0}>否</Select.Option>
|
||||
<Select.Option value={true}>是</Select.Option>
|
||||
<Select.Option value={false}>否</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
Reference in New Issue
Block a user