refactor: compact dropship PDA workspace

This commit is contained in:
2026-09-11 14:18:53 +08:00
parent 454e8b5f73
commit a04a1c9cbc
2 changed files with 17 additions and 21 deletions
@@ -7,17 +7,14 @@
<view v-if="loading && !task" class="state">正在加载入库任务…</view>
<template v-if="task">
<view class="task-summary-card">
<view class="summary-head"><view><text class="eyebrow">入仓码</text><text class="inbound-code">{{ task.inboundCode || '—' }}</text></view><text class="status">{{ statusLabel }}</text></view>
<view class="summary-head"><text class="inbound-code">{{ task.inboundCode || '—' }}</text><text class="status">{{ statusLabel }}</text></view>
<text class="customer">{{ task.customerName || '客户未关联' }} · {{ task.warehouseName || '仓库未关联' }}</text>
<view class="metrics">
<view><strong>{{ task.totalCartons || 0 }}</strong><text>总箱</text></view>
<view><strong>{{ task.countedCartons || 0 }}</strong><text>已清点</text></view>
<view><strong>{{ task.boundCartons || 0 }}</strong><text>已归托</text></view>
<view><strong>{{ task.unboundCartons || 0 }}</strong><text>待归托</text></view>
</view>
<view class="pallet-readiness" :class="{ invalid: !palletSetupReady }">
<text>预计 {{ task.expectedPalletCount || 0 }} 托</text>
<text>已生成 {{ task.generatedPalletCount || 0 }} 托</text>
<view class="compact-metrics">
<text><strong>{{ task.totalCartons || 0 }}</strong>总箱</text>
<text><strong>{{ task.countedCartons || 0 }}</strong>清点</text>
<text><strong>{{ task.boundCartons || 0 }}</strong>归托</text>
<text><strong>{{ task.unboundCartons || 0 }}</strong>待归</text>
<text class="pallet-readiness" :class="{ invalid: !palletSetupReady }"><strong>{{ task.generatedPalletCount || 0 }}/{{ task.expectedPalletCount || 0 }}</strong>托盘</text>
</view>
<view v-if="!palletSetupReady" class="message error pallet-setup-error" role="alert">{{ palletSetupMessage }}</view>
<button v-if="task.taskStatus === 'PENDING'" class="primary-button" :disabled="submitting" @tap="claimTask">领取并开始入库</button>
@@ -25,12 +22,11 @@
<view v-if="task.taskStatus === 'CLAIMED'" class="section-card">
<text class="section-title">1. 扫箱清点</text>
<text class="section-desc">扫描箱码,确认 SKU 后点数。</text>
<template v-if="palletSetupReady">
<ScanTargetInput v-model="boxCode" name="boxCode" label="箱码" placeholder="扫描或输入箱唛箱码" :active="activeScan === 'boxCode'" @activate="activeScan = 'boxCode'" @submit="resolveCarton" />
<view v-if="!carton" class="count-entry">
<text>请扫描箱码</text>
<button class="primary-button count-button" :disabled="!canResolveCarton" @tap="resolveCarton()">读取 SKU</button>
<button class="compact-action" :disabled="!canResolveCarton" @tap="resolveCarton()">读取 SKU</button>
</view>
<view v-if="carton" class="carton-card">
<text class="sku-label">SKU</text>
@@ -48,7 +44,7 @@
<button class="primary-button" :disabled="!canCount || submitting" @tap="countCarton">确认清点</button>
</view>
<view class="pallet-scan-block">
<text class="field-label">扫托盘(可选)</text>
<text class="field-label">扫托盘 <text>可选</text></text>
<ScanTargetInput v-model="palletCode" name="palletCode" label="托盘码" placeholder="扫描或输入已生成的 DP 托盘码" :active="activeScan === 'palletCode'" @activate="activeScan = 'palletCode'" @submit="selectPallet" />
</view>
</template>
@@ -167,17 +163,16 @@
.content { padding: 12px 12px 42px; }
.state, .message { margin-bottom: 10px; padding: 12px; border-radius: 9px; color: #667085; font-size: 12px; line-height: 18px; text-align: center; background: #fff; }
.message.error { color: #b42318; background: #fff1f0; }.message.success { color: #067647; background: #ecfdf3; }
.task-summary-card, .section-card { margin-bottom: 12px; padding: 13px; border: 1px solid #dce6f1; border-radius: 12px; background: #fff; box-shadow: 0 2px 8px rgba(23,55,93,.05); }
.task-summary-card, .section-card { margin-bottom: 8px; padding: 10px; border: 1px solid #dce6f1; border-radius: 10px; background: #fff; box-shadow: 0 1px 4px rgba(23,55,93,.04); }
.task-summary-card { display: block; }
.summary-head, .section-heading, .pallet-head, .carton-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.summary-head > view { display: flex; min-width: 0; flex-direction: column; }.eyebrow { color: #667085; font-size: 10px; }.inbound-code { margin-top: 2px; color: #12233f; font-size: 19px; font-weight: 700; overflow-wrap: anywhere; }
.inbound-code { min-width: 0; color: #12233f; font-size: 17px; font-weight: 700; overflow-wrap: anywhere; }
.status, .pallet-status { flex-shrink: 0; padding: 4px 8px; border-radius: 99px; color: #155eef; font-size: 10px; background: #eff4ff; }.pallet-status.sealed { color: #067647; background: #ecfdf3; }
.customer { display: block; margin-top: 6px; color: #667085; font-size: 11px; }
.metrics { display: grid; grid-template-columns: repeat(4,1fr); margin: 12px 0; padding: 10px 0; border-top: 1px solid #edf1f5; border-bottom: 1px solid #edf1f5; }.metrics view { display: flex; flex-direction: column; align-items: center; border-right: 1px solid #edf1f5; }.metrics view:last-child { border: 0; }.metrics strong { font-size: 17px; }.metrics text { margin-top: 2px; color: #667085; font-size: 10px; }
.pallet-readiness { display: flex; justify-content: center; gap: 18px; margin-top: -2px; color: #067647; font-size: 11px; font-weight: 700; }.pallet-readiness.invalid { color: #b42318; }.pallet-setup-error { margin-top: 10px; margin-bottom: 0; text-align: left; }
.section-title { display: block; color: #12233f; font-size: 16px; font-weight: 700; }.section-desc { display: block; margin: 3px 0 11px; color: #667085; font-size: 11px; line-height: 17px; }.section-heading > view { min-width: 0; }
.customer { display: block; overflow: hidden; margin-top: 3px; color: #667085; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
.compact-metrics { display: grid; grid-template-columns: repeat(5,1fr); margin-top: 8px; padding-top: 7px; border-top: 1px solid #edf1f5; }.compact-metrics > text { display: flex; align-items: center; justify-content: center; gap: 2px; color: #667085; font-size: 9px; white-space: nowrap; }.compact-metrics strong { color: #12233f; font-size: 13px; }.compact-metrics .pallet-readiness { color: #067647; }.compact-metrics .pallet-readiness.invalid,.compact-metrics .pallet-readiness.invalid strong { color: #b42318; }.pallet-setup-error { margin-top: 8px; margin-bottom: 0; text-align: left; }
.section-title { display: block; color: #12233f; font-size: 15px; font-weight: 700; }.section-desc { display: block; margin: 2px 0 8px; color: #667085; font-size: 10px; line-height: 15px; }.section-heading > view { min-width: 0; }
.primary-button, .secondary-button { box-sizing: border-box; width: 100%; height: 46px; margin-top: 10px; border-radius: 9px; font-size: 14px; font-weight: 700; line-height: 44px; }.primary-button { border: 0; color: #fff; background: #1a73e8; }.secondary-button { border: 1px solid #8ab8ef; color: #155eef; background: #f6f9ff; }.primary-button[disabled],.secondary-button[disabled] { opacity: .45; }
.count-entry { margin-top: 9px; padding: 10px; border: 1px solid #dbe8f8; border-radius: 9px; color: #52657a; font-size: 11px; line-height: 17px; background: #f6f9ff; }.count-button { margin-top: 8px; }.count-unavailable { padding: 18px 10px; border-radius: 9px; color: #b42318; font-size: 12px; line-height: 18px; text-align: center; background: #fff1f0; }.pallet-scan-block { margin-top: 13px; padding-top: 12px; border-top: 1px solid #e6edf5; }.field-label { display: block; margin-bottom: 7px; color: #344054; font-size: 12px; font-weight: 700; }
.count-entry { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; padding: 5px 7px; border: 1px solid #dbe8f8; border-radius: 7px; color: #667085; font-size: 10px; background: #f6f9ff; }.compact-action { width: 78px; height: 32px; margin: 0; border: 0; border-radius: 6px; color: #fff; font-size: 11px; line-height: 32px; background: #1a73e8; }.compact-action[disabled] { opacity: .35; }.count-unavailable { padding: 12px 8px; border-radius: 7px; color: #b42318; font-size: 11px; line-height: 16px; text-align: center; background: #fff1f0; }.pallet-scan-block { margin-top: 8px; padding-top: 8px; border-top: 1px solid #e6edf5; }.field-label { display: block; margin-bottom: 5px; color: #344054; font-size: 11px; font-weight: 700; }.field-label text { margin-left: 3px; color: #98a2b3; font-size: 9px; font-weight: 400; }
.carton-card, .pallet-card { margin-top: 11px; padding: 11px; border: 1px solid #e3eaf2; border-radius: 9px; background: #f8fbff; }.sku-label { display: block; color: #667085; font-size: 10px; }.sku-code { display: block; margin-top: 3px; color: #12233f; font-size: 17px; font-weight: 700; overflow-wrap: anywhere; }.pallet-meta { display: block; margin-top: 5px; color: #667085; font-size: 11px; line-height: 17px; }
.quantity-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 14px; color: #344054; font-size: 13px; font-weight: 700; }.quantity-stepper { display: grid; grid-template-columns: 44px 72px 44px; overflow: hidden; border: 1px solid #b8c6d8; border-radius: 9px; background: #fff; }.quantity-stepper button,.quantity-stepper input { box-sizing: border-box; height: 44px; margin: 0; border: 0; border-radius: 0; text-align: center; background: #fff; }.quantity-stepper button { color: #155eef; font-size: 21px; line-height: 44px; }.quantity-stepper input { width: 72px; border-right: 1px solid #dce4ed; border-left: 1px solid #dce4ed; color: #12233f; font-size: 16px; }.pallet-hint { display: block; margin-top: 10px; color: #155eef; font-size: 11px; text-align: center; }
.field { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; color: #344054; font-size: 12px; }.field input { box-sizing: border-box; width: 120px; height: 42px; padding: 0 10px; border: 1px solid #cfd8e3; border-radius: 8px; background: #fff; }.field.stacked { align-items: stretch; flex-direction: column; gap: 6px; }.field textarea { box-sizing: border-box; width: 100%; height: 70px; padding: 9px; border: 1px solid #cfd8e3; border-radius: 8px; background: #fff; }
+2 -1
View File
@@ -43,8 +43,9 @@ test('palletization UI accepts carton and pre-generated pallet scans in either o
assert.match(tasks, /\.search-card\s*\{[^}]*display:\s*flex;[^}]*box-shadow:\s*none;/)
assert.match(tasks, /\.task-list\s*\{[^}]*overflow:\s*hidden;[^}]*border-radius:\s*7px;/)
assert.match(tasks, /\.task-card\s*\{[^}]*margin:\s*0;[^}]*border-radius:\s*0;[^}]*box-shadow:\s*none;/)
for (const text of ['一件代发入库', '领取并开始入库', '扫箱清点', '读取 SKU', '确认清点', '扫托盘(可选)', '差异说明', '预生成托盘码', '打印托盘码', '封托']) assert.match(work, new RegExp(text))
for (const text of ['一件代发入库', '领取并开始入库', '扫箱清点', '读取 SKU', '确认清点', '扫托盘', '可选', '差异说明', '预生成托盘码', '打印托盘码', '封托']) assert.match(work, new RegExp(text))
for (const text of ['sku-label', 'sku-code', 'quantity-stepper', '减少清点数量', '增加清点数量', 'adjustQuantity']) assert.match(work, new RegExp(text))
for (const text of ['compact-metrics', 'compact-action']) assert.match(work, new RegExp(text))
for (const text of ['计划 {{ carton.expectedSkuQuantity', "carton.cargoName", "carton.countStatus === 'UNCOUNTED'"]) assert.doesNotMatch(work, new RegExp(text))
assert.match(work, /canResolveCarton\(\)[\s\S]*?boxCode[\s\S]*?!this\.loading[\s\S]*?!this\.submitting/)
for (const text of ['expectedPalletCount', 'generatedPalletCount', 'palletSetupReady', 'palletSetupMessage']) assert.match(work, new RegExp(text))