报价单魔魁

This commit is contained in:
‘Liammcl’
2024-12-18 02:01:29 +08:00
parent 585c9b7bf8
commit 9b4a7f5fd8
14 changed files with 1326 additions and 390 deletions

View File

@@ -0,0 +1,264 @@
import React, { useEffect, useState } from 'react';
import { Card, Typography, Descriptions, Table, Button, Space, Spin } from 'antd';
import { ArrowLeftOutlined, PrinterOutlined, EditOutlined } from '@ant-design/icons';
import { useNavigate, useParams } from 'react-router-dom';
import { supabase } from '@/config/supabase';
import ReactDOMServer from 'react-dom/server';
import PrintView from '@/components/PrintView';
const { Title, Text } = Typography;
const printStyles = `
@media print {
/* 隐藏所有按钮和不需要的元素 */
button, .no-print {
display: none !important;
}
/* 只打印卡片内容 */
.ant-card {
box-shadow: none !important;
margin: 0 !important;
padding: 0 !important;
}
/* 移除背景色 */
body, .bg-gray-50 {
background: white !important;
min-height: auto !important;
padding: 0 !important;
}
/* 确保内容完整打印 */
.ant-card-body {
padding: 24px !important;
page-break-inside: avoid;
}
/* 优化表格打印样式 */
.ant-table {
page-break-inside: auto !important;
}
.ant-table-row {
page-break-inside: avoid !important;
}
}
`;
const QuotationView = () => {
const navigate = useNavigate();
const { id } = useParams();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchQuotationDetail = async () => {
try {
const { data: quotation, error } = await supabase
.from('resources')
.select('*')
.eq('id', id)
.single();
if (error) throw error;
setData(quotation);
} catch (error) {
console.error('获取报价单<E4BBB7><E58D95>情失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (id) {
fetchQuotationDetail();
}
}, [id]);
const columns = [
{
title: '序号',
dataIndex: 'index',
width: 100,
render: (_, __, index) => index + 1,
},
{
title: '名称',
dataIndex: 'productName',
width: '40%',
},
{
title: '数量',
dataIndex: 'quantity',
width: '20%',
align: 'right',
render: (value) => value?.toLocaleString('zh-CN', { minimumFractionDigits: 2 }),
},
{
title: '单价',
dataIndex: 'price',
width: '20%',
align: 'right',
render: (value) => value?.toLocaleString('zh-CN', { minimumFractionDigits: 2 }),
},
{
title: '小计',
width: '20%',
align: 'right',
render: (_, record) => (
(record.quantity * record.price)?.toLocaleString('zh-CN', { minimumFractionDigits: 2 })
),
},
];
const handlePrint = () => {
const printWindow = window.open('', '_blank');
const printContent = ReactDOMServer.renderToString(<PrintView data={data} />);
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>打印报价单</title>
<link rel="stylesheet" href="${window.location.origin}/antd.min.css">
<style>
@media print {
body {
padding: 0;
margin: 0;
}
}
</style>
</head>
<body>
${printContent}
<script>
window.onload = function() {
window.print();
window.onafterprint = function() {
window.close();
}
}
</script>
</body>
</html>
`);
printWindow.document.close();
};
if (loading) {
return (
<div className="flex justify-center items-center min-h-screen">
<Spin size="large" />
</div>
);
}
if (!data) {
return null;
}
return (
<>
<style>{printStyles}</style>
<div className="bg-gray-50 min-h-screen ">
<Card className="max-w-5xl mx-auto shadow-lg">
<div className="flex justify-between items-center mb-6 no-print">
<Space>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/company/quotation')}
>
返回列表
</Button>
</Space>
<Space>
<Button
icon={<EditOutlined />}
type="primary"
onClick={() => navigate(`/company/quotaInfo/${data.id}?edit=true`)}
>
编辑报价单
</Button>
<Button
icon={<PrinterOutlined />}
onClick={handlePrint}
>
打印报价单
</Button>
</Space>
</div>
<div className="text-center mb-8">
<Title level={2} className="!mb-2">{data.attributes.quataName}</Title>
<Text type="secondary">报价单号{data.id}</Text>
</div>
<div className="bg-gray-50 p-6 rounded-lg mb-8">
<Descriptions column={2} bordered>
<Descriptions.Item label="客户公司" span={1}>
{data.attributes.companyName}
</Descriptions.Item>
<Descriptions.Item label="供应商" span={1}>
{data.attributes.supplierName}
</Descriptions.Item>
<Descriptions.Item label="报价日期" span={1}>
{new Date(data.created_at).toLocaleDateString('zh-CN')}
</Descriptions.Item>
<Descriptions.Item label="报价有效期" span={1}>
{new Date(Date.now() + 30*24*60*60*1000).toLocaleDateString('zh-CN')}
</Descriptions.Item>
</Descriptions>
</div>
<div className="mb-8">
<Title level={4} className="mb-4">报价明细</Title>
<Table
columns={columns}
dataSource={data.attributes.items}
pagination={false}
rowKey={(record, index) => index}
className="border rounded-lg"
summary={() => (
<Table.Summary fixed>
<Table.Summary.Row className="bg-gray-50 font-bold">
<Table.Summary.Cell index={0} colSpan={4} align="right">
总计{data.attributes.currency}
</Table.Summary.Cell>
<Table.Summary.Cell index={1} align="right">
{data.attributes.totalAmount?.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
style: 'currency',
currency: data.attributes.currency
})}
</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
)}
/>
</div>
{data.attributes.description && (
<div className="bg-gray-50 p-6 rounded-lg">
<Title level={4} className="mb-4">补充说明</Title>
<Text>{data.attributes.description}</Text>
</div>
)}
<div className="mt-8 pt-8 border-t border-gray-200">
<Text type="secondary" className="block mb-2">
注意事项
</Text>
<ul className="text-gray-500 text-sm">
<li>1. 本报价单有效期为30天</li>
<li>2. 最终解释权归本公司所有</li>
<li>3. 如有疑问请及时联系我们</li>
</ul>
</div>
</Card>
</div>
</>
);
};
export default QuotationView;