| | |
| | | <view class="task-header"> |
| | | <view class="header-title">任务列表</view> |
| | | <view class="header-actions"> |
| | | <button class="search-toggle-btn" @click="toggleSearch"> |
| | | <!-- <button class="search-toggle-btn" @click="toggleSearch"> |
| | | <uni-icons |
| | | :type="showSearch ? 'close' : 'search'" |
| | | size="20" |
| | | ></uni-icons> |
| | | </button> |
| | | </button> --> |
| | | <button class="refresh-btn" @click="refreshList"> |
| | | <uni-icons type="refresh" size="20"></uni-icons> |
| | | </button> |
| | |
| | | </view> |
| | | </scroll-view> |
| | | </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> |
| | | </template> |
| | | |
| | | <script> |
| | | import uniDatetimePicker from "@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue"; |
| | | import { listTask, changeTaskStatus } from "@/api/task"; |
| | | import { listTask, changeTaskStatus, checkTaskConsentAttachment } from "@/api/task"; |
| | | import { getDicts } from "@/api/dict"; |
| | | import { mapState } from "vuex"; |
| | | import { formatDateTime } from "@/utils/common"; |
| | | import { checkTaskCanDepart } from "@/utils/taskValidator"; |
| | |
| | | vehicle: "", |
| | | taskNo: "", |
| | | }, |
| | | statusOptions: ["全部状态", "待处理", "处理中", "已完成"], |
| | | statusValues: ["", "pending", "processing", "completed"], |
| | | statusOptions: ["全部状态", "待处理", "处理中", "已完成", "已取消"], |
| | | statusValues: ["", "pending", "processing", "completed", "cancelled"], |
| | | selectedStatus: "", |
| | | selectedStatusText: "", |
| | | startDate: "", |
| | |
| | | pageSize: 10, |
| | | total: 0, |
| | | hasMore: true, |
| | | |
| | | // 取消原因相关 |
| | | cancelReasonList: [], // 取消原因列表 |
| | | showCancelDialog: false, // 显示取消原因对话框 |
| | | selectedCancelReason: '', // 选中的取消原因 |
| | | currentCancelTask: null // 当前要取消的任务 |
| | | }; |
| | | }, |
| | | computed: { |
| | |
| | | // 实际的筛选将在请求服务器时完成 |
| | | return this.taskList; |
| | | }, |
| | | |
| | | // 获取选中的取消原因标签(用于弹窗显示) |
| | | selectedCancelReasonLabel() { |
| | | if (!this.selectedCancelReason || !this.cancelReasonList.length) { |
| | | return '请选择' |
| | | } |
| | | const reason = this.cancelReasonList.find(r => r.value === this.selectedCancelReason) |
| | | return reason ? reason.label : '请选择' |
| | | }, |
| | | }, |
| | | onLoad() { |
| | | this.loadTaskList(); |
| | | |
| | | // 监听任务列表刷新事件 |
| | | uni.$on("refreshTaskList", this.handleRefreshEvent); |
| | | |
| | | // 加载取消原因字典 |
| | | this.loadCancelReasonDict(); |
| | | }, |
| | | onShow() { |
| | | // 页面显示时刷新列表(从其他页面返回时) |
| | |
| | | "IN_PROGRESS", |
| | | ].join(","); |
| | | } else if (this.currentFilter === "completed") { |
| | | queryParams.taskStatus = "COMPLETED"; |
| | | // 已完成包含:已完成和已取消 |
| | | queryParams.taskStatusList = "COMPLETED,CANCELLED"; |
| | | } else { |
| | | // 如果没有使用顶部tab筛选,则使用查询条件中的状态筛选 |
| | | if (this.selectedStatus) { |
| | |
| | | "IN_PROGRESS", |
| | | ].join(","), |
| | | completed: "COMPLETED", |
| | | cancelled: "CANCELLED", |
| | | }; |
| | | if (statusMap[this.selectedStatus]) { |
| | | if ( |
| | | this.selectedStatus === "pending" || |
| | | this.selectedStatus === "completed" |
| | | this.selectedStatus === "completed" || |
| | | this.selectedStatus === "cancelled" |
| | | ) { |
| | | queryParams.taskStatus = statusMap[this.selectedStatus]; |
| | | } else { |
| | |
| | | "IN_PROGRESS", |
| | | ].join(","); |
| | | } else if (this.currentFilter === "completed") { |
| | | queryParams.taskStatus = "COMPLETED"; |
| | | // 已完成包含:已完成和已取消 |
| | | queryParams.taskStatusList = "COMPLETED,CANCELLED"; |
| | | } else { |
| | | // 如果没有使用顶部tab筛选,则使用查询条件中的状态筛选 |
| | | if (this.selectedStatus) { |
| | |
| | | "IN_PROGRESS", |
| | | ].join(","), |
| | | completed: "COMPLETED", |
| | | cancelled: "CANCELLED", |
| | | }; |
| | | if (statusMap[this.selectedStatus]) { |
| | | if ( |
| | | this.selectedStatus === "pending" || |
| | | this.selectedStatus === "completed" |
| | | this.selectedStatus === "completed" || |
| | | this.selectedStatus === "cancelled" |
| | | ) { |
| | | queryParams.taskStatus = statusMap[this.selectedStatus]; |
| | | } else { |
| | |
| | | this.endDate = ""; |
| | | this.searchForm.vehicle = ""; |
| | | this.searchForm.taskNo = ""; |
| | | // 重置后重新加载数据 |
| | | this.loadTaskList(); |
| | | }, |
| | | |
| | | // 刷新列表 |
| | |
| | | // 筛选 |
| | | changeFilter(filter) { |
| | | this.currentFilter = filter; |
| | | // 重置分页 |
| | | this.currentPage = 1; |
| | | this.hasMore = true; |
| | | // 重新加载数据 |
| | | this.loadTaskList(); |
| | | }, |
| | |
| | | break; |
| | | |
| | | case "cancel": |
| | | // 取消 -> 二次确认后状态变为已取消 |
| | | this.$modal |
| | | .confirm("确定要取消此任务吗?") |
| | | .then(() => { |
| | | this.updateTaskStatus(task.taskId, "CANCELLED", "任务已取消"); |
| | | }) |
| | | .catch(() => {}); |
| | | // 取消 -> 显示取消原因选择对话框 |
| | | this.currentCancelTask = task; |
| | | this.showCancelReasonDialog(); |
| | | break; |
| | | |
| | | case "arrive": |
| | |
| | | |
| | | case "complete": |
| | | // 已完成 -> 状态变为已完成 |
| | | this.$modal |
| | | .confirm("确认任务已完成?") |
| | | .then(() => { |
| | | this.updateTaskStatus(task.taskId, "COMPLETED", "任务已完成"); |
| | | }) |
| | | .catch(() => {}); |
| | | // 需要检查是否上传了知情同意书 |
| | | this.checkConsentAttachmentAndThen(task.taskId, "COMPLETED", "任务已完成"); |
| | | break; |
| | | } |
| | | }, |
| | |
| | | // 获取GPS位置信息 |
| | | this.getLocationAndUpdateStatus(taskId, status, remark); |
| | | }, |
| | | |
| | | // 加载取消原因字典 |
| | | 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(this.currentCancelTask.taskId, 'CANCELLED', '任务已取消', this.selectedCancelReason) |
| | | }, |
| | | |
| | | // 取消对话框关闭 |
| | | closeCancelDialog() { |
| | | this.$refs.cancelPopup.close() |
| | | this.selectedCancelReason = '' |
| | | this.currentCancelTask = null |
| | | }, |
| | | |
| | | // 选择取消原因 |
| | | selectCancelReason(e) { |
| | | this.selectedCancelReason = this.cancelReasonList[e.detail.value].value |
| | | }, |
| | | |
| | | // 带取消原因的状态更新 |
| | | updateTaskStatusWithCancelReason(taskId, status, remark, cancelReason) { |
| | | this.getLocationAndUpdateStatus(taskId, status, remark, cancelReason) |
| | | }, |
| | | |
| | | // 检查知情同意书附件并更新状态 |
| | | async checkConsentAttachmentAndThen(taskId, status, remark) { |
| | | try { |
| | | uni.showLoading({ |
| | | title: '检查附件...' |
| | | }); |
| | | |
| | | // 注意:这里会被请求拦截器处理,code !== 200 时会 reject |
| | | const response = await checkTaskConsentAttachment(taskId).catch(err => { |
| | | // 拦截器 reject 的情况,返回一个默认对象 |
| | | console.log('请求被拦截器 reject,err:', err); |
| | | return { code: -1, msg: '未上传知情同意书' }; |
| | | }); |
| | | |
| | | uni.hideLoading(); |
| | | console.log('检查附件结果:', response); |
| | | |
| | | if (response && response.code === 200) { |
| | | // 已上传知情同意书,继续更新状态 |
| | | console.log('已上传知情同意书,继续完成任务'); |
| | | this.$modal |
| | | .confirm("确认任务已完成?") |
| | | .then(() => { |
| | | this.updateTaskStatus(taskId, status, remark); |
| | | }) |
| | | .catch(() => {}); |
| | | } else { |
| | | // 未上传知情同意书或其他错误,阻止完成 |
| | | const message = (response && response.msg) || '任务未上传知情同意书,无法完成任务'; |
| | | console.log('未上传知情同意书,阻止完成'); |
| | | |
| | | this.$modal.confirm(message + '。是否现在去上传?').then(() => { |
| | | // 跳转到任务详情页上传附件 |
| | | uni.navigateTo({ |
| | | url: `/pagesTask/detail?id=${taskId}` |
| | | }); |
| | | }).catch(() => {}); |
| | | } |
| | | } catch (error) { |
| | | uni.hideLoading(); |
| | | console.error('检查附件异常:', error); |
| | | |
| | | // 如果检查失败(网络异常等),不允许完成任务 |
| | | this.$modal.showToast('检查附件状态失败,无法完成任务'); |
| | | } |
| | | }, |
| | | |
| | | // 获取位置信息并更新状态 |
| | | getLocationAndUpdateStatus(taskId, status, remark) { |
| | | getLocationAndUpdateStatus(taskId, status, remark, cancelReason) { |
| | | const that = this; |
| | | |
| | | // 使用uni.getLocation获取GPS位置 |
| | |
| | | speed: res.speed, |
| | | heading: res.direction || res.heading, |
| | | }; |
| | | |
| | | // 如果有取消原因,添加到请求数据中 |
| | | if (cancelReason) { |
| | | statusData.cancelReason = cancelReason |
| | | } |
| | | |
| | | changeTaskStatus(taskId, statusData) |
| | | .then((response) => { |
| | |
| | | taskStatus: status, |
| | | remark: remark, |
| | | }; |
| | | |
| | | // 如果有取消原因,添加到请求数据中 |
| | | if (cancelReason) { |
| | | statusData.cancelReason = cancelReason |
| | | } |
| | | |
| | | changeTaskStatus(taskId, statusData) |
| | | .then((response) => { |
| | |
| | | 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; |
| | | margin: 0 20rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .dialog-buttons { |
| | | display: flex; |
| | | gap: 20rpx; |
| | | |
| | | button { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | line-height: 80rpx; |
| | | border-radius: 10rpx; |
| | | font-size: 28rpx; |
| | | border: none; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | background-color: #f5f5f5; |
| | | color: #666; |
| | | } |
| | | |
| | | .confirm-btn { |
| | | background-color: #007AFF; |
| | | color: white; |
| | | } |
| | | } |
| | | } |
| | | </style> |