接入订单支付与钱包充值前端入口
This commit is contained in:
@@ -50,6 +50,11 @@ export async function createOrder(listingId: number) {
|
|||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function payOrder(id: number) {
|
||||||
|
const { data } = await apiClient.post<ApiResponse<{ paid: boolean }>>(`/orders/${id}/pay`)
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchOrders() {
|
export async function fetchOrders() {
|
||||||
const { data } = await apiClient.get<ApiResponse<{ items: Order[] }>>('/orders')
|
const { data } = await apiClient.get<ApiResponse<{ items: Order[] }>>('/orders')
|
||||||
return data.data.items
|
return data.data.items
|
||||||
|
|||||||
@@ -37,3 +37,8 @@ export async function fetchWalletLedger() {
|
|||||||
const { data } = await apiClient.get<ApiResponse<{ items: WalletLedger[] }>>('/wallet/ledger')
|
const { data } = await apiClient.get<ApiResponse<{ items: WalletLedger[] }>>('/wallet/ledger')
|
||||||
return data.data.items
|
return data.data.items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function rechargeWallet(amount: number) {
|
||||||
|
const { data } = await apiClient.post<ApiResponse<WalletAccount>>('/wallet/recharge', { amount })
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const listingReviewStatusMap: Record<string, string> = {
|
|||||||
|
|
||||||
const orderStatusMap: Record<string, string> = {
|
const orderStatusMap: Record<string, string> = {
|
||||||
pending_confirm: '待确认',
|
pending_confirm: '待确认',
|
||||||
|
pending_payment: '待支付',
|
||||||
pending_handoff: '待交接',
|
pending_handoff: '待交接',
|
||||||
renting: '使用中',
|
renting: '使用中',
|
||||||
overdue: '已逾期',
|
overdue: '已逾期',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
confirmReturn,
|
confirmReturn,
|
||||||
fetchHandoffRecords,
|
fetchHandoffRecords,
|
||||||
fetchOrder,
|
fetchOrder,
|
||||||
|
payOrder,
|
||||||
submitHandoff,
|
submitHandoff,
|
||||||
submitReturn,
|
submitReturn,
|
||||||
type HandoffRecord,
|
type HandoffRecord,
|
||||||
@@ -25,6 +26,7 @@ const router = useRouter()
|
|||||||
const session = useSessionStore()
|
const session = useSessionStore()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const cancelling = ref(false)
|
const cancelling = ref(false)
|
||||||
|
const paying = ref(false)
|
||||||
const handoffing = ref(false)
|
const handoffing = ref(false)
|
||||||
const confirming = ref(false)
|
const confirming = ref(false)
|
||||||
const returning = ref(false)
|
const returning = ref(false)
|
||||||
@@ -72,6 +74,20 @@ async function handleCancel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handlePay() {
|
||||||
|
if (!order.value) return
|
||||||
|
paying.value = true
|
||||||
|
try {
|
||||||
|
await payOrder(order.value.id)
|
||||||
|
ElMessage.success('支付成功,等待号主交接')
|
||||||
|
await loadOrder()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(readError(error, '支付失败'))
|
||||||
|
} finally {
|
||||||
|
paying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmitHandoff() {
|
async function handleSubmitHandoff() {
|
||||||
if (!order.value) return
|
if (!order.value) return
|
||||||
handoffing.value = true
|
handoffing.value = true
|
||||||
@@ -210,7 +226,11 @@ function readError(error: unknown, fallback: string) {
|
|||||||
<div v-if="order" class="order-panel">
|
<div v-if="order" class="order-panel">
|
||||||
<p>开始:{{ formatDateTime(order.rent_start_at, '未开始') }}</p>
|
<p>开始:{{ formatDateTime(order.rent_start_at, '未开始') }}</p>
|
||||||
<p>预计截止:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
<p>预计截止:{{ formatDateTime(order.rent_end_at, '未设置') }}</p>
|
||||||
<el-button v-if="order.status === 'pending_handoff'" type="danger" :loading="cancelling" @click="handleCancel">
|
<p v-if="order.status === 'pending_payment'">订单待支付,支付后账号进入交接流程。</p>
|
||||||
|
<el-button v-if="order.status === 'pending_payment' && isRenter" type="primary" :loading="paying" @click="handlePay">
|
||||||
|
支付订单
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="['pending_payment', 'pending_handoff'].includes(order.status)" type="danger" :loading="cancelling" @click="handleCancel">
|
||||||
取消订单并释放账号
|
取消订单并释放账号
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
import { fetchOrders, type Order } from '@/api/orders'
|
import { fetchOrders, payOrder, type Order } from '@/api/orders'
|
||||||
import { orderStatusLabel } from '@/utils/statusLabels'
|
import { orderStatusLabel } from '@/utils/statusLabels'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const orders = ref<Order[]>([])
|
const orders = ref<Order[]>([])
|
||||||
|
const payingOrderId = ref<number | null>(null)
|
||||||
|
|
||||||
onMounted(loadOrders)
|
onMounted(loadOrders)
|
||||||
|
|
||||||
@@ -17,6 +19,27 @@ async function loadOrders() {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handlePay(order: Order) {
|
||||||
|
payingOrderId.value = order.id
|
||||||
|
try {
|
||||||
|
await payOrder(order.id)
|
||||||
|
ElMessage.success('支付成功,等待号主交接')
|
||||||
|
await loadOrders()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(readError(error, '支付失败'))
|
||||||
|
} finally {
|
||||||
|
payingOrderId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readError(error: unknown, fallback: string) {
|
||||||
|
if (typeof error === 'object' && error && 'response' in error) {
|
||||||
|
const response = (error as { response?: { data?: { message?: string } } }).response
|
||||||
|
return response?.data?.message || fallback
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -24,7 +47,7 @@ async function loadOrders() {
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<p class="eyebrow">Orders</p>
|
<p class="eyebrow">Orders</p>
|
||||||
<h1>我的订单</h1>
|
<h1>我的订单</h1>
|
||||||
<p>跟踪待交接、使用中、待归还、申诉中和已完成订单。</p>
|
<p>跟踪待支付、待交接、使用中、待归还、申诉中和已完成订单。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table v-loading="loading" class="table-panel" :data="orders">
|
<el-table v-loading="loading" class="table-panel" :data="orders">
|
||||||
@@ -35,8 +58,17 @@ async function loadOrders() {
|
|||||||
<el-table-column label="状态" width="140">
|
<el-table-column label="状态" width="140">
|
||||||
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
<template #default="{ row }">{{ orderStatusLabel(row.status) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="100">
|
<el-table-column label="操作" width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<el-button
|
||||||
|
v-if="row.status === 'pending_payment'"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:loading="payingOrderId === row.id"
|
||||||
|
@click="handlePay(row)"
|
||||||
|
>
|
||||||
|
支付
|
||||||
|
</el-button>
|
||||||
<RouterLink :to="`/orders/${row.id}`">
|
<RouterLink :to="`/orders/${row.id}`">
|
||||||
<el-button size="small">详情</el-button>
|
<el-button size="small">详情</el-button>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
import { fetchWalletBalance, fetchWalletLedger, type WalletAccount, type WalletLedger } from '@/api/wallet'
|
import { fetchWalletBalance, fetchWalletLedger, rechargeWallet, type WalletAccount, type WalletLedger } from '@/api/wallet'
|
||||||
import { balanceTypeLabel, ledgerDirectionLabel, walletStatusLabel } from '@/utils/statusLabels'
|
import { balanceTypeLabel, ledgerDirectionLabel, walletStatusLabel } from '@/utils/statusLabels'
|
||||||
import { formatDateTime } from '@/utils/time'
|
import { formatDateTime } from '@/utils/time'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const account = ref<WalletAccount | null>(null)
|
const account = ref<WalletAccount | null>(null)
|
||||||
const ledger = ref<WalletLedger[]>([])
|
const ledger = ref<WalletLedger[]>([])
|
||||||
|
const rechargeAmount = ref(100)
|
||||||
|
const recharging = ref(false)
|
||||||
|
|
||||||
onMounted(loadWallet)
|
onMounted(loadWallet)
|
||||||
|
|
||||||
@@ -21,6 +24,27 @@ async function loadWallet() {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRecharge() {
|
||||||
|
recharging.value = true
|
||||||
|
try {
|
||||||
|
account.value = await rechargeWallet(rechargeAmount.value)
|
||||||
|
ElMessage.success('充值成功')
|
||||||
|
await loadWallet()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(readError(error, '充值失败'))
|
||||||
|
} finally {
|
||||||
|
recharging.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readError(error: unknown, fallback: string) {
|
||||||
|
if (typeof error === 'object' && error && 'response' in error) {
|
||||||
|
const response = (error as { response?: { data?: { message?: string } } }).response
|
||||||
|
return response?.data?.message || fallback
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -28,7 +52,7 @@ async function loadWallet() {
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<p class="eyebrow">Wallet</p>
|
<p class="eyebrow">Wallet</p>
|
||||||
<h1>钱包</h1>
|
<h1>钱包</h1>
|
||||||
<p>查看可用余额、冻结余额和不可变资金流水。当前是开发态模拟账务,不代表真实支付余额。</p>
|
<p>查看可用余额、冻结余额和资金流水。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="account" class="metric-grid">
|
<div v-if="account" class="metric-grid">
|
||||||
@@ -46,6 +70,12 @@ async function loadWallet() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="table-panel recharge-panel">
|
||||||
|
<h2>开发充值</h2>
|
||||||
|
<el-input-number v-model="rechargeAmount" :min="0.01" :precision="2" controls-position="right" />
|
||||||
|
<el-button type="primary" :loading="recharging" @click="handleRecharge">充值</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-table class="table-panel" :data="ledger">
|
<el-table class="table-panel" :data="ledger">
|
||||||
<el-table-column prop="ledger_no" label="流水号" min-width="220" />
|
<el-table-column prop="ledger_no" label="流水号" min-width="220" />
|
||||||
<el-table-column prop="biz_type" label="业务" width="140" />
|
<el-table-column prop="biz_type" label="业务" width="140" />
|
||||||
@@ -64,3 +94,18 @@ async function loadWallet() {
|
|||||||
</el-table>
|
</el-table>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.recharge-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recharge-panel h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ async function loadOrders() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
<el-select v-model="status" clearable placeholder="订单状态" style="width: 180px">
|
<el-select v-model="status" clearable placeholder="订单状态" style="width: 180px">
|
||||||
|
<el-option label="待支付" value="pending_payment" />
|
||||||
<el-option label="待交接" value="pending_handoff" />
|
<el-option label="待交接" value="pending_handoff" />
|
||||||
<el-option label="使用中" value="renting" />
|
<el-option label="使用中" value="renting" />
|
||||||
<el-option label="逾期中" value="overdue" />
|
<el-option label="逾期中" value="overdue" />
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ async function handleCreateOrder() {
|
|||||||
ordering.value = true;
|
ordering.value = true;
|
||||||
try {
|
try {
|
||||||
await createOrder(listing.value.id);
|
await createOrder(listing.value.id);
|
||||||
showToast({ message: "订单已创建,账号已锁定", icon: "passed" });
|
showToast({ message: "订单已创建,请完成支付", icon: "passed" });
|
||||||
await router.push(`/m/orders`);
|
await router.push(`/m/orders`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast({ message: readError(error, "下单失败"), icon: "cross" });
|
showToast({ message: readError(error, "下单失败"), icon: "cross" });
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { onMounted, ref, computed } from "vue";
|
|||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { showToast } from "vant";
|
import { showToast } from "vant";
|
||||||
|
|
||||||
import { fetchOrders, type Order } from "@/api/orders";
|
import { fetchOrders, payOrder, type Order } from "@/api/orders";
|
||||||
import { formatDateMinute } from "@/utils/time";
|
import { formatDateMinute } from "@/utils/time";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -11,6 +11,7 @@ const route = useRoute();
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const orders = ref<Order[]>([]);
|
const orders = ref<Order[]>([]);
|
||||||
const activeTab = ref("all");
|
const activeTab = ref("all");
|
||||||
|
const payingOrderId = ref<number | null>(null);
|
||||||
|
|
||||||
/** 判断当前底部导航是否激活 */
|
/** 判断当前底部导航是否激活 */
|
||||||
function isNavActive(path: string) {
|
function isNavActive(path: string) {
|
||||||
@@ -34,6 +35,7 @@ async function loadOrders() {
|
|||||||
/* 状态筛选 */
|
/* 状态筛选 */
|
||||||
const statusTabs = [
|
const statusTabs = [
|
||||||
{ key: "all", label: "全部" },
|
{ key: "all", label: "全部" },
|
||||||
|
{ key: "pending_payment", label: "待支付" },
|
||||||
{ key: "pending_handoff", label: "待交接" },
|
{ key: "pending_handoff", label: "待交接" },
|
||||||
{ key: "renting", label: "使用中" },
|
{ key: "renting", label: "使用中" },
|
||||||
{ key: "pending_return_confirm", label: "待归还" },
|
{ key: "pending_return_confirm", label: "待归还" },
|
||||||
@@ -48,6 +50,7 @@ const displayOrders = computed(() => {
|
|||||||
/* 状态颜色映射 */
|
/* 状态颜色映射 */
|
||||||
function statusColor(status: string) {
|
function statusColor(status: string) {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
|
pending_payment: "#ff6a00",
|
||||||
pending_handoff: "#ff9800",
|
pending_handoff: "#ff9800",
|
||||||
renting: "#1477ff",
|
renting: "#1477ff",
|
||||||
overdue: "#e91e63",
|
overdue: "#e91e63",
|
||||||
@@ -63,6 +66,7 @@ function statusColor(status: string) {
|
|||||||
|
|
||||||
function statusLabel(status: string) {
|
function statusLabel(status: string) {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
|
pending_payment: "待支付",
|
||||||
pending_handoff: "待交接",
|
pending_handoff: "待交接",
|
||||||
renting: "使用中",
|
renting: "使用中",
|
||||||
overdue: "已逾期",
|
overdue: "已逾期",
|
||||||
@@ -79,6 +83,28 @@ function statusLabel(status: string) {
|
|||||||
function goDetail(id: number) {
|
function goDetail(id: number) {
|
||||||
router.push(`/m/orders/${id}`);
|
router.push(`/m/orders/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handlePay(order: Order) {
|
||||||
|
payingOrderId.value = order.id;
|
||||||
|
try {
|
||||||
|
await payOrder(order.id);
|
||||||
|
showToast({ message: "支付成功", icon: "passed" });
|
||||||
|
await loadOrders();
|
||||||
|
} catch (error) {
|
||||||
|
showToast({ message: readError(error, "支付失败"), icon: "cross" });
|
||||||
|
} finally {
|
||||||
|
payingOrderId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readError(error: unknown, fallback: string) {
|
||||||
|
if (typeof error === "object" && error && "response" in error) {
|
||||||
|
const response = (error as { response?: { data?: { message?: string } } })
|
||||||
|
.response;
|
||||||
|
return response?.data?.message || fallback;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -155,7 +181,16 @@ function goDetail(id: number) {
|
|||||||
<!-- 卡片底:时间 + 操作 -->
|
<!-- 卡片底:时间 + 操作 -->
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<span class="order-time">{{ formatDateMinute(order.created_at) }}</span>
|
<span class="order-time">{{ formatDateMinute(order.created_at) }}</span>
|
||||||
<span class="detail-link">查看详情 ›</span>
|
<van-button
|
||||||
|
v-if="order.status === 'pending_payment'"
|
||||||
|
size="mini"
|
||||||
|
type="primary"
|
||||||
|
:loading="payingOrderId === order.id"
|
||||||
|
@click.stop="handlePay(order)"
|
||||||
|
>
|
||||||
|
支付
|
||||||
|
</van-button>
|
||||||
|
<span v-else class="detail-link">查看详情 ›</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ async function handleCreateOrder() {
|
|||||||
ordering.value = true;
|
ordering.value = true;
|
||||||
try {
|
try {
|
||||||
const order = await createOrder(listing.value.id);
|
const order = await createOrder(listing.value.id);
|
||||||
ElMessage.success("订单已创建,账号已锁定");
|
ElMessage.success("订单已创建,请完成支付");
|
||||||
await router.push(`/orders/${order.id}`);
|
await router.push(`/orders/${order.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(readError(error, "下单失败"));
|
ElMessage.error(readError(error, "下单失败"));
|
||||||
@@ -107,7 +107,7 @@ function listingPrice(item: Listing) {
|
|||||||
class="full-control"
|
class="full-control"
|
||||||
@click="handleCreateOrder"
|
@click="handleCreateOrder"
|
||||||
>
|
>
|
||||||
立即下单并锁定账号
|
立即下单
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Reference in New Issue
Block a user