feat(订单): 添加订单详情弹窗并优化订单状态管理
- 在 `OrdersPage.vue` 中添加 `VanDialog` 组件以显示订单详情 - 更新订单状态映射,支持更多状态如“待支付”、“已支付”、“已取消”等 - 优化订单列表的加载逻辑,使用 API 获取订单数据 - 移除不必要的注释代码,简化页面结构
Showing
4 changed files
with
179 additions
and
51 deletions
| 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> | ... | ... |
-
Please register or login to post a comment