feat: MOM 扫码入库对接 Track 产品身份证自动填充

- 半成品/成品入库弹窗顶部新增'扫码入库'按钮,打开扫码面板(扫码枪/摄像头)
- 扫码 Track 身份证后自动填充物料信息与序列号,不再二次搜索
- 后端新增 track_query_service(httpx 调 Track lookup)、track-lookup 接口
- 入库成功后 notify_track 通知 Track 闭环
This commit is contained in:
yueli
2026-09-01 13:52:56 +08:00
parent 9bc3c94dd1
commit bee037f925
13 changed files with 576 additions and 2 deletions

View File

@ -265,6 +265,18 @@
<div class="dialog-scroll-container">
<el-form :model="form" label-width="110px" ref="formRef" :rules="rules" size="default" class="stylish-form">
<!-- 扫码入库(Track 身份证自动带出物料、序列号与订单号) -->
<div v-if="dialogStatus === 'create'" class="scan-inbound-banner">
<el-button type="primary" size="large" @click="trackScanVisible = true">
<el-icon><Camera /></el-icon>
<span style="margin-left: 6px;">扫码入库</span>
</el-button>
<span class="scan-banner-tip">
<el-icon><InfoFilled /></el-icon>
扫描 Track 产品身份证,自动带出物料与序列号
</span>
</div>
<div class="form-card basic-card">
<div class="card-title">
<div style="display: flex; align-items: center; gap: 8px;">
@ -579,6 +591,8 @@
<!-- 智能扫码弹窗 -->
<SmartScannerDialog v-model="scannerDialogVisible" @confirm="handleScannerConfirm" />
<!-- 顶部扫码入库面板 (Track 身份证自动填充物料+序列号+订单号) -->
<TrackScanDialog v-model="trackScanVisible" inbound-type="product" @fill="handleTrackScanFill" />
</div>
</template>
@ -604,6 +618,7 @@ import { uploadFile, deleteFile } from '@/api/inbound/buy'
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
import WarehouseSelector from '@/components/WarehouseSelector.vue'
import SmartScannerDialog from '@/components/SmartScannerDialog.vue'
import TrackScanDialog from '@/components/TrackScanDialog.vue'
import { getLabelPreview, executePrint } from '@/api/common/print'
import { getWarehouseTree } from '@/api/common/warehouse'
import { usePasteUpload } from '@/hooks/usePasteUpload'
@ -741,6 +756,8 @@ const inspection_url = ref('')
// 智能扫码弹窗
const scannerDialogVisible = ref(false)
// 顶部扫码入库面板 (Track 身份证自动填充物料+序列号+订单号)
const trackScanVisible = ref(false)
// 库位级联选择器数据
const warehouseOptions = ref<any[]>([])
@ -1377,7 +1394,7 @@ const handleCameraConfirm = async (file: File) => {
}
};
// 智能扫码
// 智能扫码 (序列号旁,仅填序列号)
const openScanner = () => {
scannerDialogVisible.value = true
}
@ -1388,6 +1405,15 @@ const handleScannerConfirm = (result: string) => {
ElMessage.success('序列号已提取')
}
// Track 扫码入库:扫码面板扫到身份证后,自动带出物料基础信息 + 序列号 + 订单号
const handleTrackScanFill = async (data: { track: any; material: any }) => {
const { track, material } = data
await onMaterialSelected(material)
form.serial_number = track.serial_number
if (track.order_no) form.order_id = track.order_no
ElMessage.success('扫码入库信息已自动填充,请核对后补充库位/数量等')
}
// 快速基于此物料创建 BOM
const createBomForMaterial = () => {
if (!form.base_id) return ElMessage.warning('请先锁定物料基础信息')
@ -1525,6 +1551,10 @@ watch([() => form.unit_total_cost, () => form.in_quantity], ([unit, qty]) => {
:deep(.table-header-gray th) { background-color: #f8f9fb !important; color: #606266; }
.tag-sn { color: #409EFF; font-weight: bold; font-family: monospace; }
.stock-num { font-weight: bold; font-size: 15px; }
/* 扫码入库横幅 */
.scan-inbound-banner { background: linear-gradient(90deg, #ecf5ff, #f0f9eb); border: 1px dashed #409EFF; border-radius: 8px; padding: 14px 20px; margin-bottom: 20px; display: flex; align-items: center; gap: 14px; }
.scan-banner-tip { font-size: 13px; color: #606266; display: flex; align-items: center; gap: 4px; }
.form-card { background: #fff; border-radius: 8px; margin-bottom: 20px; border: 1px solid #e4e7ed; overflow: hidden; }
.card-title { background: #fcfcfc; padding: 10px 20px; border-bottom: 1px solid #ebeef5; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.card-title .icon { font-size: 18px; }

View File

@ -298,6 +298,18 @@
<div class="dialog-scroll-container">
<el-form :model="form" label-width="100px" ref="formRef" :rules="rules" size="default" class="stylish-form">
<!-- 扫码入库(Track 身份证自动带出物料与序列号) -->
<div v-if="dialogStatus === 'create'" class="scan-inbound-banner">
<el-button type="primary" size="large" @click="trackScanVisible = true">
<el-icon><Camera /></el-icon>
<span style="margin-left: 6px;">扫码入库</span>
</el-button>
<span class="scan-banner-tip">
<el-icon><InfoFilled /></el-icon>
扫描 Track 产品身份证,自动带出物料与序列号
</span>
</div>
<div class="form-card basic-card">
<div class="card-title">
<div style="display: flex; align-items: center; gap: 8px;">
@ -623,6 +635,8 @@
<!-- 智能扫码弹窗 -->
<SmartScannerDialog v-model="scannerDialogVisible" @confirm="handleScannerConfirm" />
<!-- 顶部扫码入库面板 (Track 身份证自动填充物料+序列号) -->
<TrackScanDialog v-model="trackScanVisible" inbound-type="semi" @fill="handleTrackScanFill" />
</div>
</template>
@ -648,6 +662,7 @@ import { uploadFile, deleteFile } from '@/api/inbound/buy'
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
import WarehouseSelector from '@/components/WarehouseSelector.vue'
import SmartScannerDialog from '@/components/SmartScannerDialog.vue'
import TrackScanDialog from '@/components/TrackScanDialog.vue'
import {getLabelPreview, executePrint} from '@/api/common/print'
import { getWarehouseTree } from '@/api/common/warehouse'
import { usePasteUpload } from '@/hooks/usePasteUpload'
@ -808,6 +823,8 @@ const quality_report_url = ref('')
// 智能扫码弹窗
const scannerDialogVisible = ref(false)
// 顶部扫码入库面板 (Track 身份证自动填充物料+序列号)
const trackScanVisible = ref(false)
// 库位级联选择器数据
const warehouseOptions = ref<any[]>([])
@ -1470,7 +1487,7 @@ const handleCameraConfirm = async (file: File) => {
}
};
// 智能扫码
// 智能扫码 (序列号旁,仅填序列号)
const openScanner = () => {
scannerDialogVisible.value = true
}
@ -1481,6 +1498,14 @@ const handleScannerConfirm = (result: string) => {
ElMessage.success('序列号已提取')
}
// Track 扫码入库:扫码面板扫到身份证后,自动带出物料基础信息 + 序列号
const handleTrackScanFill = async (data: { track: any; material: any }) => {
const { track, material } = data
await onMaterialSelected(material)
form.serial_number = track.serial_number
ElMessage.success('扫码入库信息已自动填充,请核对后补充库位/数量等')
}
// 快速基于此物料创建 BOM
const createBomForMaterial = () => {
if (!form.base_id) return ElMessage.warning('请先锁定物料基础信息')
@ -1625,6 +1650,10 @@ onMounted(() => {
/* [修改] 增加 min-height */
.dialog-scroll-container { padding: 20px; max-height: 70vh; overflow-y: auto; overflow-x: hidden; min-height: 450px; }
/* 扫码入库横幅 */
.scan-inbound-banner { background: linear-gradient(90deg, #ecf5ff, #f0f9eb); border: 1px dashed #409EFF; border-radius: 8px; padding: 14px 20px; margin-bottom: 20px; display: flex; align-items: center; gap: 14px; }
.scan-banner-tip { font-size: 13px; color: #606266; display: flex; align-items: center; gap: 4px; }
.stylish-form .form-card { background: #fff; border-radius: 8px; border: 1px solid #e4e7ed; margin-bottom: 20px; }
.card-title { background: #fcfcfc; padding: 12px 20px; border-bottom: 1px solid #ebeef5; font-weight: 600; font-size: 15px; color: #303133; display: flex; align-items: center; }
.card-title .icon { margin-right: 8px; font-size: 18px; color: #409EFF; }