feat: count mixed-SKU cartons on PDA
This commit is contained in:
@@ -29,17 +29,15 @@
|
||||
<button class="compact-action" :disabled="!canResolveCarton" @tap="resolveCarton()">读取 SKU</button>
|
||||
</view>
|
||||
<view v-if="carton" class="carton-card">
|
||||
<text class="sku-label">SKU</text>
|
||||
<text class="sku-code">{{ carton.skuCode || '—' }}</text>
|
||||
<view class="quantity-row">
|
||||
<text>清点数量</text>
|
||||
<view v-for="(line, index) in skuCounts" :key="line.inboundDetailId" class="sku-count-row">
|
||||
<view class="sku-copy"><text class="sku-label">SKU {{ index + 1 }}</text><text class="sku-code">{{ line.skuCode || '—' }}</text></view>
|
||||
<view class="quantity-stepper">
|
||||
<button aria-label="减少清点数量" @tap="adjustQuantity(-1)">−</button>
|
||||
<input v-model="actualQuantity" type="number" aria-label="清点数量" />
|
||||
<button aria-label="增加清点数量" @tap="adjustQuantity(1)">+</button>
|
||||
<button :aria-label="`减少 ${line.skuCode} 清点数量`" @tap="adjustQuantity(index, -1)">−</button>
|
||||
<input v-model="line.actualQuantity" type="number" :aria-label="`${line.skuCode} 清点数量`" />
|
||||
<button :aria-label="`增加 ${line.skuCode} 清点数量`" @tap="adjustQuantity(index, 1)">+</button>
|
||||
</view>
|
||||
<view v-if="lineDiffers(line)" class="field stacked difference-field"><text>差异说明</text><textarea v-model="line.discrepancyRemark" maxlength="500" placeholder="数量不一致时必填" /></view>
|
||||
</view>
|
||||
<view v-if="quantityDiffers" class="field stacked"><text>差异说明</text><textarea v-model="discrepancyRemark" maxlength="500" placeholder="数量不一致时必填" /></view>
|
||||
<text v-if="currentPallet" class="pallet-hint">归入 {{ currentPallet.palletNo }}</text>
|
||||
<button class="primary-button" :disabled="!canCount || submitting" @tap="countCarton">确认清点</button>
|
||||
</view>
|
||||
@@ -52,13 +50,13 @@
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<view class="section-heading"><view><text class="section-title">2. 托盘码</text><text class="section-desc">打印贴码,装完后封托</text></view><button class="text-button" :disabled="loading" @tap="loadTask">刷新</button></view>
|
||||
<view class="section-heading"><view><text class="section-title">2. 托盘码</text><text class="section-desc">打开 A4 托盘信息单,装完后封托</text></view><button class="text-button" :disabled="loading" @tap="loadTask">刷新</button></view>
|
||||
<view v-if="!task.pallets || !task.pallets.length" class="empty">暂无托盘</view>
|
||||
<view v-for="pallet in task.pallets" :key="pallet.palletId" class="pallet-card">
|
||||
<view class="pallet-head"><text class="pallet-code">{{ pallet.palletNo }}</text><text class="pallet-status" :class="pallet.status === 'SEALED' ? 'sealed' : ''">{{ pallet.status === 'SEALED' ? '已封托' : '待封托' }}</text></view>
|
||||
<text class="pallet-meta">{{ pallet.cartonCount || 0 }} 箱 · {{ pallet.skuCount || 0 }} SKU · {{ pallet.weightKg || 0 }} kg · {{ pallet.volumeCbm || 0 }} m³</text>
|
||||
<button v-if="pallet.status === 'OPEN'" class="secondary-button" :disabled="submitting" @tap="selectPallet(pallet.palletNo)">{{ currentPallet?.palletId === pallet.palletId ? '当前托盘' : '设为当前托盘' }}</button>
|
||||
<button class="secondary-button" :disabled="printing" @tap="printPallet(pallet)">{{ printing ? '正在生成打印文件…' : '打印托盘码' }}</button>
|
||||
<button class="secondary-button" :disabled="printing" @tap="printPallet(pallet)">{{ printing ? '正在生成 A4 文件…' : '打开 A4 托盘单' }}</button>
|
||||
<button v-if="pallet.status === 'OPEN'" class="primary-button" :disabled="!pallet.cartonCount || submitting" @tap="sealPallet(pallet)">封托</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -77,14 +75,13 @@
|
||||
|
||||
export default {
|
||||
components: { PortalHeader, ScanTargetInput },
|
||||
data() { return { user: session.currentUser, taskId: '', warehouseId: '', task: null, palletCode: '', selectedPalletId: '', boxCode: '', carton: null, actualQuantity: '', discrepancyRemark: '', activeScan: 'boxCode', loading: false, submitting: false, printing: false, error: '', notice: '' } },
|
||||
data() { return { user: session.currentUser, taskId: '', warehouseId: '', task: null, palletCode: '', selectedPalletId: '', boxCode: '', carton: null, skuCounts: [], activeScan: 'boxCode', loading: false, submitting: false, printing: false, error: '', notice: '' } },
|
||||
computed: {
|
||||
statusLabel() { return this.task?.taskStatus === 'COMPLETED' ? '已完成' : (this.task?.taskStatus === 'CLAIMED' ? '作业中' : '待领取') },
|
||||
palletSetupReady() { return this.task?.palletSetupReady === true },
|
||||
palletSetupMessage() { return this.task?.palletSetupMessage || `预计 ${this.task?.expectedPalletCount || 0} 托,当前仅生成 ${this.task?.generatedPalletCount || 0} 个托盘码,请联系后台处理后刷新` },
|
||||
quantityDiffers() { return this.carton && Number(this.actualQuantity) !== Number(this.carton.expectedSkuQuantity) },
|
||||
canResolveCarton() { return Boolean(this.palletSetupReady && this.task?.taskStatus === 'CLAIMED' && String(this.boxCode || '').trim() && !this.loading && !this.submitting) },
|
||||
canCount() { return Boolean(this.carton?.canCount && Number(this.actualQuantity) > 0 && (!this.quantityDiffers || this.discrepancyRemark.trim())) },
|
||||
canCount() { return Boolean(this.carton?.canCount && this.skuCounts.length && this.skuCounts.every((line) => Number.isInteger(Number(line.actualQuantity)) && Number(line.actualQuantity) >= 0 && (!this.lineDiffers(line) || String(line.discrepancyRemark || '').trim()))) },
|
||||
currentPallet() { return (this.task?.pallets || []).find((item) => String(item.palletId) === String(this.selectedPalletId) && item.status === 'OPEN') || null }
|
||||
},
|
||||
onLoad(options) { this.taskId = String(options.taskId || ''); this.warehouseId = String(options.warehouseId || '') },
|
||||
@@ -107,15 +104,17 @@
|
||||
if (!this.palletSetupReady) { this.error = this.palletSetupMessage; return }
|
||||
const code = String(value || this.boxCode || '').trim().toUpperCase(); if (!code || this.loading) return
|
||||
this.boxCode = code; this.loading = true; this.resetMessages(); this.carton = null
|
||||
try { this.carton = await resolveDropshipCarton(this.taskId, this.warehouseId, code); this.actualQuantity = String(this.carton.expectedSkuQuantity || '') }
|
||||
try { this.carton = await resolveDropshipCarton(this.taskId, this.warehouseId, code); this.skuCounts = (this.carton.skuLines || []).map((line) => ({ ...line, actualQuantity: String(line.expectedQuantity || ''), discrepancyRemark: '' })) }
|
||||
catch (error) { this.error = formatApiError(error, '箱码解析失败') }
|
||||
finally { this.loading = false }
|
||||
},
|
||||
async countCarton() {
|
||||
const result = await this.runMutation(() => countDropshipCarton(this.taskId, { warehouseId: this.warehouseId, boxCode: this.carton.boxCode, palletNo: this.currentPallet?.palletNo || null, actualSkuQuantity: Number(this.actualQuantity), discrepancyAction: this.quantityDiffers ? 'ACCEPT_VARIANCE' : 'MATCH', discrepancyRemark: this.quantityDiffers ? this.discrepancyRemark.trim() : null }), this.currentPallet ? '本箱已清点并归托' : '本箱已完成入库清点')
|
||||
if (result) { this.boxCode = ''; this.carton = null; this.actualQuantity = ''; this.discrepancyRemark = '' }
|
||||
const skuLines = this.skuCounts.map((line) => ({ inboundDetailId: line.inboundDetailId, skuCode: line.skuCode, actualQuantity: Number(line.actualQuantity), discrepancyAction: this.lineDiffers(line) ? 'ACCEPT_VARIANCE' : 'MATCH', discrepancyRemark: this.lineDiffers(line) ? String(line.discrepancyRemark || '').trim() : null }))
|
||||
const result = await this.runMutation(() => countDropshipCarton(this.taskId, { warehouseId: this.warehouseId, boxCode: this.carton.boxCode, palletNo: this.currentPallet?.palletNo || null, skuLines }), this.currentPallet ? '本箱已清点并归托' : '本箱已完成入库清点')
|
||||
if (result) { this.boxCode = ''; this.carton = null; this.skuCounts = [] }
|
||||
},
|
||||
adjustQuantity(step) { this.actualQuantity = String(Math.max(1, Number(this.actualQuantity || 0) + step)) },
|
||||
lineDiffers(line) { return Number(line.actualQuantity) !== Number(line.expectedQuantity) },
|
||||
adjustQuantity(index, step) { const line = this.skuCounts[index]; if (line) line.actualQuantity = String(Math.max(0, Number(line.actualQuantity || 0) + step)) },
|
||||
async selectPallet(value) {
|
||||
if (!this.palletSetupReady) { this.error = this.palletSetupMessage; return }
|
||||
const code = String(value || this.palletCode || '').trim().toUpperCase(); const pallet = (this.task?.pallets || []).find((item) => item.palletNo === code)
|
||||
@@ -137,7 +136,7 @@
|
||||
// #ifndef H5
|
||||
await new Promise((resolve, reject) => uni.openDocument({ filePath, fileType: 'pdf', showMenu: true, success: resolve, fail: reject }))
|
||||
// #endif
|
||||
this.notice = `${pallet.palletNo} 打印文件已打开`
|
||||
this.notice = `${pallet.palletNo} A4 托盘信息单已打开`
|
||||
} catch (error) { this.error = formatApiError(error, '托盘码打印失败') }
|
||||
finally { this.printing = false }
|
||||
},
|
||||
@@ -173,8 +172,8 @@
|
||||
.section-title { display: block; color: #12233f; font-size: 18px; font-weight: 700; }.section-desc { display: block; margin: 2px 0 8px; color: #667085; font-size: 13px; line-height: 18px; }.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 { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 6px; padding: 5px 7px; border: 0; border-radius: 0; color: #667085; font-size: 13px; background: #f6f9ff; }.compact-action { width: 86px; height: 34px; margin: 0; border: 0; border-radius: 6px; color: #fff; font-size: 13px; line-height: 34px; background: #1a73e8; }.compact-action[disabled] { opacity: .35; }.count-unavailable { padding: 12px 8px; color: #b42318; font-size: 13px; line-height: 18px; 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: 15px; font-weight: 700; }.field-label text { margin-left: 3px; color: #98a2b3; font-size: 12px; font-weight: 400; }
|
||||
.carton-card, .pallet-card { margin-top: 8px; padding: 10px 0 0; border: 0; border-top: 1px solid #e3eaf2; border-radius: 0; background: transparent; }.sku-label { display: block; color: #667085; font-size: 12px; }.sku-code { display: block; margin-top: 3px; color: #12233f; font-size: 19px; font-weight: 700; overflow-wrap: anywhere; }.pallet-meta { display: block; margin-top: 5px; color: #667085; font-size: 14px; line-height: 20px; }
|
||||
.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; }
|
||||
.carton-card, .pallet-card { margin-top: 8px; padding: 8px 0 0; border: 0; border-top: 1px solid #e3eaf2; border-radius: 0; background: transparent; }.sku-count-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid #edf1f5; }.sku-copy { min-width: 0; }.sku-label { display: block; color: #667085; font-size: 11px; }.sku-code { display: block; margin-top: 2px; color: #12233f; font-size: 16px; font-weight: 700; overflow-wrap: anywhere; }.pallet-meta { display: block; margin-top: 5px; color: #667085; font-size: 14px; line-height: 20px; }
|
||||
.quantity-stepper { display: grid; grid-template-columns: 38px 56px 38px; overflow: hidden; border: 1px solid #b8c6d8; border-radius: 8px; background: #fff; }.quantity-stepper button,.quantity-stepper input { box-sizing: border-box; height: 40px; margin: 0; border: 0; border-radius: 0; text-align: center; background: #fff; }.quantity-stepper button { color: #155eef; font-size: 20px; line-height: 40px; }.quantity-stepper input { width: 56px; border-right: 1px solid #dce4ed; border-left: 1px solid #dce4ed; color: #12233f; font-size: 15px; }.difference-field { grid-column: 1 / -1; margin-top: 0; }.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; }
|
||||
.pallet-code { color: #12233f; font-size: 19px; font-weight: 700; overflow-wrap: anywhere; }.text-button { min-width: 58px; height: 40px; border: 0; color: #155eef; font-size: 14px; line-height: 40px; background: transparent; }.empty { padding: 18px 0 8px; color: #98a2b3; font-size: 14px; text-align: center; }
|
||||
.palletization-page :deep(.scan-target-value) { font-size: 17px; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<view class="module-hero" :class="`module-hero-${currentModule.key}`"><view class="module-hero-icon">{{ currentModule.icon }}</view><view><text class="module-hero-title">{{ currentModule.name }}</text><text class="module-hero-desc">{{ currentModule.description }}</text></view></view>
|
||||
<text class="group-title">业务功能</text>
|
||||
<view class="operation-list"><button v-for="operation in currentModule.operations" :key="operation.name" class="operation-item" :class="{ 'operation-item-disabled': !operation.available }" :disabled="!operation.available" @tap="openOperation(operation)"><view class="operation-icon">{{ operation.icon }}</view><view class="operation-info"><text class="operation-name">{{ operation.name }}</text><text class="operation-desc">{{ operation.description }}</text></view><text v-if="operation.available" class="operation-count">{{ overview.pendingBoxCount }}</text><text v-else class="operation-prototype">未开放</text><text v-if="operation.available" class="operation-arrow">›</text></button></view>
|
||||
<view class="module-empty-note">当前展示“一件代发”“出库拣货”和“库位查询”。</view>
|
||||
<view class="module-empty-note">当前展示“一件代发”和“出库拣货”。</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user