| | |
| | | <image class="guide-avatar__icon" :src="order.guideAvatar || '/static/ui-icons/user-blue.svg'" mode="aspectFill" /> |
| | | </view> |
| | | <view class="guide-main"> |
| | | <text class="guide-name">?? ? {{ order.guideName }}</text> |
| | | <text class="guide-name">导游 · {{ order.guideName }}</text> |
| | | <view class="guide-stars"> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">?</text> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">★</text> |
| | | </view> |
| | | </view> |
| | | <view class="phone-action" @tap="handleCallGuide"> |
| | |
| | | |
| | | <view class="price-row"> |
| | | <view class="unit-block"> |
| | | <text class="unit-price">¥{{ order.unitPrice }}/人</text> |
| | | <text class="unit-price">¥{{ order.unitPrice }}{{ order.unit }}</text> |
| | | <text class="quantity">x{{ order.quantity }}</text> |
| | | </view> |
| | | <text class="total-price">¥{{ order.total }}</text> |
| | |
| | | id: orderDetail.value.id || orderId.value, |
| | | scenic: orderDetail.value.scenicName || '-', |
| | | service: formatServiceTitle(orderDetail.value.packageName, orderDetail.value.orderType), |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}??` : '-', |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}分钟` : '-', |
| | | meetingPoint: orderDetail.value.meetingPointName || '-', |
| | | unitPrice: formatAmount(orderDetail.value.unitPrice), |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/?' : '/?', |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/人' : '/团', |
| | | quantity: Number(orderDetail.value.peopleCount || 1), |
| | | total: formatAmount(orderDetail.value.amount), |
| | | familyNo: orderDetail.value.familyNo || '', |
| | | guideName: orderDetail.value.guideName || '???', |
| | | guideName: orderDetail.value.guideName || '未分配', |
| | | guideMobile: orderDetail.value.guideMobile || '', |
| | | guideAvatar: resolveProfileAssetUrl(orderDetail.value.guideAvatar), |
| | | guideRating: Number(orderDetail.value.guideRating || 0) |
| | |
| | | const guideStarCount = computed(() => Math.max(0, Math.min(5, Math.round(order.value.guideRating)))) |
| | | |
| | | const detailRows = computed(() => [ |
| | | { label: '????', value: formatDateTime(orderDetail.value.placedAt) }, |
| | | { label: '????', value: formatDateTime(orderDetail.value.acceptedAt) }, |
| | | { label: '????', value: `?${formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount)}` }, |
| | | { label: '?????', value: orderDetail.value.paymentOrderNo || '-' }, |
| | | { label: '??????', value: formatDateTime(orderDetail.value.serviceStartedAt) }, |
| | | { label: '??????', value: formatDateTime(orderDetail.value.completedAt) } |
| | | { label: '下单时间', value: formatDateTime(orderDetail.value.placedAt) }, |
| | | { label: '接单时间', value: formatDateTime(orderDetail.value.acceptedAt) }, |
| | | { label: '支付金额', value: `¥${formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount)}` }, |
| | | { label: '支付流水号', value: orderDetail.value.paymentOrderNo || '-' }, |
| | | { label: '讲解开始时间', value: formatDateTime(orderDetail.value.serviceStartedAt) }, |
| | | { label: '讲解结束时间', value: formatDateTime(orderDetail.value.completedAt) } |
| | | ]) |
| | | |
| | | onLoad((options = {}) => { |
| | |
| | | |
| | | async function loadOrderDetail() { |
| | | if (!orderId.value) { |
| | | uni.showToast({ title: '??????', icon: 'none' }) |
| | | uni.showToast({ title: '订单参数缺失', icon: 'none' }) |
| | | return |
| | | } |
| | | try { |
| | | orderDetail.value = await getTourOrder(orderId.value) |
| | | } catch (error) { |
| | | uni.showToast({ title: error.message || '??????', icon: 'none' }) |
| | | uni.showToast({ title: error.message || '订单加载失败', icon: 'none' }) |
| | | } |
| | | } |
| | | |
| | | function handleCallGuide() { |
| | | if (!order.value.guideMobile) { |
| | | uni.showToast({ title: '????????', icon: 'none' }) |
| | | uni.showToast({ title: '暂无导游联系电话', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.makePhoneCall({ phoneNumber: order.value.guideMobile }) |
| | |
| | | } |
| | | |
| | | function formatServiceTitle(packageName, orderType) { |
| | | if (packageName) return String(packageName).replace(/\s*?\s*/g, '?') |
| | | if (String(orderType || '').startsWith('GROUP_')) return '?????' |
| | | if (String(orderType || '').startsWith('PRIVATE_')) return '?????' |
| | | return '????' |
| | | if (packageName) return String(packageName).replace(/\s*·\s*/g, '·') |
| | | const serviceTitleMap = { |
| | | GROUP_QUICK: '拼团·快讲', |
| | | GROUP_PREMIUM: '拼团·精讲', |
| | | PRIVATE_QUICK: '包团·快讲', |
| | | PRIVATE_PREMIUM: '包团·精讲' |
| | | } |
| | | return serviceTitleMap[orderType] || '讲解服务' |
| | | } |
| | | </script> |
| | | |
| | |
| | | <image class="guide-avatar__icon" :src="order.guideAvatar || '/static/ui-icons/user-blue.svg'" mode="aspectFill" /> |
| | | </view> |
| | | <view class="guide-main"> |
| | | <text class="guide-name">?? ? {{ order.guideName }}</text> |
| | | <text class="guide-name">导游 · {{ order.guideName }}</text> |
| | | <view class="guide-stars"> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">?</text> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">★</text> |
| | | </view> |
| | | </view> |
| | | <view class="phone-action" @tap="handleCallGuide"> |
| | |
| | | id: orderDetail.value.id || orderId.value, |
| | | scenic: orderDetail.value.scenicName || '-', |
| | | service: formatServiceTitle(orderDetail.value.packageName, orderDetail.value.orderType), |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}??` : '-', |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}分钟` : '-', |
| | | meetingPoint: orderDetail.value.meetingPointName || '-', |
| | | unitPrice: formatAmount(orderDetail.value.unitPrice), |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/?' : '/?', |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/人' : '/团', |
| | | quantity: Number(orderDetail.value.peopleCount || 1), |
| | | total: formatAmount(orderDetail.value.amount), |
| | | guideName: orderDetail.value.guideName || '???', |
| | | guideName: orderDetail.value.guideName || '未分配', |
| | | guideMobile: orderDetail.value.guideMobile || '', |
| | | guideAvatar: resolveProfileAssetUrl(orderDetail.value.guideAvatar), |
| | | guideRating: Number(orderDetail.value.guideRating || 0) |
| | |
| | | const guideStarCount = computed(() => Math.max(0, Math.min(5, Math.round(order.value.guideRating)))) |
| | | |
| | | const detailRows = computed(() => [ |
| | | { label: '????', value: formatDateTime(orderDetail.value.placedAt) }, |
| | | { label: '????', value: formatDateTime(orderDetail.value.acceptedAt) }, |
| | | { label: '????', value: `?${formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount)}` }, |
| | | { label: '?????', value: orderDetail.value.paymentOrderNo || '-' }, |
| | | { label: '??????', value: formatDateTime(orderDetail.value.serviceStartedAt) }, |
| | | { label: '??????', value: formatDateTime(orderDetail.value.completedAt) } |
| | | { label: '下单时间', value: formatDateTime(orderDetail.value.placedAt) }, |
| | | { label: '接单时间', value: formatDateTime(orderDetail.value.acceptedAt) }, |
| | | { label: '支付金额', value: `¥${formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount)}` }, |
| | | { label: '支付流水号', value: orderDetail.value.paymentOrderNo || '-' }, |
| | | { label: '讲解开始时间', value: formatDateTime(orderDetail.value.serviceStartedAt) }, |
| | | { label: '讲解结束时间', value: formatDateTime(orderDetail.value.completedAt) } |
| | | ]) |
| | | |
| | | onLoad((options = {}) => { |
| | |
| | | |
| | | async function loadOrderDetail() { |
| | | if (!orderId.value) { |
| | | uni.showToast({ title: '??????', icon: 'none' }) |
| | | uni.showToast({ title: '订单参数缺失', icon: 'none' }) |
| | | return |
| | | } |
| | | try { |
| | | orderDetail.value = await getTourOrder(orderId.value) |
| | | } catch (error) { |
| | | uni.showToast({ title: error.message || '??????', icon: 'none' }) |
| | | uni.showToast({ title: error.message || '订单加载失败', icon: 'none' }) |
| | | } |
| | | } |
| | | |
| | | function handleCallGuide() { |
| | | if (!order.value.guideMobile) { |
| | | uni.showToast({ title: '????????', icon: 'none' }) |
| | | uni.showToast({ title: '暂无导游联系电话', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.makePhoneCall({ phoneNumber: order.value.guideMobile }) |
| | |
| | | |
| | | async function submitReview() { |
| | | if (!rating.value) { |
| | | uni.showToast({ title: '??????', icon: 'none' }) |
| | | uni.showToast({ title: '请先选择评分', icon: 'none' }) |
| | | return |
| | | } |
| | | if (!order.value.id || submitting.value || submitSuccess.value) return |
| | |
| | | }) |
| | | submitting.value = false |
| | | submitSuccess.value = true |
| | | uni.showToast({ title: '?????', icon: 'success' }) |
| | | uni.showToast({ title: '评价成功', icon: 'success' }) |
| | | setTimeout(() => { |
| | | uni.redirectTo({ url: `/pages/order-review-detail/index?id=${order.value.id}` }) |
| | | }, 700) |
| | | } catch (error) { |
| | | submitting.value = false |
| | | uni.showToast({ title: error.message || '??????', icon: 'none' }) |
| | | uni.showToast({ title: error.message || '评价提交失败', icon: 'none' }) |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | function formatServiceTitle(packageName, orderType) { |
| | | if (packageName) return String(packageName).replace(/\s*?\s*/g, '?') |
| | | if (String(orderType || '').startsWith('GROUP_')) return '?????' |
| | | if (String(orderType || '').startsWith('PRIVATE_')) return '?????' |
| | | return '????' |
| | | if (packageName) return String(packageName).replace(/\s*·\s*/g, '·') |
| | | const serviceTitleMap = { |
| | | GROUP_QUICK: '拼团·快讲', |
| | | GROUP_PREMIUM: '拼团·精讲', |
| | | PRIVATE_QUICK: '包团·快讲', |
| | | PRIVATE_PREMIUM: '包团·精讲' |
| | | } |
| | | return serviceTitleMap[orderType] || '讲解服务' |
| | | } |
| | | </script> |
| | | |
| | |
| | | <image class="guide-avatar__icon" :src="order.guideAvatar || '/static/ui-icons/user-blue.svg'" mode="aspectFill" /> |
| | | </view> |
| | | <view class="guide-main"> |
| | | <text class="guide-name">?? ? {{ order.guideName }}</text> |
| | | <text class="guide-name">导游 · {{ order.guideName }}</text> |
| | | <view class="guide-stars"> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">?</text> |
| | | <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">★</text> |
| | | </view> |
| | | </view> |
| | | <view class="phone-action" @tap="handleCallGuide"> |
| | |
| | | |
| | | <view class="price-row"> |
| | | <view class="unit-block"> |
| | | <text class="unit-price">¥{{ order.unitPrice }}/人</text> |
| | | <text class="unit-price">¥{{ order.unitPrice }}{{ order.unit }}</text> |
| | | <text class="quantity">x{{ order.quantity }}</text> |
| | | </view> |
| | | <text class="total-price">¥{{ order.total }}</text> |
| | |
| | | <text class="review-content__text">{{ review.content }}</text> |
| | | </view> |
| | | |
| | | <view class="review-images"> |
| | | <view |
| | | v-for="(image, index) in review.images" |
| | | :key="image" |
| | | class="review-image" |
| | | @tap="previewImage(index)" |
| | | > |
| | | <image class="review-image__icon" src="/static/image.png" mode="aspectFit" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | |
| | | id: orderDetail.value.id || orderId.value, |
| | | scenic: orderDetail.value.scenicName || '-', |
| | | service: formatServiceTitle(orderDetail.value.packageName, orderDetail.value.orderType), |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}??` : '-', |
| | | duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}分钟` : '-', |
| | | meetingPoint: orderDetail.value.meetingPointName || '-', |
| | | unitPrice: formatAmount(orderDetail.value.unitPrice), |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/?' : '/?', |
| | | unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/人' : '/团', |
| | | quantity: Number(orderDetail.value.peopleCount || 1), |
| | | total: formatAmount(orderDetail.value.amount), |
| | | guideName: orderDetail.value.guideName || reviewDetail.value.guideName || '???', |
| | | guideName: orderDetail.value.guideName || reviewDetail.value.guideName || '未分配', |
| | | guideMobile: orderDetail.value.guideMobile || reviewDetail.value.guideMobile || '', |
| | | guideAvatar: resolveProfileAssetUrl(orderDetail.value.guideAvatar), |
| | | guideRating: Number(orderDetail.value.guideRating || 0) |
| | |
| | | |
| | | const review = computed(() => ({ |
| | | rating: Number(reviewDetail.value.score || 0), |
| | | content: reviewDetail.value.content || '??????', |
| | | images: [] |
| | | content: reviewDetail.value.content || '暂无评价内容' |
| | | })) |
| | | |
| | | onLoad((options = {}) => { |
| | |
| | | |
| | | async function loadPageData() { |
| | | if (!orderId.value) { |
| | | uni.showToast({ title: '??????', icon: 'none' }) |
| | | uni.showToast({ title: '订单参数缺失', icon: 'none' }) |
| | | return |
| | | } |
| | | try { |
| | |
| | | orderDetail.value = orderResult || {} |
| | | reviewDetail.value = reviewResult || {} |
| | | } catch (error) { |
| | | uni.showToast({ title: error.message || '??????', icon: 'none' }) |
| | | uni.showToast({ title: error.message || '评价加载失败', icon: 'none' }) |
| | | } |
| | | } |
| | | |
| | | function handleCallGuide() { |
| | | if (!order.value.guideMobile) { |
| | | uni.showToast({ title: '????????', icon: 'none' }) |
| | | uni.showToast({ title: '暂无导游联系电话', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.makePhoneCall({ phoneNumber: order.value.guideMobile }) |
| | | } |
| | | |
| | | function previewImage(index) { |
| | | uni.previewImage({ |
| | | current: review.value.images[index], |
| | | urls: review.value.images |
| | | }) |
| | | } |
| | | |
| | | function formatAmount(value) { |
| | |
| | | } |
| | | |
| | | function formatServiceTitle(packageName, orderType) { |
| | | if (packageName) return String(packageName).replace(/\s*?\s*/g, '?') |
| | | if (String(orderType || '').startsWith('GROUP_')) return '?????' |
| | | if (String(orderType || '').startsWith('PRIVATE_')) return '?????' |
| | | return '????' |
| | | if (packageName) return String(packageName).replace(/\s*·\s*/g, '·') |
| | | const serviceTitleMap = { |
| | | GROUP_QUICK: '拼团·快讲', |
| | | GROUP_PREMIUM: '拼团·精讲', |
| | | PRIVATE_QUICK: '包团·快讲', |
| | | PRIVATE_PREMIUM: '包团·精讲' |
| | | } |
| | | return serviceTitleMap[orderType] || '讲解服务' |
| | | } |
| | | </script> |
| | | |
| | |
| | | line-height: 1.45; |
| | | } |
| | | |
| | | .review-images { |
| | | width: 100%; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 18rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | |
| | | .review-image { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | aspect-ratio: 1; |
| | | border-radius: 4rpx; |
| | | background: #e5e7eb; |
| | | } |
| | | |
| | | .review-image__icon { |
| | | width: 34rpx; |
| | | height: 34rpx; |
| | | opacity: 0.56; |
| | | } |
| | | </style> |