format amount split

This commit is contained in:
Timi
2026-09-08 15:21:09 +08:00
parent cbc8197057
commit 74127fc20f
12 changed files with 78 additions and 40 deletions
@@ -14,7 +14,7 @@
import type { EChartsOption } from "echarts"; import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import type { GaoSalesOrderConsumptionDailyStatView } from "@/types/GaoChart"; import type { GaoSalesOrderConsumptionDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils"; import { buildAxisChartOption, formatChartYuan, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
defineOptions({ defineOptions({
name: "CustomerConsumptionDailyChart" name: "CustomerConsumptionDailyChart"
@@ -46,7 +46,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
{ {
type: "value", type: "value",
name: "元", name: "元",
axisLabel: { color: "#52607a" }, axisLabel: { color: "#52607a", formatter: (value: number) => formatChartYuan(value) },
splitLine: { show: false } splitLine: { show: false }
} }
], ],
@@ -62,6 +62,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.amount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.amount) / 100)
}, },
{ {
@@ -69,6 +70,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.cumulativeAmount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.cumulativeAmount) / 100)
} }
] ]
@@ -14,7 +14,7 @@
import type { EChartsOption } from "echarts"; import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import type { GaoSalesOrderCalendarStatView } from "@/types/GaoChart"; import type { GaoSalesOrderCalendarStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils"; import { buildAxisChartOption, formatChartYuan, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
defineOptions({ defineOptions({
name: "CustomerOrderCalendarChart" name: "CustomerOrderCalendarChart"
@@ -46,7 +46,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
{ {
type: "value", type: "value",
name: "元", name: "元",
axisLabel: { color: "#52607a" }, axisLabel: { color: "#52607a", formatter: (value: number) => formatChartYuan(value) },
splitLine: { show: false } splitLine: { show: false }
} }
], ],
@@ -62,6 +62,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100)
}, },
{ {
@@ -69,6 +70,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100)
} }
] ]
@@ -13,7 +13,7 @@
import type { EChartsOption } from "echarts"; import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import type { GaoCustomerOrderRankView } from "@/types/GaoChart"; import type { GaoCustomerOrderRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils"; import { buildHorizontalBarOption, formatChartYuan, getNumber } from "@/pages/product/component/chartUtils";
defineOptions({ defineOptions({
name: "CustomerOrderRankChart" name: "CustomerOrderRankChart"
@@ -33,13 +33,14 @@ const chartOption = computed<EChartsOption>(() => {
type: "value", type: "value",
axisLabel: { axisLabel: {
color: "#52607a", color: "#52607a",
formatter: (value: number) => `${value.toLocaleString("zh-CN")} 元` formatter: (value: number) => `${formatChartYuan(value)} 元`
}, },
splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } } splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } }
}, },
series: [{ series: [{
name: "净收款", name: "净收款",
type: "bar", type: "bar",
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: list.map(item => getNumber(item.netPaidAmount) / 100) data: list.map(item => getNumber(item.netPaidAmount) / 100)
}] }]
}; };
@@ -14,7 +14,7 @@
import type { EChartsOption } from "echarts"; import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import type { GaoSalesOrderDailyStatView } from "@/types/GaoChart"; import type { GaoSalesOrderDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils"; import { buildAxisChartOption, formatChartYuan, formatQuantity, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
defineOptions({ defineOptions({
name: "OrderDailyChart" name: "OrderDailyChart"
@@ -48,7 +48,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
name: "元", name: "元",
axisLabel: { axisLabel: {
color: "#52607a", color: "#52607a",
formatter: (value: number) => value.toLocaleString("zh-CN") formatter: (value: number) => formatChartYuan(value)
}, },
splitLine: { show: false } splitLine: { show: false }
} }
@@ -58,6 +58,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
name: "订单数", name: "订单数",
type: "bar", type: "bar",
yAxisIndex: 0, yAxisIndex: 0,
tooltip: { valueFormatter: value => formatQuantity(Number(value)) },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.orderCount)) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.orderCount))
}, },
{ {
@@ -65,6 +66,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100)
}, },
{ {
@@ -72,6 +74,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
type: "line", type: "line",
yAxisIndex: 1, yAxisIndex: 1,
smooth: true, smooth: true,
tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` },
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100) data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100)
} }
] ]
@@ -148,7 +148,7 @@ import { BottomPopup } from "timi-tdesign-mobile";
import type { ProductType } from "@/types/Product"; import type { ProductType } from "@/types/Product";
import type { ProductSkuForm, ProductSpecForm, ProductSkuUnitForm } from "@/types/ProductEditor"; import type { ProductSkuForm, ProductSpecForm, ProductSkuUnitForm } from "@/types/ProductEditor";
import PhotoScroller from "@/components/PhotoScroller.vue"; import PhotoScroller from "@/components/PhotoScroller.vue";
import { getProductSkuSpecEntries } from "@/utils/product"; import { formatProductAmount, getProductSkuSpecEntries, parseProductAmount } from "@/utils/product";
import { uploadPhotoList } from "@/utils/photo"; import { uploadPhotoList } from "@/utils/photo";
defineOptions({ name: "ProductSkuEditor" }); defineOptions({ name: "ProductSkuEditor" });
@@ -195,7 +195,8 @@ function setSkuStatus(value: boolean | string | number): void {
function getUnitSummary(unit: ProductSkuUnitForm): string { function getUnitSummary(unit: ProductSkuUnitForm): string {
const quantityText = unit.isBase ? "基础单位" : `1 ${unit.name || "单位"} = ${unit.baseQuantityPerUnit || "?"} 基础单位`; const quantityText = unit.isBase ? "基础单位" : `1 ${unit.name || "单位"} = ${unit.baseQuantityPerUnit || "?"} 基础单位`;
const priceText = unit.salePriceText ? `¥${unit.salePriceText}` : "未定价"; const salePrice = parseProductAmount(unit.salePriceText);
const priceText = salePrice === undefined ? "未定价" : `¥${formatProductAmount(salePrice)}`;
const saleText = unit.allowSale ? "可销售" : "停售"; const saleText = unit.allowSale ? "可销售" : "停售";
const purchaseText = unit.allowPurchase ? "可采购" : "禁采购"; const purchaseText = unit.allowPurchase ? "可采购" : "禁采购";
return `${quantityText} · ${priceText} · ${saleText} · ${purchaseText}`; return `${quantityText} · ${priceText} · ${saleText} · ${purchaseText}`;
+6 -4
View File
@@ -1,4 +1,5 @@
import type { EChartsOption } from "echarts"; import type { EChartsOption } from "echarts";
import { formatCentsAmount, formatYuanAmount } from "@/utils/money";
export const chartColorList = ["#2563eb", "#0f766e", "#d97706", "#db2777", "#7c3aed", "#0891b2"]; export const chartColorList = ["#2563eb", "#0f766e", "#d97706", "#db2777", "#7c3aed", "#0891b2"];
@@ -175,10 +176,11 @@ export function formatQuantity(value?: number): string {
} }
export function formatMoney(value?: number): string { export function formatMoney(value?: number): string {
return `¥${(getNumber(value) / 100).toLocaleString("zh-CN", { return `¥${formatCentsAmount(getNumber(value))}`;
minimumFractionDigits: 2, }
maximumFractionDigits: 2
})}`; export function formatChartYuan(value?: number): string {
return formatYuanAmount(getNumber(value));
} }
export function getDateStart(date: Date): Date { export function getDateStart(date: Date): Date {
@@ -54,7 +54,7 @@
<t-cell title="记录数" :note="`共 ${total} 条`" /> <t-cell title="记录数" :note="`共 ${total} 条`" />
<t-cell title="消费金额合计" :note="`¥${formatPackageAmount(amountTotal)}`" /> <t-cell title="消费金额合计" :note="`¥${formatPackageAmount(amountTotal)}`" />
</t-cell-group> </t-cell-group>
<t-cell-group v-if="recordList.length" theme="card"> <t-cell-group v-if="recordList.length" class="empty-cell-title-margin" theme="card">
<t-cell <t-cell
class="item" class="item"
v-for="(item, index) in recordList" v-for="(item, index) in recordList"
@@ -275,7 +275,7 @@ function getCustomerText(record: ConsumptionRecord): string {
} }
function getStatusText(value?: ConsumptionRecordStatus): string { function getStatusText(value?: ConsumptionRecordStatus): string {
return value === "REVOKED" ? "已失效" : "有效"; return value === "REVOKED" ? "失效" : "有效";
} }
function canOpenSource(record: ConsumptionRecord): boolean { function canOpenSource(record: ConsumptionRecord): boolean {
@@ -46,6 +46,7 @@ import ChartRangeSlider from "@/components/ChartRangeSlider.vue";
import type { EventItem } from "@/types/EventItem"; import type { EventItem } from "@/types/EventItem";
import type { StoreEventRecordNumberValueDailyStatView } from "@/types/Store"; import type { StoreEventRecordNumberValueDailyStatView } from "@/types/Store";
import { formatEventNumberValue, getEventValueUnitText } from "@/utils/eventValue"; import { formatEventNumberValue, getEventValueUnitText } from "@/utils/eventValue";
import { formatYuanAmount } from "@/utils/money";
import { buildStoreBaseChartOption, useStoreChart, useStoreChartWindow } from "@/pages/store/component/useStoreChart"; import { buildStoreBaseChartOption, useStoreChart, useStoreChartWindow } from "@/pages/store/component/useStoreChart";
const props = defineProps<{ dateTextList: string[] }>(); const props = defineProps<{ dateTextList: string[] }>();
@@ -86,10 +87,13 @@ const chartOption = computed<EChartsOption>(() => ({
trigger: "axis", trigger: "axis",
valueFormatter: value => formatChartValue(Number(value)) valueFormatter: value => formatChartValue(Number(value))
}, },
yAxis: { yAxis: {
type: "value", type: "value",
name: selectedEvent.value ? getEventValueUnitText(selectedEvent.value.valueUnit) : "", name: selectedEvent.value ? getEventValueUnitText(selectedEvent.value.valueUnit) : "",
axisLabel: { color: "#52607a" }, axisLabel: {
color: "#52607a",
formatter: value => selectedEvent.value?.valueUnit === "CENT" ? formatYuanAmount(Number(value)) : String(value)
},
splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } } splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } }
} }
})); }));
@@ -160,7 +164,7 @@ function toDisplayValue(value: number): number {
function formatChartValue(value: number): string { function formatChartValue(value: number): string {
if (selectedEvent.value?.valueUnit === "CENT") { if (selectedEvent.value?.valueUnit === "CENT") {
return `${value.toFixed(2).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1")} 元`; return `${formatYuanAmount(value)} 元`;
} }
return formatEventNumberValue(selectedEvent.value, value); return formatEventNumberValue(selectedEvent.value, value);
} }
+2 -4
View File
@@ -1,4 +1,5 @@
import type { EventItem } from "@/types/EventItem"; import type { EventItem } from "@/types/EventItem";
import { formatCentsAmount } from "@/utils/money";
/** /**
* 返回事件数值单位的前端文案。 * 返回事件数值单位的前端文案。
@@ -82,8 +83,5 @@ export function formatCentValue(value: number): string {
if (!Number.isSafeInteger(value) || value < 0) { if (!Number.isSafeInteger(value) || value < 0) {
return ""; return "";
} }
const centText = String(value); return formatCentsAmount(value).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1");
const yuanText = centText.length < 3 ? "0" : centText.slice(0, -2);
const decimalText = centText.slice(-2).padStart(2, "0").replace(/0+$/, "");
return `${yuanText}${decimalText ? `.${decimalText}` : ""}`;
} }
+36
View File
@@ -0,0 +1,36 @@
/**
* 将整数部分按每 4 位分组,适用于中文金额展示。
* 例如:999999.99 -> 99,9999.99。
*/
export function formatWanInteger(value: number | string): string {
const text = String(value).trim();
const negative = text.startsWith("-");
const integerText = (negative || text.startsWith("+")) ? text.slice(1) : text;
const normalizedText = integerText.replace(/^0+(?=\d)/, "") || "0";
const groupedText = normalizedText.replace(/\B(?=(\d{4})+(?!\d))/g, ",");
return `${negative ? "-" : ""}${groupedText}`;
}
/** 将整数分转换为带两位小数的金额展示文本。 */
export function formatCentsAmount(cents?: number | null): string {
if (cents === undefined || cents === null || !Number.isFinite(cents)) {
return "未设置";
}
const negative = cents < 0;
const absolute = Math.abs(Math.trunc(cents));
const yuan = Math.floor(absolute / 100);
const fen = String(absolute % 100).padStart(2, "0");
return `${negative ? "-" : ""}${formatWanInteger(yuan)}.${fen}`;
}
/** 将元金额转换为按每 4 位分组的展示文本,最多保留两位小数。 */
export function formatYuanAmount(value?: number | null): string {
if (value === undefined || value === null || !Number.isFinite(value)) {
return "0";
}
const [integerText, fractionText = ""] = value.toFixed(2).split(".");
const normalizedFractionText = fractionText.replace(/0+$/, "");
return `${formatWanInteger(integerText)}${normalizedFractionText ? `.${normalizedFractionText}` : ""}`;
}
+2 -8
View File
@@ -1,5 +1,6 @@
import type { PackageConfigStatus } from "@/types/Package"; import type { PackageConfigStatus } from "@/types/Package";
import type { FundOperationType, FundRecordStatus } from "@/types/SalesOrder"; import type { FundOperationType, FundRecordStatus } from "@/types/SalesOrder";
import { formatCentsAmount } from "@/utils/money";
/** 生成套餐业务幂等请求号。每次新操作必须重新生成。 */ /** 生成套餐业务幂等请求号。每次新操作必须重新生成。 */
export function createPackageRequestNo(): string { export function createPackageRequestNo(): string {
@@ -8,14 +9,7 @@ export function createPackageRequestNo(): string {
/** 将分转换为金额展示文本,不用浮点数做金额格式化。 */ /** 将分转换为金额展示文本,不用浮点数做金额格式化。 */
export function formatPackageAmount(cents?: number | null): string { export function formatPackageAmount(cents?: number | null): string {
if (cents === undefined || cents === null) { return formatCentsAmount(cents);
return "未设置";
}
const negative = cents < 0;
const absolute = Math.abs(Math.trunc(cents));
const yuan = Math.floor(absolute / 100);
const fen = String(absolute % 100).padStart(2, "0");
return `${negative ? "-" : ""}${yuan}.${fen}`;
} }
/** 将分转换为金额输入框文本。 */ /** 将分转换为金额输入框文本。 */
+2 -7
View File
@@ -1,5 +1,6 @@
import type { Product, ProductCategoryStatus, ProductSku, ProductSpec, ProductSpecValue, ProductStatus, ProductType, StockOperationType } from "@/types/Product"; import type { Product, ProductCategoryStatus, ProductSku, ProductSpec, ProductSpecValue, ProductStatus, ProductType, StockOperationType } from "@/types/Product";
import { getAttachmentReadURL } from "@/utils/attachment.ts"; import { getAttachmentReadURL } from "@/utils/attachment.ts";
import { formatCentsAmount } from "@/utils/money";
/** 生成产品域写操作的幂等请求号。一次失败重试应复用调用方已经生成的请求号。 */ /** 生成产品域写操作的幂等请求号。一次失败重试应复用调用方已经生成的请求号。 */
export function createProductRequestNo(): string { export function createProductRequestNo(): string {
@@ -8,13 +9,7 @@ export function createProductRequestNo(): string {
/** 将分转换成金额展示文本。金额不使用浮点数格式化。 */ /** 将分转换成金额展示文本。金额不使用浮点数格式化。 */
export function formatProductAmount(cents?: number | null): string { export function formatProductAmount(cents?: number | null): string {
if (cents === undefined || cents === null) { return formatCentsAmount(cents);
return "未设置";
}
const negative = cents < 0;
const absolute = Math.abs(Math.trunc(cents));
return `${negative ? "-" : ""}${Math.floor(absolute / 100)}.${String(absolute % 100).padStart(2, "0")}`;
} }
/** 将元金额输入精确转换为分。 */ /** 将元金额输入精确转换为分。 */