feat:v1
This commit is contained in:
@@ -1,119 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Table } from 'antd';
|
||||
|
||||
const PrintView = ({ data }) => {
|
||||
const columns = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
width: 60,
|
||||
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 })
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="print-wrapper">
|
||||
<div className="print-header">
|
||||
<h1>{data.attributes.quataName}</h1>
|
||||
<div className="quotation-id">报价单号:{data.id}</div>
|
||||
</div>
|
||||
|
||||
<div className="info-grid">
|
||||
<div className="info-item">
|
||||
<span className="info-label">客户公司</span>
|
||||
<span className="info-value">{data.attributes.companyName}</span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span className="info-label">供应商</span>
|
||||
<span className="info-value">{data.attributes.supplierName}</span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span className="info-label">报价日期</span>
|
||||
<span className="info-value">
|
||||
{new Date(data.created_at).toLocaleDateString('zh-CN')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<span className="info-label">报价有效期</span>
|
||||
<span className="info-value">
|
||||
{new Date(Date.now() + 30*24*60*60*1000).toLocaleDateString('zh-CN')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="section-title">报价明细</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data.attributes.items}
|
||||
pagination={false}
|
||||
rowKey={(record, index) => index}
|
||||
bordered
|
||||
summary={() => (
|
||||
<Table.Summary fixed>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell index={0} colSpan={4} align="right">
|
||||
总计({data.attributes.currency}):
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell index={1} align="right">
|
||||
<span style={{ color: '#1890ff', fontSize: '16px' }}>
|
||||
{data.attributes.totalAmount?.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
currency: data.attributes.currency
|
||||
})}
|
||||
</span>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
</Table.Summary>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{data.attributes.description && (
|
||||
<div className="description-section">
|
||||
<div className="section-title">补充说明</div>
|
||||
<div>{data.attributes.description}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="notes-section">
|
||||
<div className="notes-title">注意事项:</div>
|
||||
<ul className="notes-list">
|
||||
<li>本报价单有效期为30天</li>
|
||||
<li>最终解释权归本公司所有</li>
|
||||
<li>如有疑问请及时联系我们</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintView;
|
||||
@@ -328,6 +328,49 @@ const SectionList = ({
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const addSectionFn = async (field) => {
|
||||
try {
|
||||
await form.validateFields(['sections', field.name]);
|
||||
|
||||
const allValues = form.getFieldsValue(true);
|
||||
const currentSection = allValues.sections[field.name];
|
||||
|
||||
if (!currentSection) {
|
||||
throw new Error('未找到section数据');
|
||||
}
|
||||
|
||||
const templateData = {
|
||||
type: 'sections',
|
||||
attributes: {
|
||||
name: currentSection.sectionName,
|
||||
template_type: type,
|
||||
items: currentSection.items.map(item => ({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
unit: item.unit
|
||||
}))
|
||||
},
|
||||
schema_version: 1
|
||||
};
|
||||
|
||||
console.log('准备保存的数据:', templateData);
|
||||
|
||||
const { error } = await supabase
|
||||
.from('resources')
|
||||
.insert([templateData]);
|
||||
|
||||
if (error) throw error;
|
||||
message.success('保存模板成功');
|
||||
|
||||
fetchAvailableSections();
|
||||
|
||||
} catch (error) {
|
||||
message.error('保存模板失败');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Form.List name="sections">
|
||||
@@ -400,12 +443,21 @@ const SectionList = ({
|
||||
)}
|
||||
</div>
|
||||
{!isView && (
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => addSectionFn(field)}
|
||||
>
|
||||
添加到模版
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -289,7 +289,48 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const addSectionFn = async (field) => {
|
||||
try {
|
||||
await form.validateFields(['sections', field.name]);
|
||||
|
||||
const allValues = form.getFieldsValue(true);
|
||||
const currentSection = allValues.sections[field.name];
|
||||
|
||||
if (!currentSection) {
|
||||
throw new Error('未找到section数据');
|
||||
}
|
||||
|
||||
const templateData = {
|
||||
type: 'sections',
|
||||
attributes: {
|
||||
name: currentSection.sectionName,
|
||||
template_type: type,
|
||||
items: currentSection.items.map(item => ({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
quantity: item.quantity,
|
||||
price: item.price,
|
||||
unit: item.unit
|
||||
}))
|
||||
},
|
||||
schema_version: 1
|
||||
};
|
||||
|
||||
|
||||
const { error } = await supabase
|
||||
.from('resources')
|
||||
.insert([templateData]);
|
||||
|
||||
if (error) throw error;
|
||||
message.success('保存模板成功');
|
||||
|
||||
fetchAvailableSections();
|
||||
|
||||
} catch (error) {
|
||||
message.error('保存模板失败');
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Form.List name="sections">
|
||||
@@ -362,12 +403,23 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
||||
)}
|
||||
</div>
|
||||
{!isView && (
|
||||
<Button
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => addSectionFn(field)}
|
||||
className="ml-2"
|
||||
>
|
||||
添加到模版
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => remove(field.name)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
@@ -516,13 +568,15 @@ const SectionList = ({ form, isView, formValues, type }) => {
|
||||
</Form.Item>
|
||||
|
||||
{!isView && itemFields.length > 1 && (
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => removeItem(itemField.name)}
|
||||
className="flex items-center justify-center"
|
||||
/>
|
||||
/>
|
||||
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,12 +9,12 @@ const TEMPLATE_TYPES = [
|
||||
icon: <FileTextOutlined className="text-2xl text-blue-500" />,
|
||||
description: '创建标准化的报价单模板,包含服务项目、价格等信息'
|
||||
},
|
||||
{
|
||||
key: 'project',
|
||||
title: '专案模板',
|
||||
icon: <ProjectOutlined className="text-2xl text-green-500" />,
|
||||
description: '创建专案流程模板,包含项目阶段、时间线等信息'
|
||||
},
|
||||
// {
|
||||
// key: 'project',
|
||||
// title: '专案模板',
|
||||
// icon: <ProjectOutlined className="text-2xl text-green-500" />,
|
||||
// description: '创建专案流程模板,包含项目阶段、时间线等信息'
|
||||
// },
|
||||
{
|
||||
key: 'task',
|
||||
title: '任务模板',
|
||||
|
||||
Reference in New Issue
Block a user