feat:服务模版 抽离
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Form, Input, InputNumber, Button, Card, Typography, Modal, message, Divider, Select } from 'antd';
|
||||
import { PlusOutlined, DeleteOutlined, EditOutlined, CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -247,12 +247,53 @@ const SectionList = ({
|
||||
onClick={() => handleCreateCustom(add, fieldsLength)}
|
||||
className="w-1/3 border-2"
|
||||
>
|
||||
自定义小节
|
||||
自定义模块
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 修改项目小计的计算,将其封装为 memo 组件
|
||||
const ItemSubtotal = React.memo(({ quantity, price, currentCurrency }) => {
|
||||
const subtotal = useMemo(() => {
|
||||
const safeQuantity = Number(quantity) || 0;
|
||||
const safePrice = Number(price) || 0;
|
||||
return safeQuantity * safePrice;
|
||||
}, [quantity, price]);
|
||||
|
||||
return (
|
||||
<div className="text-right">
|
||||
<span className="text-gray-500">
|
||||
{formatCurrency(subtotal, currentCurrency)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// 修改小节总计的计算,将其封装为 memo 组件
|
||||
const SectionTotal = React.memo(({ items, currentCurrency }) => {
|
||||
const total = useMemo(() => {
|
||||
if (!Array.isArray(items)) return 0;
|
||||
return items.reduce((sum, item) => {
|
||||
if (!item) return sum;
|
||||
const safeQuantity = Number(item.quantity) || 0;
|
||||
const safePrice = Number(item.price) || 0;
|
||||
return sum + (safeQuantity * safePrice);
|
||||
}, 0);
|
||||
}, [items]);
|
||||
|
||||
return (
|
||||
<div className="flex justify-end border-t pt-4">
|
||||
<span className="text-gray-500">
|
||||
小计总额:
|
||||
<span className="text-blue-500 font-medium ml-2">
|
||||
{formatCurrency(total, currentCurrency)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.List name="sections">
|
||||
@@ -439,16 +480,11 @@ const SectionList = ({
|
||||
>
|
||||
<InputNumber placeholder="单价" min={0} className="w-full" />
|
||||
</Form.Item>
|
||||
<div className="text-right">
|
||||
<span className="text-gray-500">
|
||||
{formatCurrency(
|
||||
calculateItemAmount(
|
||||
formValues?.sections?.[sectionIndex]?.items?.[itemIndex]?.quantity,
|
||||
formValues?.sections?.[sectionIndex]?.items?.[itemIndex]?.price
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<ItemSubtotal
|
||||
quantity={formValues?.sections?.[sectionIndex]?.items?.[itemIndex]?.quantity}
|
||||
price={formValues?.sections?.[sectionIndex]?.items?.[itemIndex]?.price}
|
||||
currentCurrency={currentCurrency}
|
||||
/>
|
||||
{!isView && itemFields.length > 1 && (
|
||||
<Button
|
||||
type="text"
|
||||
@@ -472,18 +508,10 @@ const SectionList = ({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end border-t pt-4">
|
||||
<span className="text-gray-500">
|
||||
小计总额:
|
||||
<span className="text-blue-500 font-medium ml-2">
|
||||
{formatCurrency(
|
||||
calculateSectionTotal(
|
||||
formValues?.sections?.[sectionIndex]?.items
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<SectionTotal
|
||||
items={formValues?.sections?.[sectionIndex]?.items}
|
||||
currentCurrency={currentCurrency}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
|
||||
55
src/components/TemplateTypeModal/index.jsx
Normal file
55
src/components/TemplateTypeModal/index.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { Modal, Card, Space } from 'antd';
|
||||
import { FileTextOutlined, ProjectOutlined, CheckSquareOutlined } from '@ant-design/icons';
|
||||
|
||||
const TEMPLATE_TYPES = [
|
||||
{
|
||||
key: 'quotation',
|
||||
title: '报价单模板',
|
||||
icon: <FileTextOutlined className="text-2xl text-blue-500" />,
|
||||
description: '创建标准化的报价单模板,包含服务项目、价格等信息'
|
||||
},
|
||||
{
|
||||
key: 'project',
|
||||
title: '专案模板',
|
||||
icon: <ProjectOutlined className="text-2xl text-green-500" />,
|
||||
description: '创建专案流程模板,包含项目阶段、时间线等信息'
|
||||
},
|
||||
{
|
||||
key: 'task',
|
||||
title: '任务模板',
|
||||
icon: <CheckSquareOutlined className="text-2xl text-purple-500" />,
|
||||
description: '创建标准任务模板,包含检查项、执行步骤等信息'
|
||||
}
|
||||
];
|
||||
|
||||
const TemplateTypeModal = ({ open, onClose, onSelect }) => {
|
||||
return (
|
||||
<Modal
|
||||
title="选择模板类型"
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={800}
|
||||
>
|
||||
<Space className="w-full" size="large">
|
||||
{TEMPLATE_TYPES.map(type => (
|
||||
<Card
|
||||
key={type.key}
|
||||
hoverable
|
||||
className="flex-1 cursor-pointer transition-all hover:shadow-lg"
|
||||
onClick={() => onSelect(type.key)}
|
||||
>
|
||||
<div className="text-center space-y-4">
|
||||
{type.icon}
|
||||
<h3 className="text-lg font-medium">{type.title}</h3>
|
||||
<p className="text-gray-500 text-sm">{type.description}</p>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateTypeModal;
|
||||
Reference in New Issue
Block a user