From 86c080ae40c1a0f3044a9cb9682b66dae7f562a8 Mon Sep 17 00:00:00 2001 From: yml Date: Tue, 9 Jun 2026 14:07:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E7=AB=AF=E9=80=82=E9=85=8D=EF=BC=9AMo?= =?UTF-8?q?ney=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B0=E5=92=8CWallet=20API?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心改造: 1. Money工具函数:新增formatCent、formatCentWithSymbol、yuanToCent等函数 2. Wallet API类型:WalletAccount和WalletLedger改为*_cent字段 3. API请求函数:rechargeWallet和startWalletRechargePayment自动转换元为分 4. 文档:生成完整的前端适配指南 技术细节: - formatCent: 分转角并格式化(四舍五入到0.1元) - yuanToCent: 元转分(用于表单提交) - API函数自动处理转换,组件层面仍使用元 - 生成详细的适配指南供后续组件修改参考 下一步: - 按照前端适配指南修改所有Vue组件 - 测试金额显示和计算精度 --- docs/前端金额字段适配指南.md | 453 +++++++++++++++++++++ docs/金额统一重构完成报告.md | 317 ++++++++++++++ frontend/src/features/wallet/api/wallet.ts | 18 +- frontend/src/shared/utils/money.ts | 47 ++- 4 files changed, 826 insertions(+), 9 deletions(-) create mode 100644 docs/前端金额字段适配指南.md create mode 100644 docs/金额统一重构完成报告.md diff --git a/docs/前端金额字段适配指南.md b/docs/前端金额字段适配指南.md new file mode 100644 index 0000000..0bb366e --- /dev/null +++ b/docs/前端金额字段适配指南.md @@ -0,0 +1,453 @@ +# 前端金额字段适配指南 + +**项目**: HFB_SYS +**目标**: 适配后端金额分字段重构 +**状态**: 后端已完成 ✅,前端待适配 📝 + +--- + +## 一、已完成的工作 ✅ + +### 1. Money 工具函数更新 +**文件**: `frontend/src/shared/utils/money.ts` + +新增函数: +```typescript +// 分转角(四舍五入到0.1元) +export function centToJiao(cent: number | undefined | null): number + +// 格式化分为角字符串(保留1位小数) +export function formatCent(cent: number | undefined | null): string + +// 格式化分为角字符串并添加货币符号 +export function formatCentWithSymbol(cent: number | undefined | null): string + +// 元转分(四舍五入) +export function yuanToCent(yuan: number | undefined | null): number +``` + +### 2. Wallet API 类型定义更新 +**文件**: `frontend/src/features/wallet/api/wallet.ts` + +```typescript +// 已修改 +export interface WalletAccount { + user_id: number + available_balance_cent: number // 改为 _cent + frozen_balance_cent: number // 改为 _cent + status: WalletStatus +} + +export interface WalletLedger { + id: number + ledger_no: string + user_id: number + order_id?: number + direction: LedgerDirection + amount_cent: number // 改为 _cent + balance_after_cent: number // 改为 _cent + balance_type: BalanceType + biz_type: string + biz_no: string + remark: string + created_at: string +} + +// 已修改:发送时将元转为分 +export async function rechargeWallet(amountYuan: number) { + const amount_cent = Math.round(amountYuan * 100) + const { data } = await apiClient.post>('/wallet/recharge', { amount_cent }) + return data.data +} +``` + +--- + +## 二、待适配的文件清单 + +### 模块 1: Wallet(钱包) + +#### 1.1 WalletView.vue ⚠️ 需要修改 +**文件**: `frontend/src/features/wallet/views/WalletView.vue` + +**需要修改的地方**: + +```vue + + +value: formatMoney(account.value.available_balance), + + +value: formatCent(account.value.available_balance_cent), + + + +value: formatMoney(account.value.frozen_balance), + + +value: formatCent(account.value.frozen_balance_cent), + + + +{{ account ? formatMoney(account.available_balance) : '¥0.00' }} + + +{{ account ? formatCentWithSymbol(account.available_balance_cent) : '¥0.0' }} + + + +{{ amountPrefix(row.direction) }}{{ formatMoney(row.amount) }} + + +{{ amountPrefix(row.direction) }}{{ formatCent(row.amount_cent) }} + + + +{{ formatMoney(row.balance_after) }} + + +{{ formatCent(row.balance_after_cent) }} + + + +formatMoney(activeRechargePayment.amount_cent / 100) + + +formatCent(activeRechargePayment.amount_cent) +``` + +**需要添加的 import**: +```vue + +``` + +**删除的本地函数**: +```typescript +// 删除这个函数(第 224-226 行),使用全局的 formatCent +function formatMoney(value: number) { + return `¥${(Math.round(Number(value || 0) * 10) / 10).toFixed(1)}` +} +``` + +--- + +### 模块 2: Withdrawal(提现) + +#### 2.1 withdrawal.ts API ⚠️ 需要修改 +**文件**: `frontend/src/features/wallet/api/withdrawal.ts` + +**需要修改的类型定义**: +```typescript +export interface WithdrawalRequest { + id: number + withdraw_no: string + user_id: number + amount_cent: number // 改为 _cent + fee_cent: number // 改为 _cent + actual_amount_cent: number // 改为 _cent + payment_account_id: number + // ... 其他字段 +} + +// 创建提现请求函数需要转换 +export async function createWithdrawal(amountYuan: number, payment_account_id: number) { + const amount_cent = Math.round(amountYuan * 100) + const { data } = await apiClient.post>( + '/wallet/withdrawal', + { amount_cent, payment_account_id } + ) + return data.data +} +``` + +#### 2.2 WithdrawalView.vue ⚠️ 需要修改 +**文件**: `frontend/src/features/wallet/views/WithdrawalView.vue` + +**需要修改的地方**: + +```vue + + +
¥{{ formatMoney(account?.available_balance) }}
+
¥{{ formatMoney(account?.frozen_balance) }}
+ + +
{{ formatCentWithSymbol(account?.available_balance_cent) }}
+
{{ formatCentWithSymbol(account?.frozen_balance_cent) }}
+ + + +account.value && withdrawForm.value.amount <= account.value.available_balance + + +account.value && withdrawForm.value.amount <= (account.value.available_balance_cent / 100) + + + +{{ formatCent(withdrawal.amount_cent) }} +{{ formatCent(withdrawal.fee_cent) }} +{{ formatCent(withdrawal.actual_amount_cent) }} +``` + +**常量更新**: +```typescript +// 这些是用户输入的元,不需要改 +const MIN_AMOUNT = 10 +const MAX_AMOUNT = 5000 +``` + +--- + +### 模块 3: Order(订单) + +#### 3.1 orders.ts API ⚠️ 需要修改 +**文件**: `frontend/src/features/orders/api/orders.ts` + +**需要修改的类型定义**: +```typescript +export interface Order { + id: number + order_no: string + // ... 其他字段 + display_amount_cent: number + rent_amount_cent?: number + owner_rent_amount_cent?: number + deposit_amount_cent: number + deposit_original_amount_cent: number + deposit_waived_amount_cent: number + platform_fee_cent?: number + // ... 其他字段 +} + +export interface Checkout { + id: number + order_id: number + // ... 其他字段 + display_amount_cent: number + rent_amount_cent?: number + owner_rent_amount_cent?: number + platform_fee_cent?: number + deposit_amount_cent: number + consumable_amount_cent: number + coin_consumed_m: number + other_amount_cent: number + deposit_deduct_amount_cent: number + renter_refund_amount_cent?: number + owner_income_amount_cent?: number + // ... 其他字段 +} +``` + +#### 3.2 OrderDetailView.vue ⚠️ 需要修改 +**文件**: `frontend/src/features/orders/views/OrderDetailView.vue` + +查找所有使用金额的地方,改为使用 `formatCent`: +```vue + +{{ formatCent(order.display_amount_cent) }} +{{ formatCent(order.rent_amount_cent) }} +{{ formatCent(order.deposit_amount_cent) }} +{{ formatCent(checkout.renter_refund_amount_cent) }} +{{ formatCent(checkout.owner_income_amount_cent) }} +``` + +#### 3.3 MobileOrdersView.vue ⚠️ 需要修改 +类似地修改所有金额展示。 + +--- + +### 模块 4: Listing(商品) + +#### 4.1 listings.ts API ⚠️ 需要修改 +**文件**: `frontend/src/features/listings/api/listings.ts` + +```typescript +export interface Listing { + id: number + listing_no: string + // ... 其他字段 + price_cent: number // 改为 _cent + deposit_amount_cent: number // 改为 _cent + // ... 其他字段 +} + +// 创建/更新 Listing 时需要转换 +export async function createListing(data: { + title: string + price: number + deposit_amount: number + // ... 其他字段 +}) { + const payload = { + ...data, + price_cent: Math.round(data.price * 100), + deposit_amount_cent: Math.round(data.deposit_amount * 100), + } + // 删除旧字段 + delete payload.price + delete payload.deposit_amount + + const { data: response } = await apiClient.post>( + '/listings', + payload + ) + return response.data +} +``` + +#### 4.2 ListingCard.vue ⚠️ 需要修改 +查找所有显示价格和押金的地方: +```vue +{{ formatCent(listing.price_cent) }} +{{ formatCent(listing.deposit_amount_cent) }} +``` + +--- + +## 三、批量替换建议 + +### 全局搜索替换模式 + +1. **API 响应字段名**: + ``` + available_balance → available_balance_cent + frozen_balance → frozen_balance_cent + amount → amount_cent + balance_after → balance_after_cent + price → price_cent + deposit_amount → deposit_amount_cent + rent_amount → rent_amount_cent + owner_rent_amount → owner_rent_amount_cent + platform_fee → platform_fee_cent + ``` + +2. **格式化函数调用**: + ``` + formatMoney(xxx.amount) → formatCent(xxx.amount_cent) + formatMoneyWithSymbol(xxx.amount) → formatCentWithSymbol(xxx.amount_cent) + ``` + +3. **金额比较**: + ```typescript + // 旧代码 + if (inputAmount > account.available_balance) { ... } + + // 新代码 + if (inputAmount > (account.available_balance_cent / 100)) { ... } + ``` + +--- + +## 四、测试检查清单 + +### 功能测试 + +- [ ] 钱包余额正确显示(角精度) +- [ ] 充值功能正常(输入元,后端收到分) +- [ ] 提现功能正常(输入元,后端收到分) +- [ ] 订单金额正确显示 +- [ ] 订单结算金额计算正确 +- [ ] 商品价格正确显示 +- [ ] 商品发布价格输入正常 + +### 显示精度测试 + +测试用例: +```typescript +// 123.45 元 = 12345 分 → 显示 123.5 元 +formatCent(12345) // 应该输出 "123.5" + +// 123.44 元 = 12344 分 → 显示 123.4 元 +formatCent(12344) // 应该输出 "123.4" + +// 0.01 元 = 1 分 → 显示 0.0 元 +formatCent(1) // 应该输出 "0.0" +``` + +### 边界值测试 + +- [ ] 最小充值金额:0.01 元 = 1 分 +- [ ] 最小提现金额:10 元 = 1000 分 +- [ ] 最大提现金额:5000 元 = 500000 分 +- [ ] 零金额显示:formatCent(0) → "0.0" + +--- + +## 五、常见问题 + +### Q1: 为什么显示是角精度而不是分精度? +**A**: 业务需求只展示到角(0.1元),分精度对用户来说太细致且无实际意义。 + +### Q2: 用户输入 123.45 元,后端收到多少? +**A**: `yuanToCent(123.45)` = 12345 分 + +### Q3: 后端返回 12345 分,前端显示什么? +**A**: `formatCent(12345)` = "123.5" 元(四舍五入到角) + +### Q4: 如何处理浮点输入的精度? +**A**: 使用 `Math.round(yuan * 100)` 四舍五入到分,避免浮点误差。 + +### Q5: 旧的 formatMoney 函数还能用吗? +**A**: 可以,但仅用于已经是元的数值。对于后端返回的分字段,必须使用 formatCent。 + +--- + +## 六、快速参考 + +### 常用代码片段 + +```vue + + + +``` + +--- + +## 七、推荐的适配顺序 + +1. ✅ 工具函数(已完成) +2. ✅ Wallet API 类型(已完成) +3. ⚠️ Wallet 组件(进行中) +4. ⚠️ Withdrawal API 和组件 +5. ⚠️ Order API 和组件 +6. ⚠️ Listing API 和组件 +7. ⚠️ AdminFinance 组件(如果有) +8. 🧪 完整测试 + +--- + +**预计工作量**: 2-3 小时 +**难度**: 中等(主要是查找替换和测试) +**风险**: 低(类型系统会帮助发现遗漏的地方) + +--- + +**文档生成时间**: 2026-06-09 +**作者**: Claude Code AI Assistant diff --git a/docs/金额统一重构完成报告.md b/docs/金额统一重构完成报告.md new file mode 100644 index 0000000..30b7618 --- /dev/null +++ b/docs/金额统一重构完成报告.md @@ -0,0 +1,317 @@ +# 金额统一重构完成报告 + +**项目**: HFB_SYS - 游戏账号租赁平台 +**重构目标**: 统一金额存储和计算,从 float64(元)改为 int64(分) +**完成时间**: 2026-06-09 +**状态**: ✅ 后端完成,前端待适配 + +--- + +## 一、重构目标 + +### 问题背景 +- **浮点精度问题**: 使用 float64 存储金额导致精度丢失 +- **角精度展示**: 业务需求仅展示到角(0.1元),但存储需要精确到分 +- **计算误差**: 浮点运算在累加、结算时可能产生误差 + +### 解决方案 +- **全链路整数存储**: 数据库 BIGINT(分) → Go int64 → API int64 → 前端 number +- **整数运算**: 所有金额计算使用整数,消除浮点误差 +- **角精度展示**: 存储精确到分,展示时转换为角(除以10,保留1位小数) + +--- + +## 二、已完成的工作 + +### 1. 数据库迁移 ✅ + +**文件**: `backend/migrations/000006_add_money_cent_fields.sql` + +新增字段: +```sql +-- rental_orders 表 +ALTER TABLE rental_orders + ADD COLUMN rent_amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN deposit_amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN deposit_original_amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN deposit_waived_amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN platform_fee_cent BIGINT NOT NULL DEFAULT 0; + +-- rental_listings 表 +ALTER TABLE rental_listings + ADD COLUMN price_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN deposit_amount_cent BIGINT NOT NULL DEFAULT 0; + +-- wallet_accounts 表 +ALTER TABLE wallet_accounts + ADD COLUMN available_balance_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN frozen_balance_cent BIGINT NOT NULL DEFAULT 0; + +-- wallet_ledger 表 +ALTER TABLE wallet_ledger + ADD COLUMN amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN balance_after_cent BIGINT NOT NULL DEFAULT 0; + +-- withdrawal_requests 表 +ALTER TABLE withdrawal_requests + ADD COLUMN amount_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN fee_cent BIGINT NOT NULL DEFAULT 0, + ADD COLUMN actual_amount_cent BIGINT NOT NULL DEFAULT 0; +``` + +历史数据迁移: +```sql +UPDATE rental_orders SET + rent_amount_cent = CAST(ROUND(rent_amount * 100) AS SIGNED), + owner_rent_amount_cent = CAST(ROUND(owner_rent_amount * 100) AS SIGNED), + ...; +``` + +### 2. Model 层 ✅ + +**文件**: `backend/internal/model/*.go` + +为所有金额相关表新增 *Cent 字段: +```go +type RentalOrder struct { + RentAmount float64 `gorm:"type:decimal(12,2)" json:"-"` + RentAmountCent int64 `gorm:"not null;default:0" json:"-"` + OwnerRentAmount float64 `gorm:"type:decimal(12,2)" json:"-"` + OwnerRentAmountCent int64 `gorm:"not null;default:0" json:"-"` + // ... 其他字段 +} +``` + +### 3. Money 工具包 ✅ + +**文件**: `backend/pkg/money/money.go` + +核心函数: +```go +// ToCent 元转分(四舍五入) +func ToCent(yuan float64) int64 + +// FromCent 分转元 +func FromCent(cent int64) float64 + +// FormatCent 格式化分为元字符串(2位小数) +func FormatCent(cent int64) string + +// FormatJiao 格式化分为角字符串(1位小数) +func FormatJiao(cent int64) string + +// ToJiao 分转角(四舍五入) +func ToJiao(cent int64) int64 + +// FromJiao 角转分 +func FromJiao(jiao int64) int64 +``` + +### 4. 核心业务模块 ✅ + +#### Wallet 模块 ✅ +- **DTO**: 所有金额字段改为 `*Cent int64` +- **Entry结构**: `AmountCent int64` 替代 `Amount float64` +- **整数运算**: `applyEntry` 使用纯整数加减 +- **删除**: `roundWalletMoney` 函数(不再需要) + +#### Withdrawal 模块 ✅ +- **DTO**: `WithdrawalDTO`, `CreateWithdrawalRequest` 改为 `*Cent` +- **手续费计算**: 改为分单位 +- **最小/最大金额**: MinWithdrawalAmountCent = 1000 (10元) + +#### Order 模块 ✅(最复杂) +- **DTO**: `OrderDTO`, `CheckoutDTO`, `RefundStatusDTO` 改为 `*Cent` +- **内部结构体**: `orderPricing`, `checkoutSettlement` 改为分字段 +- **结算计算**: `calculateCheckoutSettlement` 内部用元计算保持兼容,返回分 +- **钱包操作**: 所有 `wallet.AppendEntries` 调用改为 `AmountCent` +- **退款逻辑**: 3 处改为直接使用分字段相加 + +#### Dispute 模块 ✅ +- **修复**: 2 处 `wallet.Entry` 调用改为 `AmountCent` + +#### Listing 模块 ✅ +- **DTO**: `PriceCent`, `DepositAmountCent` +- **筛选排序**: 改为使用分字段 +- **价格视图**: `sanitizePriceForOwner` 使用 `PriceCent` + +#### Payment 模块 ✅(已适配) +- **DTO**: `PaymentDTO`, `RefundDTO` 已使用 `AmountCent` +- **退款函数**: `StartRefund(orderID, refundAmountCent int64, ...)` 已适配 + +#### AdminFinance 模块 ✅(已适配) +- **DTO**: `FinanceSummaryDTO`, `FinanceDailyDTO` 已使用 `*Cent` 字段 +- **查询**: 财务统计查询已适配 + +--- + +## 三、技术实现亮点 + +### 1. 数据双字段并存 +新旧字段共存,便于渐进式迁移和回滚: +```go +type RentalOrder struct { + RentAmount float64 `gorm:"type:decimal(12,2)" json:"-"` // 旧字段 + RentAmountCent int64 `gorm:"not null;default:0" json:"-"` // 新字段 +} +``` + +### 2. 整数运算消除精度问题 +```go +// 旧代码(浮点运算) +account.AvailableBalance += amount +account.AvailableBalance = roundMoney(account.AvailableBalance) + +// 新代码(整数运算) +account.AvailableBalanceCent += amountCent // 直接加减,无精度损失 +``` + +### 3. 复杂结算兼容性处理 +Order 模块的 `calculateCheckoutSettlement` 函数: +```go +func calculateCheckoutSettlement(order model.RentalOrder, consumableAmount float64, + coinConsumedM float64, depositDeductAmount float64) checkoutSettlement { + + // 读取分字段并转为元(保持现有计算逻辑) + orderRentAmount := float64(order.RentAmountCent) / 100 + orderOwnerRentAmount := float64(order.OwnerRentAmountCent) / 100 + + // 现有的复杂计算逻辑... + actualRentAmount := minMoney(roundMoney(usedBuyerCoinPrice+usedBuyerConsumablePrice), orderRentAmount) + + // 最后转换为分返回 + return checkoutSettlement{ + ActualRentAmountCent: int64(math.Round(actualRentAmount * 100)), + OwnerRentIncomeCent: int64(math.Round(ownerRentIncome * 100)), + // ... + } +} +``` + +### 4. 角精度展示 +```go +// 后端返回分 +dto.PriceCent = 12345 // 123.45 元 + +// 前端展示角(formatJiao) +formatJiao(12345) → "123.5" // 显示 123.5 元 +``` + +--- + +## 四、Git 提交记录 + +```bash +eaa10d8 - 数据库迁移:新增金额分字段并迁移历史数据 +b80719d - Model层:新增金额分字段定义 +e2780ff - Money工具包:实现分↔角转换和格式化函数 +01909ee - Wallet与Withdrawal模块:完成分字段重构 +d803089 - Order模块:完成分字段重构 +4f3be22 - Dispute模块:修复wallet.Entry调用 +2185cd1 - Listing模块:完成分字段重构 +``` + +--- + +## 五、编译验证 + +```bash +✅ go build -o /dev/null ./cmd/api +编译通过,无错误 +``` + +--- + +## 六、下一步工作:前端适配 + +### 需要修改的文件 + +#### 1. TypeScript 类型定义 +更新所有 API 接口类型定义,将金额字段改为 `*_cent: number` + +#### 2. 工具函数 +```typescript +// frontend/src/utils/money.ts +export function formatCent(cent: number): string { + return (cent / 100).toFixed(2); +} + +export function formatJiao(cent: number): string { + return (Math.round(cent / 10) / 10).toFixed(1); +} + +export function toCent(yuan: number): number { + return Math.round(yuan * 100); +} +``` + +#### 3. 涉及的模块 +- **Wallet**: 余额展示、充值输入 +- **Withdrawal**: 提现金额输入、手续费显示 +- **Order**: 订单金额展示、结算详情 +- **Listing**: 商品价格展示、发布价格输入 +- **AdminFinance**: 财务报表展示 + +### 改造原则 +1. **表单输入**: 用户输入元 → 乘以100转为分 → 发送后端 +2. **数据展示**: 后端返回分 → 除以10四舍五入 → 显示角(1位小数) +3. **内部计算**: 尽量使用分进行计算,避免浮点运算 + +--- + +## 七、测试建议 + +### 1. 单元测试 +- Order 模块结算计算 +- Money 工具函数边界值 + +### 2. 集成测试 +- 完整订单流程 +- 钱包充值提现流程 + +### 3. 手工测试场景 +- 订单结算精度(租金+押金+手续费) +- 钱包余额累加(大量小额交易) +- 提现手续费计算 +- 边界值(最小充值、最大提现) + +--- + +## 八、回滚方案 + +如需回滚,执行以下步骤: + +1. **代码回滚** +```bash +git revert 2185cd1 # Listing +git revert 4f3be22 # Dispute +git revert d803089 # Order +git revert 01909ee # Wallet & Withdrawal +git revert e2780ff # Money工具包 +git revert b80719d # Model +``` + +2. **数据库回滚** +```sql +-- 不需要删除新字段,旧字段仍然存在 +-- 如果需要,可以执行: +ALTER TABLE rental_orders DROP COLUMN rent_amount_cent; +-- ... 其他表 +``` + +--- + +## 九、总结 + +✅ **后端重构完成度**: 100% +✅ **编译状态**: 通过 +✅ **代码质量**: 保持原有逻辑,仅替换金额字段 +✅ **兼容性**: 新旧字段并存,便于迁移 + +🎯 **下一阶段**: 前端适配(约需 2-3 小时) + +--- + +**报告生成时间**: 2026-06-09 +**作者**: Claude Code AI Assistant diff --git a/frontend/src/features/wallet/api/wallet.ts b/frontend/src/features/wallet/api/wallet.ts index 579f462..6b93d38 100644 --- a/frontend/src/features/wallet/api/wallet.ts +++ b/frontend/src/features/wallet/api/wallet.ts @@ -6,8 +6,8 @@ import type { PaymentOrder } from '@/features/orders/api/orders' export interface WalletAccount { user_id: number - available_balance: number - frozen_balance: number + available_balance_cent: number + frozen_balance_cent: number status: WalletStatus } @@ -17,8 +17,8 @@ export interface WalletLedger { user_id: number order_id?: number direction: LedgerDirection - amount: number - balance_after: number + amount_cent: number + balance_after_cent: number balance_type: BalanceType biz_type: string biz_no: string @@ -41,14 +41,16 @@ export async function fetchWalletLedger(page = 1, pageSize = 20) { return data.data } -export async function rechargeWallet(amount: number) { - const { data } = await apiClient.post>('/wallet/recharge', { amount }) +export async function rechargeWallet(amountYuan: number) { + const amount_cent = Math.round(amountYuan * 100) + const { data } = await apiClient.post>('/wallet/recharge', { amount_cent }) return data.data } -export async function startWalletRechargePayment(amount: number) { +export async function startWalletRechargePayment(amountYuan: number) { + const amount_cent = Math.round(amountYuan * 100) const { data } = await apiClient.post>('/wallet/recharge/pay', { - amount, + amount_cent, }) return data.data } diff --git a/frontend/src/shared/utils/money.ts b/frontend/src/shared/utils/money.ts index 2c37be5..cdcc1a9 100644 --- a/frontend/src/shared/utils/money.ts +++ b/frontend/src/shared/utils/money.ts @@ -1,6 +1,7 @@ /** * 统一金额处理工具函数 - * 全项目金额精度统一为角(0.1元) + * 后端存储:int64(分) + * 前端展示:角精度(0.1元) */ /** @@ -29,3 +30,47 @@ export function formatMoney(value: number | undefined | null): string { export function formatMoneyWithSymbol(value: number | undefined | null): string { return `¥${formatMoney(value)}` } + +/** + * 分转元(保留2位小数) + * @example centToYuan(12345) -> 123.45 + */ +export function centToYuan(cent: number | undefined | null): number { + return Number(cent || 0) / 100 +} + +/** + * 分转角(四舍五入到0.1元) + * @example centToJiao(12345) -> 123.5 + * @example centToJiao(12344) -> 123.4 + */ +export function centToJiao(cent: number | undefined | null): number { + return Math.round(Number(cent || 0) / 10) / 10 +} + +/** + * 格式化分为角字符串(保留1位小数) + * @example formatCent(12345) -> "123.5" + * @example formatCent(12344) -> "123.4" + */ +export function formatCent(cent: number | undefined | null): string { + return centToJiao(cent).toFixed(1) +} + +/** + * 格式化分为角字符串并添加货币符号 + * @example formatCentWithSymbol(12345) -> "¥123.5" + */ +export function formatCentWithSymbol(cent: number | undefined | null): string { + return `¥${formatCent(cent)}` +} + +/** + * 元转分(四舍五入) + * 用户输入金额时使用 + * @example yuanToCent(123.45) -> 12345 + * @example yuanToCent(123.456) -> 12346 + */ +export function yuanToCent(yuan: number | undefined | null): number { + return Math.round(Number(yuan || 0) * 100) +}