feat:产品出库-产品添加扫码录入功能

master
黄伟杰 1 month ago
parent f1b6d2d3ec
commit 86c656b02f

@ -4,14 +4,20 @@
<view class="top-action-bar"> <view class="top-action-bar">
<view class="scan-input-row"> <view class="scan-input-row">
<input <view class="scan-input-wrap">
id="product-outbound-scan-input" <input
class="scan-input" id="product-outbound-scan-input"
v-model="scanCodeInput" class="scan-input"
placeholder="&#32418;&#22806;&#25195;&#30721;&#25110;&#36755;&#20837;&#20135;&#21697;&#30721;" v-model="scanCodeInput"
confirm-type="done" placeholder="请选择产品"
@confirm="onScanInputConfirm" confirm-type="done"
/> @input="onScanInput"
@confirm="onScanInputConfirm"
/>
<view class="scan-input-icon">
<uni-icons type="scan" size="20" color="#9ca3af"></uni-icons>
</view>
</view>
</view> </view>
<view class="scan-btn" @click="handleAddProduct"> <view class="scan-btn" @click="handleAddProduct">
<text class="btn-text">&#36873;&#25321;&#20135;&#21697;</text> <text class="btn-text">&#36873;&#25321;&#20135;&#21697;</text>
@ -160,7 +166,7 @@ import { onReady, onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue' import NavBar from '@/components/common/NavBar.vue'
import { createProductOutbound } from '@/api/mes/productOutbound' import { createProductOutbound } from '@/api/mes/productOutbound'
import { getProduct, getProductPage } from '@/api/erp/productInfo' import { getProduct } from '@/api/erp/productInfo'
import { setDefaultOperatorFromCurrentUser } from '@/utils/currentUser' import { setDefaultOperatorFromCurrentUser } from '@/utils/currentUser'
const { t } = useI18n() const { t } = useI18n()
@ -173,6 +179,9 @@ const attachmentList = ref([])
const scanCodeInput = ref('') const scanCodeInput = ref('')
const focusNoKeyboardRef = ref(null) const focusNoKeyboardRef = ref(null)
const keywordInputSelector = '#product-outbound-scan-input input, input#product-outbound-scan-input' const keywordInputSelector = '#product-outbound-scan-input input, input#product-outbound-scan-input'
const PRODUCT_CATEGORY_TYPE = 1
const SCAN_AUTO_SEARCH_DELAY = 300
let productScanTimer = null
const totalPalletCount = computed(() => itemList.value.reduce((sum, item) => sum + getPalletCount(item), 0)) const totalPalletCount = computed(() => itemList.value.reduce((sum, item) => sum + getPalletCount(item), 0))
const totalPackageCount = computed(() => itemList.value.reduce((sum, item) => sum + (Number(item.packageCount) || 0), 0)) const totalPackageCount = computed(() => itemList.value.reduce((sum, item) => sum + (Number(item.packageCount) || 0), 0))
@ -211,52 +220,46 @@ function goSelectOperator() {
getApp().globalData._productOutboundUserFrom = 'outbound' getApp().globalData._productOutboundUserFrom = 'outbound'
uni.navigateTo({ url: '/pages_function/pages/moldRepair/userSelect?field=operator&from=productOutbound' }) uni.navigateTo({ url: '/pages_function/pages/moldRepair/userSelect?field=operator&from=productOutbound' })
} }
function getProductMaterialScanCode(value) {
const text = String(value || '').trim()
const match = text.match(/^PRODUCTMATERIAL[-:]?\s*(.+)$/i)
return match ? match[1].trim() : ''
}
function scheduleProductScanSearch(value) {
if (!getProductMaterialScanCode(value)) return
if (productScanTimer) clearTimeout(productScanTimer)
productScanTimer = setTimeout(() => {
productScanTimer = null
onScanInputConfirm()
}, SCAN_AUTO_SEARCH_DELAY)
}
function onScanInput(e) {
scheduleProductScanSearch(e?.detail?.value ?? scanCodeInput.value)
}
function onScanInputConfirm() { function onScanInputConfirm() {
const code = scanCodeInput.value.trim() if (productScanTimer) {
clearTimeout(productScanTimer)
productScanTimer = null
}
const code = getProductMaterialScanCode(scanCodeInput.value)
if (!code) return if (!code) return
handleScanCode(code) handleScanCode(code)
} }
function normalizeProductPageList(res) {
const root = res && res.data !== undefined ? res.data : res
return Array.isArray(root) ? root : (root?.list || root?.rows || root?.records || [])
}
async function findProductByScanCode(code) { async function findProductByScanCode(code) {
const rawCode = String(code || '').trim() const id = String(code || '').trim()
const productId = rawCode.toUpperCase().startsWith('PRODUCT-') if (!/^\d+$/.test(id)) return null
? rawCode.replace(/PRODUCT-/i, '') const res = await getProduct(id)
: (/^\d+$/.test(rawCode) ? rawCode : null) const detail = res?.data || res
if (productId) { if (!detail?.id) return null
try { if (detail.categoryType && Number(detail.categoryType) !== PRODUCT_CATEGORY_TYPE) return null
const res = await getProduct(productId) return detail
const detail = res?.data || res
if (detail?.id) return detail
} catch (e) {}
}
for (let pageNo = 1; pageNo <= 5; pageNo += 1) {
const res = await getProductPage({ pageNo, pageSize: 100, categoryType: 1 })
const list = normalizeProductPageList(res)
const matched = list.find((item) =>
String(item.id || '') === rawCode ||
String(item.barCode || '') === rawCode ||
String(item.code || '') === rawCode
)
if (matched) {
try {
const detailRes = await getProduct(matched.id)
return { ...matched, ...(detailRes?.data || detailRes || {}) }
} catch (e) {
return matched
}
}
if (list.length < 100) break
}
return null
} }
async function handleScanCode(code) { async function handleScanCode(code) {
try { try {
const detail = await findProductByScanCode(code) const detail = await findProductByScanCode(code)
if (detail?.id) { if (detail?.id) {
getApp().globalData._productOutboundProductSelectResult = detail getApp().globalData._productOutboundProductSelectResult = detail
scanCodeInput.value = ''
uni.navigateTo({ url: '/pages_function/pages/productOutbound/productConfirm' }) uni.navigateTo({ url: '/pages_function/pages/productOutbound/productConfirm' })
} else { } else {
uni.showToast({ title: '\u672a\u627e\u5230\u4ea7\u54c1: ' + code, icon: 'none' }) uni.showToast({ title: '\u672a\u627e\u5230\u4ea7\u54c1: ' + code, icon: 'none' })
@ -427,8 +430,10 @@ onShow(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f7fb; } .page-container { min-height: 100vh; background: #f5f7fb; }
.top-action-bar { display: flex; align-items: center; gap: 16rpx; padding: 20rpx 24rpx; background: #ffffff; border-bottom: 1rpx solid #eef2f7; } .top-action-bar { display: flex; align-items: center; gap: 16rpx; padding: 20rpx 24rpx; background: #ffffff; border-bottom: 1rpx solid #eef2f7; }
.scan-input-row { flex: 1; } .scan-input-row { flex: 1; min-width: 0; }
.scan-input { width: 100%; height: 76rpx; padding: 0 20rpx; font-size: 26rpx; color: #333; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; } .scan-input-wrap { position: relative; width: 100%; }
.scan-input { width: 100%; height: 76rpx; padding: 0 64rpx 0 20rpx; font-size: 26rpx; color: #333; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; }
.scan-input-icon { position: absolute; right: 20rpx; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.scan-btn { display: flex; align-items: center; justify-content: center; height: 76rpx; padding: 0 24rpx; border-radius: 14rpx; background: #1f4b79; white-space: nowrap; flex-shrink: 0; } .scan-btn { display: flex; align-items: center; justify-content: center; height: 76rpx; padding: 0 24rpx; border-radius: 14rpx; background: #1f4b79; white-space: nowrap; flex-shrink: 0; }
.btn-text { font-size: 26rpx; font-weight: 600; color: #fff; } .btn-text { font-size: 26rpx; font-weight: 600; color: #fff; }
.detail-scroll { height: calc(100vh - 172rpx - 116rpx); } .detail-scroll { height: calc(100vh - 172rpx - 116rpx); }

Loading…
Cancel
Save