diff --git a/src/api/mes/moldget.js b/src/api/mes/moldget.js index dbeb3b2..3d4be81 100644 --- a/src/api/mes/moldget.js +++ b/src/api/mes/moldget.js @@ -80,3 +80,10 @@ export function getWarehouseAreaSimpleList(warehouseId) { params: { warehouseId } }) } +export function getWarehouseArea(id) { + return request({ + url: '/admin-api/erp/warehouse-area/get', + method: 'get', + params: { id } + }) +} diff --git a/src/locales/en-US.js b/src/locales/en-US.js index 8869a53..9b75f6e 100644 --- a/src/locales/en-US.js +++ b/src/locales/en-US.js @@ -1585,6 +1585,7 @@ export default { enterPackageCount: 'Enter packages', selectWarehouse: 'Select warehouse', selectArea: 'Select area', + selectAreaTitle: 'Select Area', selectUnit: 'Select unit', selectWarehouseFirst: 'Select warehouse first', emptyArea: 'No areas', diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js index 9a6f340..c19499e 100644 --- a/src/locales/zh-CN.js +++ b/src/locales/zh-CN.js @@ -1551,7 +1551,7 @@ export default { selectTaskFirst: '请先选择任务单', selectProductFirst: '请先选择产品', emptyTaskProducts: '当前任务单暂无产品', - completePalletInfo: '请完善托盘仓库/库位/包数', + completePalletInfo: '请完善托盘仓库/库区/包数', productAdded: '已添加产品', taskProductLoadFailed: '任务产品加载失败', workOrderNo: '工单号', @@ -1588,6 +1588,7 @@ export default { enterPackageCount: '请输入包数', selectWarehouse: '请选择仓库', selectArea: '请选择库区', + selectAreaTitle: '选择库区', selectUnit: '请选择单位', selectWarehouseFirst: '请先选择仓库', emptyArea: '暂无库区', @@ -1779,7 +1780,7 @@ export default { selectPallet: '请选择托盘', selectPalletFirst: '请先选择托盘', selectProductFirst: '请先选择产品', - completePalletInfo: '请完善托盘仓库/库位/包数', + completePalletInfo: '请完善托盘仓库/库区/包数', productAdded: '已添加产品', code: '编码', packagingScheme: '包装方案', diff --git a/src/pages_function/pages/productInbound/create.vue b/src/pages_function/pages/productInbound/create.vue index 2bee950..50e8c56 100644 --- a/src/pages_function/pages/productInbound/create.vue +++ b/src/pages_function/pages/productInbound/create.vue @@ -90,10 +90,10 @@ - + {{ textValue(item.productName) }} diff --git a/src/pages_function/pages/productInbound/productConfirm.vue b/src/pages_function/pages/productInbound/productConfirm.vue index 95c65fa..707e2a7 100644 --- a/src/pages_function/pages/productInbound/productConfirm.vue +++ b/src/pages_function/pages/productInbound/productConfirm.vue @@ -152,34 +152,52 @@ - + {{ t('productInbound.warehouse') }} - + {{ pallet.warehouseId ? getWarehouseName(pallet.warehouseId) : t('productInbound.choose') }} - {{ t('productInbound.location') }} - - - {{ pallet.areaId ? getAreaName(pallet.warehouseId, pallet.areaId) : t('productInbound.choose') }} - + {{ t('productInbound.area') }} + + + + + + - + + + + {{ t('productInbound.selectAreaTitle') }} + + + + + {{ t('productInbound.selectAreaTitle') }} + + - - - + {{ t('productInbound.packageCount') }} - + - + {{ t('productInbound.pieceCount') }} - {{ getPalletItemCount(pallet) }} + {{ getPalletItemCount(pallet) }} @@ -214,7 +232,7 @@ import { useI18n } from 'vue-i18n' import NavBar from '@/components/common/NavBar.vue' import { getPallet, getTaskDefaultPackagingSchemes } from '@/api/mes/productInbound' import { getProduct } from '@/api/erp/productInfo' -import { getWarehouseAreaSimpleList, getWarehouseSimpleList } from '@/api/mes/moldget' +import { getWarehouseArea, getWarehouseAreaSimpleList, getWarehouseSimpleList } from '@/api/mes/moldget' const { t } = useI18n() const product = ref({}) @@ -234,10 +252,12 @@ const warehouseAreaMap = ref({}) const taskProductList = ref([]) const PRODUCT_CATEGORY_TYPE = 1 +const WAREHOUSE_CATEGORY_TYPE = 1 const SCAN_AUTO_SEARCH_DELAY = 300 let taskProductScanTimer = null let productScanTimer = null let palletScanTimer = null +const palletAreaScanTimers = {} const productName = computed(() => selectedTaskProduct.value?.productName || product.value.name || '') const productId = computed(() => selectedTaskProduct.value?.productId || product.value.id) @@ -329,7 +349,8 @@ async function hydrateEditItem(item) { } selectedPallets.value = normalizeEditPallets(item) await loadAreasForPallets(selectedPallets.value) -}function textValue(v) { +} +function textValue(v) { if (v === 0) return '0' if (v == null) return '-' const s = String(v).trim() @@ -352,6 +373,12 @@ function getAreaName(warehouseId, areaId) { function getAreaOptions(warehouseId) { return warehouseAreaMap.value[String(warehouseId)] || [] } +function getWarehouseAreaLabel(area) { + return area?.areaName || area?.name || area?.areaCode || String(area?.id || '') +} +function getPalletAreaDisplayName(pallet) { + return getAreaName(pallet?.warehouseId, pallet?.areaId) || pallet?.areaName || pallet?.warehouseAreaName || '' +} function getWarehouseIndex(id) { const i = warehouseOptions.value.findIndex((item) => String(item.value) === String(id)) return i >= 0 ? i : 0 @@ -370,13 +397,31 @@ async function onPalletWarehouseChange(e, index) { if (!opt) return selectedPallets.value[index].warehouseId = opt.value selectedPallets.value[index].areaId = null + selectedPallets.value[index].areaName = '' + selectedPallets.value[index].warehouseAreaName = '' + selectedPallets.value[index]._areaScanInput = '' await loadAreasForWarehouse(opt.value) } +function applySelectedArea(index, opt) { + if (!opt || !selectedPallets.value[index]) return + selectedPallets.value[index].areaId = opt.value + selectedPallets.value[index].areaName = opt.label + selectedPallets.value[index]._areaScanInput = '' +} function onPalletAreaChange(e, index) { const areas = getAreaOptions(selectedPallets.value[index].warehouseId) - const opt = areas[Number(e.detail.value)] - if (!opt) return - selectedPallets.value[index].areaId = opt.value + applySelectedArea(index, areas[Number(e.detail.value)]) +} +function onAreaPickerUnavailable(index) { + const pallet = selectedPallets.value[index] + if (!pallet?.warehouseId) { + uni.showToast({ title: '\u8bf7\u5148\u9009\u62e9\u4ed3\u5e93', icon: 'none' }) + return + } + uni.showToast({ title: t('productInbound.emptyArea'), icon: 'none' }) +} +function onSelectAreaWithoutWarehouse() { + uni.showToast({ title: '\u8bf7\u5148\u9009\u62e9\u4ed3\u5e93', icon: 'none' }) } function onPalletCountInput(e, index) { const v = Number(e.detail.value) @@ -424,6 +469,11 @@ function getPalletScanCode(value) { const match = text.match(/^PALLET[-:]?\s*(.+)$/i) return match ? match[1].trim() : '' } +function getWarehouseAreaScanCode(value) { + const text = String(value || '').trim() + const match = text.match(/^WAREHOUSE_AREA[-:]?\s*(.+)$/i) + return match ? match[1].trim() : '' +} function getProductIdValue(item) { return item?.id || item?.productId } @@ -603,6 +653,88 @@ async function onPalletScanConfirm() { uni.showToast({ title: t('productInbound.loadFailed'), icon: 'none' }) } } +function getPalletAreaTimerKey(index) { + const pallet = selectedPallets.value[index] + return String(pallet?.id || index) +} +function schedulePalletAreaScanSearch(value, index) { + if (!getWarehouseAreaScanCode(value)) return + const key = getPalletAreaTimerKey(index) + if (palletAreaScanTimers[key]) clearTimeout(palletAreaScanTimers[key]) + palletAreaScanTimers[key] = setTimeout(() => { + palletAreaScanTimers[key] = null + onPalletAreaScanConfirm(index) + }, SCAN_AUTO_SEARCH_DELAY) +} +function onPalletAreaScanInput(e, index) { + const pallet = selectedPallets.value[index] + if (!pallet) return + const value = e?.detail?.value ?? pallet._areaScanInput ?? '' + pallet._areaScanInput = value + schedulePalletAreaScanSearch(value, index) +} +async function findWarehouseAreaByScanCode(scanCode) { + const id = String(scanCode || '').trim() + if (!/^\d+$/.test(id)) return null + const res = await getWarehouseArea(id) + const detail = res?.data || res + return detail?.id ? detail : null +} +function cacheWarehouseArea(area) { + if (!area?.id || !area?.warehouseId) return + const key = String(area.warehouseId) + const current = warehouseAreaMap.value[key] || [] + const nextItem = { value: area.id, label: getWarehouseAreaLabel(area) } + const exists = current.some((item) => String(item.value) === String(area.id)) + warehouseAreaMap.value = { + ...warehouseAreaMap.value, + [key]: exists ? current.map((item) => String(item.value) === String(area.id) ? nextItem : item) : [...current, nextItem] + } +} +function isWarehouseAreaMatched(pallet, area) { + return Boolean(pallet?.warehouseId && area?.warehouseId && String(pallet.warehouseId) === String(area.warehouseId)) +} +function applyScannedWarehouseArea(index, area) { + const pallet = selectedPallets.value[index] + if (!pallet || !area?.id) return + cacheWarehouseArea(area) + pallet.areaId = area.id + pallet.areaName = getWarehouseAreaLabel(area) + pallet._areaScanInput = '' +} +async function onPalletAreaScanConfirm(index) { + const key = getPalletAreaTimerKey(index) + if (palletAreaScanTimers[key]) { + clearTimeout(palletAreaScanTimers[key]) + palletAreaScanTimers[key] = null + } + const pallet = selectedPallets.value[index] + if (!pallet) return + const scanCode = getWarehouseAreaScanCode(pallet._areaScanInput) + if (!scanCode) return + try { + uni.showLoading({ title: t('productInbound.loading'), mask: true }) + const matched = await findWarehouseAreaByScanCode(scanCode) + uni.hideLoading() + if (!matched?.id) { + uni.showToast({ title: '\u6ca1\u6709\u8fd9\u4e2a\u5e93\u533a', icon: 'none' }) + return + } + if (!pallet.warehouseId) { + uni.showToast({ title: '\u8bf7\u5148\u9009\u62e9\u4ed3\u5e93', icon: 'none' }) + return + } + if (!isWarehouseAreaMatched(pallet, matched)) { + pallet._areaScanInput = '' + uni.showToast({ title: '\u5e93\u533a\u4e0d\u5c5e\u4e8e\u5f53\u524d\u4ed3\u5e93', icon: 'none' }) + return + } + applyScannedWarehouseArea(index, matched) + } catch (e) { + uni.hideLoading() + uni.showToast({ title: t('productInbound.loadFailed'), icon: 'none' }) + } +} function goSelectPallet() { if (!productId.value) { uni.showToast({ title: t('productInbound.selectProductFirst'), icon: 'none' }) @@ -617,7 +749,7 @@ function removePallet(index) { } async function loadWarehouses() { try { - const res = await getWarehouseSimpleList() + const res = await getWarehouseSimpleList({ categoryType: WAREHOUSE_CATEGORY_TYPE }) const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : []) warehouseOptions.value = data.map((w) => ({ value: w.id, label: w.name || String(w.id || '') })) } catch (e) {} @@ -816,17 +948,21 @@ onShow(async () => { .pallet-top { display: flex; align-items: center; justify-content: space-between; gap: 12rpx; } .pallet-code { flex: 1; min-width: 0; font-size: 28rpx; color: #1f2937; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .remove-btn { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #fef2f2; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } -.pallet-loc { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; } -.loc-cell { min-width: 0; display: flex; flex-direction: column; gap: 6rpx; } +.pallet-field-list { display: flex; flex-direction: column; gap: 14rpx; } +.loc-cell { min-width: 0; display: flex; flex-direction: column; gap: 8rpx; } .loc-label { font-size: 22rpx; color: #9ca3af; } -.loc-picker { min-height: 64rpx; padding: 0 16rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; display: flex; align-items: center; justify-content: space-between; gap: 8rpx; box-sizing: border-box; } -.loc-value { flex: 1; min-width: 0; font-size: 25rpx; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.loc-placeholder { flex: 1; font-size: 25rpx; color: #9ca3af; } -.pallet-count-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; } -.count-cell { min-width: 0; display: flex; flex-direction: column; gap: 6rpx; } +.loc-picker { min-height: 68rpx; padding: 0 18rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; display: flex; align-items: center; justify-content: space-between; gap: 8rpx; box-sizing: border-box; } +.pallet-field-control { width: 100%; } +.loc-value { flex: 1; min-width: 0; font-size: 26rpx; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.loc-placeholder { flex: 1; font-size: 26rpx; color: #9ca3af; } +.area-select-row { width: 100%; } +.area-select-picker { flex-shrink: 0; width: 160rpx; display: block; } +.area-select-button { width: 160rpx; } +.area-select-button.disabled { background: #94a3b8; } .count-label { font-size: 22rpx; color: #9ca3af; } -.count-input { height: 64rpx; padding: 0 16rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; font-size: 26rpx; color: #1f2937; box-sizing: border-box; } -.count-value { height: 64rpx; line-height: 64rpx; padding: 0 16rpx; background: #f1f5f9; border-radius: 12rpx; font-size: 26rpx; color: #475569; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.count-input { height: 68rpx; padding: 0 18rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; font-size: 26rpx; color: #1f2937; box-sizing: border-box; } +.pallet-piece-row { min-height: 44rpx; display: flex; align-items: center; justify-content: space-between; gap: 16rpx; } +.pallet-piece-value { flex: 1; min-width: 0; font-size: 28rpx; font-weight: 600; color: #475569; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .quantity-summary { display: grid; grid-template-columns: 1fr 1fr; gap: 14rpx; } .quantity-item { padding: 18rpx; background: #eff6ff; border-radius: 14rpx; display: flex; flex-direction: column; align-items: center; gap: 6rpx; } .quantity-value { font-size: 34rpx; color: #1f4b79; font-weight: 700; }