hookehuyr

feat(订单): 添加订单详情弹窗并优化订单状态管理

- 在 `OrdersPage.vue` 中添加 `VanDialog` 组件以显示订单详情
- 更新订单状态映射,支持更多状态如“待支付”、“已支付”、“已取消”等
- 优化订单列表的加载逻辑,使用 API 获取订单数据
- 移除不必要的注释代码,简化页面结构
/*
* @Date: 2025-04-16 16:21:37
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-04-16 16:41:16
* @LastEditTime: 2025-04-16 17:44:45
* @FilePath: /mlaj/src/api/order.js
* @Description: 订单相关接口
*/
......@@ -26,7 +26,7 @@ export const getOrderListAPI = (params) => fn(fetch.get(Api.ORDER_LIST, params))
/**
* @description: 获取订单详情
* @param: i 订单ID
* @return: data: { }
* @return: data: { id: 订单id, status: 订单状态, note: 备注, total_price: 订单总金额, pay_type: 支付方式, pay_time: 支付时间, receive_name: 姓名, receive_phone: 手机, receive_email: 邮箱, receive_address: 地址 }
*/
export const getOrderInfoAPI = (params) => fn(fetch.get(Api.ORDER_INFO, params))
......
......@@ -31,6 +31,7 @@ declare module 'vue' {
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanDatePicker: typeof import('vant/es')['DatePicker']
VanDialog: typeof import('vant/es')['Dialog']
VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
......
......@@ -79,14 +79,14 @@
</div>
<div class="mt-4 pt-3 border-t border-gray-200">
<div class="flex justify-between items-center text-sm">
<!-- <div class="flex justify-between items-center text-sm">
<span class="text-gray-600">小计</span>
<span class="font-medium">{{ formatPrice(getTotalPrice()) }}</span>
</div>
<div class="flex justify-between items-center text-sm mt-1">
<span class="text-gray-600">优惠</span>
<span class="text-red-500">- ¥0.00</span>
</div>
</div> -->
<div class="flex justify-between items-center mt-2 font-medium">
<span>总计</span>
<span class="text-lg text-green-600">{{ formatPrice(getTotalPrice()) }}</span>
......
......@@ -4,7 +4,7 @@
-->
<template>
<AppLayout title="我的订单">
<div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20">
<div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
<!-- 订单列表 -->
<van-list
v-model:loading="loading"
......@@ -19,31 +19,39 @@
class="p-4 rounded-xl"
>
<div class="flex items-center justify-between mb-3">
<span class="text-sm text-gray-500">订单号:{{ order.orderNo }}</span>
<span :class="['text-sm', order.statusColor]">{{ order.statusText }}</span>
<span class="text-sm text-gray-500">订单号:{{ order.id }}</span>
<span :class="['text-sm', statusMap[order.status]['color']]">{{ statusMap[order.status]['text'] }}</span>
</div>
<div class="flex items-start space-x-4 mb-3">
<img :src="order.image" class="w-20 h-20 object-cover rounded-lg flex-shrink-0" :alt="order.title">
<div v-for="(detail, idx) in order.details" :key="index" class="flex items-start space-x-4 mb-3">
<img :src="detail.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" class="w-20 h-20 object-cover rounded-lg flex-shrink-0" :alt="detail.product_name">
<div class="flex-1 min-w-0">
<h3 class="text-base font-medium mb-1 truncate">{{ order.title }}</h3>
<p class="text-sm text-gray-500 mb-1">{{ order.description }}</p>
<p class="text-sm text-gray-500">{{ order.createTime }}</p>
<h3 class="text-base font-medium mb-1 truncate">{{ detail.product_name }}</h3>
<p class="text-sm text-gray-500 mb-1">{{ order.note }}</p>
<p class="text-sm text-gray-500">{{ order.pay_date && formatDate(order.pay_date) }}</p>
<p class="text-sm text-gray-500">¥{{ detail.price }} x {{ detail.number }} 份</p>
</div>
</div>
<div class="flex justify-between items-center pt-3 border-t border-gray-100">
<div class="text-base font-medium text-green-600">¥{{ order.amount }}</div>
<div class="text-base font-medium text-green-600">¥{{ order.total_price }}</div>
<div class="space-x-2">
<button
v-if="order.status === 'pending'"
v-if="order.status === 'NOT_PAY'"
class="px-4 py-1.5 text-sm text-white bg-green-600 rounded-full"
@click="handlePay(order)"
>
立即支付
</button>
<button
v-if="order.status === 'paid'"
v-if="order.status === 'NOT_PAY'"
class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
@click="handleCancel(order)"
>
取消支付
</button>
<button
v-if="order.status === 'PAY'"
class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
@click="handleViewDetail(order)"
>
......@@ -62,6 +70,63 @@
<p class="mt-4 text-gray-500">暂无订单记录</p>
</div>
</div>
<!-- 订单详情弹窗 -->
<van-dialog
v-model:show="showDetailDialog"
title="订单详情"
:show-cancel-button="false"
confirm-button-text="确定"
class="rounded-lg"
confirm-button-color="#4caf50"
>
<div v-if="orderDetail" class="p-4 space-y-3">
<div class="flex justify-between items-center">
<span class="text-gray-500">订单号</span>
<span class="font-medium">{{ orderDetail.id }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">订单状态</span>
<span :class="['font-medium', statusMap[orderDetail.status].color]">{{ statusMap[orderDetail.status].text }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">支付方式</span>
<span class="font-medium">{{ orderDetail.pay_type === 'Alipay' ? '支付宝' : '微信' }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">支付时间</span>
<span class="font-medium">{{ orderDetail.pay_date && formatDate(orderDetail.pay_date) }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">订单金额</span>
<span class="font-medium text-green-600">¥{{ orderDetail.total_price }}</span>
</div>
<div class="border-t border-gray-100 pt-3 mt-3">
<div class="text-gray-500 mb-2">收货信息</div>
<div class="space-y-2">
<div class="flex justify-between items-center">
<span class="text-gray-500">姓名</span>
<span class="font-medium">{{ orderDetail.receive_name }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">手机</span>
<span class="font-medium">{{ orderDetail.receive_phone }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">邮箱</span>
<span class="font-medium">{{ orderDetail.receive_email }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">地址</span>
<span class="font-medium">{{ orderDetail.receive_address }}</span>
</div>
</div>
</div>
<div v-if="orderDetail.note" class="border-t border-gray-100 pt-3">
<div class="text-gray-500 mb-2">备注</div>
<div class="text-sm">{{ orderDetail.note }}</div>
</div>
</div>
</van-dialog>
</AppLayout>
</template>
......@@ -71,49 +136,74 @@ import { useRoute, useRouter } from 'vue-router';
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { useTitle } from '@vueuse/core';
import { showConfirmDialog, showToast, Dialog } from 'vant';
import { formatDate } from '@/utils/tools';
// 导入接口
import { getOrderListAPI, cancelOrderAPI, getOrderInfoAPI } from "@/api/order";
const $route = useRoute();
const $router = useRouter();
useTitle($route.meta.title);
const router = useRouter()
const loading = ref(false)
const finished = ref(false)
const orders = ref([
{
id: 1,
orderNo: 'ORDER202503210001',
status: 'pending',
statusText: '待支付',
statusColor: 'text-orange-500',
title: '亲子阅读课程',
description: '3-6岁儿童亲子阅读指导',
image: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
amount: 299,
createTime: '2025-03-21 10:30:00'
},
{
id: 2,
orderNo: 'ORDER202503210002',
status: 'paid',
statusText: '已支付',
statusColor: 'text-green-500',
title: '儿童绘画课程',
description: '儿童创意绘画启蒙课程',
image: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
amount: 199,
createTime: '2025-03-21 09:15:00'
}
])
const limit = ref(10)
const page = ref(0)
const orders = ref([])
const showDetailDialog = ref(false)
const orderDetail = ref(null)
// 订单状态映射
const statusMap = {
NOT_PAY: { text: '待支付', color: 'text-orange-500' },
PAY: { text: '已支付', color: 'text-green-500' },
CANCEL: { text: '已取消', color: 'text-gray-500' },
APPLY_REFUND: { text: '申请退款', color: 'text-red-500' },
REFUND: { text: '已退款', color: 'text-red-500' },
REFUND_ERROR: { text: '退款失败', color: 'text-red-500' },
}
// 模拟订单数据
// const orders = ref([
// {
// id: 1,
// orderNo: 'ORDER202503210001',
// status: 'pending',
// statusText: '待支付',
// statusColor: 'text-orange-500',
// title: '亲子阅读课程',
// description: '3-6岁儿童亲子阅读指导',
// image: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
// amount: 299,
// createTime: '2025-03-21 10:30:00'
// },
// {
// id: 2,
// orderNo: 'ORDER202503210002',
// status: 'paid',
// statusText: '已支付',
// statusColor: 'text-green-500',
// title: '儿童绘画课程',
// description: '儿童创意绘画启蒙课程',
// image: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
// amount: 199,
// createTime: '2025-03-21 09:15:00'
// }
// ])
// 初始化加载订单列表
// 加载更多
const onLoad = () => {
// 模拟异步加载
setTimeout(() => {
loading.value = false
finished.value = true
}, 1000)
}
const onLoad = async () => {
const nextPage = page.value;
const res = await getOrderListAPI({ limit: limit.value, page: nextPage, status: '' });
if (res.code) {
orders.value = [...orders.value, ...res.data];
finished.value = res.data.length < limit.value;
page.value = nextPage + 1;
}
loading.value = false;
};
// 支付订单
const handlePay = (order) => {
......@@ -121,7 +211,44 @@ const handlePay = (order) => {
}
// 查看订单详情
const handleViewDetail = (order) => {
router.push(`/profile/orders/${order.id}`)
}
const handleViewDetail = async (order) => {
try {
const { code, data } = await getOrderInfoAPI({ i: order.id });
if (code) {
orderDetail.value = data;
showDetailDialog.value = true;
}
} catch (error) {
console.error('获取订单详情失败:', error);
showToast('获取订单详情失败');
}
};
// 取消订单
const handleCancel = async (order) => {
showConfirmDialog({
title: '温馨提示',
message: '您是否确定要取消该订单?',
confirmButtonColor: '#4caf50',
})
.then(async() => {
try {
const { code } = await cancelOrderAPI({ i: order.id });
if (code) {
const index = orders.value.findIndex((item) => item.id === order.id);
// 更新订单状态为已取消
orders.value[index].status = 'CANCEL';
orders.value[index].statusText = '已取消';
orders.value[index].statusColor = 'text-gray-500';
// 显示提示信息
showToast('订单已取消');
}
} catch (error) {
console.error('取消订单失败:', error);
}
})
.catch(() => {
// on cancel
});
};
</script>
......