Merge branch 'main' of github.com:xuqssq/uppmkt-admin
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"@supabase/supabase-js": "^2.38.4",
|
"@supabase/supabase-js": "^2.38.4",
|
||||||
"antd": "^5.11.0",
|
"antd": "^5.11.0",
|
||||||
"dnd-kit": "^0.0.2",
|
"dnd-kit": "^0.0.2",
|
||||||
|
"html2pdf": "^0.0.11",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
dnd-kit:
|
dnd-kit:
|
||||||
specifier: ^0.0.2
|
specifier: ^0.0.2
|
||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
|
html2pdf:
|
||||||
|
specifier: ^0.0.11
|
||||||
|
version: 0.0.11
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
@@ -1386,6 +1389,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
html2pdf@0.0.11:
|
||||||
|
resolution: {integrity: sha512-Kx3I5r7lazHZzZv5lDk59jQoTxqaEGlf2BcGNvzWGPQVu0hE8jYCk4hboksuaCvmMJPa0S0A+156ZbE71Wdo2w==}
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -4137,6 +4143,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
html2pdf@0.0.11: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
immutable@5.0.3: {}
|
immutable@5.0.3: {}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const QuotationForm = () => {
|
|||||||
if (data?.attributes) {
|
if (data?.attributes) {
|
||||||
const formData = {
|
const formData = {
|
||||||
quataName: data.attributes.quataName,
|
quataName: data.attributes.quataName,
|
||||||
customers: data.attributes.customers || [],
|
customers: data.attributes.customers.map((customer) => customer.id) || [],
|
||||||
description: data.attributes.description,
|
description: data.attributes.description,
|
||||||
currency: data.attributes.currency || "CNY",
|
currency: data.attributes.currency || "CNY",
|
||||||
sections: data.attributes.sections.map((section) => ({
|
sections: data.attributes.sections.map((section) => ({
|
||||||
@@ -716,7 +716,7 @@ const QuotationForm = () => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="customers"
|
name="customers"
|
||||||
label={<span className="text-gray-700 font-medium">客户名称</span>}
|
label={<span className="text-gray-700 font-medium">客户名称</span>}
|
||||||
rules={[{ required: true, message: "请选择至少一个客<EFBFBD><EFBFBD>" }]}
|
rules={[{ required: true, message: "请选择至少一个客户" }]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ const QuotationPage = () => {
|
|||||||
dataIndex: ['attributes'],
|
dataIndex: ['attributes'],
|
||||||
key: 'totalAmount',
|
key: 'totalAmount',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
width: 200,
|
|
||||||
render: (attributes) => {
|
render: (attributes) => {
|
||||||
// 获取货币符号
|
// 获取货币符号
|
||||||
const currencySymbol = CURRENCY_SYMBOLS[attributes.currency] || '¥';
|
const currencySymbol = CURRENCY_SYMBOLS[attributes.currency] || '¥';
|
||||||
@@ -183,7 +182,6 @@ const QuotationPage = () => {
|
|||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
width: 120,
|
|
||||||
render: (text) => (
|
render: (text) => (
|
||||||
<span>{new Date(text).toLocaleString('zh-CN', {
|
<span>{new Date(text).toLocaleString('zh-CN', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@@ -197,15 +195,14 @@ const QuotationPage = () => {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width:250,
|
width: 280,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space size={0}>
|
<Space size={0}>
|
||||||
<Button
|
<Button
|
||||||
size='small'
|
size='small'
|
||||||
type="link"
|
type="link"
|
||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => copyItem(record)}
|
||||||
// onClick={() => navigate(`/company/quotaInfo/preview/${record.id}`)}
|
|
||||||
>
|
>
|
||||||
复制
|
复制
|
||||||
</Button>
|
</Button>
|
||||||
@@ -268,7 +265,37 @@ const QuotationPage = () => {
|
|||||||
|
|
||||||
return Array.from(groups.values()).filter(group => group.templates.length > 0);
|
return Array.from(groups.values()).filter(group => group.templates.length > 0);
|
||||||
};
|
};
|
||||||
|
const copyItem = async (record) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
// 深拷贝原有数据的 attributes
|
||||||
|
const newAttributes = JSON.parse(JSON.stringify(record.attributes));
|
||||||
|
|
||||||
|
// 修改报价单名称,添加"副本"标识
|
||||||
|
newAttributes.quataName = `${newAttributes.quataName} (副本)`;
|
||||||
|
|
||||||
|
// 创建新的报价单记录
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('resources')
|
||||||
|
.insert([
|
||||||
|
{
|
||||||
|
type: 'quota',
|
||||||
|
attributes: newAttributes
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
message.success('复制成功');
|
||||||
|
// 刷新列表
|
||||||
|
fetchQuotations();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制报价单失败:', error);
|
||||||
|
message.error('复制失败:' + error.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card
|
<Card
|
||||||
@@ -290,6 +317,7 @@ const QuotationPage = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
|
className='w-full'
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={quotations}
|
dataSource={quotations}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|||||||
@@ -1,69 +1,37 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Card, Typography, Descriptions, Table, Button, Space, Spin } from 'antd';
|
import { Card, Button, Typography, Space, Spin, Divider, Tag } from 'antd';
|
||||||
import { ArrowLeftOutlined, PrinterOutlined, EditOutlined } from '@ant-design/icons';
|
import { FileTextOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { supabase } from '@/config/supabase';
|
import { supabase } from '@/config/supabase';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import html2pdf from 'html2pdf.js';
|
||||||
import PrintView from '@/components/PrintView';
|
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
const printStyles = `
|
const CURRENCY_SYMBOLS = {
|
||||||
@media print {
|
CNY: "¥",
|
||||||
/* 隐藏所有按钮和不需要的元素 */
|
TWD: "NT$",
|
||||||
button, .no-print {
|
USD: "$",
|
||||||
display: none !important;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/* 只打印卡片内容 */
|
const QuotationPreview = () => {
|
||||||
.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 { id } = useParams();
|
||||||
const [data, setData] = useState(null);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [quotation, setQuotation] = useState(null);
|
||||||
|
|
||||||
|
// 获取报价单详情
|
||||||
const fetchQuotationDetail = async () => {
|
const fetchQuotationDetail = async () => {
|
||||||
try {
|
try {
|
||||||
const { data: quotation, error } = await supabase
|
setLoading(true);
|
||||||
.from('resources')
|
const { data, error } = await supabase
|
||||||
.select('*')
|
.from("resources")
|
||||||
.eq('id', id)
|
.select("*")
|
||||||
|
.eq("id", id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setData(quotation);
|
setQuotation(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取报价单失败:', error);
|
message.error("获取报价单详情失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -75,187 +43,165 @@ const QuotationView = () => {
|
|||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const columns = [
|
// 导出PDF
|
||||||
{
|
const handleExportPDF = () => {
|
||||||
title: '序号',
|
const element = document.getElementById('quotation-content');
|
||||||
dataIndex: 'index',
|
const opt = {
|
||||||
width: 100,
|
margin: [10, 10],
|
||||||
render: (_, __, index) => index + 1,
|
filename: `${quotation.attributes.quataName || '报价单'}.pdf`,
|
||||||
},
|
image: { type: 'jpeg', quality: 0.98 },
|
||||||
{
|
html2canvas: { scale: 2 },
|
||||||
title: '名称',
|
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
|
||||||
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 = () => {
|
html2pdf().set(opt).from(element).save();
|
||||||
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen">
|
<div className="flex justify-center items-center h-full">
|
||||||
<Spin size="large" />
|
<Spin size="large" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data) {
|
if (!quotation) return null;
|
||||||
return null;
|
|
||||||
}
|
const { attributes } = quotation;
|
||||||
|
const currencySymbol = CURRENCY_SYMBOLS[attributes.currency] || '¥';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
<style>{printStyles}</style>
|
<Card
|
||||||
|
title={
|
||||||
<div className="bg-gray-50 min-h-screen ">
|
<div className="flex justify-between items-center">
|
||||||
<Card className="max-w-5xl mx-auto shadow-lg">
|
|
||||||
<div className="flex justify-between items-center mb-6 no-print">
|
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<FileTextOutlined className="text-blue-500" />
|
||||||
icon={<ArrowLeftOutlined />}
|
<span>报价单预览</span>
|
||||||
onClick={() => navigate('/company/quotation')}
|
|
||||||
>
|
|
||||||
返回列表
|
|
||||||
</Button>
|
|
||||||
</Space>
|
</Space>
|
||||||
<Space>
|
|
||||||
<Button
|
<Button
|
||||||
icon={<EditOutlined />}
|
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => navigate(`/company/quotaInfo/${data.id}?edit=true`)}
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={handleExportPDF}
|
||||||
>
|
>
|
||||||
编辑报价单
|
导出PDF
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
</div>
|
||||||
icon={<PrinterOutlined />}
|
}
|
||||||
onClick={handlePrint}
|
|
||||||
>
|
>
|
||||||
打印报价单
|
<div id="quotation-content" className="p-6">
|
||||||
</Button>
|
{/* 报价单标题 */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<Title level={2}>{attributes.quataName}</Title>
|
||||||
|
<Text type="secondary">创建日期:{new Date(quotation.created_at).toLocaleDateString()}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 基本信息 */}
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg mb-6">
|
||||||
|
<Title level={4}>基本信息</Title>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">客户:</Text>
|
||||||
|
<Space>
|
||||||
|
{attributes.customers?.map(customer => (
|
||||||
|
<Tag key={customer.id} color="blue">{customer.name}</Tag>
|
||||||
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<div className="text-center mb-8">
|
<Text type="secondary">货币类型:</Text>
|
||||||
<Title level={2} className="!mb-2">{data.attributes.quataName}</Title>
|
<Text>{attributes.currency}</Text>
|
||||||
<Text type="secondary">报价单号:{data.id}</Text>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-6 rounded-lg mb-8">
|
{/* 报价明细 */}
|
||||||
<Descriptions column={2} bordered>
|
{attributes.sections?.map((section, sIndex) => (
|
||||||
<Descriptions.Item label="客户公司" span={1}>
|
<div key={sIndex} className="mb-6">
|
||||||
{data.attributes.customerName}
|
<div className="flex items-center gap-2 mb-3">
|
||||||
</Descriptions.Item>
|
<div className="h-4 w-1 bg-blue-500 rounded-full" />
|
||||||
<Descriptions.Item label="报价日期" span={1}>
|
<Title level={4} className="!mb-0">{section.sectionName}</Title>
|
||||||
{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>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="overflow-x-auto">
|
||||||
<Title level={4} className="mb-4">报价明细</Title>
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<Table
|
<thead className="bg-gray-50">
|
||||||
columns={columns}
|
<tr>
|
||||||
dataSource={data.attributes.items}
|
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">项目明细</th>
|
||||||
pagination={false}
|
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">描述/备注</th>
|
||||||
rowKey={(record, index) => index}
|
<th className="px-4 py-3 text-left text-sm font-medium text-gray-500">单位</th>
|
||||||
className="border rounded-lg"
|
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">数量</th>
|
||||||
summary={() => (
|
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">单价</th>
|
||||||
<Table.Summary fixed>
|
<th className="px-4 py-3 text-right text-sm font-medium text-gray-500">小计</th>
|
||||||
<Table.Summary.Row className="bg-gray-50 font-bold">
|
</tr>
|
||||||
<Table.Summary.Cell index={0} colSpan={4} align="right">
|
</thead>
|
||||||
总计({data.attributes.currency}):
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
</Table.Summary.Cell>
|
{section.items.map((item, iIndex) => (
|
||||||
<Table.Summary.Cell index={1} align="right">
|
<tr key={iIndex}>
|
||||||
{data.attributes.totalAmount?.toLocaleString('zh-CN', {
|
<td className="px-4 py-3 text-sm text-gray-900">{item.name}</td>
|
||||||
minimumFractionDigits: 2,
|
<td className="px-4 py-3 text-sm text-gray-500">{item.description}</td>
|
||||||
style: 'currency',
|
<td className="px-4 py-3 text-sm text-gray-500">{item.unit}</td>
|
||||||
currency: data.attributes.currency
|
<td className="px-4 py-3 text-sm text-gray-900 text-right">{item.quantity}</td>
|
||||||
})}
|
<td className="px-4 py-3 text-sm text-gray-900 text-right">
|
||||||
</Table.Summary.Cell>
|
{currencySymbol}{item.price?.toLocaleString()}
|
||||||
</Table.Summary.Row>
|
</td>
|
||||||
</Table.Summary>
|
<td className="px-4 py-3 text-sm text-gray-900 text-right">
|
||||||
)}
|
{currencySymbol}{(item.quantity * item.price)?.toLocaleString()}
|
||||||
/>
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
{data.attributes.description && (
|
{/* 金额汇总 */}
|
||||||
<div className="bg-gray-50 p-6 rounded-lg">
|
<div className="mt-8 border-t pt-4">
|
||||||
<Title level={4} className="mb-4">补充说明</Title>
|
<div className="flex justify-end space-y-2">
|
||||||
<Text>{data.attributes.description}</Text>
|
<div className="w-64 space-y-2">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Text>税前总计:</Text>
|
||||||
|
<Text>{currencySymbol}{attributes.beforeTaxAmount?.toLocaleString()}</Text>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Text>税率:</Text>
|
||||||
|
<Text>{attributes.taxRate}%</Text>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Text>税后总计:</Text>
|
||||||
|
<Text>{currencySymbol}{attributes.afterTaxAmount?.toLocaleString()}</Text>
|
||||||
|
</div>
|
||||||
|
{attributes.discount > 0 && (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<Text>折扣价:</Text>
|
||||||
|
<Text type="success">{currencySymbol}{attributes.discount?.toLocaleString()}</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Divider className="my-2" />
|
||||||
<div className="mt-8 pt-8 border-t border-gray-200">
|
<div className="flex justify-between">
|
||||||
<Text type="secondary" className="block mb-2">
|
<Text strong>最终金额:</Text>
|
||||||
注意事项:
|
<Text strong className="text-blue-500 text-xl">
|
||||||
|
{currencySymbol}{(attributes.discount || attributes.afterTaxAmount)?.toLocaleString()}
|
||||||
</Text>
|
</Text>
|
||||||
<ul className="text-gray-500 text-sm">
|
</div>
|
||||||
<li>1. 本报价单有效期为30天</li>
|
</div>
|
||||||
<li>2. 最终解释权归本公司所有</li>
|
</div>
|
||||||
<li>3. 如有疑问请及时联系我们</li>
|
</div>
|
||||||
</ul>
|
|
||||||
|
{/* 补充说明 */}
|
||||||
|
{attributes.description && (
|
||||||
|
<div className="mt-8">
|
||||||
|
<Title level={4}>补充说明</Title>
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg">
|
||||||
|
<Text>{attributes.description}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QuotationView;
|
export default QuotationPreview;
|
||||||
Reference in New Issue
Block a user