diff --git a/src/pages/customer/component/CustomerConsumptionDailyChart.vue b/src/pages/customer/component/CustomerConsumptionDailyChart.vue index 2ddb73b..188d25a 100644 --- a/src/pages/customer/component/CustomerConsumptionDailyChart.vue +++ b/src/pages/customer/component/CustomerConsumptionDailyChart.vue @@ -14,7 +14,7 @@ import type { EChartsOption } from "echarts"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; 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({ name: "CustomerConsumptionDailyChart" @@ -46,7 +46,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { { type: "value", name: "元", - axisLabel: { color: "#52607a" }, + axisLabel: { color: "#52607a", formatter: (value: number) => formatChartYuan(value) }, splitLine: { show: false } } ], @@ -62,6 +62,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.amount) / 100) }, { @@ -69,6 +70,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.cumulativeAmount) / 100) } ] diff --git a/src/pages/customer/component/CustomerOrderCalendarChart.vue b/src/pages/customer/component/CustomerOrderCalendarChart.vue index 5577fac..7442a84 100644 --- a/src/pages/customer/component/CustomerOrderCalendarChart.vue +++ b/src/pages/customer/component/CustomerOrderCalendarChart.vue @@ -14,7 +14,7 @@ import type { EChartsOption } from "echarts"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; 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({ name: "CustomerOrderCalendarChart" @@ -46,7 +46,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { { type: "value", name: "元", - axisLabel: { color: "#52607a" }, + axisLabel: { color: "#52607a", formatter: (value: number) => formatChartYuan(value) }, splitLine: { show: false } } ], @@ -62,6 +62,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100) }, { @@ -69,6 +70,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100) } ] diff --git a/src/pages/product/component/CustomerOrderRankChart.vue b/src/pages/product/component/CustomerOrderRankChart.vue index a9c6ab9..c9cae09 100644 --- a/src/pages/product/component/CustomerOrderRankChart.vue +++ b/src/pages/product/component/CustomerOrderRankChart.vue @@ -13,7 +13,7 @@ import type { EChartsOption } from "echarts"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; import type { GaoCustomerOrderRankView } from "@/types/GaoChart"; -import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils"; +import { buildHorizontalBarOption, formatChartYuan, getNumber } from "@/pages/product/component/chartUtils"; defineOptions({ name: "CustomerOrderRankChart" @@ -33,13 +33,14 @@ const chartOption = computed(() => { type: "value", axisLabel: { color: "#52607a", - formatter: (value: number) => `${value.toLocaleString("zh-CN")} 元` + formatter: (value: number) => `${formatChartYuan(value)} 元` }, splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } } }, series: [{ name: "净收款", type: "bar", + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: list.map(item => getNumber(item.netPaidAmount) / 100) }] }; diff --git a/src/pages/product/component/OrderDailyChart.vue b/src/pages/product/component/OrderDailyChart.vue index 79bb46e..919c0af 100644 --- a/src/pages/product/component/OrderDailyChart.vue +++ b/src/pages/product/component/OrderDailyChart.vue @@ -14,7 +14,7 @@ import type { EChartsOption } from "echarts"; import GaoChartCard from "@/pages/product/component/GaoChartCard.vue"; 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({ name: "OrderDailyChart" @@ -48,7 +48,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { name: "元", axisLabel: { color: "#52607a", - formatter: (value: number) => value.toLocaleString("zh-CN") + formatter: (value: number) => formatChartYuan(value) }, splitLine: { show: false } } @@ -58,6 +58,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { name: "订单数", type: "bar", yAxisIndex: 0, + tooltip: { valueFormatter: value => formatQuantity(Number(value)) }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.orderCount)) }, { @@ -65,6 +66,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100) }, { @@ -72,6 +74,7 @@ function buildOption(visibleDateTextList: string[]): EChartsOption { type: "line", yAxisIndex: 1, smooth: true, + tooltip: { valueFormatter: value => `${formatChartYuan(Number(value))} 元` }, data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100) } ] diff --git a/src/pages/product/component/ProductSkuEditor.vue b/src/pages/product/component/ProductSkuEditor.vue index d702ed0..742b328 100644 --- a/src/pages/product/component/ProductSkuEditor.vue +++ b/src/pages/product/component/ProductSkuEditor.vue @@ -148,7 +148,7 @@ import { BottomPopup } from "timi-tdesign-mobile"; import type { ProductType } from "@/types/Product"; import type { ProductSkuForm, ProductSpecForm, ProductSkuUnitForm } from "@/types/ProductEditor"; import PhotoScroller from "@/components/PhotoScroller.vue"; -import { getProductSkuSpecEntries } from "@/utils/product"; +import { formatProductAmount, getProductSkuSpecEntries, parseProductAmount } from "@/utils/product"; import { uploadPhotoList } from "@/utils/photo"; defineOptions({ name: "ProductSkuEditor" }); @@ -195,7 +195,8 @@ function setSkuStatus(value: boolean | string | number): void { function getUnitSummary(unit: ProductSkuUnitForm): string { 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 purchaseText = unit.allowPurchase ? "可采购" : "禁采购"; return `${quantityText} · ${priceText} · ${saleText} · ${purchaseText}`; diff --git a/src/pages/product/component/chartUtils.ts b/src/pages/product/component/chartUtils.ts index 9c6fa88..acdae5b 100644 --- a/src/pages/product/component/chartUtils.ts +++ b/src/pages/product/component/chartUtils.ts @@ -1,4 +1,5 @@ import type { EChartsOption } from "echarts"; +import { formatCentsAmount, formatYuanAmount } from "@/utils/money"; 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 { - return `¥${(getNumber(value) / 100).toLocaleString("zh-CN", { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - })}`; + return `¥${formatCentsAmount(getNumber(value))}`; +} + +export function formatChartYuan(value?: number): string { + return formatYuanAmount(getNumber(value)); } export function getDateStart(date: Date): Date { diff --git a/src/pages/salesOrder/ConsumptionRecordList.vue b/src/pages/salesOrder/ConsumptionRecordList.vue index 94356a9..7679ed4 100644 --- a/src/pages/salesOrder/ConsumptionRecordList.vue +++ b/src/pages/salesOrder/ConsumptionRecordList.vue @@ -54,7 +54,7 @@ - + (); @@ -86,10 +87,13 @@ const chartOption = computed(() => ({ trigger: "axis", valueFormatter: value => formatChartValue(Number(value)) }, - yAxis: { - type: "value", - name: selectedEvent.value ? getEventValueUnitText(selectedEvent.value.valueUnit) : "", - axisLabel: { color: "#52607a" }, + yAxis: { + type: "value", + name: selectedEvent.value ? getEventValueUnitText(selectedEvent.value.valueUnit) : "", + axisLabel: { + color: "#52607a", + formatter: value => selectedEvent.value?.valueUnit === "CENT" ? formatYuanAmount(Number(value)) : String(value) + }, splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } } } })); @@ -160,7 +164,7 @@ function toDisplayValue(value: number): number { function formatChartValue(value: number): string { if (selectedEvent.value?.valueUnit === "CENT") { - return `${value.toFixed(2).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1")} 元`; + return `${formatYuanAmount(value)} 元`; } return formatEventNumberValue(selectedEvent.value, value); } diff --git a/src/utils/eventValue.ts b/src/utils/eventValue.ts index eee6d43..3a3e3b7 100644 --- a/src/utils/eventValue.ts +++ b/src/utils/eventValue.ts @@ -1,4 +1,5 @@ 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) { return ""; } - const centText = String(value); - const yuanText = centText.length < 3 ? "0" : centText.slice(0, -2); - const decimalText = centText.slice(-2).padStart(2, "0").replace(/0+$/, ""); - return `${yuanText}${decimalText ? `.${decimalText}` : ""}`; + return formatCentsAmount(value).replace(/\.00$/, "").replace(/(\.\d)0$/, "$1"); } diff --git a/src/utils/money.ts b/src/utils/money.ts new file mode 100644 index 0000000..b07f198 --- /dev/null +++ b/src/utils/money.ts @@ -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}` : ""}`; +} diff --git a/src/utils/package.ts b/src/utils/package.ts index 91b9480..70c556a 100644 --- a/src/utils/package.ts +++ b/src/utils/package.ts @@ -1,5 +1,6 @@ import type { PackageConfigStatus } from "@/types/Package"; import type { FundOperationType, FundRecordStatus } from "@/types/SalesOrder"; +import { formatCentsAmount } from "@/utils/money"; /** 生成套餐业务幂等请求号。每次新操作必须重新生成。 */ export function createPackageRequestNo(): string { @@ -8,14 +9,7 @@ export function createPackageRequestNo(): string { /** 将分转换为金额展示文本,不用浮点数做金额格式化。 */ export function formatPackageAmount(cents?: number | null): string { - if (cents === undefined || cents === null) { - 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}`; + return formatCentsAmount(cents); } /** 将分转换为金额输入框文本。 */ diff --git a/src/utils/product.ts b/src/utils/product.ts index f4a728a..a794d38 100644 --- a/src/utils/product.ts +++ b/src/utils/product.ts @@ -1,5 +1,6 @@ import type { Product, ProductCategoryStatus, ProductSku, ProductSpec, ProductSpecValue, ProductStatus, ProductType, StockOperationType } from "@/types/Product"; import { getAttachmentReadURL } from "@/utils/attachment.ts"; +import { formatCentsAmount } from "@/utils/money"; /** 生成产品域写操作的幂等请求号。一次失败重试应复用调用方已经生成的请求号。 */ export function createProductRequestNo(): string { @@ -8,13 +9,7 @@ export function createProductRequestNo(): string { /** 将分转换成金额展示文本。金额不使用浮点数格式化。 */ export function formatProductAmount(cents?: number | null): string { - if (cents === undefined || cents === null) { - return "未设置"; - } - - const negative = cents < 0; - const absolute = Math.abs(Math.trunc(cents)); - return `${negative ? "-" : ""}${Math.floor(absolute / 100)}.${String(absolute % 100).padStart(2, "0")}`; + return formatCentsAmount(cents); } /** 将元金额输入精确转换为分。 */