核心改造: 1. Money工具函数:新增formatCent、formatCentWithSymbol、yuanToCent等函数 2. Wallet API类型:WalletAccount和WalletLedger改为*_cent字段 3. API请求函数:rechargeWallet和startWalletRechargePayment自动转换元为分 4. 文档:生成完整的前端适配指南 技术细节: - formatCent: 分转角并格式化(四舍五入到0.1元) - yuanToCent: 元转分(用于表单提交) - API函数自动处理转换,组件层面仍使用元 - 生成详细的适配指南供后续组件修改参考 下一步: - 按照前端适配指南修改所有Vue组件 - 测试金额显示和计算精度
454 lines
12 KiB
Markdown
454 lines
12 KiB
Markdown
# 前端金额字段适配指南
|
||
|
||
**项目**: 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<ApiResponse<WalletAccount>>('/wallet/recharge', { amount_cent })
|
||
return data.data
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 二、待适配的文件清单
|
||
|
||
### 模块 1: Wallet(钱包)
|
||
|
||
#### 1.1 WalletView.vue ⚠️ 需要修改
|
||
**文件**: `frontend/src/features/wallet/views/WalletView.vue`
|
||
|
||
**需要修改的地方**:
|
||
|
||
```vue
|
||
<!-- 第 54 行:余额展示 -->
|
||
<!-- 旧代码 -->
|
||
value: formatMoney(account.value.available_balance),
|
||
|
||
<!-- 新代码 -->
|
||
value: formatCent(account.value.available_balance_cent),
|
||
|
||
<!-- 第 61 行:冻结余额展示 -->
|
||
<!-- 旧代码 -->
|
||
value: formatMoney(account.value.frozen_balance),
|
||
|
||
<!-- 新代码 -->
|
||
value: formatCent(account.value.frozen_balance_cent),
|
||
|
||
<!-- 第 282 行:可用余额展示 -->
|
||
<!-- 旧代码 -->
|
||
<strong>{{ account ? formatMoney(account.available_balance) : '¥0.00' }}</strong>
|
||
|
||
<!-- 新代码 -->
|
||
<strong>{{ account ? formatCentWithSymbol(account.available_balance_cent) : '¥0.0' }}</strong>
|
||
|
||
<!-- 第 380 行:流水金额展示 -->
|
||
<!-- 旧代码 -->
|
||
{{ amountPrefix(row.direction) }}{{ formatMoney(row.amount) }}
|
||
|
||
<!-- 新代码 -->
|
||
{{ amountPrefix(row.direction) }}{{ formatCent(row.amount_cent) }}
|
||
|
||
<!-- 第 383 行:余额展示 -->
|
||
<!-- 旧代码 -->
|
||
{{ formatMoney(row.balance_after) }}
|
||
|
||
<!-- 新代码 -->
|
||
{{ formatCent(row.balance_after_cent) }}
|
||
|
||
<!-- 第 408 行:支付金额展示 -->
|
||
<!-- 旧代码 -->
|
||
formatMoney(activeRechargePayment.amount_cent / 100)
|
||
|
||
<!-- 新代码 -->
|
||
formatCent(activeRechargePayment.amount_cent)
|
||
```
|
||
|
||
**需要添加的 import**:
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { formatCent, formatCentWithSymbol } from '@/shared/utils/money'
|
||
// ... 其他 imports
|
||
</script>
|
||
```
|
||
|
||
**删除的本地函数**:
|
||
```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<ApiResponse<WithdrawalRequest>>(
|
||
'/wallet/withdrawal',
|
||
{ amount_cent, payment_account_id }
|
||
)
|
||
return data.data
|
||
}
|
||
```
|
||
|
||
#### 2.2 WithdrawalView.vue ⚠️ 需要修改
|
||
**文件**: `frontend/src/features/wallet/views/WithdrawalView.vue`
|
||
|
||
**需要修改的地方**:
|
||
|
||
```vue
|
||
<!-- 余额展示 -->
|
||
<!-- 旧代码 -->
|
||
<div class="balance-value">¥{{ formatMoney(account?.available_balance) }}</div>
|
||
<div class="balance-value frozen">¥{{ formatMoney(account?.frozen_balance) }}</div>
|
||
|
||
<!-- 新代码 -->
|
||
<div class="balance-value">{{ formatCentWithSymbol(account?.available_balance_cent) }}</div>
|
||
<div class="balance-value frozen">{{ formatCentWithSymbol(account?.frozen_balance_cent) }}</div>
|
||
|
||
<!-- 提现金额比较 -->
|
||
<!-- 旧代码(第 77 行)-->
|
||
account.value && withdrawForm.value.amount <= account.value.available_balance
|
||
|
||
<!-- 新代码 -->
|
||
account.value && withdrawForm.value.amount <= (account.value.available_balance_cent / 100)
|
||
|
||
<!-- 提现记录展示 -->
|
||
<!-- 需要找到所有 withdrawal.amount 的地方,改为 -->
|
||
{{ 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<ApiResponse<Listing>>(
|
||
'/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
|
||
<script setup lang="ts">
|
||
import { formatCent, formatCentWithSymbol, yuanToCent } from '@/shared/utils/money'
|
||
|
||
// 显示金额
|
||
const displayAmount = formatCent(wallet.available_balance_cent)
|
||
const displayWithSymbol = formatCentWithSymbol(order.rent_amount_cent)
|
||
|
||
// 提交表单
|
||
async function submit() {
|
||
const amountCent = yuanToCent(form.amount)
|
||
await apiClient.post('/api/endpoint', { amount_cent: amountCent })
|
||
}
|
||
|
||
// 金额比较
|
||
if (inputAmount > (account.available_balance_cent / 100)) {
|
||
console.log('余额不足')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 显示金额 -->
|
||
<div>余额:{{ formatCentWithSymbol(account.available_balance_cent) }}</div>
|
||
|
||
<!-- 输入金额(元) -->
|
||
<el-input-number v-model="form.amount" :min="0.01" :precision="2" />
|
||
</template>
|
||
```
|
||
|
||
---
|
||
|
||
## 七、推荐的适配顺序
|
||
|
||
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
|