Merge branch 'main' of github.com:xuqssq/uppmkt-admin
This commit is contained in:
@@ -22,12 +22,15 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
import { supabase } from "@/config/supabase";
|
import { supabase } from "@/config/supabase";
|
||||||
import { supabaseService } from "@/hooks/supabaseService";
|
import { supabaseService } from "@/hooks/supabaseService";
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
import { defaultSymbol, formatExchangeRate } from "@/utils/exchange_rate";
|
||||||
const SectionList = ({
|
const SectionList = ({
|
||||||
form,
|
form,
|
||||||
isView,
|
isView,
|
||||||
formValues,
|
formValues,
|
||||||
type,
|
type,
|
||||||
currentCurrency = "TWD",
|
currentCurrency = "TWD",
|
||||||
|
taxRate,
|
||||||
|
setTaxRate,
|
||||||
}) => {
|
}) => {
|
||||||
const [editingSectionIndex, setEditingSectionIndex] = useState(null);
|
const [editingSectionIndex, setEditingSectionIndex] = useState(null);
|
||||||
const [editingSectionName, setEditingSectionName] = useState("");
|
const [editingSectionName, setEditingSectionName] = useState("");
|
||||||
@@ -36,33 +39,6 @@ const SectionList = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [units, setUnits] = useState([]);
|
const [units, setUnits] = useState([]);
|
||||||
const [loadingUnits, setLoadingUnits] = useState(false);
|
const [loadingUnits, setLoadingUnits] = useState(false);
|
||||||
const CURRENCY_SYMBOLS = {
|
|
||||||
CNY: "¥",
|
|
||||||
TWD: "NT$",
|
|
||||||
USD: "$",
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateItemAmount = (quantity, price) => {
|
|
||||||
const safeQuantity = Number(quantity) || 0;
|
|
||||||
const safePrice = Number(price) || 0;
|
|
||||||
return safeQuantity * safePrice;
|
|
||||||
};
|
|
||||||
|
|
||||||
const calculateSectionTotal = (items = []) => {
|
|
||||||
if (!Array.isArray(items)) return 0;
|
|
||||||
return items.reduce((sum, item) => {
|
|
||||||
if (!item) return sum;
|
|
||||||
return sum + calculateItemAmount(item.quantity, item.price);
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
|
||||||
const safeAmount = Number(amount) || 0;
|
|
||||||
return `${CURRENCY_SYMBOLS[currentCurrency] || "NT$"}${safeAmount.toLocaleString("zh-TW", {
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
})}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchAvailableSections = async () => {
|
const fetchAvailableSections = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -236,7 +212,7 @@ const SectionList = ({
|
|||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-500 ml-2">
|
<span className="text-sm text-gray-500 ml-2">
|
||||||
{formatCurrency(item.price)}
|
{formatExchangeRate(currentCurrency, item.price)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -250,7 +226,8 @@ const SectionList = ({
|
|||||||
<div className="mt-4 pt-4 border-t flex justify-between items-center">
|
<div className="mt-4 pt-4 border-t flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">总金额</span>
|
<span className="text-sm text-gray-600">总金额</span>
|
||||||
<span className="text-base font-medium text-blue-500">
|
<span className="text-base font-medium text-blue-500">
|
||||||
{formatCurrency(
|
{formatExchangeRate(
|
||||||
|
currentCurrency,
|
||||||
(section.attributes.items || []).reduce(
|
(section.attributes.items || []).reduce(
|
||||||
(sum, item) =>
|
(sum, item) =>
|
||||||
sum + (item.price * (item.quantity || 1) || 0),
|
sum + (item.price * (item.quantity || 1) || 0),
|
||||||
@@ -323,7 +300,7 @@ const SectionList = ({
|
|||||||
return (
|
return (
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
{formatCurrency(subtotal, currentCurrency)}
|
{formatExchangeRate(currentCurrency, subtotal)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -345,13 +322,12 @@ const SectionList = ({
|
|||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
小计总额:
|
小计总额:
|
||||||
<span className="text-blue-500 font-medium ml-2">
|
<span className="text-blue-500 font-medium ml-2">
|
||||||
{formatCurrency(total, currentCurrency)}
|
{formatExchangeRate(currentCurrency, total)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.List name="sections">
|
<Form.List name="sections">
|
||||||
@@ -442,127 +418,137 @@ const SectionList = ({
|
|||||||
<div>描述/备注</div>
|
<div>描述/备注</div>
|
||||||
<div>单位</div>
|
<div>单位</div>
|
||||||
<div className="text-center">数量</div>
|
<div className="text-center">数量</div>
|
||||||
<div className="text-center">单价</div>
|
<div className="text-center">
|
||||||
|
单价({defaultSymbol})
|
||||||
|
</div>
|
||||||
<div className="text-right">小计</div>
|
<div className="text-right">小计</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{itemFields.map((itemField, itemIndex) => (
|
{itemFields.map((itemField, itemIndex) => {
|
||||||
<div
|
const { key, ...restItemField } = itemField;
|
||||||
key={itemField.key}
|
return (
|
||||||
className="grid grid-cols-[3fr_4fr_1fr_1fr_2fr_1fr_40px] gap-4 mb-4 items-start"
|
<div
|
||||||
>
|
key={key}
|
||||||
<Form.Item
|
className="grid grid-cols-[3fr_4fr_1fr_1fr_2fr_1fr_40px] gap-4 mb-4 items-start"
|
||||||
{...itemField}
|
|
||||||
name={[itemField.name, "name"]}
|
|
||||||
className="!mb-0"
|
|
||||||
>
|
>
|
||||||
<Input placeholder="服务项目名称" />
|
<Form.Item
|
||||||
</Form.Item>
|
{...restItemField}
|
||||||
<Form.Item
|
name={[itemField.name, "name"]}
|
||||||
{...itemField}
|
className="!mb-0"
|
||||||
name={[itemField.name, "description"]}
|
>
|
||||||
className="!mb-0"
|
<Input placeholder="服务项目名称" />
|
||||||
>
|
</Form.Item>
|
||||||
<Input placeholder="请输入描述/备注" />
|
<Form.Item
|
||||||
</Form.Item>
|
{...restItemField}
|
||||||
<Form.Item
|
name={[itemField.name, "description"]}
|
||||||
{...itemField}
|
className="!mb-0"
|
||||||
name={[itemField.name, "unit"]}
|
>
|
||||||
className="!mb-0"
|
<Input placeholder="请输入描述/备注" />
|
||||||
>
|
</Form.Item>
|
||||||
<Select
|
<Form.Item
|
||||||
placeholder="选择单位"
|
{...restItemField}
|
||||||
loading={loadingUnits}
|
name={[itemField.name, "unit"]}
|
||||||
showSearch
|
className="!mb-0"
|
||||||
allowClear
|
>
|
||||||
style={{ minWidth: "120px" }}
|
<Select
|
||||||
options={units.map((unit) => ({
|
placeholder="选择单位"
|
||||||
label: unit.attributes.name,
|
loading={loadingUnits}
|
||||||
value: unit.attributes.name,
|
showSearch
|
||||||
}))}
|
allowClear
|
||||||
onDropdownVisibleChange={(open) => {
|
style={{ minWidth: "120px" }}
|
||||||
if (open) fetchUnits();
|
options={units.map((unit) => ({
|
||||||
}}
|
label: unit.attributes.name,
|
||||||
dropdownRender={(menu) => (
|
value: unit.attributes.name,
|
||||||
<>
|
}))}
|
||||||
{menu}
|
onDropdownVisibleChange={(open) => {
|
||||||
<Divider style={{ margin: "12px 0" }} />
|
if (open) fetchUnits();
|
||||||
<div style={{ padding: "4px" }}>
|
}}
|
||||||
<Input.Search
|
dropdownRender={(menu) => (
|
||||||
placeholder="输入新单位名称"
|
<>
|
||||||
enterButton={<PlusOutlined />}
|
{menu}
|
||||||
onSearch={async (value) => {
|
<Divider style={{ margin: "12px 0" }} />
|
||||||
if (!value.trim()) return;
|
<div style={{ padding: "4px" }}>
|
||||||
if (
|
<Input.Search
|
||||||
await handleAddUnit(value.trim())
|
placeholder="输入新单位名称"
|
||||||
) {
|
enterButton={<PlusOutlined />}
|
||||||
const currentItems =
|
onSearch={async (value) => {
|
||||||
form.getFieldValue([
|
if (!value.trim()) return;
|
||||||
"sections",
|
if (
|
||||||
field.name,
|
await handleAddUnit(value.trim())
|
||||||
"items",
|
) {
|
||||||
]);
|
const currentItems =
|
||||||
currentItems[itemField.name].unit =
|
form.getFieldValue([
|
||||||
value.trim();
|
"sections",
|
||||||
form.setFieldValue(
|
field.name,
|
||||||
["sections", field.name, "items"],
|
"items",
|
||||||
currentItems
|
]);
|
||||||
);
|
currentItems[
|
||||||
}
|
itemField.name
|
||||||
}}
|
].unit = value.trim();
|
||||||
/>
|
form.setFieldValue(
|
||||||
</div>
|
[
|
||||||
</>
|
"sections",
|
||||||
)}
|
field.name,
|
||||||
|
"items",
|
||||||
|
],
|
||||||
|
currentItems
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restItemField}
|
||||||
|
name={[itemField.name, "quantity"]}
|
||||||
|
className="!mb-0"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
placeholder="数量"
|
||||||
|
min={0}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restItemField}
|
||||||
|
name={[itemField.name, "price"]}
|
||||||
|
className="!mb-0"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
placeholder="单价"
|
||||||
|
min={0}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<ItemSubtotal
|
||||||
|
quantity={
|
||||||
|
formValues?.sections?.[sectionIndex]?.items?.[
|
||||||
|
itemIndex
|
||||||
|
]?.quantity
|
||||||
|
}
|
||||||
|
price={
|
||||||
|
formValues?.sections?.[sectionIndex]?.items?.[
|
||||||
|
itemIndex
|
||||||
|
]?.price
|
||||||
|
}
|
||||||
|
currentCurrency={currentCurrency}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
{!isView && itemFields.length > 1 && (
|
||||||
<Form.Item
|
<Button
|
||||||
{...itemField}
|
type="text"
|
||||||
name={[itemField.name, "quantity"]}
|
danger
|
||||||
className="!mb-0"
|
icon={<DeleteOutlined />}
|
||||||
>
|
onClick={() => removeItem(itemField.name)}
|
||||||
<InputNumber
|
className="flex items-center justify-center"
|
||||||
placeholder="数量"
|
/>
|
||||||
min={0}
|
)}
|
||||||
className="w-full"
|
</div>
|
||||||
/>
|
);
|
||||||
</Form.Item>
|
})}
|
||||||
<Form.Item
|
|
||||||
{...itemField}
|
|
||||||
name={[itemField.name, "price"]}
|
|
||||||
className="!mb-0"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
placeholder="单价"
|
|
||||||
min={0}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<ItemSubtotal
|
|
||||||
quantity={
|
|
||||||
formValues?.sections?.[sectionIndex]?.items?.[
|
|
||||||
itemIndex
|
|
||||||
]?.quantity
|
|
||||||
}
|
|
||||||
price={
|
|
||||||
formValues?.sections?.[sectionIndex]?.items?.[
|
|
||||||
itemIndex
|
|
||||||
]?.price
|
|
||||||
}
|
|
||||||
currentCurrency={currentCurrency}
|
|
||||||
/>
|
|
||||||
{!isView && itemFields.length > 1 && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={() => removeItem(itemField.name)}
|
|
||||||
className="flex items-center justify-center"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{!isView && (
|
{!isView && (
|
||||||
<Button
|
<Button
|
||||||
@@ -586,21 +572,74 @@ const SectionList = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isView && (
|
<div className="flex items-center justify-center mt-6">
|
||||||
<div className="mt-6 flex justify-center">
|
{!isView && (
|
||||||
<Button
|
<div className="w-full flex justify-center">
|
||||||
type="dashed"
|
<Button
|
||||||
onClick={() => {
|
type="dashed"
|
||||||
setTemplateModalVisible(true);
|
onClick={() => {
|
||||||
fetchAvailableSections();
|
setTemplateModalVisible(true);
|
||||||
}}
|
fetchAvailableSections();
|
||||||
icon={<PlusOutlined />}
|
}}
|
||||||
className="w-1/3 border-2 hover:border-blue-400 hover:text-blue-500"
|
icon={<PlusOutlined />}
|
||||||
>
|
className="w-1/3 border-2 hover:border-blue-400 hover:text-blue-500"
|
||||||
新建模块
|
>
|
||||||
</Button>
|
新建模块
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex w-fit flex-shrink-0 flex-col gap-1">
|
||||||
|
<span className="text-gray-500 flex items-center">
|
||||||
|
税前总额:
|
||||||
|
<span className="text-blue-500 font-medium ml-2">
|
||||||
|
{formatExchangeRate(
|
||||||
|
currentCurrency,
|
||||||
|
formValues?.sections?.reduce((sum, section) => {
|
||||||
|
return (
|
||||||
|
sum +
|
||||||
|
section.items.reduce((sum, item) => {
|
||||||
|
return sum + item.price * item.quantity;
|
||||||
|
}, 0)
|
||||||
|
);
|
||||||
|
}, 0)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="text-gray-500 flex items-center">
|
||||||
|
税率:
|
||||||
|
<div className="text-blue-500 font-medium ml-2">
|
||||||
|
<InputNumber
|
||||||
|
suffix="%"
|
||||||
|
style={{ width: 120 }}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
value={taxRate}
|
||||||
|
onChange={(value) => setTaxRate(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="text-gray-500 flex items-center">
|
||||||
|
税后总额:
|
||||||
|
<span className="text-blue-500 font-medium ml-2">
|
||||||
|
{formatExchangeRate(
|
||||||
|
currentCurrency,
|
||||||
|
formValues?.sections?.reduce((sum, section) => {
|
||||||
|
return (
|
||||||
|
sum +
|
||||||
|
section.items.reduce((sum, item) => {
|
||||||
|
return sum + item.price * item.quantity;
|
||||||
|
}, 0)
|
||||||
|
);
|
||||||
|
}, 0) *
|
||||||
|
(1 + taxRate / 100)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title={<h3 className="text-lg font-medium">选择模版</h3>}
|
title={<h3 className="text-lg font-medium">选择模版</h3>}
|
||||||
@@ -619,5 +658,4 @@ const SectionList = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default SectionList;
|
export default SectionList;
|
||||||
|
|||||||
@@ -734,7 +734,7 @@ const QuotationForm = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
className="shadow-sm rounded-lg"
|
className="shadow-sm rounded-lg mt-6"
|
||||||
type="inner"
|
type="inner"
|
||||||
title={
|
title={
|
||||||
<span className="flex items-center space-x-2 text-gray-700">
|
<span className="flex items-center space-x-2 text-gray-700">
|
||||||
@@ -750,6 +750,8 @@ const QuotationForm = () => {
|
|||||||
isView={isView}
|
isView={isView}
|
||||||
formValues={formValues}
|
formValues={formValues}
|
||||||
currentCurrency={currentCurrency}
|
currentCurrency={currentCurrency}
|
||||||
|
taxRate={taxRate}
|
||||||
|
setTaxRate={setTaxRate}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, CopyOutlined,
|
|||||||
import { useResources } from '@/hooks/resource/useResource';
|
import { useResources } from '@/hooks/resource/useResource';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { supabase } from '@/config/supabase'
|
import { supabase } from '@/config/supabase'
|
||||||
const CURRENCY_SYMBOLS = {
|
import { formatExchangeRate,EXCHANGE_RATE,defaultSymbol } from '@/utils/exchange_rate';
|
||||||
CNY: "¥",
|
|
||||||
TWD: "NT$",
|
|
||||||
USD: "$",
|
|
||||||
};
|
|
||||||
const QuotationPage = () => {
|
const QuotationPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
const [pagination, setPagination] = useState({ current: 1, pageSize: 10 });
|
||||||
@@ -150,19 +147,18 @@ const QuotationPage = () => {
|
|||||||
align: 'right',
|
align: 'right',
|
||||||
render: (attributes) => {
|
render: (attributes) => {
|
||||||
// 获取货币符号
|
// 获取货币符号
|
||||||
const currencySymbol = CURRENCY_SYMBOLS[attributes.currency] || '¥';
|
const currencySymbol = EXCHANGE_RATE[attributes?.currency]?.symbol || defaultSymbol;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex justify-between items-center text-sm">
|
<div className="flex justify-between items-center text-sm">
|
||||||
<Typography.Text type="secondary" style={{ fontSize: '12px' }}>
|
<Typography.Text type="secondary" style={{ fontSize: '12px' }}>
|
||||||
税前:{currencySymbol}{attributes.beforeTaxAmount?.toLocaleString()}
|
税前:{formatExchangeRate(attributes?.currency, attributes.beforeTaxAmount)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Statistic
|
<Statistic
|
||||||
value={attributes.afterTaxAmount}
|
value={formatExchangeRate(attributes?.currency, attributes.afterTaxAmount)}
|
||||||
prefix={currencySymbol}
|
|
||||||
precision={2}
|
precision={2}
|
||||||
valueStyle={{
|
valueStyle={{
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
|
|||||||
@@ -7,12 +7,7 @@ import html2canvas from 'html2canvas';
|
|||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
import { EXCHANGE_RATE,defaultSymbol } from '@/utils/exchange_rate';
|
||||||
const CURRENCY_SYMBOLS = {
|
|
||||||
CNY: "¥",
|
|
||||||
TWD: "NT$",
|
|
||||||
USD: "$",
|
|
||||||
};
|
|
||||||
|
|
||||||
const QuotationPreview = () => {
|
const QuotationPreview = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -132,7 +127,7 @@ const QuotationPreview = () => {
|
|||||||
if (!quotation) return null;
|
if (!quotation) return null;
|
||||||
|
|
||||||
const { attributes } = quotation;
|
const { attributes } = quotation;
|
||||||
const currencySymbol = CURRENCY_SYMBOLS[attributes.currency] || '¥';
|
const currencySymbol = EXCHANGE_RATE[attributes.currency]?.symbol || defaultSymbol;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto p-6">
|
<div className="max-w-4xl mx-auto p-6">
|
||||||
|
|||||||
46
src/utils/exchange_rate.js
Normal file
46
src/utils/exchange_rate.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export const defaultSymbol = "NT$";
|
||||||
|
|
||||||
|
export const EXCHANGE_RATE = {
|
||||||
|
CNY: {
|
||||||
|
symbol: "¥",
|
||||||
|
rate: 4.5, //1人民币约等于4.5新台币
|
||||||
|
},
|
||||||
|
TWD: {
|
||||||
|
symbol: "NT$",
|
||||||
|
rate: 1,
|
||||||
|
},
|
||||||
|
USD: {
|
||||||
|
symbol: "$",
|
||||||
|
rate: 32.82, //1美元约等于32.82新台币
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatCurrency = (currency, amount) => {
|
||||||
|
const safeAmount = Number(amount) || 0;
|
||||||
|
return `${EXCHANGE_RATE[currency].symbol || "NT$"}${safeAmount.toLocaleString(
|
||||||
|
"zh-TW",
|
||||||
|
{
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}
|
||||||
|
)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExchangeRate = (fromCurrency, amount) => {
|
||||||
|
// 如果没有传入货币类型、金额,或货币类型不存在,则返回原金额
|
||||||
|
if (!fromCurrency || !amount || !EXCHANGE_RATE[fromCurrency]) {
|
||||||
|
return amount || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 因为是以新台币为基准,所以需要将其他货币转换为新台币金额
|
||||||
|
if (fromCurrency === "TWD") {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他货币转换为新台币
|
||||||
|
return (amount / EXCHANGE_RATE[fromCurrency].rate).toFixed(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatExchangeRate = (currency, amount) => {
|
||||||
|
return formatCurrency(currency, getExchangeRate(currency, amount));
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user