diff --git a/frontend/src/features/admin/api/adminWallet.ts b/frontend/src/features/admin/api/adminWallet.ts
index 5e1ab7d..69b14f8 100644
--- a/frontend/src/features/admin/api/adminWallet.ts
+++ b/frontend/src/features/admin/api/adminWallet.ts
@@ -12,8 +12,8 @@ export interface AdminWalletLedger {
order_id?: number
order_no: string
direction: LedgerDirection
- amount: number
- balance_after: number
+ amount_cent: number
+ balance_after_cent: number
balance_type: BalanceType
biz_type: string
biz_no: string
diff --git a/frontend/src/features/admin/api/adminWithdrawal.ts b/frontend/src/features/admin/api/adminWithdrawal.ts
index 9319bab..5bcffef 100644
--- a/frontend/src/features/admin/api/adminWithdrawal.ts
+++ b/frontend/src/features/admin/api/adminWithdrawal.ts
@@ -9,9 +9,9 @@ export interface WithdrawalDetail {
user_id: number
user_nickname: string
user_phone: string
- amount: number
- fee: number
- actual_amount: number
+ amount_cent: number
+ fee_cent: number
+ actual_amount_cent: number
payment_account_id: number | null
account_type: string
account_name: string
diff --git a/frontend/src/features/admin/components/WithdrawalDetailDialog.vue b/frontend/src/features/admin/components/WithdrawalDetailDialog.vue
index eddb476..e82f1a6 100644
--- a/frontend/src/features/admin/components/WithdrawalDetailDialog.vue
+++ b/frontend/src/features/admin/components/WithdrawalDetailDialog.vue
@@ -4,7 +4,7 @@ import { ref } from 'vue'
import type { WithdrawalDetail } from '@/features/admin/api/adminWithdrawal'
import { reviewWithdrawal, confirmPayment } from '@/features/admin/api/adminWithdrawal'
-import { formatMoney } from '@/shared/utils/money'
+import { formatCent } from '@/shared/utils/money'
const props = defineProps<{
modelValue: boolean
@@ -161,15 +161,15 @@ function accountTypeLabel(type: string) {
- ¥{{ formatMoney(withdrawal.amount) }}
+ ¥{{ formatCent(withdrawal.amount_cent) }}
- ¥{{ formatMoney(withdrawal.fee) }}
+ ¥{{ formatCent(withdrawal.fee_cent) }}
- ¥{{ formatMoney(withdrawal.actual_amount) }}
+ ¥{{ formatCent(withdrawal.actual_amount_cent) }}
@@ -274,7 +274,7 @@ function accountTypeLabel(type: string) {
>
- 请手动转账 ¥{{ formatMoney(withdrawal.actual_amount) }} 到用户收款账号,
+ 请手动转账 ¥{{ formatCent(withdrawal.actual_amount_cent) }} 到用户收款账号,
完成后点击"确认打款"按钮。
diff --git a/frontend/src/features/admin/views/AdminWalletLedgerView.vue b/frontend/src/features/admin/views/AdminWalletLedgerView.vue
index a657b55..78d8966 100644
--- a/frontend/src/features/admin/views/AdminWalletLedgerView.vue
+++ b/frontend/src/features/admin/views/AdminWalletLedgerView.vue
@@ -3,7 +3,7 @@ import { Search } from '@element-plus/icons-vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { fetchAdminWalletLedger, type AdminWalletLedger } from '@/features/admin/api/adminWallet'
-import { formatMoneyWithSymbol } from '@/shared/utils/money'
+import { formatCentWithSymbol } from '@/shared/utils/money'
import { balanceTypeLabel, ledgerDirectionLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
@@ -22,12 +22,12 @@ const filters = reactive({
const inAmount = computed(() =>
ledger.value
.filter(item => item.direction === 'in')
- .reduce((sum, item) => sum + Number(item.amount || 0), 0)
+ .reduce((sum, item) => sum + Number(item.amount_cent || 0), 0)
)
const outAmount = computed(() =>
ledger.value
.filter(item => item.direction === 'out')
- .reduce((sum, item) => sum + Number(item.amount || 0), 0)
+ .reduce((sum, item) => sum + Number(item.amount_cent || 0), 0)
)
onMounted(loadLedger)
@@ -64,8 +64,8 @@ async function handlePageChange() {
await loadLedger()
}
-function money(value: number) {
- return formatMoneyWithSymbol(value)
+function moneyCent(value: number) {
+ return formatCentWithSymbol(value)
}
function directionType(direction: string) {
@@ -102,11 +102,11 @@ function directionLabel(direction: string) {
入账合计
- {{ money(inAmount) }}
+ {{ moneyCent(inAmount) }}
出账合计
- {{ money(outAmount) }}
+ {{ moneyCent(outAmount) }}
@@ -149,13 +149,13 @@ function directionLabel(direction: string) {
- {{ money(Number(row.amount)) }}
+ {{ moneyCent(Number(row.amount_cent)) }}
{{ balanceTypeLabel(row.balance_type) }}
- {{ money(Number(row.balance_after)) }}
+ {{ moneyCent(Number(row.balance_after_cent)) }}
{{ formatDateTime(row.created_at) }}
diff --git a/frontend/src/features/admin/views/AdminWithdrawalsView.vue b/frontend/src/features/admin/views/AdminWithdrawalsView.vue
index 25bb8de..23145e1 100644
--- a/frontend/src/features/admin/views/AdminWithdrawalsView.vue
+++ b/frontend/src/features/admin/views/AdminWithdrawalsView.vue
@@ -8,7 +8,7 @@ import {
confirmPayment,
type WithdrawalDetail,
} from '@/features/admin/api/adminWithdrawal'
-import { formatMoney } from '@/shared/utils/money'
+import { formatCent } from '@/shared/utils/money'
import WithdrawalDetailDialog from '../components/WithdrawalDetailDialog.vue'
@@ -220,7 +220,7 @@ function onDetailDialogSaved() {
- ¥{{ formatMoney(row.amount) }}
+ ¥{{ formatCent(row.amount_cent) }}
diff --git a/frontend/src/features/orders/api/orders.ts b/frontend/src/features/orders/api/orders.ts
index c998cea..5dce4dc 100644
--- a/frontend/src/features/orders/api/orders.ts
+++ b/frontend/src/features/orders/api/orders.ts
@@ -1,5 +1,5 @@
import { apiClient } from '@/shared/api/client'
-import type { ApiResponse } from '@/shared/types/types'
+import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
import type { HandoffStatus, OrderStatus, SettlementStatus } from '@/shared/types/status'
export interface Order {
@@ -19,12 +19,26 @@ export interface Order {
estimated_duration_hours: number
price_role?: 'renter' | 'owner' | 'admin' | string
display_amount_cent: number
+ /** @deprecated use display_amount_cent */
+ display_amount?: number
rent_amount_cent?: number
+ /** @deprecated use rent_amount_cent */
+ rent_amount?: number
owner_rent_amount_cent?: number
+ /** @deprecated use owner_rent_amount_cent */
+ owner_rent_amount?: number
deposit_amount_cent: number
+ /** @deprecated use deposit_amount_cent */
+ deposit_amount?: number
deposit_original_amount_cent: number
+ /** @deprecated use deposit_original_amount_cent */
+ deposit_original_amount?: number
deposit_waived_amount_cent: number
+ /** @deprecated use deposit_waived_amount_cent */
+ deposit_waived_amount?: number
platform_fee_cent?: number
+ /** @deprecated use platform_fee_cent */
+ platform_fee?: number
account_snapshot?: Record
listing_snapshot?: string
checkout_info?: string
@@ -45,16 +59,34 @@ export interface Checkout {
status: SettlementStatus
price_role?: 'renter' | 'owner' | 'admin' | string
display_amount_cent: number
+ /** @deprecated use display_amount_cent */
+ display_amount?: number
rent_amount_cent?: number
+ /** @deprecated use rent_amount_cent */
+ rent_amount?: number
owner_rent_amount_cent?: number
+ /** @deprecated use owner_rent_amount_cent */
+ owner_rent_amount?: number
platform_fee_cent?: number
deposit_amount_cent: number
+ /** @deprecated use deposit_amount_cent */
+ deposit_amount?: number
consumable_amount_cent: number
+ /** @deprecated use consumable_amount_cent */
+ consumable_amount?: number
coin_consumed_m: number
other_amount_cent: number
+ /** @deprecated use other_amount_cent */
+ other_amount?: number
deposit_deduct_amount_cent: number
+ /** @deprecated use deposit_deduct_amount_cent */
+ deposit_deduct_amount?: number
renter_refund_amount_cent?: number
+ /** @deprecated use renter_refund_amount_cent */
+ renter_refund_amount?: number
owner_income_amount_cent?: number
+ /** @deprecated use owner_income_amount_cent */
+ owner_income_amount?: number
content: string
evidence_urls: string[]
owner_adjustment_reason: string
@@ -98,13 +130,37 @@ export interface PaymentOrder {
updated_at: string
}
-// API functions below remain the same, TypeScript will catch any mismatches
+export interface CheckoutPayload {
+ consumable_amount: number
+ coin_consumed_m: number
+ other_amount: number
+ deposit_deduct_amount?: number
+ content: string
+ evidence_urls: string[]
+ reason?: string
+}
+
+export interface RefundStatus {
+ order_id: number
+ order_no: string
+ refund_status: string
+ refund_amount_cent: number
+ refunded_at?: string
+ total_amount_cent: number
+}
+
+export interface AdminActionResponse {
+ updated?: boolean
+ closed?: boolean
+ refunded?: boolean
+}
+
export async function fetchOrders() {
const { data } = await apiClient.get>('/orders')
return data.data
}
-export async function fetchOrder(id: number) {
+export async function fetchOrder(id: string | number) {
const { data } = await apiClient.get>(`/orders/${id}`)
return data.data
}
@@ -119,53 +175,132 @@ export async function cancelOrder(id: number) {
return data.data
}
-export async function startPayment(orderId: number, payWay: string, jsPayFlag?: string) {
- const { data } = await apiClient.post>(`/orders/${orderId}/pay`, {
- pay_way: payWay,
- jspay_flag: jsPayFlag || '',
- })
+export async function fetchHandoffRecords(id: string | number) {
+ const { data } = await apiClient.get>(`/orders/${id}/handoff-records`)
+ return data.data ?? []
+}
+
+export async function submitHandoff(id: number, content: string) {
+ const { data } = await apiClient.post>(`/orders/${id}/handoff`, { content })
return data.data
}
-export async function queryPayment(orderId: number) {
- const { data } = await apiClient.post>(`/orders/${orderId}/pay/query`)
- return data.data
-}
-
-export async function confirmHandoff(orderId: number, type: string, content: string) {
+export async function confirmReceive(id: number) {
const { data } = await apiClient.post>(
- `/orders/${orderId}/handoff/${type}/confirm`,
- { content }
+ `/orders/${id}/confirm-receive`
)
return data.data
}
-export async function createCheckout(
+export async function startOrderPayment(
orderId: number,
- payload: {
- consumable_amount: number
- coin_consumed_m: number
- other_amount: number
- deposit_deduct_amount: number
- content: string
- evidence_urls: string[]
- }
+ options: { pay_way?: string; jspay_flag?: string } = {}
) {
+ const { data } = await apiClient.post>(
+ `/orders/${orderId}/start-payment`,
+ {
+ pay_way: options.pay_way || '',
+ jspay_flag: options.jspay_flag || '',
+ }
+ )
+ return data.data
+}
+
+export async function queryOrderPayment(orderId: number) {
+ const { data } = await apiClient.get>(`/orders/${orderId}/query-payment`)
+ return data.data
+}
+
+export async function submitCheckout(orderId: number, payload: CheckoutPayload) {
const { data } = await apiClient.post>(`/orders/${orderId}/checkout`, payload)
return data.data
}
+export async function confirmCheckout(orderId: number) {
+ const { data } = await apiClient.post>(
+ `/orders/${orderId}/checkout/confirm`
+ )
+ return data.data
+}
+
export async function acceptCheckout(orderId: number) {
- const { data } = await apiClient.post>(`/orders/${orderId}/checkout/accept`)
+ const { data } = await apiClient.post>(
+ `/orders/${orderId}/checkout/accept`
+ )
return data.data
}
-export async function rejectCheckout(orderId: number) {
- const { data } = await apiClient.post>(`/orders/${orderId}/checkout/reject`)
+export async function rejectCheckout(orderId: number, reason?: string) {
+ const { data } = await apiClient.post>(
+ `/orders/${orderId}/checkout/reject`,
+ { reason: reason || '' }
+ )
return data.data
}
-export async function counterCheckout(orderId: number, checkoutId: number) {
- const { data } = await apiClient.post>(`/orders/${orderId}/checkout/${checkoutId}/counter`)
+export async function counterCheckout(orderId: number, payload: CheckoutPayload) {
+ const { data } = await apiClient.post>(
+ `/orders/${orderId}/checkout/counter`,
+ payload
+ )
return data.data
}
+
+export async function fetchAdminOrders(page = 1, pageSize = 20) {
+ const { data } = await apiClient.get>>('/admin/orders', {
+ params: { page, page_size: pageSize },
+ })
+ const result = data.data
+ return {
+ items: Array.isArray(result?.items) ? result.items : [],
+ total: Number(result?.total ?? 0),
+ page: Number(result?.page ?? page),
+ page_size: Number(result?.page_size ?? pageSize),
+ }
+}
+
+export async function fetchAdminOrder(id: string | number) {
+ const { data } = await apiClient.get>(`/admin/orders/${id}`)
+ return data.data
+}
+
+export async function fetchAdminHandoffRecords(id: string | number) {
+ const { data } = await apiClient.get>(
+ `/admin/orders/${id}/handoff-records`
+ )
+ return data.data ?? []
+}
+
+export async function adminCloseOrder(id: number, reason: string) {
+ const { data } = await apiClient.post>(`/admin/orders/${id}/close`, {
+ reason,
+ })
+ return data.data
+}
+
+export async function adminMarkOrderAbnormal(id: number, reason: string) {
+ const { data } = await apiClient.post>(
+ `/admin/orders/${id}/mark-abnormal`,
+ { reason }
+ )
+ return data.data
+}
+
+export async function adminRefundOrder(id: number) {
+ const { data } = await apiClient.post>(`/admin/orders/${id}/refund`)
+ return data.data
+}
+
+export async function adminRefundStatus(id: number) {
+ const { data } = await apiClient.get>(
+ `/admin/orders/${id}/refund-status`
+ )
+ return data.data
+}
+
+// Backward-compatible names kept for existing pages/composables.
+export const startPayment = (orderId: number, payWay: string, jsPayFlag?: string) =>
+ startOrderPayment(orderId, { pay_way: payWay, jspay_flag: jsPayFlag })
+export const queryPayment = queryOrderPayment
+export const createCheckout = submitCheckout
+export const confirmHandoff = confirmReceive
diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue
index 3e297c9..1ee38a0 100644
--- a/frontend/src/features/orders/views/MobileOrderDetailView.vue
+++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue
@@ -6,7 +6,7 @@ import { showToast, showDialog } from 'vant'
import { fetchOrderChat } from '@/features/chats/api/chats'
import { createDispute } from '@/features/disputes/api/disputes'
import { uploadFile } from '@/shared/api/files'
-import { formatMoney } from '@/shared/utils/money'
+import { centToYuan, formatMoney } from '@/shared/utils/money'
import {
acceptCheckout,
cancelOrder,
@@ -555,15 +555,15 @@ function formatHandoffRecordType(type: string) {
function orderRentAmount(item: Order) {
if (item.owner_id === session.userId)
- return Number(item.owner_rent_amount ?? item.display_amount ?? 0)
- if (item.renter_id === session.userId) return Number(item.rent_amount ?? item.display_amount ?? 0)
- return Number(item.display_amount ?? 0)
+ return centToYuan(item.owner_rent_amount_cent ?? item.display_amount_cent)
+ if (item.renter_id === session.userId) return centToYuan(item.rent_amount_cent ?? item.display_amount_cent)
+ return centToYuan(item.display_amount_cent)
}
function ownerActualIncome(item: Order) {
if (item.owner_id !== session.userId) return null
- const value = item.checkout?.owner_income_amount
- return typeof value === 'number' ? value : null
+ const value = item.checkout?.owner_income_amount_cent
+ return typeof value === 'number' ? centToYuan(value) : null
}
function quantity(value: unknown) {
@@ -676,9 +676,9 @@ async function copyListingCode() {
押金
- ¥{{ money(order.deposit_amount) }}
- 已免押 ¥{{ money(order.deposit_waived_amount) }}¥{{ money(centToYuan(order.deposit_amount_cent)) }}
+ 已免押 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}
@@ -929,30 +929,30 @@ async function copyListingCode() {
>
结账结算账单
-
+
-
+
押金金额
- ¥{{ money(order.deposit_amount) }}
- 免 ¥{{ money(order.deposit_waived_amount) }} 0"
+ >免 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}
diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue
index 0aa6bdb..da4052b 100644
--- a/frontend/src/features/orders/views/OrderDetailView.vue
+++ b/frontend/src/features/orders/views/OrderDetailView.vue
@@ -8,7 +8,7 @@ import QRCode from 'qrcode'
import { fetchOrderChat } from '@/features/chats/api/chats'
import { createDispute } from '@/features/disputes'
import { uploadFile } from '@/shared/api/files'
-import { formatMoney } from '@/shared/utils/money'
+import { centToYuan, formatMoney } from '@/shared/utils/money'
import {
acceptCheckout,
cancelOrder,
@@ -602,15 +602,15 @@ function money(value: unknown) {
function orderRentAmount(item: Order) {
if (item.owner_id === session.userId)
- return Number(item.owner_rent_amount ?? item.display_amount ?? 0)
- if (item.renter_id === session.userId) return Number(item.rent_amount ?? item.display_amount ?? 0)
- return Number(item.display_amount ?? 0)
+ return centToYuan(item.owner_rent_amount_cent ?? item.display_amount_cent)
+ if (item.renter_id === session.userId) return centToYuan(item.rent_amount_cent ?? item.display_amount_cent)
+ return centToYuan(item.display_amount_cent)
}
function ownerActualIncome(item: Order) {
if (item.owner_id !== session.userId) return null
- const value = item.checkout?.owner_income_amount
- return typeof value === 'number' ? value : null
+ const value = item.checkout?.owner_income_amount_cent
+ return typeof value === 'number' ? centToYuan(value) : null
}
function quantity(value: unknown) {
@@ -767,9 +767,9 @@ async function copyListingCode() {
押金
- ¥{{ money(order.deposit_amount) }}
- 已免押 ¥{{ money(order.deposit_waived_amount) }}¥{{ money(centToYuan(order.deposit_amount_cent)) }}
+ 已免押 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}
@@ -951,32 +951,32 @@ async function copyListingCode() {
实际结算租金
- ¥{{ money(order.checkout.display_amount) }}
+ ¥{{ money(centToYuan(order.checkout.display_amount_cent)) }}
预收押金
- ¥{{ money(order.checkout.deposit_amount) }}
- 已免押 ¥{{ money(order.deposit_waived_amount) }} 0"
+ >已免押 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}
额外消耗品已用
- ¥{{ money(order.checkout.consumable_amount) }}
+ ¥{{ money(centToYuan(order.checkout.consumable_amount_cent)) }}
押金赔付扣除
- ¥{{ money(order.checkout.deposit_deduct_amount) }}
+ ¥{{ money(centToYuan(order.checkout.deposit_deduct_amount_cent)) }}
退还租客(未使用租金 + 剩余押金)
- ¥{{ money(order.checkout.renter_refund_amount) }}
+ ¥{{ money(centToYuan(order.checkout.renter_refund_amount_cent)) }}
号主最终收入(租金 + 押金赔付)
- ¥{{ money(order.checkout.owner_income_amount) }}
+ ¥{{ money(centToYuan(order.checkout.owner_income_amount_cent)) }}
@@ -1031,7 +1031,7 @@ async function copyListingCode() {
v-model="counterForm.deposit_deduct_amount"
class="full-control"
:min="0"
- :max="order.deposit_amount"
+ :max="centToYuan(order.deposit_amount_cent)"
:precision="0"
controls-position="right"
/>
diff --git a/frontend/src/features/orders/views/OrdersView.vue b/frontend/src/features/orders/views/OrdersView.vue
index 640a802..c6ee977 100644
--- a/frontend/src/features/orders/views/OrdersView.vue
+++ b/frontend/src/features/orders/views/OrdersView.vue
@@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router'
import { CopyDocument, Search } from '@element-plus/icons-vue'
import { fetchOrders, type Order } from '@/features/orders'
-import { formatMoney } from '@/shared/utils/money'
+import { centToYuan, formatMoney } from '@/shared/utils/money'
import { useSessionStore } from '@/stores/session'
import { orderStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
@@ -128,15 +128,15 @@ function amountLabel(order: Order) {
}
function orderRentAmount(order: Order) {
- if (isOwner(order)) return Number(order.owner_rent_amount ?? order.display_amount ?? 0)
- if (isRenter(order)) return Number(order.rent_amount ?? order.display_amount ?? 0)
- return Number(order.display_amount ?? 0)
+ if (isOwner(order)) return centToYuan(order.owner_rent_amount_cent ?? order.display_amount_cent)
+ if (isRenter(order)) return centToYuan(order.rent_amount_cent ?? order.display_amount_cent)
+ return centToYuan(order.display_amount_cent)
}
function ownerActualIncome(order: Order) {
if (!isOwner(order)) return null
- const value = order.checkout?.owner_income_amount
- return typeof value === 'number' ? value : null
+ const value = order.checkout?.owner_income_amount_cent
+ return typeof value === 'number' ? centToYuan(value) : null
}
function money(value: unknown) {
@@ -351,9 +351,9 @@ function getCountdownMinutes(order: Order) {
押金
- ¥{{ money(order.deposit_amount) }}
- 免 ¥{{ money(order.deposit_waived_amount) }} 0"
+ >免 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}