| | |
| | | <view class="section-title">基本信息</view> |
| | | <view class="info-item"> |
| | | <view class="label">任务编号</view> |
| | | <view class="value">{{ taskDetail.showTaskCode }}</view> |
| | | <view class="value"> |
| | | {{ taskDetail.showTaskCode }} |
| | | <text v-if="taskDetail.emergencyInfo && taskDetail.emergencyInfo.serviceOrdVip === '1'" class="vip-tag">VIP</text> |
| | | <text v-if="taskDetail.emergencyInfo && taskDetail.emergencyInfo.fromHq2Is === '1'" class="hq-tag">广总</text> |
| | | </view> |
| | | </view> |
| | | <view class="info-item"> |
| | | <view class="label">任务类型</view> |
| | |
| | | <view class="assignee-role"> |
| | | <view |
| | | class="role-tag" |
| | | :class="{ |
| | | 'role-driver': assignee.userType === 'driver', |
| | | 'role-doctor': assignee.userType === 'doctor', |
| | | 'role-nurse': assignee.userType === 'nurse' |
| | | }" |
| | | > |
| | | :class="{'role-driver': assignee.userType === 'driver','role-doctor': assignee.userType === 'doctor','role-nurse': assignee.userType === 'nurse'}"> |
| | | {{ getUserTypeLabel(assignee.userType) }} |
| | | </view> |
| | | <view |
| | | class="ready-badge" |
| | | :class="{ |
| | | 'ready': isAssigneeReady(assignee), |
| | | 'unready': !isAssigneeReady(assignee) |
| | | }" |
| | | > |
| | | :class="{'ready': isAssigneeReady(assignee),'unready': !isAssigneeReady(assignee)}"> |
| | | {{ isAssigneeReady(assignee) ? '已就绪' : '未就绪' }} |
| | | </view> |
| | | </view> |
| | |
| | | <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="info-item" v-if="taskDetail.emergencyInfo.hospitalOutName"> |
| | | <view class="label">转出医院</view> |
| | | <view class="value">{{ taskDetail.emergencyInfo.hospitalOutAddress }}</view> |
| | | <view class="value">{{ taskDetail.emergencyInfo.hospitalOutName }}</view> |
| | | </view> |
| | | <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInAddress"> |
| | | <view class="info-item" v-if="taskDetail.emergencyInfo.hospitalInName"> |
| | | <view class="label">转入医院</view> |
| | | <view class="value">{{ taskDetail.emergencyInfo.hospitalInAddress }}</view> |
| | | <view class="value">{{ taskDetail.emergencyInfo.hospitalInName }}</view> |
| | | </view> |
| | | </template> |
| | | <!-- 福祉车任务:显示接送/目的地址 --> |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 取消信息(仅在任务已取消且有取消原因时显示) --> |
| | | <view class="detail-section" v-if="taskDetail.taskStatus === 'CANCELLED' && taskDetail.emergencyInfo && taskDetail.emergencyInfo.cancelReason"> |
| | | <view class="section-title">取消信息</view> |
| | | <view class="info-item"> |
| | | <view class="label">取消原因</view> |
| | | <view class="value">{{ getCancelReasonLabel(taskDetail.emergencyInfo.cancelReason) }}</view> |
| | | </view> |
| | | <view class="info-item" v-if="taskDetail.emergencyInfo.cancelBy"> |
| | | <view class="label">取消人</view> |
| | | <view class="value">{{ taskDetail.emergencyInfo.cancelBy }}</view> |
| | | </view> |
| | | <view class="info-item" v-if="taskDetail.emergencyInfo.cancelTime"> |
| | | <view class="label">取消时间</view> |
| | | <view class="value">{{ formatTime(taskDetail.emergencyInfo.cancelTime) }}</view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 支付记录明细 --> |
| | | <view class="detail-section" v-if="paymentInfo && paymentInfo.paidPayments && paymentInfo.paidPayments.length > 0"> |
| | | <view class="section-title">支付记录</view> |
| | |
| | | <uni-icons type="spinner-cycle" size="40" color="#007AFF"></uni-icons> |
| | | <text>加载中...</text> |
| | | </view> |
| | | |
| | | <!-- 取消原因选择对话框 --> |
| | | <uni-popup ref="cancelPopup" type="center" :is-mask-click="false"> |
| | | <view class="cancel-dialog"> |
| | | <view class="dialog-title">请选择取消原因</view> |
| | | <picker mode="selector" :range="cancelReasonList" range-key="label" @change="selectCancelReason"> |
| | | <view class="reason-picker"> |
| | | <view class="picker-label">取消原因</view> |
| | | <view class="picker-value"> |
| | | {{ selectedCancelReasonLabel }} |
| | | </view> |
| | | <uni-icons type="arrowright" size="16"></uni-icons> |
| | | </view> |
| | | </picker> |
| | | <view class="dialog-buttons"> |
| | | <button class="cancel-btn" @click="closeCancelDialog">取消</button> |
| | | <button class="confirm-btn" @click="confirmCancelTask">确定</button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 操作按钮区域 --> |
| | | <view class="action-buttons" v-if="taskDetail"> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getTask, changeTaskStatus, setAssigneeReady } from '@/api/task' |
| | | import { getTask, changeTaskStatus, setAssigneeReady, checkTaskConsentAttachment } from '@/api/task' |
| | | import { checkVehicleActiveTasks } from '@/api/task' |
| | | import { getPaymentInfo } from '@/api/payment' |
| | | import { getDicts } from '@/api/dict' |
| | | import { formatDateTime } from '@/utils/common' |
| | | import { validateTaskForDepart, validateTaskForSettlement, getTaskVehicleId, checkTaskCanDepart } from '@/utils/taskValidator' |
| | | import AttachmentUpload from './components/AttachmentUpload.vue' |
| | |
| | | return { |
| | | taskDetail: null, |
| | | taskId: null, |
| | | paymentInfo: null // 支付信息 |
| | | paymentInfo: null, // 支付信息 |
| | | cancelReasonList: [], // 取消原因列表 |
| | | showCancelDialog: false, // 显示取消原因对话框 |
| | | selectedCancelReason: '' // 选中的取消原因 |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | return false |
| | | } |
| | | return ['COMPLETED', 'CANCELLED'].includes(this.taskDetail.taskStatus) |
| | | }, |
| | | |
| | | // 生成执行人员角色标签的类名 |
| | | getRoleTagClass() { |
| | | return (userType) => { |
| | | const baseClass = 'role-tag' |
| | | const roleClasses = { |
| | | 'driver': 'role-driver', |
| | | 'doctor': 'role-doctor', |
| | | 'nurse': 'role-nurse' |
| | | } |
| | | return [baseClass, roleClasses[userType] || ''] |
| | | } |
| | | }, |
| | | |
| | | // 获取选中的取消原因标签(用于弹窗显示) |
| | | selectedCancelReasonLabel() { |
| | | if (!this.selectedCancelReason || !this.cancelReasonList.length) { |
| | | return '请选择' |
| | | } |
| | | const reason = this.cancelReasonList.find(r => r.value === this.selectedCancelReason) |
| | | return reason ? reason.label : '请选择' |
| | | }, |
| | | // 显示任务类型 |
| | | displayTaskType() { |
| | |
| | | return '未设置' |
| | | } |
| | | const formatted = formatDateTime(this.taskDetail.plannedStartTime, 'YYYY-MM-DD HH:mm') |
| | | // 如果年份是1900,表示无效日期,显示为未设置 |
| | | if (formatted && formatted.startsWith('1900')) { |
| | | return '未设置' |
| | | // 如果年份是1900或1970,表示无效日期,显示为未分配时间 |
| | | if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) { |
| | | return '未分配时间' |
| | | } |
| | | return formatted |
| | | }, |
| | |
| | | return '未设置' |
| | | } |
| | | const formatted = formatDateTime(this.taskDetail.plannedEndTime, 'YYYY-MM-DD HH:mm') |
| | | // 如果年份是1900,表示无效日期,显示为未设置 |
| | | if (formatted && formatted.startsWith('1900')) { |
| | | return '未设置' |
| | | // 如果年份是1900或1970,表示无效日期,显示为未分配时间 |
| | | if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) { |
| | | return '未分配时间' |
| | | } |
| | | return formatted |
| | | }, |
| | |
| | | return '未设置' |
| | | } |
| | | const formatted = formatDateTime(this.taskDetail.actualStartTime, 'YYYY-MM-DD HH:mm') |
| | | // 如果年份是1900,表示无效日期,显示为未设置 |
| | | if (formatted && formatted.startsWith('1900')) { |
| | | return '未设置' |
| | | // 如果年份是1900或1970,表示无效日期,显示为未分配时间 |
| | | if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) { |
| | | return '未分配时间' |
| | | } |
| | | return formatted |
| | | }, |
| | |
| | | return '未设置' |
| | | } |
| | | const formatted = formatDateTime(this.taskDetail.actualEndTime, 'YYYY-MM-DD HH:mm') |
| | | // 如果年份是1900,表示无效日期,显示为未设置 |
| | | if (formatted && formatted.startsWith('1900')) { |
| | | return '未设置' |
| | | // 如果年份是1900或1970,表示无效日期,显示为未分配时间 |
| | | if (formatted && (formatted.startsWith('1900') || formatted.startsWith('1970'))) { |
| | | return '未分配时间' |
| | | } |
| | | return formatted |
| | | } |
| | |
| | | onLoad(options) { |
| | | this.taskId = options.id |
| | | this.loadTaskDetail() |
| | | this.loadCancelReasonDict() // 加载取消原因字典 |
| | | }, |
| | | onShow() { |
| | | // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据 |
| | |
| | | |
| | | 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('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo) |
| | | |
| | | |
| | | // 如果是转运任务,加载支付信息 |
| | | if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') { |
| | |
| | | |
| | | // 返回上一页 |
| | | goBack() { |
| | | uni.navigateBack() |
| | | // 检查是否有页面可以返回 |
| | | uni.navigateBack({ |
| | | delta: 1, |
| | | fail: () => { |
| | | // 如果无法返回,则跳转到任务列表页面 |
| | | uni.switchTab({ |
| | | url: '/pages/task/index' |
| | | }) |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | // 处理编辑按钮 |
| | |
| | | break; |
| | | |
| | | case 'cancel': |
| | | // 取消 -> 二次确认后状态变为已取消 |
| | | this.$modal.confirm('确定要取消此任务吗?').then(() => { |
| | | this.updateTaskStatus('CANCELLED', '任务已取消') |
| | | }).catch(() => {}); |
| | | // 取消 -> 显示取消原因选择对话框 |
| | | this.showCancelReasonDialog(); |
| | | break; |
| | | |
| | | case 'arrive': |
| | |
| | | |
| | | // 更新任务状态 |
| | | updateTaskStatus(status, remark) { |
| | | // 获取GPS位置信息 |
| | | this.getLocationAndUpdateStatus(status, remark) |
| | | // 如果是完成状态,需要检查是否上传了知情同意书 |
| | | if (status === 'COMPLETED') { |
| | | this.checkConsentAttachmentAndThen(status, remark); |
| | | } else { |
| | | // 获取GPS位置信息 |
| | | this.getLocationAndUpdateStatus(status, remark); |
| | | } |
| | | }, |
| | | |
| | | // 检查知情同意书附件并更新状态 |
| | | async checkConsentAttachmentAndThen(status, remark) { |
| | | try { |
| | | uni.showLoading({ |
| | | title: '检查附件...' |
| | | }); |
| | | |
| | | const response = await checkTaskConsentAttachment(this.taskId); |
| | | |
| | | uni.hideLoading(); |
| | | |
| | | if (response.code === 200) { |
| | | // 已上传知情同意书,继续更新状态 |
| | | this.getLocationAndUpdateStatus(status, remark); |
| | | } else { |
| | | // 未上传知情同意书,显示提示 |
| | | this.$modal.confirm('任务未上传知情同意书,无法完成任务。是否现在去上传?').then(() => { |
| | | // 滚动到附件上传区域 |
| | | this.$nextTick(() => { |
| | | uni.pageScrollTo({ |
| | | scrollTop: 9999, // 滚动到底部 |
| | | duration: 300 |
| | | }); |
| | | }); |
| | | }).catch(() => {}); |
| | | } |
| | | } catch (error) { |
| | | uni.hideLoading(); |
| | | console.error('检查附件失败:', error); |
| | | |
| | | // 如果检查失败,询问用户是否继续 |
| | | this.$modal.confirm('检查附件状态失败,是否继续完成任务?').then(() => { |
| | | this.getLocationAndUpdateStatus(status, remark); |
| | | }).catch(() => {}); |
| | | } |
| | | }, |
| | | |
| | | // 获取位置信息并更新状态 |
| | | getLocationAndUpdateStatus(status, remark) { |
| | | getLocationAndUpdateStatus(status, remark, cancelReason) { |
| | | const that = this |
| | | |
| | | // 使用uni.getLocation获取GPS位置 |
| | |
| | | heading: res.direction || res.heading |
| | | } |
| | | |
| | | // 如果有取消原因,添加到请求数据中 |
| | | if (cancelReason) { |
| | | statusData.cancelReason = cancelReason |
| | | } |
| | | |
| | | changeTaskStatus(that.taskId, statusData).then(response => { |
| | | that.$modal.showToast('状态更新成功') |
| | | // 重新加载任务详情 |
| | |
| | | const statusData = { |
| | | taskStatus: status, |
| | | remark: remark |
| | | } |
| | | |
| | | // 如果有取消原因,添加到请求数据中 |
| | | if (cancelReason) { |
| | | statusData.cancelReason = cancelReason |
| | | } |
| | | |
| | | changeTaskStatus(that.taskId, statusData).then(response => { |
| | |
| | | return 'payment-' + (key !== null && key !== undefined ? key : index); |
| | | }, |
| | | |
| | | // 加载取消原因字典 |
| | | loadCancelReasonDict() { |
| | | // 从后端获取取消原因字典 |
| | | getDicts('task_cancel_reason').then(response => { |
| | | if (response.code === 200 && response.data) { |
| | | this.cancelReasonList = response.data.map(item => ({ |
| | | value: item.dictValue, |
| | | label: item.dictLabel |
| | | })) |
| | | } |
| | | }).catch(error => { |
| | | console.error('加载取消原因字典失败:', error) |
| | | }) |
| | | }, |
| | | |
| | | // 显示取消原因对话框 |
| | | showCancelReasonDialog() { |
| | | this.selectedCancelReason = '' |
| | | this.$refs.cancelPopup.open() |
| | | }, |
| | | |
| | | // 确认取消任务 |
| | | confirmCancelTask() { |
| | | if (!this.selectedCancelReason) { |
| | | this.$modal.showToast('请选择取消原因') |
| | | return |
| | | } |
| | | |
| | | this.$refs.cancelPopup.close() |
| | | |
| | | // 调用更新状态方法,传递取消原因 |
| | | this.updateTaskStatusWithCancelReason('CANCELLED', '任务已取消', this.selectedCancelReason) |
| | | }, |
| | | |
| | | // 取消对话框关闭 |
| | | closeCancelDialog() { |
| | | this.$refs.cancelPopup.close() |
| | | this.selectedCancelReason = '' |
| | | }, |
| | | |
| | | // 选择取消原因 |
| | | selectCancelReason(e) { |
| | | this.selectedCancelReason = e.detail.value |
| | | }, |
| | | |
| | | // 带取消原因的状态更新 |
| | | updateTaskStatusWithCancelReason(status, remark, cancelReason) { |
| | | this.getLocationAndUpdateStatus(status, remark, cancelReason) |
| | | }, |
| | | |
| | | // 根据取消原因value获取label |
| | | getCancelReasonLabel(value) { |
| | | if (!value || !this.cancelReasonList.length) { |
| | | return value || '未知' |
| | | } |
| | | const reason = this.cancelReasonList.find(r => r.value === value) |
| | | return reason ? reason.label : value |
| | | }, |
| | | |
| | | } |
| | | } |
| | | </script> |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | .vip-tag { |
| | | display: inline-block; |
| | | padding: 2rpx 8rpx; |
| | | font-size: 20rpx; |
| | | color: #fff; |
| | | background-color: #ff0000; |
| | | border-radius: 4rpx; |
| | | margin-left: 10rpx; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .hq-tag { |
| | | display: inline-block; |
| | | padding: 2rpx 8rpx; |
| | | font-size: 20rpx; |
| | | color: #fff; |
| | | background-color: #5856d6; |
| | | border-radius: 4rpx; |
| | | margin-left: 10rpx; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | // 取消原因对话框样式 |
| | | .cancel-dialog { |
| | | width: 600rpx; |
| | | background-color: white; |
| | | border-radius: 20rpx; |
| | | padding: 40rpx; |
| | | |
| | | .dialog-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | text-align: center; |
| | | margin-bottom: 30rpx; |
| | | color: #333; |
| | | } |
| | | |
| | | .reason-picker { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 20rpx 30rpx; |
| | | background-color: #f5f5f5; |
| | | border-radius: 10rpx; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .picker-label { |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .picker-value { |
| | | flex: 1; |
| | | text-align: right; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | margin: 0 10rpx; |
| | | } |
| | | } |
| | | |
| | | .dialog-buttons { |
| | | display: flex; |
| | | gap: 20rpx; |
| | | |
| | | button { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | border-radius: 10rpx; |
| | | font-size: 30rpx; |
| | | border: none; |
| | | |
| | | &.cancel-btn { |
| | | background-color: #f0f0f0; |
| | | color: #666; |
| | | } |
| | | |
| | | &.confirm-btn { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |