202 lines
6.8 KiB
TypeScript
202 lines
6.8 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|||
|
|
import { useParams, useNavigate } from 'react-router-dom'
|
||
|
|
import {
|
||
|
|
Card, Descriptions, Tag, Spin, Empty, Row, Col, Button, Divider, Typography, Tabs
|
||
|
|
} from 'antd'
|
||
|
|
import {
|
||
|
|
ArrowLeftOutlined, ShopOutlined, UserOutlined, PhoneOutlined, BankOutlined, DollarOutlined
|
||
|
|
} from '@ant-design/icons'
|
||
|
|
import BusinessLedgerTab from '../components/BusinessLedgerTab'
|
||
|
|
|
||
|
|
const { Title, Text } = Typography
|
||
|
|
|
||
|
|
interface Contact {
|
||
|
|
name: string
|
||
|
|
position: string
|
||
|
|
phone: string
|
||
|
|
is_primary?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PaymentInfo {
|
||
|
|
id: number
|
||
|
|
account_name: string
|
||
|
|
bank_account: string
|
||
|
|
bank_name: string
|
||
|
|
qr_code?: string
|
||
|
|
is_primary: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
interface LedgerSummary {
|
||
|
|
item_count: number
|
||
|
|
total_order_amount: number
|
||
|
|
total_paid_amount: number
|
||
|
|
total_unpaid_amount: number
|
||
|
|
}
|
||
|
|
|
||
|
|
interface LedgerItem {
|
||
|
|
id: number
|
||
|
|
type: string
|
||
|
|
code: string
|
||
|
|
name: string
|
||
|
|
order_amount: number
|
||
|
|
paid_amount: number
|
||
|
|
unpaid_amount: number
|
||
|
|
status: string
|
||
|
|
}
|
||
|
|
|
||
|
|
interface Supplier {
|
||
|
|
id: number
|
||
|
|
code: string
|
||
|
|
name: string
|
||
|
|
supply_category: string
|
||
|
|
country: string
|
||
|
|
contacts: Contact[]
|
||
|
|
payment_infos: PaymentInfo[]
|
||
|
|
remark: string
|
||
|
|
total_purchase_amount: number
|
||
|
|
total_paid: number
|
||
|
|
total_payable: number
|
||
|
|
ledger?: {
|
||
|
|
summary: LedgerSummary
|
||
|
|
items: LedgerItem[]
|
||
|
|
}
|
||
|
|
created_at: string
|
||
|
|
}
|
||
|
|
|
||
|
|
const SupplierDetail: React.FC = () => {
|
||
|
|
const { id } = useParams<{ id: string }>()
|
||
|
|
const navigate = useNavigate()
|
||
|
|
const [supplier, setSupplier] = useState<Supplier | null>(null)
|
||
|
|
const [loading, setLoading] = useState(true)
|
||
|
|
const [activeTab, setActiveTab] = useState('basic')
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
fetchSupplierDetail()
|
||
|
|
}, [id])
|
||
|
|
|
||
|
|
const fetchSupplierDetail = async () => {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/suppliers/${id}`)
|
||
|
|
const data = await res.json()
|
||
|
|
if (data.success) setSupplier(data.data)
|
||
|
|
} catch (error) {
|
||
|
|
console.error('获取供应商详情失败:', error)
|
||
|
|
} finally {
|
||
|
|
setLoading(false)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (loading) return <Spin style={{ display: 'flex', justifyContent: 'center', padding: 50 }} />
|
||
|
|
if (!supplier) return <Empty description="供应商不存在" style={{ marginTop: 100 }} />
|
||
|
|
|
||
|
|
const tabItems = [
|
||
|
|
{
|
||
|
|
key: 'basic',
|
||
|
|
label: <span><UserOutlined /> 基本信息</span>,
|
||
|
|
children: (
|
||
|
|
<>
|
||
|
|
<Descriptions bordered column={{ xs: 1, sm: 2, md: 3 }} size="small">
|
||
|
|
<Descriptions.Item label="编号">{supplier.code}</Descriptions.Item>
|
||
|
|
<Descriptions.Item label="供应类别">{supplier.supply_category || '-'}</Descriptions.Item>
|
||
|
|
<Descriptions.Item label="国家"><Tag color="blue">{supplier.country || '-'}</Tag></Descriptions.Item>
|
||
|
|
</Descriptions>
|
||
|
|
{supplier.remark && (
|
||
|
|
<div style={{ marginTop: 16 }}>
|
||
|
|
<Text type="secondary">备注:</Text>
|
||
|
|
<div style={{ padding: 12, background: '#e6f7ff', borderRadius: 4, border: '1px solid #91d5ff', marginTop: 8 }}>{supplier.remark}</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'contacts',
|
||
|
|
label: <span><PhoneOutlined /> 联系人</span>,
|
||
|
|
children: (
|
||
|
|
<>
|
||
|
|
<Row gutter={[16, 16]}>
|
||
|
|
{(supplier.contacts || []).map((contact, i) => (
|
||
|
|
<Col key={i} xs={24} sm={12} lg={8}>
|
||
|
|
<Card size="small" style={{ borderLeft: contact.is_primary ? '3px solid #1890ff' : '3px solid #d9d9d9', background: contact.is_primary ? '#f0f5ff' : '#fff' }}>
|
||
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||
|
|
<Text strong>{contact.name || '未命名'}</Text>
|
||
|
|
{contact.is_primary && <Tag color="blue">主联系人</Tag>}
|
||
|
|
</div>
|
||
|
|
<div style={{ color: '#666', fontSize: 13 }}>
|
||
|
|
{contact.position && <div>职位:{contact.position}</div>}
|
||
|
|
{contact.phone && <div>电话:{contact.phone}</div>}
|
||
|
|
</div>
|
||
|
|
</Card>
|
||
|
|
</Col>
|
||
|
|
))}
|
||
|
|
</Row>
|
||
|
|
{(supplier.contacts || []).length === 0 && <Empty description="暂无联系人" image={Empty.PRESENTED_IMAGE_SIMPLE} />}
|
||
|
|
</>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'payment',
|
||
|
|
label: <span><BankOutlined /> 收款信息</span>,
|
||
|
|
children: (
|
||
|
|
<>
|
||
|
|
<Row gutter={[16, 16]}>
|
||
|
|
{(supplier.payment_infos || []).map((payment, i) => (
|
||
|
|
<Col key={i} xs={24} sm={12} lg={8}>
|
||
|
|
<Card size="small" style={{ borderLeft: payment.is_primary ? '3px solid #1890ff' : '3px solid #d9d9d9', background: payment.is_primary ? '#f0f5ff' : '#fff' }}>
|
||
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||
|
|
<Text strong>{payment.bank_name || '未命名'}</Text>
|
||
|
|
{payment.is_primary && <Tag color="blue">主要收款账户</Tag>}
|
||
|
|
</div>
|
||
|
|
<div style={{ color: '#666', fontSize: 13 }}>
|
||
|
|
{payment.account_name && <div>户名:{payment.account_name}</div>}
|
||
|
|
{payment.bank_account && <div>账号:{payment.bank_account}</div>}
|
||
|
|
{payment.qr_code && (
|
||
|
|
<div style={{ marginTop: 8 }}>
|
||
|
|
<Text type="secondary">收款码:</Text>
|
||
|
|
<div style={{ marginTop: 4 }}>
|
||
|
|
<img src={payment.qr_code} alt="收款码" style={{ maxWidth: '100px', maxHeight: '100px' }} />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</Card>
|
||
|
|
</Col>
|
||
|
|
))}
|
||
|
|
</Row>
|
||
|
|
{(supplier.payment_infos || []).length === 0 && <Empty description="暂无收款信息" image={Empty.PRESENTED_IMAGE_SIMPLE} />}
|
||
|
|
</>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'ledger',
|
||
|
|
label: <span><DollarOutlined /> 业务台账</span>,
|
||
|
|
children: (
|
||
|
|
<BusinessLedgerTab
|
||
|
|
partnerType="supplier"
|
||
|
|
summary={supplier.ledger?.summary || { item_count: 0, total_order_amount: 0, total_paid_amount: 0, total_unpaid_amount: 0 }}
|
||
|
|
items={supplier.ledger?.items || []}
|
||
|
|
/>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={{ padding: '16px', maxWidth: 1200, margin: '0 auto' }}>
|
||
|
|
<Button icon={<ArrowLeftOutlined />} onClick={() => navigate('/suppliers')} style={{ marginBottom: 16 }} type="text">
|
||
|
|
返回列表
|
||
|
|
</Button>
|
||
|
|
|
||
|
|
<Title level={4} style={{ marginBottom: 24 }}>
|
||
|
|
<ShopOutlined style={{ marginRight: 8, color: '#1890ff' }} />
|
||
|
|
{supplier.name}
|
||
|
|
</Title>
|
||
|
|
|
||
|
|
<Card style={{ borderRadius: 8 }}>
|
||
|
|
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export default SupplierDetail
|