|
|
|
|
@ -4,14 +4,20 @@
|
|
|
|
|
|
|
|
|
|
<view class="top-action-bar">
|
|
|
|
|
<view class="scan-input-row">
|
|
|
|
|
<input
|
|
|
|
|
id="product-outbound-scan-input"
|
|
|
|
|
class="scan-input"
|
|
|
|
|
v-model="scanCodeInput"
|
|
|
|
|
placeholder="红外扫码或输入产品码"
|
|
|
|
|
confirm-type="done"
|
|
|
|
|
@confirm="onScanInputConfirm"
|
|
|
|
|
/>
|
|
|
|
|
<view class="scan-input-wrap">
|
|
|
|
|
<input
|
|
|
|
|
id="product-outbound-scan-input"
|
|
|
|
|
class="scan-input"
|
|
|
|
|
v-model="scanCodeInput"
|
|
|
|
|
placeholder="请选择产品"
|
|
|
|
|
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 class="scan-btn" @click="handleAddProduct">
|
|
|
|
|
<text class="btn-text">选择产品</text>
|
|
|
|
|
@ -160,7 +166,7 @@ import { onReady, onShow } from '@dcloudio/uni-app'
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
|
|
import { createProductOutbound } from '@/api/mes/productOutbound'
|
|
|
|
|
import { getProduct, getProductPage } from '@/api/erp/productInfo'
|
|
|
|
|
import { getProduct } from '@/api/erp/productInfo'
|
|
|
|
|
import { setDefaultOperatorFromCurrentUser } from '@/utils/currentUser'
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
@ -173,6 +179,9 @@ const attachmentList = ref([])
|
|
|
|
|
const scanCodeInput = ref('')
|
|
|
|
|
const focusNoKeyboardRef = ref(null)
|
|
|
|
|
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 totalPackageCount = computed(() => itemList.value.reduce((sum, item) => sum + (Number(item.packageCount) || 0), 0))
|
|
|
|
|
@ -211,52 +220,46 @@ function goSelectOperator() {
|
|
|
|
|
getApp().globalData._productOutboundUserFrom = 'outbound'
|
|
|
|
|
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() {
|
|
|
|
|
const code = scanCodeInput.value.trim()
|
|
|
|
|
if (productScanTimer) {
|
|
|
|
|
clearTimeout(productScanTimer)
|
|
|
|
|
productScanTimer = null
|
|
|
|
|
}
|
|
|
|
|
const code = getProductMaterialScanCode(scanCodeInput.value)
|
|
|
|
|
if (!code) return
|
|
|
|
|
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) {
|
|
|
|
|
const rawCode = String(code || '').trim()
|
|
|
|
|
const productId = rawCode.toUpperCase().startsWith('PRODUCT-')
|
|
|
|
|
? rawCode.replace(/PRODUCT-/i, '')
|
|
|
|
|
: (/^\d+$/.test(rawCode) ? rawCode : null)
|
|
|
|
|
if (productId) {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getProduct(productId)
|
|
|
|
|
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
|
|
|
|
|
const id = String(code || '').trim()
|
|
|
|
|
if (!/^\d+$/.test(id)) return null
|
|
|
|
|
const res = await getProduct(id)
|
|
|
|
|
const detail = res?.data || res
|
|
|
|
|
if (!detail?.id) return null
|
|
|
|
|
if (detail.categoryType && Number(detail.categoryType) !== PRODUCT_CATEGORY_TYPE) return null
|
|
|
|
|
return detail
|
|
|
|
|
}
|
|
|
|
|
async function handleScanCode(code) {
|
|
|
|
|
try {
|
|
|
|
|
const detail = await findProductByScanCode(code)
|
|
|
|
|
if (detail?.id) {
|
|
|
|
|
getApp().globalData._productOutboundProductSelectResult = detail
|
|
|
|
|
scanCodeInput.value = ''
|
|
|
|
|
uni.navigateTo({ url: '/pages_function/pages/productOutbound/productConfirm' })
|
|
|
|
|
} else {
|
|
|
|
|
uni.showToast({ title: '\u672a\u627e\u5230\u4ea7\u54c1: ' + code, icon: 'none' })
|
|
|
|
|
@ -427,8 +430,10 @@ onShow(() => {
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.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; }
|
|
|
|
|
.scan-input-row { flex: 1; }
|
|
|
|
|
.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-row { flex: 1; min-width: 0; }
|
|
|
|
|
.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; }
|
|
|
|
|
.btn-text { font-size: 26rpx; font-weight: 600; color: #fff; }
|
|
|
|
|
.detail-scroll { height: calc(100vh - 172rpx - 116rpx); }
|
|
|
|
|
|