This commit is contained in:
2026-10-21 14:30:33 +08:00
parent 3953453090
commit 8498798a9c
47 changed files with 791 additions and 409 deletions
@@ -6,6 +6,7 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import InlineHelp from '@/shared/components/InlineHelp.vue'
import * as api from '../api/dispatches'
import { useDispatchCandidates } from '../useDispatchCandidates'
import { useDispatchCommandRecovery, type PendingDispatchCommand } from '../dispatchCommandRecovery'
@@ -202,25 +203,31 @@ onBeforeUnmount(() => { alive = false; generation++; listGeneration++; candidate
<el-alert v-if="pending" :title="pendingMessage" type="warning" :closable="false" show-icon />
<el-button v-if="cargoPending" :loading="busy" @click="execute">{{ pending?.result ? '刷新当前资料' : '核对并重试原请求' }}</el-button>
<LoadErrorState v-if="error" title="排车货物资料待核对" :message="error.message" :loading="loading || busy" retry-text="重新核对" @retry="retry"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<section class="density-dialog-section existing-cargo-search" aria-label="已有排车查询">
<div class="existing-cargo-query">
<el-input v-model="keyword" aria-label="查找已有排车" placeholder="排车单号" :disabled="frozen || listing" @keyup.enter="searchDispatches" />
<el-button :disabled="frozen || listing" @click="searchDispatches">查询排车</el-button>
<label for="existing-dispatch-number">排车单号</label>
<el-input id="existing-dispatch-number" v-model="keyword" aria-label="查找已有排车" placeholder="排车单号" :disabled="frozen || listing" @keyup.enter="searchDispatches" />
<el-button type="primary" :disabled="frozen || listing" @click="searchDispatches">查询排车</el-button>
<el-select :model-value="selectedNo" aria-label="选择已有排车" placeholder="选择装车前排车" :disabled="frozen || listing" @update:model-value="(value: string) => selectDispatch(value, rows.find(row => row.dispatchNo === value))">
<el-option v-for="row in rows" :key="row.dispatchNo" :value="row.dispatchNo" :label="`${row.dispatchNo} · ${row.warehouseName || row.warehouseCode || '—'}`" :disabled="row.taskType !== 'DELIVERY' || row.status !== 'ACTIVE' || row.stage !== 'BEFORE_LOADING'" />
<el-option v-if="selectedNo && !rows.some(row => row.dispatchNo === selectedNo)" :value="selectedNo" :label="selectedNo" disabled />
</el-select>
<el-pagination aria-label="已有排车分页" :current-page="page" :page-size="size" :total="total" :disabled="frozen || listing" layout="total, prev, pager, next" @update:current-page="changePage" />
</div>
<div v-if="selectedNo" class="existing-cargo-query existing-cargo-filter"><label for="existing-cargo-keyword">货物查询</label><el-input id="existing-cargo-keyword" v-model="cargoKeyword" aria-label="查找可排货物" placeholder="订单号/入仓码" :disabled="locked || !canAdd" @keyup.enter="searchCargo" /><el-button type="primary" :disabled="locked || !canAdd" @click="searchCargo">查询货物</el-button><el-button :disabled="locked" @click="selectDispatch(selectedNo)">刷新本车</el-button></div>
</section>
<p v-if="!selectedNo" class="density-dialog-note">选择已有排车后,在这里统一增减本车计划货物。</p>
<template v-if="selectedNo">
<div class="existing-cargo-query"><strong>{{ selectedNo }} · {{ warehouseName || '—' }}</strong><el-input v-model="cargoKeyword" aria-label="查找可排货物" placeholder="订单号/入仓码" :disabled="locked || !canAdd" @keyup.enter="searchCargo" /><el-button :disabled="locked || !canAdd" @click="searchCargo">查询货物</el-button><el-button :disabled="locked" @click="selectDispatch(selectedNo)">刷新本车</el-button></div>
<p v-if="detail && !editable" class="density-dialog-note">仅有效、装车前的仓库派送排车允许调整计划货物。真实拣货仍在 PDA 完成。</p>
<p v-else-if="detail && !canAdd" class="density-dialog-note">{{ detail.actions.ADD_CARGO?.blockReason || '当前状态或权限不允许添加货物' }}</p>
<SmartDispatchCargoBoard :candidates="candidates" :draft="null" :active-container-id="selectedNo" :capacity="0" :disabled="locked || !canAdd" :warehouse-selected="Boolean(warehouseId)" :page="cargoPage" :size="cargoSize" :total="cargoTotal" @add="addCargo" @change-page="changeCargoPage" @change-size="changeCargoSize">
<template #vehicle-heading>
<h3 class="existing-cargo-title">本车货物 · {{ selectedNo }} · {{ warehouseName || '—' }}</h3>
<InlineHelp title="本车货物调整说明"><p>加入后立即保存;移除前填写原因并确认。货物变化后需重新核对审核。</p></InlineHelp>
</template>
<template #vehicles="{ dragOver, drop, dropTarget }">
<section class="existing-cargo-manifest" :class="{ 'is-drop-target': dropTarget === selectedNo }" aria-label="当前排车货物" @dragover="dragOver($event, selectedNo)" @drop="drop($event, selectedNo)">
<h3>本车货物 · {{ selectedNo }}</h3>
<p class="density-dialog-note">加入后立即保存;移除前填写原因并确认。货物变化后需重新核对审核。</p>
<p v-if="!items" class="density-dialog-note">请读取完整货物清单</p>
<p v-else-if="!items.items.length" class="density-dialog-note">本车暂无货物,可从左侧选择托盘加入。</p>
<div v-for="row in items?.items || []" :key="row.dispatchItemId" class="existing-cargo-item">
@@ -236,12 +243,15 @@ onBeforeUnmount(() => { alive = false; generation++; listGeneration++; candidate
</template>
<style scoped>
.existing-cargo-query { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-4); }
.existing-cargo-search { display: flex; flex-direction: column; gap: var(--admin-space-5); margin-top: var(--admin-space-5); }
.existing-cargo-query { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3); }
.existing-cargo-query > label { width: 64px; flex: none; color: var(--cc-muted); }
.existing-cargo-query > .el-button { margin-left: 0; }
.existing-cargo-query > .el-input { width: 240px; }
.existing-cargo-query > .el-select { width: 350px; }
.existing-cargo-manifest { min-width: 0; border: 1px solid var(--admin-table-border); border-radius: var(--admin-dialog-section-radius); padding: var(--admin-space-5); max-height: 58vh; overflow-y: auto; }
.existing-cargo-manifest.is-drop-target { border-color: var(--cc-primary); background: var(--admin-action-primary-hover); }
.existing-cargo-manifest h3 { margin: 0; font-size: var(--admin-font-section); }
.existing-cargo-title { margin: 0; font-size: var(--admin-font-section); overflow-wrap: anywhere; }
.existing-cargo-item { display: flex; gap: var(--admin-space-3); justify-content: space-between; padding: var(--admin-space-4) 0; border-bottom: 1px solid var(--admin-table-border); overflow-wrap: anywhere; }
.existing-cargo-item > div { min-width: 0; }
.existing-cargo-item > .el-button { flex: none; }