hookehuyr

feat(订单): 添加订单评价功能及支付组件集成

- 新增订单评价弹窗组件,支持评分和评价内容输入
- 实现评价提交逻辑,包含表单验证和状态管理
- 集成支付组件,优化支付流程
- 添加评价样式和响应式布局
- 完善订单列表交互,区分已评价和未评价状态
...@@ -23,6 +23,7 @@ declare module 'vue' { ...@@ -23,6 +23,7 @@ declare module 'vue' {
23 NutPopup: typeof import('@nutui/nutui-taro')['Popup'] 23 NutPopup: typeof import('@nutui/nutui-taro')['Popup']
24 NutRadio: typeof import('@nutui/nutui-taro')['Radio'] 24 NutRadio: typeof import('@nutui/nutui-taro')['Radio']
25 NutRadioGroup: typeof import('@nutui/nutui-taro')['RadioGroup'] 25 NutRadioGroup: typeof import('@nutui/nutui-taro')['RadioGroup']
26 + NutRate: typeof import('@nutui/nutui-taro')['Rate']
26 NutRow: typeof import('@nutui/nutui-taro')['Row'] 27 NutRow: typeof import('@nutui/nutui-taro')['Row']
27 NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar'] 28 NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar']
28 NutSticky: typeof import('@nutui/nutui-taro')['Sticky'] 29 NutSticky: typeof import('@nutui/nutui-taro')['Sticky']
......
...@@ -247,6 +247,141 @@ ...@@ -247,6 +247,141 @@
247 color: #9ca3af; 247 color: #9ca3af;
248 } 248 }
249 249
250 +// 评价弹窗样式
251 +.rate-popup {
252 + padding: 40rpx;
253 + height: 100%;
254 + display: flex;
255 + flex-direction: column;
256 +
257 + .rate-header {
258 + text-align: center;
259 + margin-bottom: 40rpx;
260 +
261 + .rate-title {
262 + font-size: 36rpx;
263 + font-weight: 600;
264 + color: #333;
265 + }
266 + }
267 +
268 + .rate-content {
269 + flex: 1;
270 +
271 + .product-info {
272 + display: flex;
273 + padding: 30rpx;
274 + background: #f8f9fa;
275 + border-radius: 16rpx;
276 + margin-bottom: 40rpx;
277 +
278 + .product-image {
279 + width: 120rpx;
280 + height: 120rpx;
281 + border-radius: 12rpx;
282 + margin-right: 24rpx;
283 + }
284 +
285 + .product-details {
286 + flex: 1;
287 + display: flex;
288 + flex-direction: column;
289 + justify-content: space-between;
290 +
291 + .product-name {
292 + font-size: 32rpx;
293 + font-weight: 600;
294 + color: #333;
295 + margin-bottom: 8rpx;
296 + }
297 +
298 + .product-specs {
299 + font-size: 26rpx;
300 + color: #666;
301 + margin-bottom: 8rpx;
302 + }
303 +
304 + .product-price {
305 + font-size: 32rpx;
306 + font-weight: 600;
307 + color: #ff6b35;
308 + }
309 + }
310 + }
311 +
312 + .rate-score-section {
313 + margin-bottom: 40rpx;
314 +
315 + .score-label {
316 + display: block;
317 + font-size: 30rpx;
318 + color: #333;
319 + margin-bottom: 20rpx;
320 + font-weight: 500;
321 + }
322 +
323 + .rate-stars {
324 + margin-bottom: 16rpx;
325 + }
326 +
327 + .score-text {
328 + font-size: 26rpx;
329 + color: #666;
330 + margin-left: 16rpx;
331 + }
332 + }
333 +
334 + .rate-input-section {
335 + .input-label {
336 + display: block;
337 + font-size: 30rpx;
338 + color: #333;
339 + margin-bottom: 20rpx;
340 + font-weight: 500;
341 + }
342 +
343 + .rate-textarea {
344 + width: 100%;
345 + min-height: 200rpx;
346 + border: 2rpx solid #e5e5e5;
347 + border-radius: 12rpx;
348 + padding: 20rpx;
349 + font-size: 28rpx;
350 + line-height: 1.5;
351 +
352 + &:focus {
353 + border-color: #ff6b35;
354 + }
355 +
356 + &.readonly {
357 + background-color: #f8f9fa;
358 + border-color: #e9ecef;
359 + color: #666;
360 + }
361 + }
362 +
363 + .review-date {
364 + display: block;
365 + font-size: 24rpx;
366 + color: #999;
367 + margin-top: 16rpx;
368 + text-align: right;
369 + }
370 + }
371 + }
372 +
373 + .rate-footer {
374 + padding-top: 40rpx;
375 +
376 + .nut-button {
377 + height: 88rpx;
378 + border-radius: 44rpx;
379 + font-size: 32rpx;
380 + font-weight: 600;
381 + }
382 + }
383 +}
384 +
250 /* 响应式适配 */ 385 /* 响应式适配 */
251 @media (max-width: 750px) { 386 @media (max-width: 750px) {
252 .header { 387 .header {
...@@ -304,4 +439,31 @@ ...@@ -304,4 +439,31 @@
304 .order-actions { 439 .order-actions {
305 gap: 16rpx; 440 gap: 16rpx;
306 } 441 }
307 -}
...\ No newline at end of file ...\ No newline at end of file
442 +}
443 +
444 +
445 + /* 加载指示器 */
446 + .loading-container {
447 + display: flex;
448 + align-items: center;
449 + justify-content: center;
450 + padding: 40rpx;
451 + gap: 16rpx;
452 + }
453 +
454 + .loading-text {
455 + font-size: 28rpx;
456 + color: #9ca3af;
457 + }
458 +
459 + :deep(.nut-textarea) {
460 + border: 1rpx solid #e5e7eb;
461 + border-radius: 16rpx;
462 + padding: 24rpx;
463 +}
464 +
465 +:deep(.nut-textarea__textarea) {
466 + font-size: 28rpx;
467 + color: #374151;
468 + line-height: 1.5;
469 +}
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-03 15:55:17 4 + * @LastEditTime: 2025-07-03 16:32:53
5 * @FilePath: /jgdl/src/pages/myOrders/index.vue 5 * @FilePath: /jgdl/src/pages/myOrders/index.vue
6 * @Description: 订单管理页面 6 * @Description: 订单管理页面
7 --> 7 -->
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 <view class="order-management-page"> 9 <view class="order-management-page">
10 <nut-sticky> 10 <nut-sticky>
11 <!-- 买车/卖车切换 --> 11 <!-- 买车/卖车切换 -->
12 - <view class="view-mode-toggle"> 12 + <view id="mode-toggle" class="view-mode-toggle">
13 <view class="toggle-container"> 13 <view class="toggle-container">
14 <view 14 <view
15 class="toggle-option" 15 class="toggle-option"
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
29 </view> 29 </view>
30 30
31 <!-- 状态筛选标签 --> 31 <!-- 状态筛选标签 -->
32 - <view class="status-tabs"> 32 + <view id="status-tabs" class="status-tabs">
33 <view 33 <view
34 class="tab-item" 34 class="tab-item"
35 :class="{ active: activeTab === 'all' }" 35 :class="{ active: activeTab === 'all' }"
...@@ -123,7 +123,7 @@ ...@@ -123,7 +123,7 @@
123 <nut-button 123 <nut-button
124 type="primary" 124 type="primary"
125 size="small" 125 size="small"
126 - @click="handlePayment(order.id)" 126 + @click="handlePayment(order)"
127 > 127 >
128 去支付 128 去支付
129 </nut-button> 129 </nut-button>
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
144 @click="rateOrder(order.id)" 144 @click="rateOrder(order.id)"
145 class="ml-2" 145 class="ml-2"
146 > 146 >
147 - 评价 147 + {{ order.review ? '查看评价' : '评价' }}
148 </nut-button> 148 </nut-button>
149 </template> 149 </template>
150 150
...@@ -173,6 +173,87 @@ ...@@ -173,6 +173,87 @@
173 </view> 173 </view>
174 </scroll-view> 174 </scroll-view>
175 </view> 175 </view>
176 +
177 + <!-- 支付组件 -->
178 + <payCard :visible="show_pay" :data="payData" @close="onPayClose" />
179 +
180 + <!-- 评价弹窗 -->
181 + <nut-popup
182 + v-model:visible="showRatePopup"
183 + position="bottom"
184 + :style="{ height: '60%' }"
185 + round
186 + closeable
187 + @close="closeRatePopup"
188 + >
189 + <view class="rate-popup">
190 + <view class="rate-header">
191 + <text class="rate-title">评价商品</text>
192 + </view>
193 +
194 + <view class="rate-content">
195 + <!-- 商品信息 -->
196 + <view class="product-info" v-if="currentRateOrder">
197 + <image
198 + :src="currentRateOrder.vehicle.imageUrl"
199 + class="product-image"
200 + mode="aspectFill"
201 + />
202 + <view class="product-details">
203 + <text class="product-name">{{ currentRateOrder.vehicle.name }}</text>
204 + <text class="product-specs">{{ currentRateOrder.vehicle.year }} · {{ currentRateOrder.vehicle.mileage }}</text>
205 + <text class="product-price">¥{{ currentRateOrder.vehicle.price }}</text>
206 + </view>
207 + </view>
208 +
209 + <!-- 评分组件 -->
210 + <view class="rate-score-section">
211 + <text class="score-label">{{ isReadOnlyMode ? '评分' : '请给商品评分' }}</text>
212 + <nut-rate
213 + v-model="rateScore"
214 + :readonly="isReadOnlyMode"
215 + :size="isReadOnlyMode ? '20' : '24'"
216 + active-color="#ff6b35"
217 + void-color="#e5e5e5"
218 + class="rate-stars"
219 + />
220 + <text v-if="isReadOnlyMode" class="score-text">{{ rateScore }}/5分</text>
221 + </view>
222 +
223 + <!-- 评价输入框 -->
224 + <view class="rate-input-section">
225 + <text class="input-label">{{ isReadOnlyMode ? '评价内容' : '请输入评价内容' }}</text>
226 + <view class="border border-gray-100">
227 + <nut-textarea
228 + v-model="rateContent"
229 + :placeholder="isReadOnlyMode ? '' : '请输入您的评价内容...'"
230 + :max-length="200"
231 + :rows="4"
232 + :show-word-limit="!isReadOnlyMode"
233 + :readonly="isReadOnlyMode"
234 + :class="{ 'readonly': isReadOnlyMode }"
235 + />
236 + </view>
237 + <text v-if="isReadOnlyMode && currentRateOrder?.review?.date" class="review-date">
238 + 评价时间:{{ currentRateOrder.review.date }}
239 + </text>
240 + </view>
241 + </view>
242 +
243 + <!-- 提交按钮 -->
244 + <view class="rate-footer" v-if="!isReadOnlyMode">
245 + <nut-button
246 + type="primary"
247 + size="large"
248 + @click="submitRate"
249 + :loading="submittingRate"
250 + block
251 + >
252 + 提交评价
253 + </nut-button>
254 + </view>
255 + </view>
256 + </nut-popup>
176 </view> 257 </view>
177 </template> 258 </template>
178 259
...@@ -180,6 +261,13 @@ ...@@ -180,6 +261,13 @@
180 import { ref, computed, onMounted } from 'vue' 261 import { ref, computed, onMounted } from 'vue'
181 import Taro from '@tarojs/taro' 262 import Taro from '@tarojs/taro'
182 import './index.less' 263 import './index.less'
264 +import { $ } from '@tarojs/extend'
265 +import payCard from '@/components/payCard.vue'
266 +// NutUI组件已全局注册,无需单独导入Rate
267 +
268 +const scrollStyle = ref({
269 + height: 'calc(100vh)'
270 +})
183 271
184 // 页面状态 272 // 页面状态
185 const activeTab = ref('all') 273 const activeTab = ref('all')
...@@ -187,10 +275,26 @@ const viewMode = ref('bought') ...@@ -187,10 +275,26 @@ const viewMode = ref('bought')
187 const loading = ref(false) 275 const loading = ref(false)
188 const hasMore = ref(true) 276 const hasMore = ref(true)
189 277
278 +const show_pay = ref(false)
279 +const payData = ref({
280 + id: '',
281 + price: 0,
282 + remain_time: 0
283 +})
284 +
285 +// 评价相关状态
286 +const showRatePopup = ref(false)
287 +const currentRateOrder = ref(null)
288 +const rateContent = ref('')
289 +const rateScore = ref(5)
290 +const submittingRate = ref(false)
291 +const isReadOnlyMode = ref(false)
292 +
190 // 模拟订单数据 293 // 模拟订单数据
191 const boughtOrders = ref([ 294 const boughtOrders = ref([
192 { 295 {
193 id: '1', 296 id: '1',
297 + price: 999,
194 date: '2023-11-15 14:30', 298 date: '2023-11-15 14:30',
195 status: 'pending', 299 status: 'pending',
196 vehicle: { 300 vehicle: {
...@@ -204,6 +308,7 @@ const boughtOrders = ref([ ...@@ -204,6 +308,7 @@ const boughtOrders = ref([
204 }, 308 },
205 { 309 {
206 id: '2', 310 id: '2',
311 + price: 999,
207 date: '2023-10-28 09:15', 312 date: '2023-10-28 09:15',
208 status: 'completed', 313 status: 'completed',
209 vehicle: { 314 vehicle: {
...@@ -213,10 +318,17 @@ const boughtOrders = ref([ ...@@ -213,10 +318,17 @@ const boughtOrders = ref([
213 batteryCapacity: '电池容量:1.2kWh', 318 batteryCapacity: '电池容量:1.2kWh',
214 price: 2599, 319 price: 2599,
215 imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' 320 imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
321 + },
322 + // 已评价信息
323 + review: {
324 + rating: 4,
325 + content: '车子质量不错,续航也很给力,卖家服务态度很好,推荐购买!',
326 + date: '2023-10-30 10:20'
216 } 327 }
217 }, 328 },
218 { 329 {
219 id: '3', 330 id: '3',
331 + price: 999,
220 date: '2023-09-05 16:45', 332 date: '2023-09-05 16:45',
221 status: 'cancelled', 333 status: 'cancelled',
222 vehicle: { 334 vehicle: {
...@@ -233,6 +345,7 @@ const boughtOrders = ref([ ...@@ -233,6 +345,7 @@ const boughtOrders = ref([
233 const soldOrders = ref([ 345 const soldOrders = ref([
234 { 346 {
235 id: '4', 347 id: '4',
348 + price: 999,
236 date: '2023-11-10 11:20', 349 date: '2023-11-10 11:20',
237 status: 'pending', 350 status: 'pending',
238 vehicle: { 351 vehicle: {
...@@ -246,6 +359,7 @@ const soldOrders = ref([ ...@@ -246,6 +359,7 @@ const soldOrders = ref([
246 }, 359 },
247 { 360 {
248 id: '5', 361 id: '5',
362 + price: 999,
249 date: '2023-10-15 13:45', 363 date: '2023-10-15 13:45',
250 status: 'completed', 364 status: 'completed',
251 vehicle: { 365 vehicle: {
...@@ -255,6 +369,12 @@ const soldOrders = ref([ ...@@ -255,6 +369,12 @@ const soldOrders = ref([
255 batteryCapacity: '电池容量:1.6kWh', 369 batteryCapacity: '电池容量:1.6kWh',
256 price: 3599, 370 price: 3599,
257 imageUrl: 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' 371 imageUrl: 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
372 + },
373 + // 已评价信息
374 + review: {
375 + rating: 5,
376 + content: '非常满意的一次购买体验,车辆性能超出预期,卖家很专业,物流也很快!',
377 + date: '2023-10-18 16:30'
258 } 378 }
259 } 379 }
260 ]) 380 ])
...@@ -277,13 +397,6 @@ const filteredOrders = computed(() => { ...@@ -277,13 +397,6 @@ const filteredOrders = computed(() => {
277 }) 397 })
278 }) 398 })
279 399
280 -// 滚动样式
281 -const scrollStyle = computed(() => {
282 - return {
283 - height: 'calc(100vh - 320rpx)' // 减去头部和标签的高度
284 - }
285 -})
286 -
287 /** 400 /**
288 * 设置视图模式(买车/卖车) 401 * 设置视图模式(买车/卖车)
289 */ 402 */
...@@ -370,20 +483,42 @@ const getStatusClass = (status) => { ...@@ -370,20 +483,42 @@ const getStatusClass = (status) => {
370 /** 483 /**
371 * 处理支付 484 * 处理支付
372 */ 485 */
373 -const handlePayment = (orderId) => { 486 +const handlePayment = ({ id, price }) => {
374 - Taro.showToast({ 487 + onPay({
375 - title: '跳转到支付页面', 488 + id,
376 - icon: 'none' 489 + remain_time: 1800, // 30分钟
490 + price
377 }) 491 })
378 - // TODO: 实现支付逻辑 492 +}
493 +
494 +/**
495 + * 发送订单支付信息到支付组件
496 + * @param {Object} payInfo - 支付信息
497 + * @param {string} payInfo.id - 订单ID
498 + * @param {number} payInfo.remain_time - 剩余时间
499 + * @param {number} payInfo.price - 价格
500 + */
501 +const onPay = ({ id, remain_time, price }) => {
502 + show_pay.value = true
503 + payData.value.id = id
504 + payData.value.price = price
505 + payData.value.remain_time = remain_time
506 +}
507 +
508 +/**
509 + * 关闭支付弹框
510 + */
511 +const onPayClose = () => {
512 + show_pay.value = false
379 } 513 }
380 514
381 /** 515 /**
382 * 查看订单详情 516 * 查看订单详情
383 */ 517 */
384 const viewOrderDetail = (orderId) => { 518 const viewOrderDetail = (orderId) => {
385 - Taro.navigateTo({ 519 + Taro.showToast({
386 - url: `/pages/orderDetail/index?id=${orderId}` 520 + title: '查看订单详情',
521 + icon: 'none'
387 }) 522 })
388 } 523 }
389 524
...@@ -391,11 +526,102 @@ const viewOrderDetail = (orderId) => { ...@@ -391,11 +526,102 @@ const viewOrderDetail = (orderId) => {
391 * 评价订单 526 * 评价订单
392 */ 527 */
393 const rateOrder = (orderId) => { 528 const rateOrder = (orderId) => {
394 - Taro.showToast({ 529 + // 找到对应的订单
395 - title: '跳转到评价页面', 530 + const orders = viewMode.value === 'bought' ? boughtOrders.value : soldOrders.value
396 - icon: 'none' 531 + const order = orders.find(o => o.id === orderId)
397 - }) 532 +
398 - // TODO: 实现评价逻辑 533 + if (order) {
534 + currentRateOrder.value = order
535 +
536 + // 检查是否已有评价
537 + if (order.review) {
538 + // 已评价,显示只读模式
539 + isReadOnlyMode.value = true
540 + rateContent.value = order.review.content
541 + rateScore.value = order.review.rating
542 + } else {
543 + // 未评价,显示编辑模式
544 + isReadOnlyMode.value = false
545 + rateContent.value = ''
546 + rateScore.value = 5
547 + }
548 +
549 + showRatePopup.value = true
550 + }
551 +}
552 +
553 +/**
554 + * 关闭评价弹窗
555 + */
556 +const closeRatePopup = () => {
557 + showRatePopup.value = false
558 + currentRateOrder.value = null
559 + rateContent.value = ''
560 + rateScore.value = 5
561 + isReadOnlyMode.value = false
562 +}
563 +
564 +/**
565 + * 提交评价
566 + */
567 +const submitRate = async () => {
568 + if (!rateContent.value.trim()) {
569 + Taro.showToast({
570 + title: '请输入评价内容',
571 + icon: 'none'
572 + })
573 + return
574 + }
575 +
576 + if (rateScore.value < 1 || rateScore.value > 5) {
577 + Taro.showToast({
578 + title: '请选择评分',
579 + icon: 'none'
580 + })
581 + return
582 + }
583 +
584 + submittingRate.value = true
585 +
586 + try {
587 + // 模拟提交评价
588 + await new Promise(resolve => setTimeout(resolve, 1500))
589 +
590 + // 更新订单评价信息
591 + if (currentRateOrder.value) {
592 + const currentDate = new Date()
593 + const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')} ${String(currentDate.getHours()).padStart(2, '0')}:${String(currentDate.getMinutes()).padStart(2, '0')}`
594 +
595 + currentRateOrder.value.review = {
596 + rating: rateScore.value,
597 + content: rateContent.value,
598 + date: dateStr
599 + }
600 + }
601 +
602 + Taro.showToast({
603 + title: '评价提交成功',
604 + icon: 'success'
605 + })
606 +
607 + // 关闭弹窗
608 + closeRatePopup()
609 +
610 + // TODO: 这里可以添加实际的API调用逻辑
611 + // await api.submitOrderRate({
612 + // orderId: currentRateOrder.value.id,
613 + // rating: rateScore.value,
614 + // content: rateContent.value
615 + // })
616 +
617 + } catch (error) {
618 + Taro.showToast({
619 + title: '提交失败,请重试',
620 + icon: 'none'
621 + })
622 + } finally {
623 + submittingRate.value = false
624 + }
399 } 625 }
400 626
401 /** 627 /**
...@@ -418,8 +644,17 @@ const deleteOrder = (orderId) => { ...@@ -418,8 +644,17 @@ const deleteOrder = (orderId) => {
418 } 644 }
419 645
420 // 页面加载时的初始化 646 // 页面加载时的初始化
421 -onMounted(() => { 647 +onMounted(async () => {
422 // TODO: 加载订单数据 648 // TODO: 加载订单数据
649 + // 设置滚动列表可视高度
650 + const windowHeight = wx.getWindowInfo().windowHeight;
651 + setTimeout(async () => {
652 + const headerHeight = await $('#mode-toggle').height();
653 + const navHeight = await $('#status-tabs').height();
654 + scrollStyle.value = {
655 + height: windowHeight - headerHeight - navHeight + 'px'
656 + }
657 + }, 500);
423 }) 658 })
424 </script> 659 </script>
425 660
......