| | |
| | | <view class="title">任务详情</view> |
| | | <view class="edit-btn" @click="handleEdit" v-if="taskDetail && !isTaskFinished"> |
| | | <uni-icons type="compose" size="20" color="#007AFF"></uni-icons> |
| | | <text class="edit-text">修改</text> |
| | | </view> |
| | | </view> |
| | | |
| | |
| | | </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" |
| | |
| | | |
| | | <!-- 转运 - 费用信息 --> |
| | | <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> |
| | |
| | | </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> |
| | |
| | | |
| | | <!-- 操作按钮区域 --> |
| | | <view class="action-buttons" v-if="taskDetail"> |
| | | <!-- 待处理状态: 显示出发、取消、强制完成 --> |
| | | <template v-if="taskDetail.taskStatus === 'PENDING'"> |
| | | <template v-if="isCurrentUserAssignee()"> |
| | | <button |
| | | class="action-btn primary" |
| | | @click="handleDepartAction()" |
| | | > |
| | | 出发 |
| | | </button> |
| | | <button |
| | | class="action-btn cancel" |
| | | @click="handleTaskAction('cancel')" |
| | | > |
| | | 取消 |
| | | </button> |
| | | <button |
| | | class="action-btn force-complete" |
| | | @click="showForceCompleteTimeDialog()" |
| | | > |
| | | 强制完成 |
| | | </button> |
| | | </template> |
| | | <!-- 待处理状态:显示出发、取消、强制完成 --> |
| | | <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="canOperateTask()" |
| | | class="action-btn primary" |
| | | @click="handleDepartAction()" |
| | | > |
| | | 出发 |
| | | </button> |
| | | <button |
| | | class="action-btn cancel" |
| | | @click="handleTaskAction('cancel')" |
| | | > |
| | | 取消 |
| | | </button> |
| | | <button |
| | | 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')" |
| | |
| | | > |
| | | 强制结束 |
| | | </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')" |
| | |
| | | </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> |
| | | |
| | |
| | | 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' |
| | | |
| | |
| | | }, |
| | | data() { |
| | | return { |
| | | TaskStatus, // 暴露 TaskStatus 给模板使用 |
| | | taskDetail: null, |
| | | taskId: null, |
| | | paymentInfo: null, // 支付信息 |
| | |
| | | forceCompleteForm: { |
| | | actualStartTime: '', |
| | | actualEndTime: '' |
| | | } |
| | | }, |
| | | hasInvoiceApplied: false, // 是否已申请发票 |
| | | invoiceStatus: null // 发票状态:0-待审核, 1-已通过, 2-已驳回 |
| | | } |
| | | }, |
| | | computed: { |
| | |
| | | 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 |
| | | }, |
| | | |
| | | // 生成执行人员角色标签的类名 |
| | |
| | | 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 '' |
| | | }, |
| | | // 显示计划开始时间 |
| | |
| | | this.taskId = options.id |
| | | this.loadTaskDetail() |
| | | this.loadCancelReasonDict() // 加载取消原因字典 |
| | | // 检查发票申请状态 |
| | | this.checkInvoiceStatus() |
| | | }, |
| | | onShow() { |
| | | // 每次页面显示时重新加载数据,确保从编辑页面返回后能看到最新数据 |
| | |
| | | |
| | | // 获取状态文本 |
| | | 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) |
| | | }, |
| | | |
| | | // 获取用户类型标签 |
| | |
| | | break; |
| | | |
| | | case 'forceCancel': |
| | | // 强制结束 -> 状态变为已取消 |
| | | this.$modal.confirm('确定要强制结束此任务吗?').then(() => { |
| | | this.updateTaskStatus('CANCELLED', '任务已强制结束') |
| | | }).catch(() => {}); |
| | | // 强制结束 -> 显示取消原因选择对话框 |
| | | this.showCancelReasonDialog(); |
| | | break; |
| | | |
| | | case 'return': |
| | |
| | | } |
| | | |
| | | 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}` |
| | | }); |
| | | }, |
| | | |
| | | // 更新任务状态 |
| | |
| | | console.log('附件删除成功:', attachmentId) |
| | | }, |
| | | |
| | | // 是否显示“就绪”功能(配置开关) |
| | | // 是否显示"就绪"功能(配置开关) |
| | | showAssigneeReadyFeature() { |
| | | return !!(config && config.features && config.features.showAssigneeReadyButton) |
| | | }, |
| | | |
| | | // 是否显示"强制完成"功能(配置开关) |
| | | showForceCompleteFeature() { |
| | | return !!(config && config.features && config.features.showForceCompleteButton) |
| | | }, |
| | | |
| | | // 当前用户是否为该执行人 |
| | |
| | | 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() |
| | | }, |
| | | |
| | | // 是否多人执行 |
| | |
| | | } |
| | | |
| | | .edit-btn { |
| | | width: 60rpx; |
| | | width: 120rpx; |
| | | height: 60rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | |
| | | .edit-text { |
| | | margin-left: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #007AFF; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | |
| | | &.cancel { |
| | | background-color: #ff3b30; |
| | | color: white; |
| | | } |
| | | |
| | | &.force-end { |
| | | background-color: #ff6b22; |
| | | color: white; |
| | | } |
| | | |
| | |
| | | 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 { |