wlzboy
2025-12-25 ae478a3d5dab28dd598d39f27429e4a544b15ad2
app/pagesTask/detail.vue
@@ -249,6 +249,23 @@
        </view>
      </view>
      
      <!-- 取消信息(仅在任务已取消且有取消原因时显示) -->
      <view class="detail-section" v-if="taskDetail.taskStatus === 'CANCELLED' && taskDetail.emergencyInfo && taskDetail.emergencyInfo.cancelReason">
        <view class="section-title">取消信息</view>
        <view class="info-item">
          <view class="label">取消原因</view>
          <view class="value">{{ getCancelReasonLabel(taskDetail.emergencyInfo.cancelReason) }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.cancelBy">
          <view class="label">取消人</view>
          <view class="value">{{ taskDetail.emergencyInfo.cancelBy }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.cancelTime">
          <view class="label">取消时间</view>
          <view class="value">{{ formatTime(taskDetail.emergencyInfo.cancelTime) }}</view>
        </view>
      </view>
      <!-- 支付记录明细 -->
      <view class="detail-section" v-if="paymentInfo && paymentInfo.paidPayments && paymentInfo.paidPayments.length > 0">
        <view class="section-title">支付记录</view>
@@ -355,6 +372,26 @@
      <uni-icons type="spinner-cycle" size="40" color="#007AFF"></uni-icons>
      <text>加载中...</text>
    </view>
    <!-- 取消原因选择对话框 -->
    <uni-popup ref="cancelPopup" type="center" :is-mask-click="false">
      <view class="cancel-dialog">
        <view class="dialog-title">请选择取消原因</view>
        <picker mode="selector" :range="cancelReasonList" range-key="label" @change="selectCancelReason">
          <view class="reason-picker">
            <view class="picker-label">取消原因</view>
            <view class="picker-value">
              {{ selectedCancelReasonLabel }}
            </view>
            <uni-icons type="arrowright" size="16"></uni-icons>
          </view>
        </picker>
        <view class="dialog-buttons">
          <button class="cancel-btn" @click="closeCancelDialog">取消</button>
          <button class="confirm-btn" @click="confirmCancelTask">确定</button>
        </view>
      </view>
    </uni-popup>
    
    <!-- 操作按钮区域 -->
    <view class="action-buttons" v-if="taskDetail">
@@ -464,9 +501,10 @@
</template>
<script>
  import { getTask, changeTaskStatus, setAssigneeReady } from '@/api/task'
  import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment } from '@/api/task'
  import { checkVehicleActiveTasks } from '@/api/task'
  import { getPaymentInfo } from '@/api/payment'
  import { getDicts } from '@/api/dict'
  import { formatDateTime } from '@/utils/common'
  import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator'
  import AttachmentUpload from './components/AttachmentUpload.vue'
@@ -480,7 +518,10 @@
      return {
        taskDetail: null,
        taskId: null,
        paymentInfo: null // 支付信息
        paymentInfo: null, // 支付信息
        cancelReasonList: [], // 取消原因列表
        showCancelDialog: false, // 显示取消原因对话框
        selectedCancelReason: '' // 选中的取消原因
      }
    },
    computed: {
@@ -503,7 +544,17 @@
          }
          return [baseClass, roleClasses[userType] || '']
        }
      },      // 显示任务类型
      },
      // 获取选中的取消原因标签(用于弹窗显示)
      selectedCancelReasonLabel() {
        if (!this.selectedCancelReason || !this.cancelReasonList.length) {
          return '请选择'
        }
        const reason = this.cancelReasonList.find(r => r.value === this.selectedCancelReason)
        return reason ? reason.label : '请选择'
      },
      // 显示任务类型
      displayTaskType() {
        if (!this.taskDetail || !this.taskDetail.taskType) {
          return '未设置'
@@ -581,6 +632,7 @@
    onLoad(options) {
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadCancelReasonDict() // 加载取消原因字典
    },
    onShow() {
      // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据
@@ -810,10 +862,8 @@
            break;
            
          case 'cancel':
            // 取消 -> 二次确认后状态变为已取消
            this.$modal.confirm('确定要取消此任务吗?').then(() => {
              this.updateTaskStatus('CANCELLED', '任务已取消')
            }).catch(() => {});
            // 取消 -> 显示取消原因选择对话框
            this.showCancelReasonDialog();
            break;
            
          case 'arrive':
@@ -948,12 +998,63 @@
      
      // 更新任务状态
      updateTaskStatus(status, remark) {
        // 获取GPS位置信息
        this.getLocationAndUpdateStatus(status, remark)
        // 如果是完成状态,需要检查是否上传了知情同意书
        if (status === 'COMPLETED') {
          this.checkConsentAttachmentAndThen(status, remark);
        } else {
          // 获取GPS位置信息
          this.getLocationAndUpdateStatus(status, remark);
        }
      },
      // 检查知情同意书附件并更新状态
      async checkConsentAttachmentAndThen(status, remark) {
        try {
          uni.showLoading({
            title: '检查附件...'
          });
          // 注意:这里会被请求拦截器处理,code !== 200 时会 reject
          const response = await checkTaskConsentAttachment(this.taskId).catch(err => {
            // 拦截器 reject 的情况,返回一个默认对象
            console.log('请求被拦截器 reject,err:', err);
            return { code: -1, msg: '未上传知情同意书' };
          });
          uni.hideLoading();
          console.log('检查附件结果:', response);
          // 后台返回 code: 200 表示已上传,code: -1 表示未上传
          if (response && response.code === 200) {
            // 已上传知情同意书,继续更新状态
            console.log('已上传知情同意书,继续完成任务');
            this.getLocationAndUpdateStatus(status, remark);
          } else {
            // 未上传知情同意书或其他错误,阻止完成
            const message = (response && response.msg) || '任务未上传知情同意书,无法完成任务';
            console.log('未上传知情同意书,阻止完成');
            this.$modal.confirm(message + '。是否现在去上传?').then(() => {
              // 滚动到附件上传区域
              this.$nextTick(() => {
                uni.pageScrollTo({
                  scrollTop: 9999, // 滚动到底部
                  duration: 300
                });
              });
            }).catch(() => {});
          }
        } catch (error) {
          uni.hideLoading();
          console.error('检查附件异常:', error);
          // 如果检查失败(网络异常等),不允许完成任务
          this.$modal.showToast('检查附件状态失败,无法完成任务');
        }
      },
      
      // 获取位置信息并更新状态
      getLocationAndUpdateStatus(status, remark) {
      getLocationAndUpdateStatus(status, remark, cancelReason) {
        const that = this
        
        // 使用uni.getLocation获取GPS位置
@@ -980,6 +1081,11 @@
              heading: res.direction || res.heading
            }
            
            // 如果有取消原因,添加到请求数据中
            if (cancelReason) {
              statusData.cancelReason = cancelReason
            }
            changeTaskStatus(that.taskId, statusData).then(response => {
              that.$modal.showToast('状态更新成功')
              // 重新加载任务详情
@@ -997,6 +1103,11 @@
              const statusData = {
                taskStatus: status,
                remark: remark
              }
              // 如果有取消原因,添加到请求数据中
              if (cancelReason) {
                statusData.cancelReason = cancelReason
              }
              
              changeTaskStatus(that.taskId, statusData).then(response => {
@@ -1428,6 +1539,65 @@
        return 'payment-' + (key !== null && key !== undefined ? key : index);
      },
      
      // 加载取消原因字典
      loadCancelReasonDict() {
        // 从后端获取取消原因字典
        getDicts('task_cancel_reason').then(response => {
          if (response.code === 200 && response.data) {
            this.cancelReasonList = response.data.map(item => ({
              value: item.dictValue,
              label: item.dictLabel
            }))
          }
        }).catch(error => {
          console.error('加载取消原因字典失败:', error)
        })
      },
      // 显示取消原因对话框
      showCancelReasonDialog() {
        this.selectedCancelReason = ''
        this.$refs.cancelPopup.open()
      },
      // 确认取消任务
      confirmCancelTask() {
        if (!this.selectedCancelReason) {
          this.$modal.showToast('请选择取消原因')
          return
        }
        this.$refs.cancelPopup.close()
        // 调用更新状态方法,传递取消原因
        this.updateTaskStatusWithCancelReason('CANCELLED', '任务已取消', this.selectedCancelReason)
      },
      // 取消对话框关闭
      closeCancelDialog() {
        this.$refs.cancelPopup.close()
        this.selectedCancelReason = ''
      },
      // 选择取消原因
      selectCancelReason(e) {
        this.selectedCancelReason = e.detail.value
      },
      // 带取消原因的状态更新
      updateTaskStatusWithCancelReason(status, remark, cancelReason) {
        this.getLocationAndUpdateStatus(status, remark, cancelReason)
      },
      // 根据取消原因value获取label
      getCancelReasonLabel(value) {
        if (!value || !this.cancelReasonList.length) {
          return value || '未知'
        }
        const reason = this.cancelReasonList.find(r => r.value === value)
        return reason ? reason.label : value
      },
    }
  }
</script>
@@ -1825,5 +1995,67 @@
      margin-left: 10rpx;
      vertical-align: middle;
    }
    // 取消原因对话框样式
    .cancel-dialog {
      width: 600rpx;
      background-color: white;
      border-radius: 20rpx;
      padding: 40rpx;
      .dialog-title {
        font-size: 32rpx;
        font-weight: bold;
        text-align: center;
        margin-bottom: 30rpx;
        color: #333;
      }
      .reason-picker {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 20rpx 30rpx;
        background-color: #f5f5f5;
        border-radius: 10rpx;
        margin-bottom: 30rpx;
        .picker-label {
          font-size: 28rpx;
          color: #666;
        }
        .picker-value {
          flex: 1;
          text-align: right;
          font-size: 28rpx;
          color: #333;
          margin: 0 10rpx;
        }
      }
      .dialog-buttons {
        display: flex;
        gap: 20rpx;
        button {
          flex: 1;
          height: 80rpx;
          border-radius: 10rpx;
          font-size: 30rpx;
          border: none;
          &.cancel-btn {
            background-color: #f0f0f0;
            color: #666;
          }
          &.confirm-btn {
            background-color: #007AFF;
            color: white;
          }
        }
      }
    }
  }
</style>