import React, { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { Card, Descriptions, Tag, Spin, Empty, Row, Col, Statistic, Table, Button, Divider, Typography, Badge } from 'antd' import { ArrowLeftOutlined, HomeOutlined, FileTextOutlined, DollarOutlined, UserOutlined, PhoneOutlined } from '@ant-design/icons' import axios from 'axios' const { Title, Text } = Typography interface Contact { name: string position: string phone: string is_primary?: boolean } interface Customer { id: number code: string name: string address: string contacts: Contact[] remark: string total_contract_amount: number total_received: number total_receivable: number created_at: string } interface Project { id: number project_code: string name: string contract_amount: string status: string customer_id: number } interface PaymentNode { id: number project_id: number amount: number paid_amount: number } interface Quotation { id: number version: number quotation_date: string amount: number currency: string status: string file_url?: string remark?: string created_at: string } interface BudgetProject { id: number name: string customer_id: number customer_name: string manager_id: number manager_name: string location?: string survey_date?: string intermediary?: string intermediary_fee_type?: string intermediary_fee_value?: number customer_requirements?: string project_overview?: string attachments?: string[] survey_photos?: string[] status: string days_in_status: number created_at: string quotations: Quotation[] } const CustomerDetail: React.FC = () => { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const [customer, setCustomer] = useState(null) const [projects, setProjects] = useState([]) const [paymentNodes, setPaymentNodes] = useState([]) const [budgetProjects, setBudgetProjects] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { fetchCustomerDetail() fetchRelatedProjects() fetchRelatedBudgetProjects() }, [id]) const fetchCustomerDetail = async () => { try { const res = await fetch(`/api/customers/${id}`) const data = await res.json() if (data.success) setCustomer(data.data) } catch (error) { console.error('获取客户详情失败:', error) } finally { setLoading(false) } } const fetchRelatedProjects = async () => { try { // 获取所有项目,筛选关联到此客户的 const res = await fetch('/api/projects') const data = await res.json() if (data.success) { const customerProjects = (data.data || []).filter((p: Project) => p.customer_id === parseInt(id)) setProjects(customerProjects) // 获取所有付款节点 const nodesRes = await fetch('/api/payment-nodes') const nodesData = await nodesRes.json() if (nodesData.success) { setPaymentNodes(nodesData.data || []) } } } catch (error) { console.error('获取项目失败:', error) } } const fetchRelatedBudgetProjects = async () => { try { // 获取与当前客户关联的预算项目 const res = await axios.get('/api/budget-projects', { params: { customer_id: id } }) if (res.data.success) { setBudgetProjects(res.data.data || []) } } catch (error) { console.error('获取预算项目失败:', error) } } if (loading) return if (!customer) return // 计算财务数据 const totalContract = projects.reduce((sum, p) => sum + (parseFloat(p.contract_amount) || 0), 0) // 从付款节点计算已收金额 const projectIds = projects.map(p => p.id) const relatedNodes = paymentNodes.filter(n => projectIds.includes(n.project_id)) const totalReceived = relatedNodes.reduce((sum, n) => sum + (n.paid_amount || 0), 0) const totalReceivable = relatedNodes.reduce((sum, n) => sum + ((n.amount || 0) - (n.paid_amount || 0)), 0) const projectColumns = [ { title: '项目编号', dataIndex: 'project_code', key: 'project_code', width: 120 }, { title: '项目名称', dataIndex: 'name', key: 'name', render: (v: string) => {v} }, { title: '合同金额', dataIndex: 'contract_amount', key: 'contract_amount', align: 'right' as const, render: (v: string) => `¥${(parseFloat(v) || 0).toLocaleString()}` }, { title: '状态', dataIndex: 'status', key: 'status', align: 'center' as const, render: (v: string) => } ] const budgetProjectColumns = [ { title: '项目名称', dataIndex: 'name', key: 'name', render: (v: string, record: BudgetProject) => ( navigate(`/budget-projects/${record.id}`)} style={{ cursor: 'pointer', color: '#1890ff' }}> {v} ) }, { title: '业务经理', dataIndex: 'manager_name', key: 'manager_name' }, { title: '状态', dataIndex: 'status', key: 'status', align: 'center' as const, render: (v: string) => { const statusMap: Record = { negotiating: { status: 'processing', text: '商谈中' }, signed: { status: 'success', text: '已签约' }, unsigned: { status: 'error', text: '未签约' } } const config = statusMap[v] || { status: 'default', text: v } return } }, { title: '报价版本数', dataIndex: 'quotations', key: 'quotations', align: 'center' as const, render: (quotations: Quotation[]) => (quotations || []).length }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (v: string) => v.split('T')[0] } ] return (
<HomeOutlined style={{ marginRight: 8, color: '#52c41a' }} /> {customer.name} {/* ========== 卡片1:基本信息 ========== */} 基本信息} style={{ marginBottom: 24, borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}> {customer.code} {customer.address || '-'} {customer.remark && ( <>
备注:
{customer.remark}
)}
联系人
{(customer.contacts || []).map((contact, i) => (
{contact.name || '未命名'} {contact.is_primary && 主联系人}
{contact.position &&
职位:{contact.position}
} {contact.phone &&
电话:{contact.phone}
}
))}
{(customer.contacts || []).length === 0 && }
{/* ========== 卡片2:关联项目 ========== */} 关联项目 ({projects.length}个)} style={{ marginBottom: 24, borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}> {projects.length > 0 ? ( ) : ( )} {/* ========== 卡片4:关联预算项目 ========== */} 关联预算项目 ({budgetProjects.length}个)} style={{ marginBottom: 24, borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}> {budgetProjects.length > 0 ? (
) : ( )} {/* ========== 卡片3:财务信息 ========== */} 财务信息} style={{ borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}>
项目明细
{projects.length > 0 ? (
) : ( )} ) } export default CustomerDetail