wlzboy
2026-02-05 57e98ac3f59e9ca12d3fdbc6f89c9c0b1f86be4d
app/pagesTask/detail.vue
@@ -7,6 +7,7 @@
      <view class="title">任务详情</view>
      <view class="edit-btn" @click="handleEdit" v-if="taskDetail && !isTaskFinished">
        <uni-icons type="compose" size="20" color="#007AFF"></uni-icons>
        <text class="edit-text">修改</text>
      </view>
    </view>
    
@@ -236,7 +237,17 @@
      
      <!-- 转运 - 费用信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
        <view class="section-title">费用信息</view>
        <view class="section-title">
          费用信息
          <!-- 已完成且未申请发票时显示申请发票按钮 -->
          <button
            v-if="canApplyInvoice"
            class="apply-invoice-btn"
            @click="handleApplyInvoice"
          >
            <text class="cuIcon-form"></text> 申请发票
          </button>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.transferDistance">
          <view class="label">转运公里数</view>
          <view class="value">{{ taskDetail.emergencyInfo.transferDistance }}公里</view>
@@ -455,32 +466,32 @@
    <!-- 操作按钮区域 -->
    <view class="action-buttons" v-if="taskDetail">
      <!-- 待处理状态: 显示出发、取消、强制完成 -->
      <template v-if="taskDetail.taskStatus === 'PENDING'">
        <template v-if="isCurrentUserAssignee()">
          <button
            class="action-btn primary"
            @click="handleDepartAction()"
          >
            出发
          </button>
          <button
            class="action-btn cancel"
            @click="handleTaskAction('cancel')"
          >
            取消
          </button>
          <button
            class="action-btn force-complete"
            @click="showForceCompleteTimeDialog()"
          >
            强制完成
          </button>
        </template>
      <template v-if="taskDetail.taskStatus === 'PENDING' ">
        <button
          v-if="canOperateTask()"
          class="action-btn primary"
          @click="handleDepartAction()"
        >
          出发
        </button>
        <button
          class="action-btn cancel"
          @click="handleTaskAction('cancel')"
        >
          取消
        </button>
        <button
          v-if="canOperateTask() && showForceCompleteFeature()"
          class="action-btn force-complete"
          @click="showForceCompleteTimeDialog()"
        >
          强制完成
        </button>
      </template>
      
      <!-- 出发中状态: 显示已到达、强制结束 -->
      <!-- 出发中状态: 显示已到达、强制结束、强制完成 -->
      <template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
        <template v-if="isCurrentUserAssignee()">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('arrive')"
@@ -493,12 +504,19 @@
          >
            强制结束
          </button>
          <button
            v-if="showForceCompleteFeature()"
            class="action-btn force-complete"
            @click="showForceCompleteTimeDialog()"
          >
            强制完成
          </button>
        </template>
      </template>
      
      <!-- 已到达状态: 显示已返程 -->
      <template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
        <template v-if="isCurrentUserAssignee()">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('return')"
@@ -510,13 +528,32 @@
      
      <!-- 返程中状态: 显示已完成 -->
      <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
        <template v-if="isCurrentUserAssignee()">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('complete')"
          >
            已完成
          </button>
        </template>
      </template>
      <!-- 处理中状态: 显示强制完成、取消 -->
      <template v-else-if="taskDetail.taskStatus === 'IN_PROGRESS'">
        <template v-if="canOperateTask()">
           <button
            class="action-btn primary"
            @click="handleTaskAction('arrive')"
          >
            已到达
          </button>
          <button
            v-if="showForceCompleteFeature()"
            class="action-btn force-complete"
            @click="showForceCompleteTimeDialog()"
          >
            强制完成
          </button>
        </template>
      </template>
      
@@ -539,6 +576,7 @@
  import { checkVehicleActiveTasks } from '@/api/task'
  import { getPaymentInfo } from '@/api/payment'
  import { getDicts } from '@/api/dict'
  import { checkTaskInvoice } from '@/api/invoice'
  import { formatDateTime } from '@/utils/common'
  import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator'
  import AttachmentUpload from './components/AttachmentUpload.vue'
@@ -560,7 +598,9 @@
        forceCompleteForm: {
          actualStartTime: '',
          actualEndTime: ''
        }
        },
        hasInvoiceApplied: false, // 是否已申请发票
        invoiceStatus: null // 发票状态:0-待审核, 1-已通过, 2-已驳回
      }
    },
    computed: {
@@ -570,6 +610,16 @@
          return false
        }
        return ['COMPLETED', 'CANCELLED'].includes(this.taskDetail.taskStatus)
      },
      // 是否可以申请发票
      canApplyInvoice() {
        // 仅急救转运任务
        if (this.taskDetail?.taskType !== 'EMERGENCY_TRANSFER') return false
        // 任务必须已完成
        if (this.taskDetail?.taskStatus !== 'COMPLETED') return false
        // 未申请过发票,或曾被驳回
        return !this.hasInvoiceApplied || this.invoiceStatus === 2
      },
      
      // 生成执行人员角色标签的类名
@@ -672,6 +722,8 @@
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadCancelReasonDict() // 加载取消原因字典
      // 检查发票申请状态
      this.checkInvoiceStatus()
    },
    onShow() {
      // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据
@@ -913,10 +965,8 @@
            break;
            
          case 'forceCancel':
            // 强制结束 -> 状态变为已取消
            this.$modal.confirm('确定要强制结束此任务吗?').then(() => {
              this.updateTaskStatus('CANCELLED', '任务已强制结束')
            }).catch(() => {});
            // 强制结束 -> 显示取消原因选择对话框
            this.showCancelReasonDialog();
            break;
            
          case 'return':
@@ -1033,6 +1083,45 @@
        }
        
        return null;
      },
      // 检查发票申请状态
      checkInvoiceStatus() {
        if (!this.taskId) return;
        // 调用后端接口检查该任务是否已申请发票
        checkTaskInvoice(this.taskId).then(response => {
          if (response.code === 200 && response.data) {
            this.hasInvoiceApplied = true;
            this.invoiceStatus = response.data.status;
          }
        }).catch(error => {
          console.error('检查发票申请状态失败:', error);
          // 忽略错误,默认未申请
        });
      },
      // 申请发票
      handleApplyInvoice() {
        // 准备任务信息
        const taskInfo = {
          taskId: this.taskDetail.taskId,
          taskCode: this.taskDetail.showTaskCode || this.taskDetail.taskCode,
          legacyServiceOrderId: this.taskDetail.emergencyInfo?.legacyServiceOrdId,
          serviceCode: this.taskDetail.emergencyInfo?.serviceCode,
          departure: this.taskDetail.departureAddress,
          destination: this.taskDetail.destinationAddress,
          completionTime: this.formatTime(this.taskDetail.actualEndTime),
          transferPrice: this.paymentInfo?.transferPrice || this.paymentInfo?.totalAmount
        };
        // 将任务信息序列化为 URL 参数
        const taskInfoParam = encodeURIComponent(JSON.stringify(taskInfo));
        // 跳转到发票申请页面,传递任务信息
        uni.navigateTo({
          url: `/pages/mine/invoice/apply?taskInfo=${taskInfoParam}`
        });
      },
      
      // 更新任务状态
@@ -1421,9 +1510,14 @@
        console.log('附件删除成功:', attachmentId)
      },
      // 是否显示“就绪”功能(配置开关)
      // 是否显示"就绪"功能(配置开关)
      showAssigneeReadyFeature() {
        return !!(config && config.features && config.features.showAssigneeReadyButton)
      },
      // 是否显示"强制完成"功能(配置开关)
      showForceCompleteFeature() {
        return !!(config && config.features && config.features.showForceCompleteButton)
      },
      // 当前用户是否为该执行人
@@ -1497,6 +1591,19 @@
        console.log("当前用户ID:", userId)
        const list = (this.taskDetail && Array.isArray(this.taskDetail.assignees)) ? this.taskDetail.assignees : []
        return list.some(a => a && (a.userId === userId || a.oaUserId === userId))
      },
      // 是否当前用户可以操作任务(执行人或管理员)
      canOperateTask() {
        // 检查是否是管理员(canViewAllConsult === '1')
        const canViewAllConsult = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.canViewAllConsult
        console.log("当前用户是否是管理员:", canViewAllConsult)
        if (canViewAllConsult === '1') {
          return true
        }
        // 检查是否是任务执行人
        return this.isCurrentUserAssignee()
      },
      // 是否多人执行
@@ -1871,12 +1978,18 @@
      }
      
      .edit-btn {
        width: 60rpx;
        width: 120rpx;
        height: 60rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        .edit-text {
          margin-left: 8rpx;
          font-size: 28rpx;
          color: #007AFF;
        }
      }
    }
    
@@ -2192,10 +2305,13 @@
        flex: 1;
        height: 80rpx;
        border-radius: 10rpx;
        font-size: 30rpx;
        font-size: 28rpx;
        margin: 0 10rpx;
        background-color: #f0f0f0;
        color: #333;
        white-space: nowrap;
        padding: 0 10rpx;
        min-width: 0;
        
        &.edit {
          background-color: #ff9500;
@@ -2209,6 +2325,11 @@
        
        &.cancel {
          background-color: #ff3b30;
          color: white;
        }
        &.force-end {
          background-color: #ff6b22;
          color: white;
        }
        
@@ -2253,6 +2374,20 @@
      margin-left: 10rpx;
      vertical-align: middle;
    }
    .apply-invoice-btn {
      padding: 8rpx 16rpx;
      font-size: 24rpx;
      color: #fff;
      background-color: #34C759;
      border: none;
      border-radius: 6rpx;
      margin-left: 20rpx;
    }
    .apply-invoice-btn::after {
      border: none;
    }
    
    // 取消原因对话框样式
    .cancel-dialog {