Files
pda-uniapp/pages/tasks/tasks.vue
T
2026-08-17 17:49:08 +08:00

120 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-shell task-page">
<PortalHeader title="拣货任务" :user="user" :fallback-url="'/pages/home/home'" />
<view class="task-content">
<view class="task-toolbar">
<view class="task-heading">
<text class="task-title">待处理任务</text>
<text class="task-summary">{{ loading ? '正在同步' : `共 ${tasks.length} 个可处理任务` }}</text>
</view>
<button class="task-refresh" :disabled="loading" @tap="loadTasks">{{ loading ? '刷新中' : '刷新' }}</button>
</view>
<view v-if="error" class="task-error" role="alert">{{ error }}</view>
<view v-if="loading" class="task-state">正在加载真实任务...</view>
<view v-else-if="!tasks.length && !error" class="task-state">暂无可领取的拣货任务。请确认预装已补充集装箱号、已关联放舱订舱且预装已确认。</view>
<view v-if="notice" class="task-notice" role="status">{{ notice }}</view>
<view class="task-list">
<button v-for="task in tasks" :key="task.taskId" class="task-card" :disabled="!isTaskReady(task)" :aria-label="`${taskActionLabel(task)},集装箱号 ${task.currentContainerNo}`" @tap="enterTask(task)">
<view class="task-card-head">
<view class="task-container-info"><text class="task-label">集装箱号</text><text class="task-code">{{ task.currentContainerNo }}</text></view>
<text class="task-status" :class="{ progress: task.workflowStatus === 'IN_PROGRESS', evidence: task.workflowStatus === 'AWAITING_REVIEW' }">{{ statusLabel(task.workflowStatus) }}</text>
</view>
<view class="task-identifiers"><text class="task-cabinet">柜次号 {{ task.cabinetNo }}</text><text class="task-order">{{ task.displayNo || '未命名订单' }}</text></view>
<view class="task-card-foot">
<view class="task-meta-group"><text class="task-warehouse">{{ task.warehouseName || (task.warehouseId ? `仓库 ${task.warehouseId}` : '缺少仓库信息') }}</text><text class="task-pending">待处理 {{ task.pendingItemCount ?? task.itemCount ?? 0 }} 项</text></view>
<view class="task-enter"><text>{{ taskActionLabel(task) }}</text><text class="task-enter-arrow">›</text></view>
</view>
</button>
</view>
</view>
<view v-if="toast.visible" class="app-toast" :class="toast.type" role="status">{{ toast.message }}</view>
</view>
</template>
<script>
import PortalHeader from '@/components/PortalHeader.vue'
import { listLoadingOrders } from '@/api/pda-loading.js'
import { session, requireSession } from '@/stores/session.js'
import { clearPickingDraft } from '@/stores/picking.js'
import { isOnline, watchNetwork } from '@/utils/network.js'
export default {
components: { PortalHeader },
data() { return { user: session.currentUser, tasks: [], loading: false, error: '', notice: '', online: true, toast: { visible: false, message: '', type: '' } } },
onLoad() { if (!requireSession()) return; this._stopNetwork = watchNetwork((value) => { this.online = value }) },
onShow() { if (!requireSession()) return; this.user = session.currentUser; this.loadTasks() },
onUnload() { if (this._stopNetwork) this._stopNetwork(); clearTimeout(this._toastTimer) },
methods: {
statusLabel(status) {
if (status === 'AWAITING_REVIEW') return '待上传证据/确认装柜'
return status === 'IN_PROGRESS' ? '拣货中' : '待领取'
},
taskActionLabel(task) {
if (!this.isTaskReady(task)) return '任务暂不可进入'
return task.workflowStatus === 'AWAITING_REVIEW' ? '继续装柜取证' : '进入并开始拣货'
},
isMergedTask(task) { return task?.recordStatus === 'MERGED' || Boolean(task?.mergedIntoContainerBookingId) },
clearMergedTaskDraft(task) {
const operatorId = session.currentUser?.id || session.currentUser?.userId || session.currentUser?.accountId
clearPickingDraft({ operatorId, taskId: task.taskId, loadingPlanId: task.loadingPlanId, containerBookingId: task.containerBookingId, warehouseId: task.warehouseId })
},
isTaskReady(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && task.loadingPlanId && task.containerBookingId && task.cabinetNo && task.currentContainerNo && task.warehouseId && task.version !== null && task.version !== undefined && Number.isInteger(Number(task.version)) && (task.workflowStatus === 'AWAITING_REVIEW' || (task.assignmentVersion !== null && task.assignmentVersion !== undefined && Number.isInteger(Number(task.assignmentVersion)))) && ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'].includes(task.workflowStatus)) },
async loadTasks() {
if (!isOnline()) { this.error = '当前网络不可用,请联网后刷新'; return }
this.loading = true; this.error = ''; this.notice = ''
try {
const result = await listLoadingOrders({ page: 1, size: 50 })
const records = result.records || []
records.filter((task) => this.isMergedTask(task)).forEach((task) => this.clearMergedTaskDraft(task))
this.tasks = records.filter((task) => this.isTaskReady(task))
if (records.length > this.tasks.length) this.notice = '部分任务尚未完成服务端就绪校验,本机已隐藏,请稍后刷新'
} catch (error) {
this.error = error.network ? '当前网络不可用,请联网后刷新' : (error.message || '任务加载失败')
} finally { this.loading = false }
},
enterTask(task) {
if (!this.isTaskReady(task)) { this.showToast('任务尚未满足后端进入条件', 'error'); return }
uni.navigateTo({ url: `/pages/picking/picking?cabinetNo=${encodeURIComponent(task.cabinetNo)}&currentContainerNo=${encodeURIComponent(task.currentContainerNo)}&warehouseId=${encodeURIComponent(task.warehouseId)}&taskId=${encodeURIComponent(task.taskId)}&orderNo=${encodeURIComponent(task.displayNo || '')}` })
},
showToast(message, type) { clearTimeout(this._toastTimer); this.toast = { visible: true, message, type }; this._toastTimer = setTimeout(() => { this.toast.visible = false }, 2200) }
}
}
</script>
<style scoped>
.task-content { padding: 12px 12px 40px; }
.task-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.task-heading { display: flex; min-width: 0; flex-direction: column; }
.task-title { color: #12233f; font-size: 18px; font-weight: 700; line-height: 25px; }
.task-summary { margin-top: 2px; color: #667085; font-size: 11px; line-height: 16px; }
.task-refresh { flex-shrink: 0; margin: 0; min-width: 76px; height: 44px; border: 0; border-radius: 9px; background: #1a73e8; color: #fff; font-size: 14px; line-height: 44px; }
.task-refresh[disabled] { opacity: .6; }
.task-error, .task-state { padding: 20px 14px; border-radius: 10px; background: #fff; color: #667085; font-size: 13px; line-height: 20px; text-align: center; }
.task-error { color: #b42318; background: #fff1f0; }
.task-notice { padding: 10px 12px; margin-bottom: 10px; border-left: 3px solid #f79009; border-radius: 6px; background: #fffaeb; color: #7a2e0e; font-size: 12px; line-height: 18px; }
.task-list { display: flex; flex-direction: column; gap: 9px; }
.task-card { box-sizing: border-box; width: 100%; margin: 0; padding: 12px; border: 1px solid #dce6f1; border-radius: 10px; color: #12233f; text-align: left; background: #fff; box-shadow: 0 2px 8px rgba(23, 55, 93, .05); }
.task-card:active { border-color: #8ab8ef; background: #f7faff; }
.task-card[disabled] { opacity: .62; }
.task-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.task-container-info { display: flex; min-width: 0; flex-direction: column; }
.task-label { color: #667085; font-size: 10px; line-height: 15px; }
.task-code { overflow: hidden; margin-top: 1px; color: #12233f; font-size: 17px; font-weight: 700; line-height: 23px; text-overflow: ellipsis; white-space: nowrap; }
.task-status { flex-shrink: 0; padding: 4px 7px; border-radius: 99px; color: #52616f; font-size: 10px; line-height: 16px; background: #eef2f6; }
.task-status.progress { color: #155eef; background: #eff4ff; }
.task-status.evidence { color: #9a5b13; background: #fff4e5; }
.task-identifiers { display: flex; flex-wrap: wrap; gap: 5px 8px; margin-top: 8px; }
.task-cabinet, .task-order { padding: 3px 7px; border-radius: 5px; color: #475467; font-size: 11px; line-height: 17px; background: #f2f4f7; }
.task-order { overflow: hidden; max-width: 100%; text-overflow: ellipsis; white-space: nowrap; }
.task-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; padding-top: 9px; border-top: 1px solid #edf1f5; }
.task-meta-group { display: flex; min-width: 0; flex-direction: column; gap: 2px; }
.task-warehouse { overflow: hidden; color: #667085; font-size: 10px; line-height: 15px; text-overflow: ellipsis; white-space: nowrap; }
.task-pending { color: #344054; font-size: 12px; font-weight: 600; line-height: 17px; }
.task-enter { display: flex; min-height: 40px; flex-shrink: 0; align-items: center; gap: 5px; padding-left: 10px; color: #155eef; font-size: 12px; font-weight: 600; }
.task-enter-arrow { font-size: 22px; font-weight: 400; line-height: 22px; }
@media (max-width: 340px) {
.task-content { padding-right: 10px; padding-left: 10px; }
.task-enter { max-width: 124px; text-align: right; }
}
</style>