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) { > 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) { - + - + 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() { 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) }}免 ¥{{ 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) }}已免押 ¥{{ 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) }}免 ¥{{ money(centToYuan(order.deposit_waived_amount_cent)) }}