备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Table, Button, Modal, Form, Input, Select, message, Space, Tag, Card, Row, Col, Statistic } from 'antd'
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ShopOutlined } from '@ant-design/icons'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
|
||||
interface Supplier {
|
||||
id: number
|
||||
code: string
|
||||
name: string
|
||||
type: string
|
||||
country: string
|
||||
total_purchase_amount: number
|
||||
total_paid: number
|
||||
total_payable: number
|
||||
rating: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const SupplierPage: React.FC = () => {
|
||||
const [suppliers, setSuppliers] = useState<Supplier[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [modalVisible, setModalVisible] = useState(false)
|
||||
const [editingSupplier, setEditingSupplier] = useState<Supplier | null>(null)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [form] = Form.useForm()
|
||||
|
||||
// 统计数据
|
||||
const stats = {
|
||||
total: suppliers.length,
|
||||
totalPurchase: suppliers.reduce((sum, s) => sum + s.total_purchase_amount, 0),
|
||||
totalPayable: suppliers.reduce((sum, s) => sum + s.total_payable, 0),
|
||||
avgRating: suppliers.length > 0
|
||||
? suppliers.reduce((sum, s) => sum + s.rating, 0) / suppliers.length
|
||||
: 0
|
||||
}
|
||||
|
||||
// 获取供应商列表
|
||||
const fetchSuppliers = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch('/api/suppliers')
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
setSuppliers(data.data || [])
|
||||
} else {
|
||||
message.error('获取供应商列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取供应商失败:', error)
|
||||
message.error('网络错误')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchSuppliers()
|
||||
}, [])
|
||||
|
||||
// 表格列定义
|
||||
const columns: ColumnsType<Supplier> = [
|
||||
{
|
||||
title: '编号',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
width: 120,
|
||||
sorter: (a, b) => a.code.localeCompare(b.code)
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text) => <span style={{ fontWeight: 'bold' }}>{text}</span>
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 100,
|
||||
render: (type) => {
|
||||
const typeMap: Record<string, { color: string, text: string }> = {
|
||||
'china': { color: 'red', text: '中国供应商' },
|
||||
'local': { color: 'green', text: '本地供应商' },
|
||||
'international': { color: 'blue', text: '国际供应商' }
|
||||
}
|
||||
const info = typeMap[type] || { color: 'default', text: type }
|
||||
return <Tag color={info.color}>{info.text}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '国家',
|
||||
dataIndex: 'country',
|
||||
key: 'country',
|
||||
width: 100,
|
||||
render: (country) => (
|
||||
<Tag color={country === 'China' ? 'red' : country === 'Thailand' ? 'purple' : 'blue'}>
|
||||
{country}
|
||||
</Tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '评分',
|
||||
dataIndex: 'rating',
|
||||
key: 'rating',
|
||||
width: 100,
|
||||
render: (rating) => {
|
||||
const stars = '★'.repeat(rating) + '☆'.repeat(5 - rating)
|
||||
return (
|
||||
<div style={{ color: rating >= 4 ? '#52c41a' : rating >= 3 ? '#faad14' : '#ff4d4f' }}>
|
||||
{stars}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
sorter: (a, b) => a.rating - b.rating
|
||||
},
|
||||
{
|
||||
title: '采购金额',
|
||||
dataIndex: 'total_purchase_amount',
|
||||
key: 'total_purchase_amount',
|
||||
width: 150,
|
||||
render: (amount) => `¥${amount.toLocaleString()}`,
|
||||
sorter: (a, b) => a.total_purchase_amount - b.total_purchase_amount
|
||||
},
|
||||
{
|
||||
title: '应付金额',
|
||||
dataIndex: 'total_payable',
|
||||
key: 'total_payable',
|
||||
width: 150,
|
||||
render: (amount) => (
|
||||
<span style={{ color: amount > 0 ? '#ff4d4f' : '#52c41a' }}>
|
||||
¥{amount.toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.total_payable - b.total_payable
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => handleEdit(record)} size="small" />
|
||||
<Button type="text" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)} size="small" />
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
// 处理搜索
|
||||
const filteredSuppliers = suppliers.filter(supplier =>
|
||||
supplier.code.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
supplier.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
|
||||
// 处理提交
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
const url = editingSupplier ? `/api/suppliers/${editingSupplier.id}` : '/api/suppliers'
|
||||
const method = editingSupplier ? 'PUT' : 'POST'
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(values)
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
message.success(editingSupplier ? '更新成功' : '创建成功')
|
||||
setModalVisible(false)
|
||||
form.resetFields()
|
||||
setEditingSupplier(null)
|
||||
fetchSuppliers()
|
||||
} else {
|
||||
message.error(data.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存供应商失败:', error)
|
||||
message.error('操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (supplier: Supplier) => {
|
||||
setEditingSupplier(supplier)
|
||||
form.setFieldsValue(supplier)
|
||||
setModalVisible(true)
|
||||
}
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此供应商吗?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/suppliers/${id}`, { method: 'DELETE' })
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
message.success('删除成功')
|
||||
fetchSuppliers()
|
||||
} else {
|
||||
message.error(data.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('删除失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
setEditingSupplier(null)
|
||||
form.resetFields()
|
||||
setModalVisible(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
{/* 统计卡片 */}
|
||||
<Row gutter={16} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="供应商总数" value={stats.total} prefix={<ShopOutlined />} /></Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="采购总金额" value={stats.totalPurchase} prefix="¥" valueStyle={{ color: '#1890ff' }} /></Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="应付总金额" value={stats.totalPayable} prefix="¥" valueStyle={{ color: stats.totalPayable > 0 ? '#ff4d4f' : '#52c41a' }} /></Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card><Statistic title="平均评分" value={stats.avgRating} precision={1} prefix="★" suffix="/5" /></Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 操作栏 */}
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Input
|
||||
placeholder="搜索供应商编号或名称"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
allowClear
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>新增供应商</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 表格 */}
|
||||
<Card>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredSuppliers}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, showSizeChanger: true, showTotal: (total) => `共 ${total} 条` }}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 模态框 */}
|
||||
<Modal
|
||||
title={editingSupplier ? '编辑供应商' : '新增供应商'}
|
||||
open={modalVisible}
|
||||
onCancel={() => { setModalVisible(false); form.resetFields(); setEditingSupplier(null) }}
|
||||
onOk={() => form.submit()}
|
||||
width={600}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||
<Form.Item name="code" label="编号" rules={[{ required: true, message: '请输入编号' }]}>
|
||||
<Input placeholder="如:SUP-001" />
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||
<Input placeholder="供应商名称" />
|
||||
</Form.Item>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="type" label="类型" initialValue="local">
|
||||
<Select>
|
||||
<Select.Option value="china">中国供应商</Select.Option>
|
||||
<Select.Option value="local">本地供应商</Select.Option>
|
||||
<Select.Option value="international">国际供应商</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="country" label="国家" initialValue="Laos">
|
||||
<Select>
|
||||
<Select.Option value="China">中国</Select.Option>
|
||||
<Select.Option value="Thailand">泰国</Select.Option>
|
||||
<Select.Option value="Laos">老挝</Select.Option>
|
||||
<Select.Option value="Vietnam">越南</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="rating" label="评分" initialValue={5}>
|
||||
<Select>
|
||||
<Select.Option value={5}>★★★★★ (5)</Select.Option>
|
||||
<Select.Option value={4}>★★★★☆ (4)</Select.Option>
|
||||
<Select.Option value={3}>★★★☆☆ (3)</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SupplierPage
|
||||
Reference in New Issue
Block a user