Files
yunhaifinance/frontend/src/pages/PaymentPlansPage.tsx
T

488 lines
15 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import {
Table, Button, Modal, Form, Input, Select, message, Space, Tag, Card,
Row, Col, DatePicker, InputNumber, Descriptions, Divider
} from 'antd'
import {
PlusOutlined, EditOutlined, EyeOutlined, CheckOutlined,
CloseOutlined
} from '@ant-design/icons'
import type { ColumnsType } from 'antd/es/table'
import dayjs from 'dayjs'
// ==================== 类型定义 ====================
interface PaymentPlan {
id: number
purchase_order_id: number
code: string
payment_date: string
amount: number
currency: string
payment_type: string
status: string
description: string
created_by: string
created_at: string
updated_at: string
}
interface PurchaseOrder {
id: number
code: string
supplier_name: string
total_amount: number
currency: string
}
// ==================== 组件 ====================
const PaymentPlansPage: React.FC = () => {
// 状态
const [paymentPlans, setPaymentPlans] = useState<PaymentPlan[]>([])
const [loading, setLoading] = useState(false)
const [purchaseOrders, setPurchaseOrders] = useState<PurchaseOrder[]>([])
// 弹窗状态
const [modalVisible, setModalVisible] = useState(false)
const [detailModalVisible, setDetailModalVisible] = useState(false)
const [editingPlan, setEditingPlan] = useState<PaymentPlan | null>(null)
const [viewingPlan, setViewingPlan] = useState<PaymentPlan | null>(null)
// 表单
const [form] = Form.useForm()
const navigate = useNavigate()
// ==================== 数据加载 ====================
const fetchPaymentPlans = async () => {
setLoading(true)
try {
const response = await fetch('/api/payment-plans')
const data = await response.json()
if (data.success) {
setPaymentPlans(data.data)
} else {
message.error('获取付款计划列表失败')
}
} catch (error) {
console.error('获取付款计划列表失败:', error)
message.error('获取付款计划列表失败')
} finally {
setLoading(false)
}
}
const fetchPurchaseOrders = async () => {
try {
const response = await fetch('/api/purchase-orders')
const data = await response.json()
if (data.success) {
setPurchaseOrders(data.data)
}
} catch (error) {
console.error('获取采购订单列表失败:', error)
}
}
const fetchPlanDetail = async (id: number) => {
try {
const response = await fetch(`/api/payment-plans/${id}`)
const data = await response.json()
if (data.success) {
setViewingPlan(data.data)
setDetailModalVisible(true)
} else {
message.error('获取付款计划详情失败')
}
} catch (error) {
console.error('获取付款计划详情失败:', error)
message.error('获取付款计划详情失败')
}
}
useEffect(() => {
fetchPurchaseOrders()
}, [])
useEffect(() => {
fetchPaymentPlans()
}, [])
// ==================== 操作函数 ====================
const handleCreate = () => {
setEditingPlan(null)
form.resetFields()
form.setFieldsValue({
payment_date: dayjs(),
currency: 'CNY',
payment_type: 'partial',
status: 'pending',
created_by: '系统管理员'
})
setModalVisible(true)
}
const handleEdit = async (record: PaymentPlan) => {
try {
// 获取完整的付款计划详情
const response = await fetch(`/api/payment-plans/${record.id}`)
const data = await response.json()
if (data.success && data.data) {
const fullRecord = data.data
setEditingPlan(fullRecord)
// 打开模态框
setModalVisible(true);
// 使用 setTimeout 确保模态框已渲染后再设置表单值
setTimeout(() => {
form.resetFields();
// 设置表单值
form.setFieldsValue({
purchase_order_id: fullRecord.purchase_order_id,
payment_date: fullRecord.payment_date ? dayjs(fullRecord.payment_date) : dayjs(),
amount: fullRecord.amount,
currency: fullRecord.currency || 'CNY',
payment_type: fullRecord.payment_type || 'partial',
status: fullRecord.status || 'pending',
description: fullRecord.description,
created_by: fullRecord.created_by || '系统管理员'
});
}, 100);
} else {
message.error('获取付款计划详情失败')
}
} catch (error) {
console.error('获取付款计划详情失败:', error)
message.error('获取付款计划详情失败')
}
}
const handleSave = async () => {
try {
const values = await form.validateFields()
const requestData = {
...values,
payment_date: values.payment_date.format('YYYY-MM-DD')
}
let response
if (editingPlan) {
// 更新现有付款计划
response = await fetch(`/api/payment-plans/${editingPlan.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
})
} else {
// 创建新付款计划
response = await fetch('/api/payment-plans', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
})
}
const data = await response.json()
if (data.success) {
message.success(editingPlan ? '保存成功' : '创建成功')
setModalVisible(false)
fetchPaymentPlans()
} else {
message.error(editingPlan ? '保存失败' : '创建失败')
}
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败')
}
}
// ==================== 渲染 ====================
const getStatusTag = (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = {
pending: { color: 'blue', text: '待处理' },
approved: { color: 'green', text: '已审批' },
executed: { color: 'purple', text: '已执行' },
cancelled: { color: 'red', text: '已取消' }
}
const info = statusMap[status] || { color: 'default', text: status }
return <Tag color={info.color}>{info.text}</Tag>
}
const getPaymentTypeTag = (type: string) => {
const typeMap: Record<string, { color: string; text: string }> = {
partial: { color: 'blue', text: '部分付款' },
full: { color: 'green', text: '全额付款' }
}
const info = typeMap[type] || { color: 'default', text: type }
return <Tag color={info.color}>{info.text}</Tag>
}
const columns: ColumnsType<PaymentPlan> = [
{
title: '计划编号',
dataIndex: 'code',
key: 'code',
width: 150,
ellipsis: true
},
{
title: '采购订单',
dataIndex: 'purchase_order_id',
key: 'purchase_order_id',
width: 140,
render: (id) => {
const order = purchaseOrders.find(o => o.id == id)
return order ? order.code : id
}
},
{
title: '付款日期',
dataIndex: 'payment_date',
key: 'payment_date',
width: 110,
render: (date) => dayjs(date).format('MM-DD')
},
{
title: '金额',
dataIndex: 'amount',
key: 'amount',
width: 120,
align: 'right',
render: (amount, record) => (
<span style={{ fontWeight: 500, color: '#1890ff' }}>
{record.currency} {amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</span>
)
},
{
title: '付款类型',
dataIndex: 'payment_type',
key: 'payment_type',
width: 100,
align: 'center',
render: getPaymentTypeTag
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 90,
align: 'center',
render: getStatusTag
},
{
title: '创建人',
dataIndex: 'created_by',
key: 'created_by',
width: 100
},
{
title: '操作',
key: 'actions',
width: 150,
fixed: 'right',
render: (_, record) => (
<Space size={4}>
<Button
size="small"
type="text"
icon={<EyeOutlined />}
onClick={() => fetchPlanDetail(record.id)}
>
详情
</Button>
<Button
size="small"
type="primary"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
编辑
</Button>
</Space>
)
}
]
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<h2 style={{ marginBottom: 8 }}>付款计划</h2>
<p style={{ color: '#888', marginBottom: 0 }}>管理采购订单的付款计划</p>
</div>
<Card extra={<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>新建付款计划</Button>}>
<Table
columns={columns}
dataSource={paymentPlans}
rowKey="id"
loading={loading}
pagination={{ pageSize: 20 }}
size="small"
scroll={{ x: 1200 }}
/>
</Card>
{/* 编辑/新建弹窗 */}
<Modal
title={editingPlan ? '编辑付款计划' : '新建付款计划'}
open={modalVisible}
onCancel={() => {
setModalVisible(false)
setEditingPlan(null)
}}
footer={[
<Button key="cancel" onClick={() => {
setModalVisible(false)
setEditingPlan(null)
}}>取消</Button>,
<Button key="save" type="primary" onClick={handleSave}>保存</Button>
]}
width={600}
>
<Form form={form} layout="vertical">
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="purchase_order_id"
label="关联采购订单"
rules={[{ required: true, message: '请选择采购订单' }]}
>
<Select placeholder="请选择采购订单" allowClear>
{purchaseOrders.map(order => (
<Select.Option key={order.id} value={order.id}>
{order.code} - {order.supplier_name} ({order.currency} {order.total_amount})
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="payment_date"
label="付款日期"
rules={[{ required: true, message: '请选择付款日期' }]}
>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="amount"
label="付款金额"
rules={[{ required: true, message: '请输入付款金额' }]}
>
<InputNumber min={0} precision={2} style={{ width: '100%' }} placeholder="付款金额" />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="currency"
label="币种"
rules={[{ required: true, message: '请选择币种' }]}
>
<Select placeholder="请选择币种">
<Select.Option value="CNY">人民币</Select.Option>
<Select.Option value="USD">美元</Select.Option>
<Select.Option value="LAK">老挝基普</Select.Option>
<Select.Option value="THB">泰铢</Select.Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="payment_type"
label="付款类型"
rules={[{ required: true, message: '请选择付款类型' }]}
>
<Select placeholder="请选择付款类型">
<Select.Option value="partial">部分付款</Select.Option>
<Select.Option value="full">全额付款</Select.Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="status"
label="状态"
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
<Select.Option value="pending">待处理</Select.Option>
<Select.Option value="approved">已审批</Select.Option>
<Select.Option value="executed">已执行</Select.Option>
<Select.Option value="cancelled">已取消</Select.Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="created_by"
label="创建人"
rules={[{ required: true, message: '请输入创建人' }]}
>
<Input placeholder="请输入创建人" />
</Form.Item>
</Col>
</Row>
<Form.Item
name="description"
label="描述"
>
<Input.TextArea rows={3} placeholder="请输入付款计划描述" />
</Form.Item>
</Form>
</Modal>
{/* 详情弹窗 */}
<Modal
title="付款计划详情"
open={detailModalVisible}
onCancel={() => setDetailModalVisible(false)}
footer={null}
width={600}
>
{viewingPlan && (
<>
<Descriptions bordered column={2} size="small">
<Descriptions.Item label="计划编号">{viewingPlan.code}</Descriptions.Item>
<Descriptions.Item label="状态">{getStatusTag(viewingPlan.status)}</Descriptions.Item>
<Descriptions.Item label="采购订单">
{(() => {
const order = purchaseOrders.find(o => o.id == viewingPlan.purchase_order_id)
return order ? order.code : viewingPlan.purchase_order_id
})()}
</Descriptions.Item>
<Descriptions.Item label="付款类型">{getPaymentTypeTag(viewingPlan.payment_type)}</Descriptions.Item>
<Descriptions.Item label="付款日期">{viewingPlan.payment_date}</Descriptions.Item>
<Descriptions.Item label="币种">{viewingPlan.currency}</Descriptions.Item>
<Descriptions.Item label="金额" span={2}>{viewingPlan.amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</Descriptions.Item>
<Descriptions.Item label="描述" span={2}>{viewingPlan.description || '-'}</Descriptions.Item>
<Descriptions.Item label="创建人" span={2}>{viewingPlan.created_by}</Descriptions.Item>
</Descriptions>
</>
)}
</Modal>
</div>
)
}
export default PaymentPlansPage;