wlzboy
2025-11-08 4dd78acfe298217ebc5dd247c5b45c6f33deea9b
app/pages/task/detail.vue
@@ -16,12 +16,12 @@
        </view>
        <view class="info-item">
          <view class="label">任务类型</view>
          <view class="value">{{ getTaskTypeText(taskDetail.taskType) }}</view>
          <view class="value">{{ displayTaskType }}</view>
        </view>
        <view class="info-item">
          <view class="label">任务状态</view>
          <view class="value status" :class="taskDetail.taskStatus === 'PENDING' ? 'pending' : taskDetail.taskStatus === 'DEPARTING' ? 'in_progress' : taskDetail.taskStatus === 'ARRIVED' ? 'in_progress' : taskDetail.taskStatus === 'RETURNING' ? 'in_progress' : taskDetail.taskStatus === 'IN_PROGRESS' ? 'in_progress' : taskDetail.taskStatus === 'COMPLETED' ? 'completed' : taskDetail.taskStatus === 'CANCELLED' ? 'cancelled' : ''">
            {{ getStatusText(taskDetail.taskStatus) }}
          <view class="value status" :class="statusClass">
            {{ displayTaskStatus }}
          </view>
        </view>
        <view class="info-item">
@@ -38,25 +38,25 @@
        <view class="section-title">时间信息</view>
        <view class="info-item">
          <view class="label">计划开始时间</view>
          <view class="value">{{ formatDateTime(taskDetail.plannedStartTime) }}</view>
          <view class="value">{{ displayPlannedStartTime }}</view>
        </view>
        <view class="info-item">
          <view class="label">计划结束时间</view>
          <view class="value">{{ formatDateTime(taskDetail.plannedEndTime) }}</view>
          <view class="value">{{ displayPlannedEndTime }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.actualStartTime">
          <view class="label">实际开始时间</view>
          <view class="value">{{ formatDateTime(taskDetail.actualStartTime) }}</view>
          <view class="value">{{ displayActualStartTime }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.actualEndTime">
          <view class="label">实际结束时间</view>
          <view class="value">{{ formatDateTime(taskDetail.actualEndTime) }}</view>
          <view class="value">{{ displayActualEndTime }}</view>
        </view>
      </view>
      
      <view class="detail-section">
        <view class="section-title">位置信息</view>
        <!-- 急救转运任务:显示转出/转入医院地址 -->
        <!-- 转运任务:显示转出/转入医院地址 -->
        <template v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
          <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutAddress">
            <view class="label">转出医院</view>
@@ -106,16 +106,20 @@
        <view class="description">{{ taskDetail.remark }}</view>
      </view>
      
      <!-- 急救转运任务特有信息 -->
      <!-- 转运任务特有信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
        <view class="section-title">患者信息</view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.patientName">
          <view class="label">患者姓名</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientName }}</view>
        <view class="info-item">
          <view class="label">联系人</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientContact || '未设置' }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.patientPhone">
        <view class="info-item">
          <view class="label">患者姓名</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientName || '未设置' }}</view>
        </view>
        <view class="info-item">
          <view class="label">联系电话</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientPhone }}</view>
          <view class="value">{{ taskDetail.emergencyInfo.patientPhone || '未设置' }}</view>
        </view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.patientGender">
          <view class="label">性别</view>
@@ -135,7 +139,7 @@
        </view>
      </view>
      
      <!-- 急救转运 - 转出医院信息 -->
      <!-- 转运 - 转出医院信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
        <view class="section-title">转出医院信息</view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutName">
@@ -156,7 +160,7 @@
        </view>
      </view>
      
      <!-- 急救转运 - 转入医院信息 -->
      <!-- 转运 - 转入医院信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
        <view class="section-title">转入医院信息</view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInName">
@@ -177,7 +181,7 @@
        </view>
      </view>
      
      <!-- 急救转运 - 费用信息 -->
      <!-- 转运 - 费用信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo">
        <view class="section-title">费用信息</view>
        <view class="info-item" v-if="taskDetail.emergencyInfo.transferDistance">
@@ -189,6 +193,14 @@
          <view class="value">¥{{ taskDetail.emergencyInfo.transferPrice }}</view>
        </view>
      </view>
      <!-- 附件信息 -->
      <AttachmentUpload
        :taskId="taskId"
        title="任务附件"
        @uploaded="onAttachmentUploaded"
        @deleted="onAttachmentDeleted"
      />
      
      <!-- 福祉车任务特有信息 -->
      <view class="detail-section" v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
@@ -315,12 +327,73 @@
<script>
  import { getTask, changeTaskStatus } from '@/api/task'
  import { formatDateTime } from '@/utils/common'
  import AttachmentUpload from '@/components/AttachmentUpload.vue'
  
  export default {
    components: {
      AttachmentUpload
    },
    data() {
      return {
        taskDetail: null,
        taskId: null
      }
    },
    computed: {
      // 显示任务类型
      displayTaskType() {
        if (!this.taskDetail || !this.taskDetail.taskType) {
          return '未设置'
        }
        return this.getTaskTypeText(this.taskDetail.taskType)
      },
      // 显示任务状态
      displayTaskStatus() {
        if (!this.taskDetail || !this.taskDetail.taskStatus) {
          return '未设置'
        }
        return this.getStatusText(this.taskDetail.taskStatus)
      },
      // 状态样式类
      statusClass() {
        if (!this.taskDetail || !this.taskDetail.taskStatus) {
          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'
        return ''
      },
      // 显示计划开始时间
      displayPlannedStartTime() {
        if (!this.taskDetail || !this.taskDetail.plannedStartTime) {
          return '未设置'
        }
        return formatDateTime(this.taskDetail.plannedStartTime, 'YYYY-MM-DD HH:mm')
      },
      // 显示计划结束时间
      displayPlannedEndTime() {
        if (!this.taskDetail || !this.taskDetail.plannedEndTime) {
          return '未设置'
        }
        return formatDateTime(this.taskDetail.plannedEndTime, 'YYYY-MM-DD HH:mm')
      },
      // 显示实际开始时间
      displayActualStartTime() {
        if (!this.taskDetail || !this.taskDetail.actualStartTime) {
          return '未设置'
        }
        return formatDateTime(this.taskDetail.actualStartTime, 'YYYY-MM-DD HH:mm')
      },
      // 显示实际结束时间
      displayActualEndTime() {
        if (!this.taskDetail || !this.taskDetail.actualEndTime) {
          return '未设置'
        }
        return formatDateTime(this.taskDetail.actualEndTime, 'YYYY-MM-DD HH:mm')
      }
    },
    onLoad(options) {
@@ -338,9 +411,11 @@
        getTask(this.taskId).then(response => {
          this.taskDetail = response.data || response
          // 调试:打印返回的数据
          console.log('任务详情数据:', this.taskDetail)
          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)
        }).catch(error => {
          console.error('加载任务详情失败:', error)
          this.$modal.showToast('加载任务详情失败')
@@ -374,7 +449,7 @@
      
      // 获取距离信息:根据任务类型返回不同字段
      getDistanceInfo(task) {
        // 急救转运:优先使用transferDistance
        // 转运:优先使用transferDistance
        if (task.taskType === 'EMERGENCY_TRANSFER' && task.emergencyInfo && task.emergencyInfo.transferDistance) {
          return task.emergencyInfo.transferDistance
        }
@@ -389,19 +464,6 @@
      // 返回上一页
      goBack() {
        uni.navigateBack()
      },
      // 格式化日期时间
      formatDateTime(dateTime) {
        if (!dateTime) return '未设置'
        const date = new Date(dateTime)
        return date.toLocaleString('zh-CN', {
          year: 'numeric',
          month: '2-digit',
          day: '2-digit',
          hour: '2-digit',
          minute: '2-digit'
        })
      },
      
      // 获取状态文本
@@ -424,7 +486,7 @@
          'MAINTENANCE': '维修保养',
          'FUEL': '加油',
          'OTHER': '其他',
          'EMERGENCY_TRANSFER': '急救转运',
          'EMERGENCY_TRANSFER': '转运任务',
          'WELFARE': '福祉车'
        }
        return typeMap[type] || '未知类型'
@@ -542,6 +604,264 @@
            })
          }
        })
      },
      // 加载附件列表
      loadAttachmentList() {
        if (!this.taskId) {
          return
        }
        getAttachmentList(this.taskId).then(response => {
          this.attachmentList = response.data || response || []
        }).catch(error => {
          console.error('加载附件列表失败:', error)
        })
      },
      // 显示上传对话框
      showUploadDialog() {
        this.selectedCategoryIndex = 0
        this.tempImagePath = null
        this.$refs.uploadPopup.open()
      },
      // 关闭上传对话框
      closeUploadDialog() {
        this.$refs.uploadPopup.close()
      },
      // 分类选择变化
      onCategoryChange(e) {
        this.selectedCategoryIndex = e.detail.value
      },
      // 选择图片
      chooseImage() {
        const that = this
        uni.chooseImage({
          count: 1,
          sizeType: ['compressed'],
          sourceType: ['album', 'camera'],
          success: function(res) {
            that.tempImagePath = res.tempFilePaths[0]
          },
          fail: function(err) {
            console.error('选择图片失败:', err)
            that.$modal.showToast('选择图片失败')
          }
        })
      },
      // 确认上传
      confirmUpload() {
        if (!this.tempImagePath) {
          this.$modal.showToast('请先选择图片')
          return
        }
        const that = this
        const category = this.categoryList[this.selectedCategoryIndex].value
        // 微信小程序环境:先获取AccessToken,再上传到微信服务器,最后提交mediaId到后端
        // #ifdef MP-WEIXIN
        if (this.isWechatMiniProgram) {
          uni.showLoading({
            title: '上传中...'
          })
          // 第一步:从后端获取AccessToken
          getWechatAccessToken().then(tokenResponse => {
            // 接口返回格式:{"msg":"token值","code":200}
            console.log('获取AccessToken成功:', tokenResponse)
            const accessToken = tokenResponse.msg || tokenResponse.data || tokenResponse
            if (!accessToken) {
              uni.hideLoading()
              that.$modal.showToast('获取AccessToken失败')
              console.error('获取AccessToken失败,响应数据:', tokenResponse)
              return
            }
            console.log('获取到AccessToken:', accessToken)
            // 第二步:上传到微信服务器
            const uploadUrl = `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${accessToken}&type=image`
            uni.uploadFile({
              url: uploadUrl,
              filePath: that.tempImagePath,
              name: 'media',
              success: function(res) {
                console.log('微信上传响应:', res)
                try {
                  const data = JSON.parse(res.data)
                  if (data.media_id) {
                    // 第三步:提交mediaId到后端
                    uploadAttachmentFromWechat(that.taskId, data.media_id, category).then(response => {
                      uni.hideLoading()
                      that.$modal.showToast('上传成功')
                      that.closeUploadDialog()
                      that.loadAttachmentList()
                    }).catch(error => {
                      uni.hideLoading()
                      console.error('提交mediaId失败:', error)
                      that.$modal.showToast('上传失败:' + (error.msg || '请重试'))
                    })
                  } else {
                    uni.hideLoading()
                    const errMsg = data.errmsg || '未知错误'
                    console.error('微信返回错误:', data)
                    that.$modal.showToast('微信上传失败:' + errMsg)
                  }
                } catch (e) {
                  uni.hideLoading()
                  console.error('解析微信响应失败:', e, res.data)
                  that.$modal.showToast('上传失败:响应解析错误')
                }
              },
              fail: function(err) {
                uni.hideLoading()
                console.error('上传到微信失败:', err)
                that.$modal.showToast('上传失败:' + (err.errMsg || '请检查网络'))
              }
            })
          }).catch(error => {
            uni.hideLoading()
            console.error('获取AccessToken失败:', error)
            that.$modal.showToast('获取AccessToken失败')
          })
          return
        }
        // #endif
        // 非微信小程序环境:直接上传到后端服务器
        uni.showLoading({
          title: '上传中...'
        })
        uni.uploadFile({
          url: that.$baseUrl + '/task/attachment/upload/' + that.taskId,
          filePath: that.tempImagePath,
          name: 'file',
          formData: {
            'category': category
          },
          header: {
            'Authorization': 'Bearer ' + uni.getStorageSync('token')
          },
          success: function(uploadRes) {
            uni.hideLoading()
            if (uploadRes.statusCode === 200) {
              const result = JSON.parse(uploadRes.data)
              if (result.code === 200) {
                that.$modal.showToast('上传成功')
                that.closeUploadDialog()
                that.loadAttachmentList()
              } else {
                that.$modal.showToast(result.msg || '上传失败')
              }
            } else {
              that.$modal.showToast('上传失败')
            }
          },
          fail: function(err) {
            uni.hideLoading()
            console.error('上传失败:', err)
            that.$modal.showToast('上传失败')
          }
        })
      },
      // 查看附件
      viewAttachment(item) {
        // 如果是图片,使用图片预览
        const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
        const fileExt = item.fileName.split('.').pop().toLowerCase()
        if (imageTypes.includes(fileExt)) {
          // 构建图片访问地址
          // 如果是filePath是完整路径,需要通过下载接口访问
          const imageUrl = this.$baseUrl + '/task/attachment/download/' + item.attachmentId
          // 微信小程序中预览图片
          // #ifdef MP-WEIXIN
          // 微信小程序需要先下载到本地再预览
          uni.showLoading({ title: '加载中...' })
          uni.downloadFile({
            url: imageUrl,
            success: function(res) {
              uni.hideLoading()
              if (res.statusCode === 200) {
                uni.previewImage({
                  urls: [res.tempFilePath],
                  current: res.tempFilePath
                })
              } else {
                uni.showToast({ title: '加载图片失败', icon: 'none' })
              }
            },
            fail: function() {
              uni.hideLoading()
              uni.showToast({ title: '下载失败', icon: 'none' })
            }
          })
          // #endif
          // 非微信小程序环境,直接预览
          // #ifndef MP-WEIXIN
          uni.previewImage({
            urls: [imageUrl],
            current: imageUrl
          })
          // #endif
        } else {
          this.$modal.showToast('仅支持预览图片')
        }
      },
      // 删除附件
      deleteAttachment(attachmentId, index) {
        const that = this
        this.$modal.confirm('确定要删除该附件吗?').then(() => {
          deleteAttachment(attachmentId).then(response => {
            that.$modal.showToast('删除成功')
            that.attachmentList.splice(index, 1)
          }).catch(error => {
            console.error('删除附件失败:', error)
            that.$modal.showToast('删除失败')
          })
        }).catch(() => {})
      },
      // 获取分类名称
      getCategoryName(category) {
        const item = this.categoryList.find(c => c.value === category)
        return item ? item.label : '未分类'
      },
      // 格式化时间
      formatTime(time) {
        if (!time) return ''
        return formatDateTime(time, 'YYYY-MM-DD HH:mm')
      },
      // 格式化文件大小
      formatFileSize(size) {
        if (!size) return '0B'
        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)
      }
    }
  }
@@ -597,6 +917,18 @@
        color: #333;
        border-bottom: 1rpx solid #f0f0f0;
        padding-bottom: 10rpx;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .upload-btn {
          font-size: 24rpx;
          padding: 8rpx 20rpx;
          background-color: #007AFF;
          color: white;
          border-radius: 8rpx;
          border: none;
        }
      }
      
      .info-item {