app
MrShi
2026-04-27 e56792f78e4df0df2f12552d1a61dd8ca1db5c67
app/pages/order/order.vue
@@ -19,7 +19,7 @@
               <view class="order-card__head">
               <view class="order-card__head-left">
                  <image class="order-card__badge-icon" :src="getBadgeIcon(item)" mode="widthFix"></image>
                  <text class="order-card__time-text">{{ item.code }}</text>
                  <text class="order-card__time-text">下单时间:{{ item.createTime }}</text>
               </view>
                  <text class="order-card__status" :class="{ 'order-card__status--highlight': item.status === 3 || item.status === 4 }">{{ getStatusText(item.status) }}</text>
               </view>
@@ -51,15 +51,20 @@
               </view>
               <view v-if="getActions(item).length" class="order-card__actions">
                  <button
                     v-for="action in getActions(item)"
                     :key="action.text"
                     class="order-card__action-btn"
                     :class="['order-card__action-btn--' + action.type, { 'order-card__action-btn--primary-fill': action.fill }]"
                     hover-class="order-card__action-btn--hover"
                  >
                     {{ action.text }}
                  </button>
                  <view></view>
                  <view style="display: flex;flex-wrap: wrap;gap: 20rpx;">
                     <button
                        v-for="action in getActions(item)"
                        :key="action.text"
                        class="order-card__action-btn"
                        :class="['order-card__action-btn--' + action.type, { 'order-card__action-btn--primary-fill': action.fill }]"
                        hover-class="order-card__action-btn--hover"
                        @click.stop="handleAction(item, action)"
                     >
                        {{ action.text }}
                     </button>
                  </view>
               </view>
            </view>
@@ -72,6 +77,70 @@
            </view>
         </view>
      </scroll-view>
      <u-modal
         :show="showCancelModal"
         showCancelButton
         @cancel="showCancelModal = false"
         cancelColor="#666666"
         confirmColor="#0055FF"
         title="取消订单确认"
         @confirm="confirmCancelOrder">
         <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
            您今日还可取消 {{ cancelRemain }} 次订单,次数用尽后今日将无法接单,是否确认取消?
         </view>
      </u-modal>
      <u-popup :show="showQRPopup" round="20" mode="bottom">
         <view class="qrcode">
            <view class="qrcode-title">
               <image src="/static/image/ic_close@2x.png" mode="widthFix" style="opacity: 0;"></image>
               <text>{{ selectedOrder && selectedOrder.status === 4 ? '存件码' : '取货码' }}</text>
               <image src="/static/image/ic_close@2x.png" mode="widthFix" @click="showQRPopup = false"></image>
            </view>
            <view class="qrcode-image">
               <image v-if="selectedOrder && selectedOrder.driverVerifyCode" :src="'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + selectedOrder.driverVerifyCode" mode="widthFix"></image>
            </view>
            <view class="qrcode-btn" hover-class="qrcode-btn--hover" @click="showQRPopup = false">关闭</view>
         </view>
      </u-popup>
      <u-popup :show="showPhotoPopup" round="20" mode="bottom">
         <view class="photo-popup">
            <view class="photo-popup__header">
               <view class="photo-popup__placeholder"></view>
               <text class="photo-popup__title">{{ photoPopupTitle }}</text>
               <image class="photo-popup__close" src="/static/image/ic_close2@2x.png" mode="aspectFit" @click="closePhotoPopup"></image>
            </view>
            <view class="photo-popup__section">
               <view class="photo-popup__label-row">
                  <text class="photo-popup__label">{{ photoPopupLabel }}</text>
                  <text class="photo-popup__required">*</text>
                  <text class="photo-popup__hint">最多3张照片</text>
               </view>
               <view class="photo-popup__photos">
                  <view v-for="(photo, index) in uploadedPhotos" :key="index" class="photo-popup__preview-card">
                     <image class="photo-popup__preview-image" :src="photo" mode="aspectFill"></image>
                     <view class="photo-popup__preview-mask" @click="deletePhoto(index)">
                        <text class="photo-popup__preview-delete">删除</text>
                     </view>
                  </view>
                  <view v-if="uploadedPhotos.length < 3" class="photo-popup__upload-btn" @click="choosePhoto">
                     <image src="/static/image/btn_upload2@2x.png" mode="aspectFit"></image>
                  </view>
               </view>
            </view>
            <view class="photo-popup__section photo-popup__section--remark">
               <text class="photo-popup__remark-title">备注信息</text>
               <textarea v-model="photoRemark" class="photo-popup__textarea" maxlength="200" placeholder="请输入" placeholder-style="color: #c7cbd3;" />
            </view>
            <button class="photo-popup__submit" hover-class="photo-popup__submit--hover" @click="submitPhotoPopup">{{ photoPopupSubmitText }}</button>
         </view>
      </u-popup>
   </view>
</template>
@@ -91,29 +160,34 @@
            orders: [],
            page: 1,
            hasMore: true,
            loading: false
            loading: false,
            showCancelModal: false,
            showQRPopup: false,
            showPhotoPopup: false,
            selectedOrder: null,
            cancelRemain: 0,
            photoPopupMode: '',
            photoRemark: '',
            uploadedPhotos: [],
            activeOrderCount: null
         }
      },
      computed: {
         displayTabs() {
            const countMap = {}
            this.tabs.forEach(tab => {
               if (tab.value === null) {
                  countMap[null] = this.orders.length
               } else {
                  countMap[tab.value] = this.orders.filter((item) => item.status === tab.value).length
               }
            })
            return this.tabs.map((tab) => {
               if (!countMap[tab.value]) {
                  return tab
               let count = null
               if (tab.value === 3) {
                  count = this.activeOrderCount?.grabbedCount
               } else if (tab.value === 4) {
                  count = this.activeOrderCount?.deliveringCount
               }
               return {
                  ...tab,
                  label: `${tab.label} ${countMap[tab.value]}`
               if (count) {
                  return {
                     ...tab,
                     label: `${tab.label} ${count}`
                  }
               }
               return tab
            })
         },
@@ -122,6 +196,15 @@
               marginTop: this.navHeight + uni.upx2px(88) + 'px',
               height: `calc(100vh - ${this.navHeight + uni.upx2px(88)}px)`
            }
         },
         photoPopupTitle() {
            return this.photoPopupMode === 'deliver' ? '拍照送达' : '拍照取货'
         },
         photoPopupLabel() {
            return this.photoPopupMode === 'deliver' ? '拍摄送达照片' : '拍摄取货照片'
         },
         photoPopupSubmitText() {
            return this.photoPopupMode === 'deliver' ? '确认送达' : '确认取货'
         }
      },
      onLoad() {
@@ -131,10 +214,7 @@
         this.getOrderList()
      },
      onShow() {
         this.page = 1
         this.hasMore = true
         this.orders = []
         this.getOrderList()
         this.getActiveOrderCount()
      },
      onReachBottom() {
         if (!this.hasMore || this.loading) return
@@ -150,6 +230,17 @@
         }
      },
      methods: {
         getActiveOrderCount() {
            this.$u.api.activeOrderCount().then(res => {
               if (res.code === 200) {
                  this.activeOrderCount = res.data
               } else {
                  this.activeOrderCount = null
               }
            }).catch(() => {
               this.activeOrderCount = null
            })
         },
         getBadgeIcon(item) {
            return item.isUrgent ? '/static/image/ic_jisuda@2x.png' : '/static/image/ic_biaosuda@2x.png'
         },
@@ -158,6 +249,7 @@
               2: '待接单',
               3: '待取货',
               4: '配送中',
               5: '已完成',
               7: '已完成',
               99: '已取消'
            }
@@ -171,9 +263,14 @@
                  { text: '拍照取货', type: 'primary', fill: true }
               ]
            }
            if (item.status === 4) {
            if (item.status === 4 && item.takeShopId) {
               return [
                  { text: '存件码', type: 'primary', fill: false }
               ]
            }
            if (item.status === 4 && !item.takeShopId) {
               return [
                  { text: '拍照送达', type: 'primary', fill: true }
               ]
            }
            return []
@@ -197,15 +294,140 @@
                  }
                  this.hasMore = list.length >= 10
               }
            }).catch((err) => {
               console.log(err)
            }).finally(() => {
               this.loading = false
            })
         },
         goToOrderDetail(item, index) {
            uni.navigateTo({
               url: `/pages/order-detail/order-detail?id=${item.id}&index=${index + 1}`
               url: `/pages/order-detail/order-detail?id=${item.id}`
            })
         },
         handleAction(item, action) {
            const text = action.text
            if (text === '取消订单') {
               this.handleCancelOrder(item)
            } else if (text === '取货码' || text === '存件码') {
               this.handleShowQRCode(item)
            } else if (text === '拍照取货') {
               this.handlePhotoPickup(item)
            } else if (text === '拍照送达') {
               this.handlePhotoDeliver(item)
            }
         },
         handleCancelOrder(item) {
            this.selectedOrder = item
            this.$u.api.cancelLimit().then(res => {
               if (res.code === 200) {
                  this.cancelRemain = res.data.remain
               }
            }).finally(() => {
               this.showCancelModal = true
            })
         },
         confirmCancelOrder() {
            if (!this.selectedOrder) return
            this.$u.api.cancelOrder({ orderId: this.selectedOrder.id }).then(res => {
               this.showCancelModal = false
               if (res.code === 200) {
                  uni.showToast({ title: '取消成功', icon: 'success' })
                  this.getOrderList()
                  this.getActiveOrderCount()
               } else {
                  uni.showToast({ title: res.message || '取消失败', icon: 'none' })
               }
            }).catch(() => {
               this.showCancelModal = false
            })
         },
         handleShowQRCode(item) {
            this.selectedOrder = item
            this.showQRPopup = true
         },
         handlePhotoPickup(item) {
            this.selectedOrder = item
            this.photoPopupMode = 'pickup'
            this.uploadedPhotos = []
            this.photoRemark = ''
            this.showPhotoPopup = true
         },
         handlePhotoDeliver(item) {
            this.selectedOrder = item
            this.photoPopupMode = 'deliver'
            this.uploadedPhotos = []
            this.photoRemark = ''
            this.showPhotoPopup = true
         },
         closePhotoPopup() {
            this.showPhotoPopup = false
         },
         choosePhoto() {
            if (this.uploadedPhotos.length >= 3) {
               uni.showToast({ title: '最多上传3张照片', icon: 'none' })
               return
            }
            uni.chooseImage({
               count: 3 - this.uploadedPhotos.length,
               sourceType: ['camera', 'album'],
               success: (res) => {
                  this.uploadedPhotos = [...this.uploadedPhotos, ...res.tempFilePaths]
               }
            })
         },
         deletePhoto(index) {
            this.uploadedPhotos.splice(index, 1)
         },
         submitPhotoPopup() {
            if (this.uploadedPhotos.length === 0) {
               uni.showToast({ title: '请上传照片', icon: 'none' })
               return
            }
            uni.showLoading({ title: '上传中...' })
            const uploadTasks = this.uploadedPhotos.map(path => {
               return new Promise((resolve, reject) => {
                  uni.uploadFile({
                     url: this.$baseUrl + 'web/public/upload',
                     filePath: path,
                     name: 'file',
                     formData: {
                        folder: 'order'
                     },
                     success: (uploadRes) => {
                        const data = JSON.parse(uploadRes.data)
                        if (data.code === 200) {
                           resolve(data.data)
                        } else {
                           reject(new Error(data.msg))
                        }
                     },
                     fail: (err) => {
                        reject(err)
                     }
                  })
               })
            })
            Promise.all(uploadTasks).then(images => {
               const api = this.photoPopupMode === 'deliver' ? 'confirmDeliver' : 'confirmPickup'
               const params = {
                  images: images.map(img => img.imgaddr),
                  orderId: this.selectedOrder.id,
                  remark: this.photoRemark
               }
               return this.$u.api[api](params)
            }).then(res => {
               uni.hideLoading()
               if (res.code === 200) {
                  uni.showToast({ title: '提交成功', icon: 'success' })
                  this.showPhotoPopup = false
                  this.getOrderList()
                  this.getActiveOrderCount()
               } else {
                  uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
               }
            }).catch(err => {
               uni.hideLoading()
               uni.showToast({ title: err.message || '上传失败', icon: 'none' })
            })
         }
      }
@@ -472,8 +694,7 @@
      &__actions {
         display: flex;
         justify-content: flex-end;
         gap: 20rpx;
         justify-content: space-between;
         margin-top: 18rpx;
         padding-top: 18rpx;
         border-top: 1rpx solid #f0f2f6;
@@ -515,4 +736,204 @@
         }
      }
   }
.qrcode {
   padding: 30rpx 40rpx 50rpx;
   display: flex;
   flex-direction: column;
   align-items: center;
   &-title {
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 40rpx;
      image {
         width: 40rpx;
         height: 40rpx;
      }
      text {
         font-size: 34rpx;
         font-weight: 700;
         color: #2d3139;
      }
   }
   &-image {
      width: 400rpx;
      height: 400rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      background: #f5f7fb;
      border-radius: 16rpx;
      image {
         width: 360rpx;
         height: 360rpx;
      }
   }
   &-btn {
      width: 100%;
      height: 88rpx;
      line-height: 88rpx;
      text-align: center;
      background: #106EFA;
      border-radius: 44rpx;
      font-size: 32rpx;
      font-weight: 600;
      color: #ffffff;
      margin-top: 40rpx;
      &--hover {
         background: #0d5fc7;
      }
   }
}
.photo-popup {
   padding: 30rpx 30rpx calc(env(safe-area-inset-bottom) + 30rpx);
   &__header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 40rpx;
   }
   &__placeholder {
      width: 40rpx;
      height: 40rpx;
   }
   &__title {
      font-size: 34rpx;
      font-weight: 700;
      color: #2d3139;
   }
   &__close {
      width: 40rpx;
      height: 40rpx;
   }
   &__section {
      margin-bottom: 30rpx;
      &--remark {
         margin-top: 30rpx;
      }
   }
   &__label-row {
      display: flex;
      align-items: center;
      margin-bottom: 20rpx;
   }
   &__label {
      font-size: 30rpx;
      font-weight: 600;
      color: #2d3139;
   }
   &__required {
      color: #ff4030;
      margin-left: 8rpx;
   }
   &__hint {
      font-size: 24rpx;
      color: #8f96a3;
      margin-left: auto;
   }
   &__photos {
      display: flex;
      flex-wrap: wrap;
      gap: 20rpx;
   }
   &__preview-card {
      position: relative;
      width: 144rpx;
      height: 144rpx;
      border-radius: 12rpx;
      overflow: hidden;
   }
   &__preview-image {
      width: 100%;
      height: 100%;
   }
   &__preview-mask {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 56rpx;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
   }
   &__preview-delete {
      font-size: 26rpx;
      color: #ffffff;
   }
   &__upload-btn {
      width: 144rpx;
      height: 144rpx;
      display: flex;
      align-items: center;
      justify-content: center;
      image {
         width: 100%;
         height: 100%;
      }
   }
   &__remark-title {
      font-size: 30rpx;
      font-weight: 600;
      color: #2d3139;
      margin-bottom: 20rpx;
      display: block;
   }
   &__textarea {
      width: 100%;
      height: 160rpx;
      padding: 20rpx;
      background: #f5f7fb;
      border-radius: 12rpx;
      font-size: 28rpx;
      color: #2d3139;
      box-sizing: border-box;
   }
   &__submit {
      width: 100%;
      height: 88rpx;
      line-height: 88rpx;
      background: #2c7cff;
      border-radius: 44rpx;
      font-size: 32rpx;
      font-weight: 600;
      color: #ffffff;
      margin-top: 40rpx;
      &--hover {
         background: #2678e8;
      }
   }
}
</style>