wlzboy
2025-11-27 668e570bd1db6bd00e4293b6977e6d3d051053ce
app/pages/task/detail.vue
@@ -135,7 +135,7 @@
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.patientCondition">
          <view class="label">病情描述</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientCondition }}</view>
          <view class="value" style="white-space: pre-line;">{{ taskDetail.emergencyInfo.patientCondition }}</view>
        </view>
      </view>
      
@@ -189,39 +189,69 @@
          <view class="value">{{ taskDetail.emergencyInfo.transferDistance }}公里</view>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.transferPrice">
          <view class="label">转运费用</view>
          <view class="label">基础费用</view>
          <view class="value">¥{{ taskDetail.emergencyInfo.transferPrice }}</view>
        </view>
        <view class="info-item" v-if="paymentInfo">
          <view class="label">附加费用</view>
          <view class="value">¥{{ paymentInfo.additionalAmount || 0 }}</view>
        </view>
        <view class="info-item" v-if="paymentInfo">
          <view class="label">总费用</view>
          <view class="value" style="color: #e54d42; font-weight: bold;">¥{{ paymentInfo.totalAmount || 0 }}</view>
        </view>
        <view class="info-item" v-if="paymentInfo && paymentInfo.paidAmount > 0">
          <view class="label">已支付</view>
          <view class="value" style="color: #34C759;">¥{{ paymentInfo.paidAmount }}</view>
        </view>
        <view class="info-item" v-if="paymentInfo && paymentInfo.paidAmount > 0">
          <view class="label">剩余未付</view>
          <view class="value" style="color: #ff9900; font-weight: bold;">¥{{ getRemainingAmount() }}</view>
        </view>
      </view>
      <!-- 支付记录明细 -->
      <view class="detail-section" v-if="paymentInfo && paymentInfo.paidPayments && paymentInfo.paidPayments.length > 0">
        <view class="section-title">支付记录</view>
        <view
          class="payment-record-item"
          v-for="payment in paymentInfo.paidPayments"
          :key="payment.id"
        >
          <view class="payment-header">
            <view
              class="payment-method-tag"
              :class="[
                payment.paymentMethod === '1' ? 'method-cash' : '',
                payment.paymentMethod === '2' ? 'method-bank' : '',
                payment.paymentMethod === '3' ? 'method-wechat' : '',
                payment.paymentMethod === '4' ? 'method-alipay' : '',
                payment.paymentMethod === '5' ? 'method-pos' : '',
                payment.paymentMethod === '6' ? 'method-account' : '',
                payment.paymentMethod === '7' ? 'method-yyt' : ''
              ]"
            >
              {{ getPaymentMethodLabel(payment.paymentMethod) }}
            </view>
            <view class="payment-amount">¥{{ payment.settlementAmount }}</view>
          </view>
          <view class="payment-time" v-if="payment.payTime">
            {{ formatPaymentTime(payment.payTime) }}
          </view>
          <view class="payment-remark" v-if="payment.remark">
            备注:{{ payment.remark }}
          </view>
        </view>
      </view>
      
      <!-- 附件信息 -->
      <view class="detail-section">
        <view class="section-title">
          任务附件
          <button class="upload-btn" @click="showUploadDialog">上传附件</button>
        </view>
        <view v-if="attachmentList && attachmentList.length > 0">
          <view class="attachment-item" v-for="(item, index) in attachmentList" :key="item.attachmentId">
            <view class="attachment-info">
              <view class="attachment-category">
                <text class="category-tag">{{ getCategoryName(item.attachmentCategory) }}</text>
              </view>
              <view class="attachment-name">{{ item.fileName }}</view>
              <view class="attachment-meta">
                <text class="upload-time">{{ formatTime(item.uploadTime) }}</text>
                <text class="file-size">{{ formatFileSize(item.fileSize) }}</text>
              </view>
            </view>
            <view class="attachment-actions">
              <button class="action-btn view-btn" @click="viewAttachment(item)">查看</button>
              <button class="action-btn delete-btn" @click="deleteAttachment(item.attachmentId, index)">删除</button>
            </view>
          </view>
        </view>
        <view v-else class="no-attachment">
          <text>暂无附件</text>
        </view>
      </view>
      <AttachmentUpload
        :taskId="taskId"
        title="任务附件"
        :readonly="isTaskFinished"
        @uploaded="onAttachmentUploaded"
        @deleted="onAttachmentDeleted"
      />
      
      <!-- 福祉车任务特有信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
@@ -289,8 +319,14 @@
    
    <!-- 操作按钮区域 -->
    <view class="action-buttons" v-if="taskDetail">
      <!-- 待处理状态: 显示出发、取消 -->
      <!-- 待处理状态: 显示编辑、出发、取消 -->
      <template v-if="taskDetail.taskStatus === 'PENDING'">
        <button
          class="action-btn edit"
          @click="handleEdit"
        >
          修改
        </button>
        <button 
          class="action-btn primary" 
          @click="handleTaskAction('depart')"
@@ -305,8 +341,14 @@
        </button>
      </template>
      
      <!-- 出发中状态: 显示已到达、强制结束 -->
      <!-- 出发中状态: 显示编辑、已到达、强制结束 -->
      <template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
        <button
          class="action-btn edit"
          @click="handleEdit"
        >
          修改
        </button>
        <button 
          class="action-btn primary" 
          @click="handleTaskAction('arrive')"
@@ -321,8 +363,14 @@
        </button>
      </template>
      
      <!-- 已到达状态: 显示已返程 -->
      <!-- 已到达状态: 显示编辑、已返程 -->
      <template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
        <button
          class="action-btn edit"
          @click="handleEdit"
        >
          修改
        </button>
        <button 
          class="action-btn primary" 
          @click="handleTaskAction('return')"
@@ -331,8 +379,14 @@
        </button>
      </template>
      
      <!-- 返程中状态: 显示已完成 -->
      <!-- 返程中状态: 显示编辑、已完成 -->
      <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
        <button
          class="action-btn edit"
          @click="handleEdit"
        >
          修改
        </button>
        <button 
          class="action-btn primary" 
          @click="handleTaskAction('complete')"
@@ -341,71 +395,46 @@
        </button>
      </template>
      
      <!-- 已完成/已取消: 不显示按钮 -->
      <!-- 已完成/已取消: 不显示按钮,但如果是转运任务则显示结算按钮 -->
      <!-- 转运任务的结算按钮:仅完成/取消状态不显示 -->
      <button
        v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && !isTaskFinished"
        class="action-btn settlement"
        @click="handleSettlement"
      >
        结算
      </button>
    </view>
    <!-- 附件上传对话框 -->
    <uni-popup ref="uploadPopup" type="bottom">
      <view class="upload-dialog">
        <view class="dialog-header">
          <text class="dialog-title">上传附件</text>
          <uni-icons type="closeempty" size="24" @click="closeUploadDialog"></uni-icons>
        </view>
        <view class="dialog-content">
          <view class="form-item">
            <view class="form-label">附件分类</view>
            <picker @change="onCategoryChange" :value="selectedCategoryIndex" :range="categoryList" range-key="label">
              <view class="picker-value">
                {{ categoryList[selectedCategoryIndex].label }}
                <uni-icons type="arrowdown" size="16"></uni-icons>
              </view>
            </picker>
          </view>
          <view class="form-item">
            <view class="form-label">选择图片</view>
            <button class="choose-image-btn" @click="chooseImage">
              <uni-icons type="image" size="20"></uni-icons>
              <text>点击选择</text>
            </button>
          </view>
          <view class="preview-area" v-if="tempImagePath">
            <image :src="tempImagePath" mode="aspectFit" class="preview-image"></image>
          </view>
        </view>
        <view class="dialog-footer">
          <button class="cancel-btn" @click="closeUploadDialog">取消</button>
          <button class="confirm-btn" @click="confirmUpload" :disabled="!tempImagePath">确定上传</button>
        </view>
      </view>
    </uni-popup>
  </view>
</template>
<script>
  import { getTask, changeTaskStatus } from '@/api/task'
  import { getAttachmentList, uploadAttachmentFromWechat, deleteAttachment, getWechatAccessToken } from '@/api/task'
  import { checkVehicleActiveTasks } from '@/api/task'
  import { getPaymentInfo } from '@/api/payment'
  import { formatDateTime } from '@/utils/common'
  import AttachmentUpload from '@/components/AttachmentUpload.vue'
  
  export default {
    components: {
      AttachmentUpload
    },
    data() {
      return {
        taskDetail: null,
        taskId: null,
        attachmentList: [],
        categoryList: [
          { label: '知情同意书', value: '1' },
          { label: '病人资料', value: '2' },
          { label: '操作记录', value: '3' },
          { label: '出车前', value: '4' },
          { label: '出车后', value: '5' },
          { label: '系安全带', value: '6' }
        ],
        selectedCategoryIndex: 0,
        tempImagePath: null,
        isWechatMiniProgram: false // 是否是微信小程序环境
        paymentInfo: null // 支付信息
      }
    },
    computed: {
      // 判断任务是否已结束(已完成或已取消)
      isTaskFinished() {
        if (!this.taskDetail || !this.taskDetail.taskStatus) {
          return false
        }
        return ['COMPLETED', 'CANCELLED'].includes(this.taskDetail.taskStatus)
      },
      // 显示任务类型
      displayTaskType() {
        if (!this.taskDetail || !this.taskDetail.taskType) {
@@ -464,12 +493,12 @@
    onLoad(options) {
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadAttachmentList()
      // 检测是否是微信小程序环境
      // #ifdef MP-WEIXIN
      this.isWechatMiniProgram = true
      // #endif
    },
    onShow() {
      // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据
      if (this.taskId) {
        this.loadTaskDetail()
      }
    },
    methods: {
      // 加载任务详情
@@ -482,15 +511,73 @@
        getTask(this.taskId).then(response => {
          this.taskDetail = response.data || response
          // 调试:打印返回的数据
          // console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2))
          // console.log('任务类型字段值:', this.taskDetail.taskType)
          // console.log('任务状态字段值:', this.taskDetail.taskStatus)
          // console.log('出发地址:', this.taskDetail.departureAddress)
          // console.log('目的地址:', this.taskDetail.destinationAddress)
          console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2))
          console.log('任务类型字段值:', this.taskDetail.taskType)
          console.log('任务状态字段值:', this.taskDetail.taskStatus)
          console.log('出发地址:', this.taskDetail.departureAddress)
          console.log('目的地址:', this.taskDetail.destinationAddress)
          console.log('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo)
          // 如果是转运任务,加载支付信息
          if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') {
            this.loadPaymentInfo()
          }
        }).catch(error => {
          console.error('加载任务详情失败:', error)
          this.$modal.showToast('加载任务详情失败')
        })
      },
      // 加载支付信息
      loadPaymentInfo() {
        getPaymentInfo(this.taskId).then(res => {
          this.paymentInfo = res.data
          console.log('支付信息:', this.paymentInfo)
        }).catch(err => {
          console.error('加载支付信息失败', err)
        })
      },
      // 根据支付方式字典值获取显示标签
      getPaymentMethodLabel(dictValue) {
        const methodMap = {
          '1': '现金',
          '2': '银行转账',
          '3': '微信支付',
          '4': '支付宝',
          '5': 'POS收款',
          '6': '挂账',
          '7': '易医通挂账',
          '8': '退款',
          '9': '积分'
        }
        return methodMap[dictValue] || dictValue
      },
      // 格式化支付时间
      formatPaymentTime(time) {
        if (!time) return '未知'
        const date = new Date(time)
        const year = date.getFullYear()
        const month = String(date.getMonth() + 1).padStart(2, '0')
        const day = String(date.getDate()).padStart(2, '0')
        const hour = String(date.getHours()).padStart(2, '0')
        const minute = String(date.getMinutes()).padStart(2, '0')
        return `${year}-${month}-${day} ${hour}:${minute}`
      },
      // 计算剩余未付金额
      getRemainingAmount() {
        if (!this.paymentInfo) {
          return '0.00'
        }
        const total = parseFloat(this.paymentInfo.totalAmount || 0)
        const paid = parseFloat(this.paymentInfo.paidAmount || 0)
        const remaining = total - paid
        console.log('总金额:', total.toFixed(2))
        console.log('已付金额:', paid.toFixed(2))
        console.log('剩余未付金额:', remaining.toFixed(2))
        return remaining > 0 ? remaining.toFixed(2) : '0.00'
      },
      
      // 获取车辆信息
@@ -537,6 +624,41 @@
        uni.navigateBack()
      },
      
      // 处理编辑按钮
      handleEdit() {
        if (!this.taskDetail) {
          this.$modal.showToast('任务信息不存在')
          return
        }
        // 检查任务状态,已完成或已取消的任务不能编辑
        if (this.isTaskFinished) {
          this.$modal.showToast('已完成或已取消的任务不能编辑')
          return
        }
        const taskType = this.taskDetail.taskType
        const taskId = this.taskDetail.taskId
        // 根据任务类型跳转到不同的编辑页面
        if (taskType === 'EMERGENCY_TRANSFER') {
          // 转运任务:跳转到转运任务编辑页面
          uni.navigateTo({
            url: `/pages/task/edit-emergency?id=${taskId}`
          })
        } else if (taskType === 'WELFARE') {
          // 福祗车任务:跳转到福祗车编辑页面
          uni.navigateTo({
            url: `/pages/task/edit-welfare?id=${taskId}`
          })
        } else {
          // 其他任务:跳转到通用任务编辑页面
          uni.navigateTo({
            url: `/pages/task/edit?id=${taskId}`
          })
        }
      },
      // 获取状态文本
      getStatusText(status) {
        const statusMap = {
@@ -563,14 +685,19 @@
        return typeMap[type] || '未知类型'
      },
      
      // 处理结算
      handleSettlement() {
        uni.navigateTo({
          url: '/pages/task/settlement?taskId=' + this.taskId
        })
      },
      // 处理任务操作
      handleTaskAction(action) {
        switch (action) {
          case 'depart':
            // 出发 -> 状态变为出发中
            this.$modal.confirm('确定要出发吗?').then(() => {
              this.updateTaskStatus('DEPARTING', '任务已出发')
            }).catch(() => {});
            // 出发 -> 检查车辆是否有其他正在进行中的任务
            this.checkVehicleAndDepart();
            break;
            
          case 'cancel':
@@ -608,6 +735,82 @@
            }).catch(() => {});
            break;
        }
      },
      // 检查车辆状态并出发
      checkVehicleAndDepart() {
        // 获取任务车辆ID
        const vehicleId = this.getVehicleId();
        if (!vehicleId) {
          this.$modal.showToast('未找到任务车辆信息');
          return;
        }
        // 显示加载提示
        uni.showLoading({
          title: '检查车辆状态...'
        });
        checkVehicleActiveTasks(vehicleId).then(response => {
          uni.hideLoading();
          const activeTasks = response.data || [];
          // 过滤掉当前任务本身
          const otherActiveTasks = activeTasks.filter(task => task.taskId !== this.taskId);
          if (otherActiveTasks.length > 0) {
            // 车辆有其他正在进行中的任务
            const task = otherActiveTasks[0];
            const taskStatus = this.getStatusText(task.taskStatus);
            const message = `该车辆已有正在转运中的任务!
任务单号:${task.taskCode}
任务状态:${taskStatus}
请先完成当前任务后再出发新任务。`;
            uni.showModal({
              title: '提示',
              content: message,
              showCancel: false,
              confirmText: '我知道了'
            });
            return;
          }
          // 车辆没有其他正在进行中的任务,可以出发
          this.$modal.confirm('确定要出发吗?').then(() => {
            this.updateTaskStatus('DEPARTING', '任务已出发')
          }).catch(() => {});
        }).catch(error => {
          uni.hideLoading();
          console.error('检查车辆状态失败:', error);
          // 检查失败时,仍然允许出发
          this.$modal.confirm('检查车辆状态失败,是否继续出发?').then(() => {
            this.updateTaskStatus('DEPARTING', '任务已出发')
          }).catch(() => {});
        });
      },
      // 获取任务车辆ID
      getVehicleId() {
        if (!this.taskDetail) {
          return null;
        }
        console.log("taskDetail assignedVehicles",this.taskDetail.assignedVehicles);
        // 从车辆列表中获取第一个车辆的ID(后端返回的字段名是assignedVehicles)
        if (this.taskDetail.assignedVehicles && this.taskDetail.assignedVehicles.length > 0) {
          return this.taskDetail.assignedVehicles[0].vehicleId;
        }
        // 或者从单个车辆对象获取
        if (this.taskDetail.vehicleId) {
          return this.taskDetail.vehicleId;
        }
        return null;
      },
      
      // 更新任务状态
@@ -923,6 +1126,16 @@
        if (size < 1024) return size + 'B'
        if (size < 1024 * 1024) return (size / 1024).toFixed(2) + 'KB'
        return (size / 1024 / 1024).toFixed(2) + 'MB'
      },
      // 附件上传成功回调
      onAttachmentUploaded(response) {
        console.log('附件上传成功:', response)
      },
      // 附件删除成功回调
      onAttachmentDeleted(attachmentId) {
        console.log('附件删除成功:', attachmentId)
      }
    }
  }
@@ -1038,83 +1251,79 @@
        padding: 20rpx;
        border-radius: 10rpx;
      }
    }
    // 支付记录样式
    .payment-record-item {
      background-color: #f9f9f9;
      border-radius: 10rpx;
      padding: 20rpx;
      margin-bottom: 20rpx;
      
      .no-attachment {
        text-align: center;
        padding: 40rpx 0;
        color: #999;
        font-size: 28rpx;
      &:last-child {
        margin-bottom: 0;
      }
      
      .attachment-item {
      .payment-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 20rpx;
        margin-bottom: 15rpx;
        background-color: #f9f9f9;
        border-radius: 10rpx;
        
        &:last-child {
          margin-bottom: 0;
        }
        .attachment-info {
          flex: 1;
          margin-right: 20rpx;
        .payment-method-tag {
          display: inline-block;
          padding: 6rpx 16rpx;
          border-radius: 6rpx;
          font-size: 24rpx;
          color: white;
          font-weight: 500;
          
          .attachment-category {
            margin-bottom: 8rpx;
            .category-tag {
              display: inline-block;
              padding: 4rpx 12rpx;
              background-color: #007AFF;
              color: white;
              font-size: 22rpx;
              border-radius: 4rpx;
            }
          &.method-cash {
            background-color: #34C759;
          }
          
          .attachment-name {
            font-size: 28rpx;
            color: #333;
            margin-bottom: 8rpx;
            word-break: break-all;
          &.method-bank {
            background-color: #5AC8FA;
          }
          
          .attachment-meta {
            font-size: 24rpx;
            color: #999;
            .upload-time {
              margin-right: 20rpx;
            }
          &.method-wechat {
            background-color: #09BB07;
          }
          &.method-alipay {
            background-color: #1677FF;
          }
          &.method-pos {
            background-color: #FF9500;
          }
          &.method-account {
            background-color: #FF9500;
          }
          &.method-yyt {
            background-color: #AF52DE;
          }
        }
        
        .attachment-actions {
          display: flex;
          flex-direction: column;
          gap: 10rpx;
          .action-btn {
            padding: 8rpx 20rpx;
            font-size: 24rpx;
            border-radius: 6rpx;
            border: none;
            &.view-btn {
              background-color: #007AFF;
              color: white;
            }
            &.delete-btn {
              background-color: #ff3b30;
              color: white;
            }
          }
        .payment-amount {
          font-size: 32rpx;
          font-weight: bold;
          color: #34C759;
        }
      }
      .payment-time {
        font-size: 24rpx;
        color: #999;
        margin-bottom: 10rpx;
      }
      .payment-remark {
        font-size: 24rpx;
        color: #666;
        line-height: 1.5;
      }
    }
    
@@ -1151,6 +1360,11 @@
        background-color: #f0f0f0;
        color: #333;
        
        &.edit {
          background-color: #ff9500;
          color: white;
        }
        &.primary {
          background-color: #007AFF;
          color: white;
@@ -1161,108 +1375,17 @@
          color: white;
        }
        
        &.settlement {
          background-color: #34C759;
          color: white;
        }
        &:first-child {
          margin-left: 0;
        }
        
        &:last-child {
          margin-right: 0;
        }
      }
    }
    .upload-dialog {
      background-color: white;
      border-radius: 20rpx 20rpx 0 0;
      padding: 30rpx;
      .dialog-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 30rpx;
        .dialog-title {
          font-size: 32rpx;
          font-weight: bold;
          color: #333;
        }
      }
      .dialog-content {
        .form-item {
          margin-bottom: 30rpx;
          .form-label {
            font-size: 28rpx;
            color: #333;
            margin-bottom: 15rpx;
          }
          .picker-value {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20rpx;
            background-color: #f5f5f5;
            border-radius: 10rpx;
            font-size: 28rpx;
            color: #333;
          }
          .choose-image-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 30rpx;
            background-color: #f5f5f5;
            border-radius: 10rpx;
            border: 2rpx dashed #ccc;
            color: #666;
            font-size: 28rpx;
            text {
              margin-left: 10rpx;
            }
          }
        }
        .preview-area {
          margin-top: 20rpx;
          .preview-image {
            width: 100%;
            height: 400rpx;
            border-radius: 10rpx;
          }
        }
      }
      .dialog-footer {
        display: flex;
        gap: 20rpx;
        margin-top: 30rpx;
        button {
          flex: 1;
          height: 80rpx;
          border-radius: 10rpx;
          font-size: 30rpx;
          border: none;
        }
        .cancel-btn {
          background-color: #f5f5f5;
          color: #666;
        }
        .confirm-btn {
          background-color: #007AFF;
          color: white;
          &:disabled {
            background-color: #ccc;
          }
        }
      }
    }