MrShi
2026-06-05 00a7a61df86db969f2ba61c508d02ba4709ce3d4
app/pages/order-detail/order-detail.vue
@@ -280,11 +280,83 @@
            <button class="photo-popup__submit" hover-class="photo-popup__submit--hover" @click="submitPhotoPopup">{{ photoPopupSubmitText }}</button>
         </view>
      </u-popup>
      <u-popup :show="showTimelinePopup" round="20" mode="bottom" @close="closeTimelinePopup">
         <view class="track-popup">
            <view class="track-popup__header">
               <text class="track-popup__title">订单轨迹</text>
               <view class="track-popup__close" @click="closeTimelinePopup">×</view>
            </view>
            <scroll-view scroll-y class="track-popup__body">
               <view class="track-empty" v-if="!timelineList.length">暂无轨迹信息</view>
               <view class="track-list" v-else>
                  <view class="track-item" v-for="(track, index) in timelineList" :key="track.key || index">
                     <view class="track-item__content">
                        <view class="track-item__rail">
                           <image v-if="index === 0" class="track-item__dot track-item__dot--active" src="/static/image/dian.png" mode="aspectFit"></image>
                           <view v-else class="track-item__dot"></view>
                           <view class="track-item__line" v-if="index !== timelineList.length - 1"></view>
                        </view>
                        <view class="track-item__body">
                           <text class="track-item__time">{{ track.time }}</text>
                           <text class="track-item__desc">{{ track.title }}</text>
                           <view class="track-item__images" v-if="track.images && track.images.length">
                              <image
                                 v-for="(img, imgIndex) in track.images"
                                 :key="track.key + '-' + imgIndex"
                                 :src="img"
                                 mode="aspectFill"
                                 @click="previewTimelineImages(track.images, imgIndex)"
                              ></image>
                           </view>
                        </view>
                     </view>
                  </view>
               </view>
            </scroll-view>
         </view>
         <!-- <view class="timeline-popup">
            <view class="timeline-popup__header">
               <view class="timeline-popup__placeholder"></view>
               <text class="timeline-popup__title">订单轨迹</text>
               <u-icon name="close" color="#A7ACB5" size="26" @click="closeTimelinePopup"></u-icon>
            </view>
            <scroll-view class="timeline-popup__scroll" scroll-y>
               <view v-if="timelineList.length" class="timeline-popup__list">
                  <view v-for="(item, index) in timelineList" :key="index" class="timeline-popup__item">
                     <view class="timeline-popup__axis">
                        <view class="timeline-popup__dot" :class="index === 0 ? 'timeline-popup__dot--active' : ''"></view>
                        <view v-if="index !== timelineList.length - 1" class="timeline-popup__line"></view>
                     </view>
                     <view class="timeline-popup__content">
                        <text class="timeline-popup__time">{{ item.time || '-' }}</text>
                        <text class="timeline-popup__desc">{{ item.title || '-' }}</text>
                        <view v-if="item.images.length" class="timeline-popup__images">
                           <image
                              v-for="(image, imageIndex) in item.images"
                              :key="imageIndex"
                              class="timeline-popup__image"
                              :src="image"
                              mode="aspectFill"
                              @click="previewTimelineImages(item.images, imageIndex)"
                           ></image>
                        </view>
                     </view>
                  </view>
               </view>
               <view v-else class="timeline-popup__empty">暂无订单轨迹</view>
            </scroll-view>
         </view> -->
      </u-popup>
   </view>
</template>
<script>
   import { mapState } from 'vuex'
   import { chooseImageWithNotice, getLocationWithNotice } from '@/utils/utils'
   export default {
      data() {
@@ -301,6 +373,8 @@
            showCancelModal: false,
            cancelRemain: 0,
            showGrabModal: false,
            showTimelinePopup: false,
            timelineList: [],
            currentLocation: null,
            routePoints: [],
            locationTimer: null,
@@ -409,26 +483,27 @@
         footerButtons() {
            const status = this.orderDetail.status
            const takeShopId = this.orderDetail.takeShopId
            const buttons = [{ text: '订单轨迹', primary: false, action: 'timeline' }]
            if (status === 2) {
               return [{ text: '立即抢单', primary: true, action: 'grab' }]
               return buttons.concat([{ text: '立即抢单', primary: true, action: 'grab' }])
            }
            if (status === 3) {
               return [
               return buttons.concat([
                  { text: '取消订单', primary: false, action: 'cancel' },
                  { text: '拍照取货', primary: true, action: 'pickup' }
               ]
               ])
            }
            if (status === 4) {
               if (!takeShopId) {
                  return [{ text: '拍照送达', primary: true, action: 'deliver' }]
                  return buttons.concat([{ text: '拍照送达', primary: true, action: 'deliver' }])
               }
               return []
               return buttons
            }
            return []
            return buttons
         },
         photoPopupTitle() {
            return this.photoPopupMode === 'deliver' ? '拍照送达' : '拍照取货'
@@ -468,7 +543,7 @@
         initOperationRadius() {
            console.log('initOperationRadius')
            return new Promise((resolve) => {
               uni.getLocation({
               getLocationWithNotice({
                  type: 'gcj02',
                  success: (res) => {
                     this.$u.api.checkDriverOperationRadius({
@@ -503,6 +578,8 @@
                     })
                     resolve(false)
                  }
               }).catch(() => {
                  resolve(false)
               })
            })
         },
@@ -531,7 +608,7 @@
         },
         fetchLocation() {
            uni.getLocation({
            getLocationWithNotice({
               type: 'gcj02',
               success: (res) => {
                  this.currentLocation = {
@@ -543,7 +620,7 @@
               fail: (err) => {
                  console.log('获取位置失败', err)
               }
            })
            }).catch(() => {})
         },
         getRoutePlan() {
@@ -636,6 +713,59 @@
            })
         },
         normalizeTimelineImages(images) {
            if (Array.isArray(images)) {
               return images.filter(item => !!item)
            }
            if (typeof images === 'string') {
               return images.split(',').map(item => item.trim()).filter(item => !!item)
            }
            return []
         },
         openTimelinePopup() {
            if (!this.orderId) {
               uni.showToast({ title: '订单ID不存在', icon: 'none' })
               return
            }
            uni.showLoading({ title: '加载中...' })
            this.$u.api.timeline(this.orderId).then(res => {
               if (res.code === 200) {
                  const list = Array.isArray(res.data) ? res.data : []
                  this.timelineList = list.map(item => ({
                     time: item.time || '',
                     title: item.title || '',
                     images: this.normalizeTimelineImages(item.images)
                  }))
                  this.showTimelinePopup = true
               } else {
                  uni.showToast({ title: res.msg || '获取订单轨迹失败', icon: 'none' })
               }
            }).catch(() => {
               uni.showToast({ title: '获取订单轨迹失败', icon: 'none' })
            }).finally(() => {
               uni.hideLoading()
            })
         },
         closeTimelinePopup() {
            this.showTimelinePopup = false
         },
         previewTimelineImages(images, currentIndex) {
            if (!images || !images.length) {
               return
            }
            uni.previewImage({
               current: images[currentIndex],
               urls: images
            })
         },
         handleFooterAction(button) {
            const action = button.action
@@ -659,6 +789,10 @@
            if (action === 'grab') {
               this.handleGrabOrder()
               return
            }
            if (action === 'timeline') {
               this.openTimelinePopup()
            }
         },
         handleCancelOrder() {
@@ -706,25 +840,25 @@
         },
         chooseImage() {
            const count = 3 - this.uploadedPhotos.length
            uni.chooseImage({
            chooseImageWithNotice({
               count: count,
               sourceType: ['camera', 'album'],
               success: (res) => {
                  const tempFilePaths = res.tempFilePaths
                  this.uploadedPhotos = this.uploadedPhotos.concat(tempFilePaths)
               }
            })
            }).catch(() => {})
         },
         deletePhoto(index) {
            this.uploadedPhotos.splice(index, 1)
         },
         submitPhotoPopup() {
            submitPhotoPopup() {
            if (this.uploadedPhotos.length === 0) {
               uni.showToast({ title: '请上传照片', icon: 'none' })
               return
            }
            uni.showLoading({ title: '上传中...' })
            uni.getLocation({
            getLocationWithNotice({
               type: 'gcj02',
               success: (locationRes) => {
                  this.doUploadPhotos(locationRes.latitude, locationRes.longitude)
@@ -732,6 +866,8 @@
               fail: () => {
                  this.doUploadPhotos(null, null)
               }
            }).catch(() => {
               this.doUploadPhotos(null, null)
            })
         },
@@ -1326,6 +1462,128 @@
         }
      }
   }
   .track-popup {
      background: #FFFFFF;
      border-radius: 24rpx 24rpx 0 0;
      padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
      box-sizing: border-box;
      .track-popup__header {
         height: 88rpx;
         display: flex;
         align-items: center;
         justify-content: center;
         position: relative;
      }
      .track-popup__title {
         font-weight: 600;
         font-size: 32rpx;
         color: #222222;
      }
      .track-popup__close {
         position: absolute;
         right: 0;
         top: 50%;
         transform: translateY(-50%);
         width: 56rpx;
         height: 56rpx;
         display: flex;
         align-items: center;
         justify-content: center;
         font-size: 56rpx;
         line-height: 1;
         color: #A5A7AD;
      }
      .track-popup__body {
         height: 50vh;
      }
      .track-empty {
         line-height: 50vh;
         text-align: center;
         font-size: 28rpx;
         color: #999999;
      }
      .track-list {
         padding: 12rpx 0 0;
      }
      .track-item {
         position: relative;
      }
      .track-item__rail {
         width: 34rpx;
         flex-shrink: 0;
         display: flex;
         flex-direction: column;
         align-items: center;
         align-self: stretch;
         position: relative;
      }
      .track-item__dot {
         width: 16rpx;
         height: 16rpx;
         flex-shrink: 0;
         position: relative;
         z-index: 1;
         border-radius: 50%;
         background: #CCCCCC;
         margin-top: 12rpx;
      }
      .track-item__dot--active {
         width: 32rpx;
         height: 32rpx;
         flex-shrink: 0;
         display: block;
         position: relative;
         z-index: 1;
         margin-top: 4rpx;
         background: transparent;
      }
      .track-item__line {
         width: 2rpx;
         position: absolute;
         left: 50%;
         top: 28rpx;
         bottom: -30rpx;
         transform: translateX(-50%);
         background: #E6EAF0;
      }
      .track-item__content {
         display: flex;
         align-items: flex-start;
         column-gap: 22rpx;
         padding: 0 0 20rpx;
      }
      .track-item__body {
         flex: 1;
         min-width: 0;
      }
      .track-item__time {
         display: block;
         font-weight: 400;
         font-size: 26rpx;
         color: #666666;
         line-height: 1.4;
      }
      .track-item__desc {
         display: block;
         margin-top: 22rpx;
         font-weight: 500;
         font-size: 30rpx;
         color: #666666;
      }
      .track-item__images {
         display: flex;
         flex-wrap: wrap;
         gap: 18rpx;
         margin-top: 24rpx;
         image {
            width: 130rpx;
            height: 130rpx;
            border-radius: 12rpx;
            background: #F3F5F8;
         }
      }
   }
   .photo-popup {
      padding: 30rpx 28rpx calc(env(safe-area-inset-bottom) + 28rpx);
@@ -1484,4 +1742,117 @@
         }
      }
   }
   .timeline-popup {
      padding: 26rpx 28rpx calc(env(safe-area-inset-bottom) + 28rpx);
      background: #ffffff;
      border-top-left-radius: 20rpx;
      border-top-right-radius: 20rpx;
      &__header {
         display: flex;
         align-items: center;
         justify-content: space-between;
      }
      &__placeholder {
         width: 52rpx;
         height: 52rpx;
         flex-shrink: 0;
         opacity: 0;
      }
      &__title {
         font-size: 40rpx;
         font-weight: 700;
         color: #222222;
      }
      &__scroll {
         max-height: 50vh;
         margin-top: 30rpx;
      }
      &__list {
         padding-bottom: 12rpx;
      }
      &__item {
         display: flex;
         align-items: stretch;
      }
      &__axis {
         width: 40rpx;
         display: flex;
         flex-direction: column;
         align-items: center;
         flex-shrink: 0;
      }
      &__dot {
         width: 18rpx;
         height: 18rpx;
         margin-top: 8rpx;
         border-radius: 50%;
         background: #D5D9E2;
         box-shadow: 0 0 0 8rpx #ffffff;
         z-index: 1;
         &--active {
            background: #2F80FF;
            box-shadow: 0 0 0 8rpx rgba(47, 128, 255, 0.14);
         }
      }
      &__line {
         flex: 1;
         width: 2rpx;
         margin-top: 8rpx;
         background: #E5EAF2;
      }
      &__content {
         flex: 1;
         padding: 0 0 40rpx 16rpx;
      }
      &__time {
         display: block;
         font-size: 26rpx;
         font-weight: 400;
         color: #808692;
         line-height: 1.4;
      }
      &__desc {
         display: block;
         margin-top: 18rpx;
         font-size: 40rpx;
         font-weight: 600;
         color: #4A4A4A;
         line-height: 1.45;
      }
      &__images {
         display: flex;
         flex-wrap: wrap;
         gap: 16rpx;
         margin-top: 22rpx;
      }
      &__image {
         width: 124rpx;
         height: 124rpx;
         border-radius: 12rpx;
         background: #F2F4F7;
      }
      &__empty {
         line-height: 50vh;
         text-align: center;
         font-size: 28rpx;
         color: #A0A6B0;
      }
   }
</style>