<template>
|
<view class="task-detail-container">
|
<view class="detail-header">
|
<view class="back-btn" @click="goBack">
|
<uni-icons type="arrowleft" size="20"></uni-icons>
|
</view>
|
<view class="title">任务详情</view>
|
</view>
|
|
<scroll-view class="detail-content" scroll-y="true" v-if="taskDetail">
|
<view class="detail-section">
|
<view class="section-title">基本信息</view>
|
<view class="info-item">
|
<view class="label">任务编号</view>
|
<view class="value">{{ taskDetail.taskCode }}</view>
|
</view>
|
<view class="info-item">
|
<view class="label">任务类型</view>
|
<view class="value">{{ displayTaskType }}</view>
|
</view>
|
<view class="info-item">
|
<view class="label">任务状态</view>
|
<view class="value status" :class="statusClass">
|
{{ displayTaskStatus }}
|
</view>
|
</view>
|
<view class="info-item">
|
<view class="label">执行车辆</view>
|
<view class="value">{{ getVehicleInfo(taskDetail) }}</view>
|
</view>
|
<view class="info-item">
|
<view class="label">执行人员</view>
|
<view class="value">{{ taskDetail.assigneeName || '未分配' }}</view>
|
</view>
|
</view>
|
|
<view class="detail-section">
|
<view class="section-title">时间信息</view>
|
<view class="info-item">
|
<view class="label">计划开始时间</view>
|
<view class="value">{{ displayPlannedStartTime }}</view>
|
</view>
|
<view class="info-item">
|
<view class="label">计划结束时间</view>
|
<view class="value">{{ displayPlannedEndTime }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.actualStartTime">
|
<view class="label">实际开始时间</view>
|
<view class="value">{{ displayActualStartTime }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.actualEndTime">
|
<view class="label">实际结束时间</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>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalOutAddress }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInAddress">
|
<view class="label">转入医院</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalInAddress }}</view>
|
</view>
|
</template>
|
<!-- 福祉车任务:显示接送/目的地址 -->
|
<template v-else-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
|
<view class="info-item" v-if="taskDetail.welfareInfo.pickupAddress">
|
<view class="label">接送地址</view>
|
<view class="value">{{ taskDetail.welfareInfo.pickupAddress }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.destinationAddress">
|
<view class="label">目的地址</view>
|
<view class="value">{{ taskDetail.welfareInfo.destinationAddress }}</view>
|
</view>
|
</template>
|
<!-- 其他任务类型:显示通用地址 -->
|
<template v-else>
|
<view class="info-item">
|
<view class="label">出发地</view>
|
<view class="value">{{ getDepartureAddress(taskDetail) }}</view>
|
</view>
|
<view class="info-item">
|
<view class="label">目的地</view>
|
<view class="value">{{ getDestinationAddress(taskDetail) }}</view>
|
</view>
|
</template>
|
<!-- 距离信息:根据任务类型显示不同字段 -->
|
<view class="info-item" v-if="getDistanceInfo(taskDetail)">
|
<view class="label">距离</view>
|
<view class="value">{{ getDistanceInfo(taskDetail) }}公里</view>
|
</view>
|
</view>
|
|
<view class="detail-section" v-if="taskDetail.taskDescription">
|
<view class="section-title">任务描述</view>
|
<view class="description">{{ taskDetail.taskDescription }}</view>
|
</view>
|
|
<view class="detail-section" v-if="taskDetail.remark">
|
<view class="section-title">备注信息</view>
|
<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">
|
<view class="label">联系人</view>
|
<view class="value">{{ taskDetail.emergencyInfo.patientContact || '未设置' }}</view>
|
</view>
|
<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>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.patientGender">
|
<view class="label">性别</view>
|
<view class="value">{{ taskDetail.emergencyInfo.patientGender === 'male' ? '男' : '女' }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.patientAge">
|
<view class="label">年龄</view>
|
<view class="value">{{ taskDetail.emergencyInfo.patientAge }}岁</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.patientIdCard">
|
<view class="label">身份证号</view>
|
<view class="value">{{ taskDetail.emergencyInfo.patientIdCard }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.patientCondition">
|
<view class="label">病情描述</view>
|
<view class="value">{{ taskDetail.emergencyInfo.patientCondition }}</view>
|
</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">
|
<view class="label">医院名称</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalOutName }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutDepartment">
|
<view class="label">科室</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalOutDepartment }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutBedNumber">
|
<view class="label">床号</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalOutBedNumber }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalOutAddress">
|
<view class="label">医院地址</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalOutAddress }}</view>
|
</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">
|
<view class="label">医院名称</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalInName }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInDepartment">
|
<view class="label">科室</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalInDepartment }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInBedNumber">
|
<view class="label">床号</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalInBedNumber }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInAddress">
|
<view class="label">医院地址</view>
|
<view class="value">{{ taskDetail.emergencyInfo.hospitalInAddress }}</view>
|
</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">
|
<view class="label">转运公里数</view>
|
<view class="value">{{ taskDetail.emergencyInfo.transferDistance }}公里</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.emergencyInfo.transferPrice">
|
<view class="label">转运费用</view>
|
<view class="value">¥{{ taskDetail.emergencyInfo.transferPrice }}</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>
|
|
<!-- 福祉车任务特有信息 -->
|
<view class="detail-section" v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
|
<view class="section-title">乘客信息</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerContact">
|
<view class="label">联系人</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerContact }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerPhone">
|
<view class="label">联系电话</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerPhone }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerName">
|
<view class="label">乘客姓名</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerName }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerAge">
|
<view class="label">年龄</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerAge }}岁</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerGender">
|
<view class="label">性别</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerGender === 'male' ? '男' : '女' }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.passengerIdCard">
|
<view class="label">身份证号</view>
|
<view class="value">{{ taskDetail.welfareInfo.passengerIdCard }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.specialNeeds">
|
<view class="label">特殊需求</view>
|
<view class="value">{{ taskDetail.welfareInfo.specialNeeds }}</view>
|
</view>
|
</view>
|
|
<!-- 福祉车 - 服务信息 -->
|
<view class="detail-section" v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
|
<view class="section-title">服务信息</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.serviceType">
|
<view class="label">服务类型</view>
|
<view class="value">{{ taskDetail.welfareInfo.serviceType }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.pickupAddress">
|
<view class="label">接送地址</view>
|
<view class="value">{{ taskDetail.welfareInfo.pickupAddress }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.destinationAddress">
|
<view class="label">目的地址</view>
|
<view class="value">{{ taskDetail.welfareInfo.destinationAddress }}</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.serviceDistance">
|
<view class="label">服务公里数</view>
|
<view class="value">{{ taskDetail.welfareInfo.serviceDistance }}公里</view>
|
</view>
|
<view class="info-item" v-if="taskDetail.welfareInfo.servicePrice">
|
<view class="label">服务费用</view>
|
<view class="value">¥{{ taskDetail.welfareInfo.servicePrice }}</view>
|
</view>
|
</view>
|
</scroll-view>
|
|
<view class="loading" v-else>
|
<uni-icons type="spinner-cycle" size="40" color="#007AFF"></uni-icons>
|
<text>加载中...</text>
|
</view>
|
|
<!-- 操作按钮区域 -->
|
<view class="action-buttons" v-if="taskDetail">
|
<!-- 待处理状态: 显示出发、取消 -->
|
<template v-if="taskDetail.taskStatus === 'PENDING'">
|
<button
|
class="action-btn primary"
|
@click="handleTaskAction('depart')"
|
>
|
出发
|
</button>
|
<button
|
class="action-btn cancel"
|
@click="handleTaskAction('cancel')"
|
>
|
取消
|
</button>
|
</template>
|
|
<!-- 出发中状态: 显示已到达、强制结束 -->
|
<template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
|
<button
|
class="action-btn primary"
|
@click="handleTaskAction('arrive')"
|
>
|
已到达
|
</button>
|
<button
|
class="action-btn cancel"
|
@click="handleTaskAction('forceCancel')"
|
>
|
强制结束
|
</button>
|
</template>
|
|
<!-- 已到达状态: 显示已返程 -->
|
<template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
|
<button
|
class="action-btn primary"
|
@click="handleTaskAction('return')"
|
>
|
已返程
|
</button>
|
</template>
|
|
<!-- 返程中状态: 显示已完成 -->
|
<template v-else-if="taskDetail.taskStatus === 'RETURNING'">
|
<button
|
class="action-btn primary"
|
@click="handleTaskAction('complete')"
|
>
|
已完成
|
</button>
|
</template>
|
|
<!-- 已完成/已取消: 不显示按钮 -->
|
</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 { formatDateTime } from '@/utils/common'
|
|
export default {
|
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 // 是否是微信小程序环境
|
}
|
},
|
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) {
|
this.taskId = options.id
|
this.loadTaskDetail()
|
this.loadAttachmentList()
|
|
// 检测是否是微信小程序环境
|
// #ifdef MP-WEIXIN
|
this.isWechatMiniProgram = true
|
// #endif
|
},
|
methods: {
|
// 加载任务详情
|
loadTaskDetail() {
|
if (!this.taskId) {
|
this.$modal.showToast('任务ID不能为空')
|
return
|
}
|
|
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)
|
}).catch(error => {
|
console.error('加载任务详情失败:', error)
|
this.$modal.showToast('加载任务详情失败')
|
})
|
},
|
|
// 获取车辆信息
|
getVehicleInfo(task) {
|
if (task.assignedVehicles && task.assignedVehicles.length > 0) {
|
const firstVehicle = task.assignedVehicles[0]
|
let vehicleInfo = firstVehicle.vehicleNo || '未知车牌'
|
if (task.assignedVehicles.length > 1) {
|
vehicleInfo += ` 等${task.assignedVehicles.length}辆`
|
}
|
return vehicleInfo
|
}
|
return '未分配车辆'
|
},
|
|
// 获取出发地址
|
getDepartureAddress(task) {
|
// 优先使用departureAddress,如果为空则尝试其他可能的字段
|
return task.departureAddress || task.startAddress || task.startLocation || '未设置'
|
},
|
|
// 获取目的地址
|
getDestinationAddress(task) {
|
// 优先使用destinationAddress,如果为空则尝试其他可能的字段
|
return task.destinationAddress || task.endAddress || task.endLocation || '未设置'
|
},
|
|
// 获取距离信息:根据任务类型返回不同字段
|
getDistanceInfo(task) {
|
// 转运:优先使用transferDistance
|
if (task.taskType === 'EMERGENCY_TRANSFER' && task.emergencyInfo && task.emergencyInfo.transferDistance) {
|
return task.emergencyInfo.transferDistance
|
}
|
// 福祉车:优先使用serviceDistance
|
if (task.taskType === 'WELFARE' && task.welfareInfo && task.welfareInfo.serviceDistance) {
|
return task.welfareInfo.serviceDistance
|
}
|
// 其他任务类型:使用estimatedDistance
|
return task.estimatedDistance || null
|
},
|
|
// 返回上一页
|
goBack() {
|
uni.navigateBack()
|
},
|
|
// 获取状态文本
|
getStatusText(status) {
|
const statusMap = {
|
'PENDING': '待处理',
|
'DEPARTING': '出发中',
|
'ARRIVED': '已到达',
|
'RETURNING': '返程中',
|
'COMPLETED': '已完成',
|
'CANCELLED': '已取消',
|
'IN_PROGRESS': '处理中' // 兼容旧数据
|
}
|
return statusMap[status] || '未知'
|
},
|
|
// 获取任务类型文本
|
getTaskTypeText(type) {
|
const typeMap = {
|
'MAINTENANCE': '维修保养',
|
'FUEL': '加油',
|
'OTHER': '其他',
|
'EMERGENCY_TRANSFER': '转运任务',
|
'WELFARE': '福祉车'
|
}
|
return typeMap[type] || '未知类型'
|
},
|
|
// 处理任务操作
|
handleTaskAction(action) {
|
switch (action) {
|
case 'depart':
|
// 出发 -> 状态变为出发中
|
this.$modal.confirm('确定要出发吗?').then(() => {
|
this.updateTaskStatus('DEPARTING', '任务已出发')
|
}).catch(() => {});
|
break;
|
|
case 'cancel':
|
// 取消 -> 二次确认后状态变为已取消
|
this.$modal.confirm('确定要取消此任务吗?').then(() => {
|
this.updateTaskStatus('CANCELLED', '任务已取消')
|
}).catch(() => {});
|
break;
|
|
case 'arrive':
|
// 已到达 -> 状态变为已到达
|
this.$modal.confirm('确认已到达目的地?').then(() => {
|
this.updateTaskStatus('ARRIVED', '已到达目的地')
|
}).catch(() => {});
|
break;
|
|
case 'forceCancel':
|
// 强制结束 -> 状态变为已取消
|
this.$modal.confirm('确定要强制结束此任务吗?').then(() => {
|
this.updateTaskStatus('CANCELLED', '任务已强制结束')
|
}).catch(() => {});
|
break;
|
|
case 'return':
|
// 已返程 -> 状态变为返程中
|
this.$modal.confirm('确认开始返程?').then(() => {
|
this.updateTaskStatus('RETURNING', '已开始返程')
|
}).catch(() => {});
|
break;
|
|
case 'complete':
|
// 已完成 -> 状态变为已完成
|
this.$modal.confirm('确认任务已完成?').then(() => {
|
this.updateTaskStatus('COMPLETED', '任务已完成')
|
}).catch(() => {});
|
break;
|
}
|
},
|
|
// 更新任务状态
|
updateTaskStatus(status, remark) {
|
// 获取GPS位置信息
|
this.getLocationAndUpdateStatus(status, remark)
|
},
|
|
// 获取位置信息并更新状态
|
getLocationAndUpdateStatus(status, remark) {
|
const that = this
|
|
// 使用uni.getLocation获取GPS位置
|
uni.getLocation({
|
type: 'gcj02', // 返回可以用于uni.openLocation的坐标
|
geocode: true, // 解析地址信息
|
altitude: true, // 高精度定位,包含高度信息
|
success: function(res) {
|
console.log('GPS定位成功:', res)
|
|
const statusData = {
|
taskStatus: status,
|
remark: remark,
|
// GPS位置信息
|
latitude: res.latitude,
|
longitude: res.longitude,
|
locationAddress: res.address ? res.address.street || res.address.poiName || '' : '',
|
locationProvince: res.address ? res.address.province || '' : '',
|
locationCity: res.address ? res.address.city || '' : '',
|
locationDistrict: res.address ? res.address.district || '' : '',
|
gpsAccuracy: res.accuracy,
|
altitude: res.altitude,
|
speed: res.speed,
|
heading: res.direction || res.heading
|
}
|
|
changeTaskStatus(that.taskId, statusData).then(response => {
|
that.$modal.showToast('状态更新成功')
|
// 重新加载任务详情
|
that.loadTaskDetail()
|
}).catch(error => {
|
console.error('更新任务状态失败:', error)
|
that.$modal.showToast('状态更新失败,请重试')
|
})
|
},
|
fail: function(err) {
|
console.error('GPS定位失败:', err)
|
|
// 定位失败时提示用户,但仍然允许更新状态(不带GPS信息)
|
that.$modal.confirm('GPS定位失败,是否继续更新状态?').then(() => {
|
const statusData = {
|
taskStatus: status,
|
remark: remark
|
}
|
|
changeTaskStatus(that.taskId, statusData).then(response => {
|
that.$modal.showToast('状态更新成功')
|
that.loadTaskDetail()
|
}).catch(error => {
|
console.error('更新任务状态失败:', error)
|
that.$modal.showToast('状态更新失败,请重试')
|
})
|
}).catch(() => {
|
// 用户取消操作
|
})
|
}
|
})
|
},
|
|
// 加载附件列表
|
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 => {
|
const accessToken = tokenResponse.data || tokenResponse
|
if (!accessToken) {
|
uni.hideLoading()
|
that.$modal.showToast('获取AccessToken失败')
|
return
|
}
|
|
// 第二步:上传到微信服务器
|
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'
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
.task-detail-container {
|
background-color: #f5f5f5;
|
min-height: 100vh;
|
|
.detail-header {
|
display: flex;
|
align-items: center;
|
padding: 20rpx;
|
background-color: white;
|
border-bottom: 1rpx solid #f0f0f0;
|
|
.back-btn {
|
width: 60rpx;
|
height: 60rpx;
|
border-radius: 50%;
|
background-color: #f0f0f0;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 20rpx;
|
}
|
|
.title {
|
font-size: 36rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
|
.detail-content {
|
padding: 20rpx;
|
height: calc(100vh - 220rpx); // 减去header(100rpx)和按钮区域(120rpx)的高度
|
padding-bottom: 20rpx; // 底部留出空间
|
}
|
|
.detail-section {
|
background-color: white;
|
border-radius: 15rpx;
|
padding: 30rpx;
|
margin-bottom: 20rpx;
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
.section-title {
|
font-size: 32rpx;
|
font-weight: bold;
|
margin-bottom: 20rpx;
|
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 {
|
display: flex;
|
margin-bottom: 20rpx;
|
|
&:last-child {
|
margin-bottom: 0;
|
}
|
|
.label {
|
width: 200rpx;
|
font-size: 28rpx;
|
color: #666;
|
flex-shrink: 0;
|
}
|
|
.value {
|
flex: 1;
|
font-size: 28rpx;
|
color: #333;
|
word-break: break-all;
|
|
&.status {
|
&.pending {
|
color: #ff9500;
|
}
|
&.in_progress {
|
color: #007AFF;
|
}
|
&.completed {
|
color: #34C759;
|
}
|
&.cancelled {
|
color: #ff3b30;
|
}
|
}
|
}
|
}
|
|
.description {
|
font-size: 28rpx;
|
color: #333;
|
line-height: 1.6;
|
background-color: #f9f9f9;
|
padding: 20rpx;
|
border-radius: 10rpx;
|
}
|
|
.no-attachment {
|
text-align: center;
|
padding: 40rpx 0;
|
color: #999;
|
font-size: 28rpx;
|
}
|
|
.attachment-item {
|
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;
|
|
.attachment-category {
|
margin-bottom: 8rpx;
|
|
.category-tag {
|
display: inline-block;
|
padding: 4rpx 12rpx;
|
background-color: #007AFF;
|
color: white;
|
font-size: 22rpx;
|
border-radius: 4rpx;
|
}
|
}
|
|
.attachment-name {
|
font-size: 28rpx;
|
color: #333;
|
margin-bottom: 8rpx;
|
word-break: break-all;
|
}
|
|
.attachment-meta {
|
font-size: 24rpx;
|
color: #999;
|
|
.upload-time {
|
margin-right: 20rpx;
|
}
|
}
|
}
|
|
.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;
|
}
|
}
|
}
|
}
|
}
|
|
.loading {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
height: 400rpx;
|
color: #999;
|
|
text {
|
margin-top: 20rpx;
|
font-size: 28rpx;
|
}
|
}
|
|
.action-buttons {
|
position: fixed;
|
bottom: 0;
|
left: 0;
|
right: 0;
|
display: flex;
|
padding: 20rpx;
|
background-color: white;
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
.action-btn {
|
flex: 1;
|
height: 80rpx;
|
border-radius: 10rpx;
|
font-size: 30rpx;
|
margin: 0 10rpx;
|
background-color: #f0f0f0;
|
color: #333;
|
|
&.primary {
|
background-color: #007AFF;
|
color: white;
|
}
|
|
&.cancel {
|
background-color: #ff3b30;
|
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;
|
}
|
}
|
}
|
}
|
}
|
</style>
|