120 lines
9.1 KiB
Vue
120 lines
9.1 KiB
Vue
<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)}¤tContainerNo=${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>
|