备份:大改造前的完整版本 - 修复合同细节/付款节点/文件上传/施工管理/项目保存等BUG

This commit is contained in:
root
2026-05-15 12:02:24 +08:00
parent 1cad1e438d
commit fad28741bd
6157 changed files with 5147 additions and 877912 deletions
+118 -78
View File
@@ -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>