wlzboy
2026-04-01 c459808efab29dc1b8439fbb90556bdb16f4c88b
app/pagesTask/detail.vue
@@ -71,7 +71,7 @@
            </view>
            <!-- 当前登录人是该执行人且未就绪时显示就绪按钮 -->
            <view 
              v-if="showAssigneeReadyFeature() && isAssigneeSelf(assignee) && !isAssigneeReady(assignee) && taskDetail.taskStatus === 'PENDING'"
              v-if="showAssigneeReadyFeature() && isAssigneeSelf(assignee) && !isAssigneeReady(assignee) && taskDetail.taskStatus === TaskStatus.PENDING"
              class="assignee-ready-btn"
              :data-user-id="assignee.userId || assignee.oaUserId"
              :data-user-name="assignee.userName"
@@ -237,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>
@@ -265,7 +275,7 @@
      </view>
      
      <!-- 取消信息(仅在任务已取消且有取消原因时显示) -->
      <view class="detail-section" v-if="taskDetail.taskStatus === 'CANCELLED' && taskDetail.emergencyInfo && taskDetail.emergencyInfo.cancelReason">
      <view class="detail-section" v-if="taskDetail.taskStatus === TaskStatus.CANCELLED && taskDetail.emergencyInfo && taskDetail.emergencyInfo.cancelReason">
        <view class="section-title">取消信息</view>
        <view class="info-item">
          <view class="label">取消原因</view>
@@ -455,10 +465,13 @@
    
    <!-- 操作按钮区域 -->
    <view class="action-buttons" v-if="taskDetail">
      <!-- 待处理状态: 显示出发、取消、强制完成 -->
      <template v-if="taskDetail.taskStatus === 'PENDING'">
      <!-- 待处理状态:显示出发、取消、强制完成 -->
      <template v-if="taskDetail.taskStatus === TaskStatus.PENDING
      || taskDetail.taskStatus === TaskStatus.NOT_DEPARTED
      || taskDetail.taskStatus === TaskStatus.NOT_CONFIRMED
      || taskDetail.taskStatus === TaskStatus.PARTIALLY_CONFIRMED">
        <button 
          v-if="isCurrentUserAssignee()"
          v-if="canOperateTask()"
          class="action-btn primary" 
          @click="handleDepartAction()"
        >
@@ -471,17 +484,17 @@
          取消
        </button>
        <button 
          v-if="isCurrentUserAssignee() && showForceCompleteFeature()"
          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-else-if="taskDetail.taskStatus === TaskStatus.DEPARTING">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('arrive')"
@@ -492,14 +505,21 @@
            class="action-btn cancel" 
            @click="handleTaskAction('forceCancel')"
          >
            强制结束
            强制取消
          </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-else-if="taskDetail.taskStatus === TaskStatus.ARRIVED">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('return')"
@@ -509,15 +529,34 @@
        </template>
      </template>
      
      <!-- 返程中状态: 显示已完成 -->
      <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
        <template v-if="isCurrentUserAssignee()">
      <!-- 返程中状态:显示已完成 -->
      <template v-else-if="taskDetail.taskStatus === TaskStatus.RETURNING">
        <template v-if="canOperateTask()">
          <button 
            class="action-btn primary" 
            @click="handleTaskAction('complete')"
          >
            已完成
          </button>
        </template>
      </template>
      <!-- 处理中状态:显示强制完成、取消 -->
      <template v-else-if="taskDetail.taskStatus === 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>
      
@@ -536,12 +575,14 @@
</template>
<script>
  import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment } from '@/api/task'
  import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment, syncTaskStatus } from '@/api/task'
  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 { getStatusText as getTaskStatusText, getTaskTypeText as getTaskTypeTextUtil, TaskStatus } from '@/utils/TaskUtil'
  import AttachmentUpload from './components/AttachmentUpload.vue'
  import config from '@/config'
  
@@ -551,6 +592,7 @@
    },
    data() {
      return {
        TaskStatus, // 暴露 TaskStatus 给模板使用
        taskDetail: null,
        taskId: null,
        paymentInfo: null, // 支付信息
@@ -561,7 +603,9 @@
        forceCompleteForm: {
          actualStartTime: '',
          actualEndTime: ''
        }
        },
        hasInvoiceApplied: false, // 是否已申请发票
        invoiceStatus: null // 发票状态:0-待审核, 1-已通过, 2-已驳回
      }
    },
    computed: {
@@ -571,6 +615,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
      },
      
      // 生成执行人员角色标签的类名
@@ -614,10 +668,10 @@
          return ''
        }
        const status = this.taskDetail.taskStatus
        if (status === 'PENDING') return 'pending'
        if (['DEPARTING', 'ARRIVED', 'RETURNING', 'IN_PROGRESS'].includes(status)) return 'in_progress'
        if (status === 'COMPLETED') return 'completed'
        if (status === 'CANCELLED') return 'cancelled'
        if (status === TaskStatus.PENDING || status === TaskStatus.NOT_CONFIRMED || status === TaskStatus.NOT_DEPARTED || status === TaskStatus.PARTIALLY_CONFIRMED) return 'pending'
        if ([TaskStatus.DEPARTING, TaskStatus.ARRIVED, TaskStatus.RETURNING, TaskStatus.IN_PROGRESS].includes(status)) return 'in_progress'
        if (status === TaskStatus.COMPLETED) return 'completed'
        if (status === TaskStatus.CANCELLED) return 'cancelled'
        return ''
      },
      // 显示计划开始时间
@@ -673,6 +727,8 @@
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadCancelReasonDict() // 加载取消原因字典
      // 检查发票申请状态
      this.checkInvoiceStatus()
    },
    onShow() {
      // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据
@@ -844,28 +900,12 @@
      
      // 获取状态文本
      getStatusText(status) {
        const statusMap = {
          'PENDING': '待处理',
          'DEPARTING': '出发中',
          'ARRIVED': '已到达',
          'RETURNING': '返程中',
          'COMPLETED': '已完成',
          'CANCELLED': '已取消',
          'IN_PROGRESS': '处理中' // 兼容旧数据
        }
        return statusMap[status] || '未知'
        return getTaskStatusText(status)
      },
      
      // 获取任务类型文本
      getTaskTypeText(type) {
        const typeMap = {
          'MAINTENANCE': '维修保养',
          'FUEL': '加油',
          'OTHER': '其他',
          'EMERGENCY_TRANSFER': '转运任务',
          'WELFARE': '福祉车'
        }
        return typeMap[type] || '未知类型'
        return getTaskTypeTextUtil(type)
      },
      
      // 获取用户类型标签
@@ -889,9 +929,29 @@
          return
        }
        
        uni.navigateTo({
          url: '/pagesTask/settlement?taskId=' + this.taskId
        })
        // 检查任务编号是否以T2开头(未同步到旧系统)
        const serviceCode = this.taskDetail.showTaskCode;
        if (serviceCode && serviceCode.startsWith('T2')) {
          // 先同步再进入结算页
          uni.showLoading({ title: '同步中...' })
          syncTaskStatus(this.taskId).then(() => {
            uni.hideLoading()
            uni.navigateTo({
              url: '/pagesTask/settlement?taskId=' + this.taskId
            })
          }).catch((err) => {
            uni.hideLoading()
            // 同步失败不阻断结算,只记录日志
            console.warn('任务同步旧系统失败,不影响结算流程:', err)
            uni.navigateTo({
              url: '/pagesTask/settlement?taskId=' + this.taskId
            })
          })
        } else {
          uni.navigateTo({
            url: '/pagesTask/settlement?taskId=' + this.taskId
          })
        }
      },
      
      // 处理任务操作
@@ -914,7 +974,7 @@
            break;
            
          case 'forceCancel':
            // 强制结束 -> 显示取消原因选择对话框
            // 强制取消 -> 显示取消原因选择对话框
            this.showCancelReasonDialog();
            break;
            
@@ -1032,6 +1092,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}`
        });
      },
      
      // 更新任务状态
@@ -1501,6 +1600,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()
      },
      // 是否多人执行
@@ -2202,10 +2314,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;
@@ -2219,6 +2334,11 @@
        
        &.cancel {
          background-color: #ff3b30;
          color: white;
        }
        &.force-end {
          background-color: #ff6b22;
          color: white;
        }
        
@@ -2263,6 +2383,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 {