hookehuyr

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

- 在 `OrdersPage.vue` 中添加 `VanDialog` 组件以显示订单详情
- 更新订单状态映射,支持更多状态如“待支付”、“已支付”、“已取消”等
- 优化订单列表的加载逻辑,使用 API 获取订单数据
- 移除不必要的注释代码,简化页面结构
1 /* 1 /*
2 * @Date: 2025-04-16 16:21:37 2 * @Date: 2025-04-16 16:21:37
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-04-16 16:41:16 4 + * @LastEditTime: 2025-04-16 17:44:45
5 * @FilePath: /mlaj/src/api/order.js 5 * @FilePath: /mlaj/src/api/order.js
6 * @Description: 订单相关接口 6 * @Description: 订单相关接口
7 */ 7 */
...@@ -26,7 +26,7 @@ export const getOrderListAPI = (params) => fn(fetch.get(Api.ORDER_LIST, params)) ...@@ -26,7 +26,7 @@ export const getOrderListAPI = (params) => fn(fetch.get(Api.ORDER_LIST, params))
26 /** 26 /**
27 * @description: 获取订单详情 27 * @description: 获取订单详情
28 * @param: i 订单ID 28 * @param: i 订单ID
29 - * @return: data: { } 29 + * @return: data: { id: 订单id, status: 订单状态, note: 备注, total_price: 订单总金额, pay_type: 支付方式, pay_time: 支付时间, receive_name: 姓名, receive_phone: 手机, receive_email: 邮箱, receive_address: 地址 }
30 */ 30 */
31 export const getOrderInfoAPI = (params) => fn(fetch.get(Api.ORDER_INFO, params)) 31 export const getOrderInfoAPI = (params) => fn(fetch.get(Api.ORDER_INFO, params))
32 32
......
...@@ -31,6 +31,7 @@ declare module 'vue' { ...@@ -31,6 +31,7 @@ declare module 'vue' {
31 VanCellGroup: typeof import('vant/es')['CellGroup'] 31 VanCellGroup: typeof import('vant/es')['CellGroup']
32 VanCheckbox: typeof import('vant/es')['Checkbox'] 32 VanCheckbox: typeof import('vant/es')['Checkbox']
33 VanDatePicker: typeof import('vant/es')['DatePicker'] 33 VanDatePicker: typeof import('vant/es')['DatePicker']
34 + VanDialog: typeof import('vant/es')['Dialog']
34 VanEmpty: typeof import('vant/es')['Empty'] 35 VanEmpty: typeof import('vant/es')['Empty']
35 VanField: typeof import('vant/es')['Field'] 36 VanField: typeof import('vant/es')['Field']
36 VanForm: typeof import('vant/es')['Form'] 37 VanForm: typeof import('vant/es')['Form']
......
...@@ -79,14 +79,14 @@ ...@@ -79,14 +79,14 @@
79 </div> 79 </div>
80 80
81 <div class="mt-4 pt-3 border-t border-gray-200"> 81 <div class="mt-4 pt-3 border-t border-gray-200">
82 - <div class="flex justify-between items-center text-sm"> 82 + <!-- <div class="flex justify-between items-center text-sm">
83 <span class="text-gray-600">小计</span> 83 <span class="text-gray-600">小计</span>
84 <span class="font-medium">{{ formatPrice(getTotalPrice()) }}</span> 84 <span class="font-medium">{{ formatPrice(getTotalPrice()) }}</span>
85 </div> 85 </div>
86 <div class="flex justify-between items-center text-sm mt-1"> 86 <div class="flex justify-between items-center text-sm mt-1">
87 <span class="text-gray-600">优惠</span> 87 <span class="text-gray-600">优惠</span>
88 <span class="text-red-500">- ¥0.00</span> 88 <span class="text-red-500">- ¥0.00</span>
89 - </div> 89 + </div> -->
90 <div class="flex justify-between items-center mt-2 font-medium"> 90 <div class="flex justify-between items-center mt-2 font-medium">
91 <span>总计</span> 91 <span>总计</span>
92 <span class="text-lg text-green-600">{{ formatPrice(getTotalPrice()) }}</span> 92 <span class="text-lg text-green-600">{{ formatPrice(getTotalPrice()) }}</span>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 --> 4 -->
5 <template> 5 <template>
6 <AppLayout title="我的订单"> 6 <AppLayout title="我的订单">
7 - <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20"> 7 + <div class="bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
8 <!-- 订单列表 --> 8 <!-- 订单列表 -->
9 <van-list 9 <van-list
10 v-model:loading="loading" 10 v-model:loading="loading"
...@@ -19,31 +19,39 @@ ...@@ -19,31 +19,39 @@
19 class="p-4 rounded-xl" 19 class="p-4 rounded-xl"
20 > 20 >
21 <div class="flex items-center justify-between mb-3"> 21 <div class="flex items-center justify-between mb-3">
22 - <span class="text-sm text-gray-500">订单号:{{ order.orderNo }}</span> 22 + <span class="text-sm text-gray-500">订单号:{{ order.id }}</span>
23 - <span :class="['text-sm', order.statusColor]">{{ order.statusText }}</span> 23 + <span :class="['text-sm', statusMap[order.status]['color']]">{{ statusMap[order.status]['text'] }}</span>
24 </div> 24 </div>
25 25
26 - <div class="flex items-start space-x-4 mb-3"> 26 + <div v-for="(detail, idx) in order.details" :key="index" class="flex items-start space-x-4 mb-3">
27 - <img :src="order.image" class="w-20 h-20 object-cover rounded-lg flex-shrink-0" :alt="order.title"> 27 + <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">
28 <div class="flex-1 min-w-0"> 28 <div class="flex-1 min-w-0">
29 - <h3 class="text-base font-medium mb-1 truncate">{{ order.title }}</h3> 29 + <h3 class="text-base font-medium mb-1 truncate">{{ detail.product_name }}</h3>
30 - <p class="text-sm text-gray-500 mb-1">{{ order.description }}</p> 30 + <p class="text-sm text-gray-500 mb-1">{{ order.note }}</p>
31 - <p class="text-sm text-gray-500">{{ order.createTime }}</p> 31 + <p class="text-sm text-gray-500">{{ order.pay_date && formatDate(order.pay_date) }}</p>
32 + <p class="text-sm text-gray-500">¥{{ detail.price }} x {{ detail.number }} 份</p>
32 </div> 33 </div>
33 </div> 34 </div>
34 35
35 <div class="flex justify-between items-center pt-3 border-t border-gray-100"> 36 <div class="flex justify-between items-center pt-3 border-t border-gray-100">
36 - <div class="text-base font-medium text-green-600">¥{{ order.amount }}</div> 37 + <div class="text-base font-medium text-green-600">¥{{ order.total_price }}</div>
37 <div class="space-x-2"> 38 <div class="space-x-2">
38 <button 39 <button
39 - v-if="order.status === 'pending'" 40 + v-if="order.status === 'NOT_PAY'"
40 class="px-4 py-1.5 text-sm text-white bg-green-600 rounded-full" 41 class="px-4 py-1.5 text-sm text-white bg-green-600 rounded-full"
41 @click="handlePay(order)" 42 @click="handlePay(order)"
42 > 43 >
43 立即支付 44 立即支付
44 </button> 45 </button>
45 <button 46 <button
46 - v-if="order.status === 'paid'" 47 + v-if="order.status === 'NOT_PAY'"
48 + class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
49 + @click="handleCancel(order)"
50 + >
51 + 取消支付
52 + </button>
53 + <button
54 + v-if="order.status === 'PAY'"
47 class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full" 55 class="px-4 py-1.5 text-sm text-gray-600 bg-gray-100 rounded-full"
48 @click="handleViewDetail(order)" 56 @click="handleViewDetail(order)"
49 > 57 >
...@@ -62,6 +70,63 @@ ...@@ -62,6 +70,63 @@
62 <p class="mt-4 text-gray-500">暂无订单记录</p> 70 <p class="mt-4 text-gray-500">暂无订单记录</p>
63 </div> 71 </div>
64 </div> 72 </div>
73 + <!-- 订单详情弹窗 -->
74 + <van-dialog
75 + v-model:show="showDetailDialog"
76 + title="订单详情"
77 + :show-cancel-button="false"
78 + confirm-button-text="确定"
79 + class="rounded-lg"
80 + confirm-button-color="#4caf50"
81 + >
82 + <div v-if="orderDetail" class="p-4 space-y-3">
83 + <div class="flex justify-between items-center">
84 + <span class="text-gray-500">订单号</span>
85 + <span class="font-medium">{{ orderDetail.id }}</span>
86 + </div>
87 + <div class="flex justify-between items-center">
88 + <span class="text-gray-500">订单状态</span>
89 + <span :class="['font-medium', statusMap[orderDetail.status].color]">{{ statusMap[orderDetail.status].text }}</span>
90 + </div>
91 + <div class="flex justify-between items-center">
92 + <span class="text-gray-500">支付方式</span>
93 + <span class="font-medium">{{ orderDetail.pay_type === 'Alipay' ? '支付宝' : '微信' }}</span>
94 + </div>
95 + <div class="flex justify-between items-center">
96 + <span class="text-gray-500">支付时间</span>
97 + <span class="font-medium">{{ orderDetail.pay_date && formatDate(orderDetail.pay_date) }}</span>
98 + </div>
99 + <div class="flex justify-between items-center">
100 + <span class="text-gray-500">订单金额</span>
101 + <span class="font-medium text-green-600">¥{{ orderDetail.total_price }}</span>
102 + </div>
103 + <div class="border-t border-gray-100 pt-3 mt-3">
104 + <div class="text-gray-500 mb-2">收货信息</div>
105 + <div class="space-y-2">
106 + <div class="flex justify-between items-center">
107 + <span class="text-gray-500">姓名</span>
108 + <span class="font-medium">{{ orderDetail.receive_name }}</span>
109 + </div>
110 + <div class="flex justify-between items-center">
111 + <span class="text-gray-500">手机</span>
112 + <span class="font-medium">{{ orderDetail.receive_phone }}</span>
113 + </div>
114 + <div class="flex justify-between items-center">
115 + <span class="text-gray-500">邮箱</span>
116 + <span class="font-medium">{{ orderDetail.receive_email }}</span>
117 + </div>
118 + <div class="flex justify-between items-center">
119 + <span class="text-gray-500">地址</span>
120 + <span class="font-medium">{{ orderDetail.receive_address }}</span>
121 + </div>
122 + </div>
123 + </div>
124 + <div v-if="orderDetail.note" class="border-t border-gray-100 pt-3">
125 + <div class="text-gray-500 mb-2">备注</div>
126 + <div class="text-sm">{{ orderDetail.note }}</div>
127 + </div>
128 + </div>
129 + </van-dialog>
65 </AppLayout> 130 </AppLayout>
66 </template> 131 </template>
67 132
...@@ -71,49 +136,74 @@ import { useRoute, useRouter } from 'vue-router'; ...@@ -71,49 +136,74 @@ import { useRoute, useRouter } from 'vue-router';
71 import AppLayout from '@/components/layout/AppLayout.vue' 136 import AppLayout from '@/components/layout/AppLayout.vue'
72 import FrostedGlass from '@/components/ui/FrostedGlass.vue' 137 import FrostedGlass from '@/components/ui/FrostedGlass.vue'
73 import { useTitle } from '@vueuse/core'; 138 import { useTitle } from '@vueuse/core';
139 +import { showConfirmDialog, showToast, Dialog } from 'vant';
140 +import { formatDate } from '@/utils/tools';
141 +
142 +// 导入接口
143 +import { getOrderListAPI, cancelOrderAPI, getOrderInfoAPI } from "@/api/order";
74 144
75 const $route = useRoute(); 145 const $route = useRoute();
76 const $router = useRouter(); 146 const $router = useRouter();
77 useTitle($route.meta.title); 147 useTitle($route.meta.title);
78 148
79 const router = useRouter() 149 const router = useRouter()
150 +
80 const loading = ref(false) 151 const loading = ref(false)
81 const finished = ref(false) 152 const finished = ref(false)
82 -const orders = ref([ 153 +const limit = ref(10)
83 - { 154 +const page = ref(0)
84 - id: 1, 155 +const orders = ref([])
85 - orderNo: 'ORDER202503210001', 156 +const showDetailDialog = ref(false)
86 - status: 'pending', 157 +const orderDetail = ref(null)
87 - statusText: '待支付', 158 +// 订单状态映射
88 - statusColor: 'text-orange-500', 159 +const statusMap = {
89 - title: '亲子阅读课程', 160 + NOT_PAY: { text: '待支付', color: 'text-orange-500' },
90 - description: '3-6岁儿童亲子阅读指导', 161 + PAY: { text: '已支付', color: 'text-green-500' },
91 - image: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg', 162 + CANCEL: { text: '已取消', color: 'text-gray-500' },
92 - amount: 299, 163 + APPLY_REFUND: { text: '申请退款', color: 'text-red-500' },
93 - createTime: '2025-03-21 10:30:00' 164 + REFUND: { text: '已退款', color: 'text-red-500' },
94 - }, 165 + REFUND_ERROR: { text: '退款失败', color: 'text-red-500' },
95 - { 166 +}
96 - id: 2, 167 +// 模拟订单数据
97 - orderNo: 'ORDER202503210002', 168 +// const orders = ref([
98 - status: 'paid', 169 +// {
99 - statusText: '已支付', 170 +// id: 1,
100 - statusColor: 'text-green-500', 171 +// orderNo: 'ORDER202503210001',
101 - title: '儿童绘画课程', 172 +// status: 'pending',
102 - description: '儿童创意绘画启蒙课程', 173 +// statusText: '待支付',
103 - image: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg', 174 +// statusColor: 'text-orange-500',
104 - amount: 199, 175 +// title: '亲子阅读课程',
105 - createTime: '2025-03-21 09:15:00' 176 +// description: '3-6岁儿童亲子阅读指导',
106 - } 177 +// image: 'https://cdn.ipadbiz.cn/mlaj/images/jbwr0qZvpD4.jpg',
107 -]) 178 +// amount: 299,
179 +// createTime: '2025-03-21 10:30:00'
180 +// },
181 +// {
182 +// id: 2,
183 +// orderNo: 'ORDER202503210002',
184 +// status: 'paid',
185 +// statusText: '已支付',
186 +// statusColor: 'text-green-500',
187 +// title: '儿童绘画课程',
188 +// description: '儿童创意绘画启蒙课程',
189 +// image: 'https://cdn.ipadbiz.cn/mlaj/images/27kCu7bXGEI.jpg',
190 +// amount: 199,
191 +// createTime: '2025-03-21 09:15:00'
192 +// }
193 +// ])
108 194
195 +// 初始化加载订单列表
109 // 加载更多 196 // 加载更多
110 -const onLoad = () => { 197 +const onLoad = async () => {
111 - // 模拟异步加载 198 + const nextPage = page.value;
112 - setTimeout(() => { 199 + const res = await getOrderListAPI({ limit: limit.value, page: nextPage, status: '' });
113 - loading.value = false 200 + if (res.code) {
114 - finished.value = true 201 + orders.value = [...orders.value, ...res.data];
115 - }, 1000) 202 + finished.value = res.data.length < limit.value;
116 -} 203 + page.value = nextPage + 1;
204 + }
205 + loading.value = false;
206 +};
117 207
118 // 支付订单 208 // 支付订单
119 const handlePay = (order) => { 209 const handlePay = (order) => {
...@@ -121,7 +211,44 @@ const handlePay = (order) => { ...@@ -121,7 +211,44 @@ const handlePay = (order) => {
121 } 211 }
122 212
123 // 查看订单详情 213 // 查看订单详情
124 -const handleViewDetail = (order) => { 214 +const handleViewDetail = async (order) => {
125 - router.push(`/profile/orders/${order.id}`) 215 + try {
126 -} 216 + const { code, data } = await getOrderInfoAPI({ i: order.id });
217 + if (code) {
218 + orderDetail.value = data;
219 + showDetailDialog.value = true;
220 + }
221 + } catch (error) {
222 + console.error('获取订单详情失败:', error);
223 + showToast('获取订单详情失败');
224 + }
225 +};
226 +
227 +// 取消订单
228 +const handleCancel = async (order) => {
229 + showConfirmDialog({
230 + title: '温馨提示',
231 + message: '您是否确定要取消该订单?',
232 + confirmButtonColor: '#4caf50',
233 + })
234 + .then(async() => {
235 + try {
236 + const { code } = await cancelOrderAPI({ i: order.id });
237 + if (code) {
238 + const index = orders.value.findIndex((item) => item.id === order.id);
239 + // 更新订单状态为已取消
240 + orders.value[index].status = 'CANCEL';
241 + orders.value[index].statusText = '已取消';
242 + orders.value[index].statusColor = 'text-gray-500';
243 + // 显示提示信息
244 + showToast('订单已取消');
245 + }
246 + } catch (error) {
247 + console.error('取消订单失败:', error);
248 + }
249 + })
250 + .catch(() => {
251 + // on cancel
252 + });
253 +};
127 </script> 254 </script>
......