feat(scan): 出库/借库扫码增加未扫清单弹窗

- 扫码进度条旁新增「未扫清单 (N)」按钮(有未扫满物料时显示)
- 弹窗列出计划中未扫满的物料:名称/规格/计划数量/已扫数量/待扫数量
- 提供「去扫码」快捷跳转,方便补扫漏掉的物料
- 未扫满 = 计划数量 > 已扫数量(含完全未扫和扫了一部分的情况)
This commit is contained in:
yueli
2026-08-31 14:53:20 +08:00
parent 7e281cf285
commit f5aa2f481b
2 changed files with 126 additions and 0 deletions

View File

@ -80,6 +80,13 @@
<span>扫码进度</span>
<el-tag type="success" size="small">{{ scanScannedTypes }}/{{ scanTotalTypes }} </el-tag>
<el-tag type="primary" size="small">{{ scanScannedQty }}/{{ scanTotalQty }} </el-tag>
<el-button
v-if="unscannedCount > 0"
type="warning" plain size="small" style="margin-left: auto;"
@click="showUnscannedDialog = true"
>
未扫清单 ({{ unscannedCount }})
</el-button>
</div>
<el-progress
:percentage="scanTotalQty > 0 ? Math.min(100, Math.round(scanScannedQty / scanTotalQty * 100)) : 0"
@ -255,6 +262,37 @@
</div>
</div>
<!-- 未扫清单弹窗 -->
<el-dialog v-model="showUnscannedDialog" title="未扫清单" width="600px" destroy-on-close>
<el-alert
v-if="unscannedList.length > 0"
:title="`以下 ${unscannedList.length} 种物料还未扫满,请补扫:`"
type="warning" :closable="false" show-icon style="margin-bottom: 12px;"
/>
<el-table :data="unscannedList" border size="small" max-height="400">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column prop="name" label="物料名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="spec" label="规格型号" min-width="120" show-overflow-tooltip />
<el-table-column label="计划" width="70" align="center">
<template #default="{ row }">{{ row.planQty }}</template>
</el-table-column>
<el-table-column label="已扫" width="70" align="center">
<template #default="{ row }">
<span style="color: #67C23A;">{{ row.scannedQty }}</span>
</template>
</el-table-column>
<el-table-column label="待扫" width="70" align="center">
<template #default="{ row }">
<span style="color: #F56C6C; font-weight: bold;">{{ row.remaining }}</span>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="showUnscannedDialog = false">关闭</el-button>
<el-button type="primary" @click="showUnscannedDialog = false; showCamera = true">去扫码</el-button>
</template>
</el-dialog>
<el-dialog
v-model="showSignatureDialog"
fullscreen
@ -375,6 +413,31 @@ const scanScannedQty = computed(() =>
const scanScannedTypes = computed(() => cartItems.value.length)
const scanTotalTypes = computed(() => plannedItems.value.length)
// ★ 未扫清单:计划中还没扫满的物料
const unscannedList = computed(() => {
return plannedItems.value.map(plan => {
const planQty = Number(plan.quantity) || 0
// 已扫数量(按名称+规格匹配)
const scannedQty = cartItems.value
.filter(ci => {
const ciName = (ci.name || '').trim()
const ciSpec = (ci.spec_model || ci.standard || '').trim()
return ciName === (plan.name || '').trim() &&
ciSpec === (plan.spec_model || '').trim()
})
.reduce((sum, ci) => sum + (Number(ci.out_quantity) || 0), 0)
return {
name: plan.name || '',
spec: plan.spec_model || '',
planQty,
scannedQty,
remaining: Math.max(0, planQty - scannedQty)
}
}).filter(item => item.remaining > 0) // 只保留未扫满的
})
const unscannedCount = computed(() => unscannedList.value.length)
const showUnscannedDialog = ref(false)
// ★ 加载已审批通过的申请单
const loadApprovalRequests = async () => {
requestsLoading.value = true

View File

@ -71,6 +71,13 @@
<span>扫码进度</span>
<el-tag type="success" size="small">{{ scanScannedTypes }}/{{ scanTotalTypes }} </el-tag>
<el-tag type="primary" size="small">{{ scanScannedQty }}/{{ scanTotalQty }} </el-tag>
<el-button
v-if="unscannedCount > 0"
type="warning" plain size="small" style="margin-left: auto;"
@click="showUnscannedDialog = true"
>
未扫清单 ({{ unscannedCount }})
</el-button>
</div>
<el-progress
:percentage="scanTotalQty > 0 ? Math.min(100, Math.round(scanScannedQty / scanTotalQty * 100)) : 0"
@ -239,6 +246,37 @@
</div>
</div>
<!-- 未扫清单弹窗 -->
<el-dialog v-model="showUnscannedDialog" title="未扫清单" width="600px" destroy-on-close>
<el-alert
v-if="unscannedList.length > 0"
:title="`以下 ${unscannedList.length} 种物料还未扫满,请补扫:`"
type="warning" :closable="false" show-icon style="margin-bottom: 12px;"
/>
<el-table :data="unscannedList" border size="small" max-height="400">
<el-table-column type="index" label="#" width="40" align="center" />
<el-table-column prop="name" label="物料名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="spec" label="规格型号" min-width="120" show-overflow-tooltip />
<el-table-column label="计划" width="70" align="center">
<template #default="{ row }">{{ row.planQty }}</template>
</el-table-column>
<el-table-column label="已扫" width="70" align="center">
<template #default="{ row }">
<span style="color: #67C23A;">{{ row.scannedQty }}</span>
</template>
</el-table-column>
<el-table-column label="待扫" width="70" align="center">
<template #default="{ row }">
<span style="color: #F56C6C; font-weight: bold;">{{ row.remaining }}</span>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="showUnscannedDialog = false">关闭</el-button>
<el-button type="primary" @click="showUnscannedDialog = false; showCamera = true">去扫码</el-button>
</template>
</el-dialog>
<el-dialog
v-model="showSignatureDialog"
fullscreen
@ -340,6 +378,31 @@ const scanScannedQty = computed(() =>
const scanScannedTypes = computed(() => cartItems.value.length)
const scanTotalTypes = computed(() => plannedItems.value.length)
// ★ 未扫清单:计划中还没扫满的物料
const unscannedList = computed(() => {
return plannedItems.value.map(plan => {
const planQty = Number(plan.quantity) || 0
// 已扫数量(按名称+规格匹配)
const scannedQty = cartItems.value
.filter(ci => {
const ciName = (ci.name || '').trim()
const ciSpec = (ci.spec_model || ci.standard || '').trim()
return ciName === (plan.name || '').trim() &&
ciSpec === (plan.spec_model || '').trim()
})
.reduce((sum, ci) => sum + (Number(ci.out_quantity) || 0), 0)
return {
name: plan.name || '',
spec: plan.spec_model || '',
planQty,
scannedQty,
remaining: Math.max(0, planQty - scannedQty)
}
}).filter(item => item.remaining > 0) // 只保留未扫满的
})
const unscannedCount = computed(() => unscannedList.value.length)
const showUnscannedDialog = ref(false)
// ★ 加载已通过审批的借库申请单列表
const loadApprovalRequests = async () => {
requestsLoading.value = true