From 51dc76aa10c23dde24cada6cd68f90c060ed3927 Mon Sep 17 00:00:00 2001
From: zhongwenkai <3478244299@qq.com>
Date: Wed, 17 Jun 2026 11:05:51 +0800
Subject: [PATCH 1/3] =?UTF-8?q?style:=20=E5=A4=87=E4=BB=B6=E5=87=BA?=
=?UTF-8?q?=E5=85=A5=E5=BA=93=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/mes/equipment.js | 9 +
src/api/mes/sparepart.js | 9 +
src/api/mes/sparepartOutbound.js | 9 +
.../pages/sparepartInbound/create.vue | 73 +++++--
.../pages/sparepartOutbound/create.vue | 196 +++++++++++++++---
5 files changed, 252 insertions(+), 44 deletions(-)
diff --git a/src/api/mes/equipment.js b/src/api/mes/equipment.js
index b7bccd2..c62fb6e 100644
--- a/src/api/mes/equipment.js
+++ b/src/api/mes/equipment.js
@@ -31,3 +31,12 @@ export function getEquipmentRepairListByDeviceId(deviceId, params = {}) {
params: { deviceId, ...params }
})
}
+
+// 保养工单分页(planType=2为保养)
+export function getMaintenanceTicketPage(params = {}) {
+ return request({
+ url: '/admin-api/mes/ticket-management/page',
+ method: 'get',
+ params: { planType: '2', pageSize: 100, ...params }
+ })
+}
diff --git a/src/api/mes/sparepart.js b/src/api/mes/sparepart.js
index 1afc49a..161bb98 100644
--- a/src/api/mes/sparepart.js
+++ b/src/api/mes/sparepart.js
@@ -20,3 +20,12 @@ export function getSparepartDetail(id) {
params: { id }
})
}
+
+// 备件库存数量
+export function getSparepartStockCount(productId) {
+ return request({
+ url: '/admin-api/erp/stock/get-count',
+ method: 'get',
+ params: { productId }
+ })
+}
diff --git a/src/api/mes/sparepartOutbound.js b/src/api/mes/sparepartOutbound.js
index 5325f84..59404ae 100644
--- a/src/api/mes/sparepartOutbound.js
+++ b/src/api/mes/sparepartOutbound.js
@@ -1,5 +1,14 @@
import request from '@/utils/request'
+// 备件出库单创建
+export function createSparepartOutbound(data) {
+ return request({
+ url: '/admin-api/erp/stock-out/create',
+ method: 'post',
+ data: { ...data, outType: '备件出库' }
+ })
+}
+
// 备件出库单分页查询
export function getSparepartOutboundPage(params = {}) {
return request({
diff --git a/src/pages_function/pages/sparepartInbound/create.vue b/src/pages_function/pages/sparepartInbound/create.vue
index 6bafaee..c2d79e6 100644
--- a/src/pages_function/pages/sparepartInbound/create.vue
+++ b/src/pages_function/pages/sparepartInbound/create.vue
@@ -1,4 +1,4 @@
-
+n
@@ -51,7 +51,7 @@
当前库存:
- {{ textValue(selectedSparepart.stock) }}{{ textUnit(selectedSparepart.minStockUnitName) }}
+ {{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
@@ -65,7 +65,7 @@
库存单位:
- {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName) }}
+ {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
@@ -235,13 +235,27 @@ import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getSimpleUserList, getWarehouseSimpleList, getWarehouseAreaSimpleList } from '@/api/mes/moldget'
import { createSparepartInbound } from '@/api/mes/sparepartInbound'
-import { getSparepartDetail } from '@/api/mes/sparepart'
+import { getSparepartDetail, getSparepartStockCount } from '@/api/mes/sparepart'
const { t } = useI18n()
const selectedSparepart = ref({})
const inboundQty = ref(null)
+// 获取备件库存数量(汇总所有仓库)
+async function loadStockCount() {
+ const id = selectedSparepart.value.id
+ if (!id) return
+ try {
+ const res = await getSparepartStockCount(id)
+ const count = res?.data ?? res
+ selectedSparepart.value.count = (count != null) ? Number(count) : 0
+ } catch (e) {
+ console.error('获取库存失败:', e)
+ selectedSparepart.value.count = 0
+ }
+}
+
// 备件默认供应商(从备件数据中取)
const defaultSupplierName = computed(() => {
const suppliers = selectedSparepart.value.suppliers
@@ -317,12 +331,41 @@ function stockUnitLabel(item) {
function handleScan() {
uni.scanCode({
- onlyFromCamera: true,
+ onlyFromCamera: false,
scanType: ['barCode', 'qrCode'],
- success: (res) => {
- console.log('扫码结果:', res.result)
- // TODO: 根据扫码结果查询备件信息并添加到列表
- uni.showToast({ title: '扫码成功: ' + res.result, icon: 'none' })
+ success: async (res) => {
+ const code = (res.result || '').trim()
+ if (!code) return
+ console.log('[备件入库] 扫码结果:', code)
+ // 备件二维码格式: SPARE-{备件ID}
+ let sparepartId = null
+ if (code.toUpperCase().startsWith('SPARE-')) {
+ sparepartId = code.replace(/SPARE-/i, '')
+ } else {
+ // 兼容纯数字或末尾数字
+ const idMatch = code.match(/(\d+)$/)
+ if (idMatch) sparepartId = idMatch[1]
+ }
+ if (!sparepartId) {
+ uni.showToast({ title: '无法识别备件码', icon: 'none' })
+ return
+ }
+ try {
+ const res = await getSparepartDetail(sparepartId)
+ const detail = res?.data || res
+ if (detail && detail.id) {
+ selectedSparepart.value = detail
+ inboundQty.value = null
+ loadStockCount()
+ console.log('[备件入库] 扫码加载备件成功:', detail.name)
+ uni.showToast({ title: '已加载: ' + (detail.name || ''), icon: 'success', duration: 1500 })
+ } else {
+ uni.showToast({ title: '未找到备件: ' + sparepartId, icon: 'none' })
+ }
+ } catch (e) {
+ console.error('[备件入库] 扫码查询备件失败:', e)
+ uni.showToast({ title: t('functionCommon.scanFailed'), icon: 'none' })
+ }
},
fail: () => {
uni.showToast({ title: t('functionCommon.scanFailed'), icon: 'none' })
@@ -491,14 +534,14 @@ onShow(async () => {
selectedSparepart.value = selectResult
console.log('[sparepartInbound] 已选备件 suppliers:', JSON.stringify(selectResult.suppliers))
console.log('[sparepartInbound] 已选备件 defaultSupplierId:', selectResult.defaultSupplierId)
- // 补充获取备件详情(含供应商、图片等)
+ // 补充获取备件详情(含供应商、库存等完整信息)
if (selectResult.id) {
try {
const res = await getSparepartDetail(selectResult.id)
const detail = res?.data || res
- if (detail && detail.suppliers) {
- console.log('[sparepartInbound] 详情接口 suppliers:', JSON.stringify(detail.suppliers))
+ if (detail) {
selectedSparepart.value = { ...selectedSparepart.value, ...detail }
+ loadStockCount()
}
} catch (e) {
console.error('获取备件详情失败:', e)
@@ -819,7 +862,7 @@ onHide(() => {
top: 100%;
left: 0;
right: 0;
- z-index: 99;
+ z-index: 200;
margin-top: 4rpx;
background: #fff;
border: 1rpx solid #e0e0e0;
@@ -948,7 +991,7 @@ onHide(() => {
top: 68rpx;
left: 0;
right: 0;
- z-index: 99;
+ z-index: 200;
background: #fff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
@@ -957,7 +1000,7 @@ onHide(() => {
}
.dropdown-scroll {
- max-height: 360rpx;
+ height: 360rpx;
}
.dropdown-item {
diff --git a/src/pages_function/pages/sparepartOutbound/create.vue b/src/pages_function/pages/sparepartOutbound/create.vue
index 005d667..d105571 100644
--- a/src/pages_function/pages/sparepartOutbound/create.vue
+++ b/src/pages_function/pages/sparepartOutbound/create.vue
@@ -48,7 +48,7 @@
当前库存:
- {{ textValue(selectedSparepart.stock) }}{{ textUnit(selectedSparepart.minStockUnitName) }}
+ {{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
@@ -60,7 +60,7 @@
库存单位:
- {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName) }}
+ {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
@@ -92,6 +92,14 @@
保养领用
+
+
+ 其他出库
+
@@ -101,6 +109,7 @@
+
关联设备
@@ -110,15 +119,17 @@
-
+
{{ item.label }}
✓
- 暂无设备数据
+ 加载中...
+ {{ selectedPurpose === 'maintain' ? '暂无保养设备' : '暂无设备数据' }}
+
{{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}
@@ -218,9 +229,10 @@ import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getSimpleUserList } from '@/api/mes/moldget'
import { getDeviceLedgerList } from '@/api/mes/moldoperate'
-import { getEquipmentRepairListByDeviceId, getEquipmentMaintenanceByDeviceId } from '@/api/mes/equipment'
+import { getEquipmentRepairListByDeviceId, getMaintenanceTicketPage } from '@/api/mes/equipment'
import { getDvRepairPage } from '@/api/mes/dvrepair'
-import { getSparepartDetail } from '@/api/mes/sparepart'
+import { getSparepartDetail, getSparepartStockCount } from '@/api/mes/sparepart'
+import { createSparepartOutbound } from '@/api/mes/sparepartOutbound'
const { t } = useI18n()
@@ -228,6 +240,19 @@ const selectedSparepart = ref({})
const outboundQty = ref(null)
const selectedPurpose = ref('repair')
+async function loadStockCount() {
+ const id = selectedSparepart.value.id
+ if (!id) return
+ try {
+ const res = await getSparepartStockCount(id)
+ const count = res?.data ?? res
+ selectedSparepart.value.count = (count != null) ? Number(count) : 0
+ } catch (e) {
+ console.error('获取库存失败:', e)
+ selectedSparepart.value.count = 0
+ }
+}
+
function setPurpose(value) {
selectedPurpose.value = value
// 切换用途时清空关联信息
@@ -251,6 +276,18 @@ const selectedRepairOrder = ref(null)
const maintainOrderOptions = ref([])
const selectedMaintainOrder = ref(null)
+// 保养模式:有保养记录的设备ID集合
+const maintainDeviceIds = ref(new Set())
+const deviceLoading = ref(false)
+
+// 根据模式过滤设备列表
+const filteredDeviceOptions = computed(() => {
+ if (selectedPurpose.value === 'maintain') {
+ return deviceOptions.value.filter(d => maintainDeviceIds.value.has(d.value))
+ }
+ return deviceOptions.value
+})
+
// 通用的订单下拉显示状态(根据当前用途自动切换)
const showOrderDropdown = ref(false)
const currentOrder = computed(() =>
@@ -299,11 +336,37 @@ function stockUnitLabel(item) {
function handleScan() {
uni.scanCode({
- onlyFromCamera: true,
+ onlyFromCamera: false,
scanType: ['barCode', 'qrCode'],
- success: (res) => {
- console.log('扫码结果:', res.result)
- uni.showToast({ title: '扫码成功: ' + res.result, icon: 'none' })
+ success: async (res) => {
+ const code = (res.result || '').trim()
+ if (!code) return
+ let sparepartId = null
+ if (code.toUpperCase().startsWith('SPARE-')) {
+ sparepartId = code.replace(/SPARE-/i, '')
+ } else {
+ const idMatch = code.match(/(\d+)$/)
+ if (idMatch) sparepartId = idMatch[1]
+ }
+ if (!sparepartId) {
+ uni.showToast({ title: '无法识别备件码', icon: 'none' })
+ return
+ }
+ try {
+ const apiRes = await getSparepartDetail(sparepartId)
+ const detail = apiRes?.data || apiRes
+ if (detail && detail.id) {
+ selectedSparepart.value = detail
+ outboundQty.value = null
+ loadStockCount()
+ uni.showToast({ title: '已加载: ' + (detail.name || ''), icon: 'success', duration: 1500 })
+ } else {
+ uni.showToast({ title: '未找到备件: ' + sparepartId, icon: 'none' })
+ }
+ } catch (e) {
+ console.error('[备件出库] 扫码查询备件失败:', e)
+ uni.showToast({ title: '扫码失败', icon: 'none' })
+ }
},
fail: () => {
uni.showToast({ title: '扫码失败', icon: 'none' })
@@ -321,9 +384,35 @@ function handleSelectSparepart() {
})
}
-// 设备下拉
+// 设备下拉(维修模式加载全部设备,保养模式加载有保养记录的设备)
function toggleDeviceDropdown() {
showDeviceDropdown.value = !showDeviceDropdown.value
+ if (showDeviceDropdown.value && selectedPurpose.value === 'maintain' && maintainDeviceIds.value.size === 0) {
+ loadMaintainDeviceIds()
+ }
+}
+
+// 加载有保养记录的设备ID(从保养工单中提取设备名)
+async function loadMaintainDeviceIds() {
+ deviceLoading.value = true
+ try {
+ const res = await getMaintenanceTicketPage({})
+ const data = res && res.data !== undefined ? res.data : res
+ const records = Array.isArray(data) ? data : (data?.list || data?.records || [])
+ // 从保养工单提取设备名,匹配到 deviceOptions 中的设备ID
+ const deviceNames = new Set(records.map(r => r.deviceName).filter(Boolean))
+ const ids = new Set()
+ for (const d of deviceOptions.value) {
+ if (deviceNames.has(d.label) || deviceNames.has(d.deviceName)) {
+ ids.add(d.value)
+ }
+ }
+ maintainDeviceIds.value = ids
+ } catch (e) {
+ console.error('loadMaintainDeviceIds error', e)
+ } finally {
+ deviceLoading.value = false
+ }
}
async function handleSelectDevice(item) {
@@ -397,25 +486,20 @@ async function loadMaintainOrders(deviceCode) {
}
}
+// 保养单列表(根据设备名称加载保养工单)
async function loadMaintainOrdersById(deviceId) {
if (!deviceId) return
try {
- const res = await getEquipmentMaintenanceByDeviceId(deviceId)
+ const deviceName = selectedDevice.value?.deviceName || selectedDevice.value?.label || ''
+ const res = await getMaintenanceTicketPage({ deviceName })
const data = res && res.data !== undefined ? res.data : res
- let list = []
- if (Array.isArray(data)) { list = data }
- else if (data && Array.isArray(data.data)) { list = data.data }
- else if (data && data.data && Array.isArray(data.data.list)) { list = data.data.list }
- else if (data && data.data && Array.isArray(data.data.rows)) { list = data.data.rows }
- else if (data && data.data && Array.isArray(data.data.records)) { list = data.data.records }
- else if (data && Array.isArray(data.list)) { list = data.list }
- else if (data && Array.isArray(data.rows)) { list = data.rows }
- else if (data && Array.isArray(data.records)) { list = data.records }
+ const list = Array.isArray(data) ? data : (data?.list || data?.records || [])
maintainOrderOptions.value = list.map((m) => ({
value: m.id,
- label: m.maintenanceCode || m.code || m.maintenanceNo || m.no || String(m.id || '')
+ label: m.planNo || String(m.id || ''),
+ deviceId: m.deviceId,
+ deviceName: m.deviceName || ''
}))
- console.log('保养单列表:', JSON.stringify(maintainOrderOptions.value))
} catch (e) {
console.error('loadMaintainOrdersById error', e)
}
@@ -514,21 +598,75 @@ async function handleSubmit() {
uni.showToast({ title: '请选择出库用途', icon: 'none' })
return
}
- // TODO: 调用出库提交接口(后续补充)
- uni.showToast({ title: t('functionCommon.createSuccess'), icon: 'success' })
+
+ const inputCount = Number(outboundQty.value)
+ const convertRatio = Number(selectedSparepart.value.purchaseUnitConvertQuantity) || 1
+ const count = inputCount * convertRatio
+
+ const submitData = {
+ outTime: Date.now(),
+ stockUserId: String(selectedOperator.value.value),
+ status: 0,
+ totalCount: count,
+ totalPrice: 0,
+ items: [{
+ productId: selectedSparepart.value.id,
+ productName: selectedSparepart.value.name || '',
+ productBarCode: selectedSparepart.value.barCode || '',
+ productUnitName: selectedSparepart.value.unitName || selectedSparepart.value.purchaseUnitName || '',
+ purchaseUnitName: selectedSparepart.value.purchaseUnitName || '',
+ purchaseUnitConvertQuantity: convertRatio,
+ inputCount: inputCount,
+ count: count
+ }]
+ }
+
+ // 出库用途
+ submitData.purpose = selectedPurpose.value
+
+ // 关联设备
+ if (selectedDevice.value) {
+ submitData.deviceId = selectedDevice.value.value
+ submitData.deviceName = selectedDevice.value.deviceName || selectedDevice.value.label || ''
+ }
+
+ // 关联维修单/保养单
+ if (selectedPurpose.value === 'repair' && selectedRepairOrder.value) {
+ submitData.repairId = selectedRepairOrder.value.value
+ submitData.repairNo = selectedRepairOrder.value.label
+ } else if (selectedPurpose.value === 'maintain' && selectedMaintainOrder.value) {
+ submitData.maintenanceId = selectedMaintainOrder.value.value
+ submitData.maintenanceNo = selectedMaintainOrder.value.label
+ }
+
+ try {
+ uni.showLoading({ title: '提交中...', mask: true })
+ await createSparepartOutbound(submitData)
+ uni.hideLoading()
+ uni.showToast({ title: t('functionCommon.createSuccess'), icon: 'success' })
+ setTimeout(() => {
+ uni.navigateBack()
+ }, 1500)
+ } catch (e) {
+ uni.hideLoading()
+ const msg = e?.message || e?.data?.msg || e?.response?.data?.msg || t('functionCommon.saveFailed')
+ console.error('出库提交失败:', e)
+ uni.showToast({ title: String(msg).substring(0, 50), icon: 'none' })
+ }
}
onShow(async () => {
const selectResult = getApp().globalData?._sparepartSelectResult
if (selectResult) {
selectedSparepart.value = selectResult
- // 补充获取备件详情(含供应商、图片等)
+ // 补充获取备件详情(含供应商、库存等完整信息)
if (selectResult.id) {
try {
const res = await getSparepartDetail(selectResult.id)
const detail = res?.data || res
- if (detail && detail.suppliers) {
+ if (detail) {
selectedSparepart.value = { ...selectedSparepart.value, ...detail }
+ loadStockCount()
}
} catch (e) {
console.error('获取备件详情失败:', e)
@@ -685,8 +823,8 @@ onHide(() => {
.dropdown-input { display: flex; align-items: center; height: 64rpx; padding: 0 20rpx; border: 1rpx solid #e0e0e0; border-radius: 10rpx; background: #f9fafb; }
.dropdown-value { flex: 1; font-size: 27rpx; color: #333; &.placeholder { color: #bbb; } }
.dropdown-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; }
-.dropdown-panel { position: absolute; top: 68rpx; left: 0; right: 0; z-index: 99; background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx; box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1); overflow: hidden; }
-.dropdown-scroll { max-height: 360rpx; }
+.dropdown-panel { position: absolute; top: 68rpx; left: 0; right: 0; z-index: 200; background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx; box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1); overflow: hidden; }
+.dropdown-scroll { height: 360rpx; }
.dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0;
&:last-child { border-bottom: 0; }
&.active { background: #f0f5ff; }
From 226b7009b0d0d7e158138df19bf9e3e0085bd378 Mon Sep 17 00:00:00 2001
From: zhongwenkai <3478244299@qq.com>
Date: Wed, 17 Jun 2026 15:49:05 +0800
Subject: [PATCH 2/3] =?UTF-8?q?style:=20=E5=A4=87=E4=BB=B6=E5=87=BA?=
=?UTF-8?q?=E5=85=A5=E5=BA=93=E5=8A=9F=E8=83=BD=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/mes/sparepartInbound.js | 9 +
src/api/mes/sparepartOutbound.js | 9 +
src/locales/zh-CN.js | 8 +-
src/pages.json | 14 +
.../pages/sparepartInbound/create.vue | 1179 +++++------------
.../pages/sparepartInbound/index.vue | 186 ++-
.../sparepartInbound/sparepartConfirm.vue | 492 +++++++
.../sparepartInbound/sparepartSelect.vue | 12 +-
.../pages/sparepartOutbound/create.vue | 926 +++----------
.../pages/sparepartOutbound/index.vue | 672 +++-------
.../sparepartOutbound/sparepartConfirm.vue | 590 +++++++++
11 files changed, 2005 insertions(+), 2092 deletions(-)
create mode 100644 src/pages_function/pages/sparepartInbound/sparepartConfirm.vue
create mode 100644 src/pages_function/pages/sparepartOutbound/sparepartConfirm.vue
diff --git a/src/api/mes/sparepartInbound.js b/src/api/mes/sparepartInbound.js
index c4ff7a8..dd0656b 100644
--- a/src/api/mes/sparepartInbound.js
+++ b/src/api/mes/sparepartInbound.js
@@ -17,6 +17,15 @@ export function getSparepartInboundPage(params = {}) {
})
}
+// 备件入库单提交审核(提交后变为待审核状态)
+export function submitSparepartInbound(data) {
+ return request({
+ url: '/admin-api/erp/stock-in/submit',
+ method: 'put',
+ data
+ })
+}
+
// 备件入库单审核(status: 20=通过, 1=驳回)
export function auditSparepartInbound(data) {
return request({
diff --git a/src/api/mes/sparepartOutbound.js b/src/api/mes/sparepartOutbound.js
index 59404ae..0fafdd3 100644
--- a/src/api/mes/sparepartOutbound.js
+++ b/src/api/mes/sparepartOutbound.js
@@ -18,6 +18,15 @@ export function getSparepartOutboundPage(params = {}) {
})
}
+// 备件出库单提交审核
+export function submitSparepartOutbound(data) {
+ return request({
+ url: '/admin-api/erp/stock-out/submit',
+ method: 'put',
+ data
+ })
+}
+
// 备件出库单审核(status: 20=通过, 1=驳回)
export function auditSparepartOutbound(data) {
return request({
diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js
index fcd1e24..58581db 100644
--- a/src/locales/zh-CN.js
+++ b/src/locales/zh-CN.js
@@ -1396,7 +1396,7 @@ export default {
},
sparepartInbound: {
moduleName: '备件入库',
- tabPending: '待提交',
+ tabPending: '待入库',
tabAuditing: '待审核',
searchPlaceholder: '搜索入库单号',
sparepartInfo: '备件信息',
@@ -1404,7 +1404,7 @@ export default {
creator: '创建人',
quantity: '数量',
reviewer: '审核人',
- approve: '已通过',
+ approve: '已入库',
reject: '驳回',
confirmApprove: '确定审核通过该入库单吗?',
confirmReject: '确定驳回该入库单吗?',
@@ -1437,7 +1437,7 @@ export default {
},
sparepartOutbound: {
moduleName: '备件出库',
- tabPending: '待提交',
+ tabPending: '待出库',
tabAuditing: '待审核',
searchPlaceholder: '搜索出库单号',
sparepartInfo: '备件信息',
@@ -1445,7 +1445,7 @@ export default {
creator: '创建人',
quantity: '数量',
reviewer: '审核人',
- approve: '已通过',
+ approve: '已出库',
reject: '驳回',
confirmApprove: '确定审核通过该出库单吗?',
confirmReject: '确定驳回该出库单吗?',
diff --git a/src/pages.json b/src/pages.json
index 59bea2e..a9be7da 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -388,6 +388,13 @@
"navigationStyle": "custom"
}
},
+ {
+ "path": "sparepartInbound/sparepartConfirm",
+ "style": {
+ "navigationBarTitleText": "确认备件入库",
+ "navigationStyle": "custom"
+ }
+ },
{
"path": "sparepartOutbound/index",
"style": {
@@ -402,6 +409,13 @@
"navigationStyle": "custom"
}
},
+ {
+ "path": "sparepartOutbound/sparepartConfirm",
+ "style": {
+ "navigationBarTitleText": "确认备件出库",
+ "navigationStyle": "custom"
+ }
+ },
{
"path": "keypart/index",
diff --git a/src/pages_function/pages/sparepartInbound/create.vue b/src/pages_function/pages/sparepartInbound/create.vue
index c2d79e6..5d20c75 100644
--- a/src/pages_function/pages/sparepartInbound/create.vue
+++ b/src/pages_function/pages/sparepartInbound/create.vue
@@ -1,4 +1,4 @@
-n
+
@@ -18,206 +18,126 @@ n
-
-
+
+
+
- {{ t('sparepartInbound.selectedSpareparts') }}
+ *入库时间
-
-
-
-
-
-
-
- 📦
-
-
-
-
-
- 备件名称:
- {{ textValue(selectedSparepart.name) }}
-
-
- 规格:
- {{ textValue(selectedSparepart.standard || selectedSparepart.deviceSpec) }}
-
-
- 当前库存:
- {{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
-
-
+
+
+ {{ inboundDate || '请选择入库时间' }}
+ ▼
+
-
-
-
-
- 采购单位:
- {{ textValue(selectedSparepart.purchaseUnitName) }}
-
-
- 库存单位:
- {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
+
+
+
+ *经办人
+
+
+
+ {{ selectedOperator ? selectedOperator.label : '请选择经办人' }}
+
+ ▼
+
+
+
+ {{ item.label }}
+ ✓
-
-
- 换算关系:
- 1{{ textValue(selectedSparepart.purchaseUnitName) }}={{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }}
-
-
- 默认仓库
- {{ selectedSparepart.warehouseName ? textValue(selectedSparepart.warehouseName) : '库区/备件仓' }}
-
+ 暂无数据
+
-
-
-
-
- 入库数量
-
-
-
- 入库数量
-
- 单位:{{ textValue(selectedSparepart.purchaseUnitName) }}
-
-
-
- 折算后库存数量:
- {{ calculatedStock }}
- {{ stockUnitLabel(selectedSparepart) }}
-
-
- {{ inboundQty || 0 }}{{ textValue(selectedSparepart.purchaseUnitName) }} × {{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(selectedSparepart) }}
-
-
-
-
-
-
- 供应商
-
-
-
-
- {{ defaultSupplierName || '未配置默认供应商' }}
-
-
-
+
+
+
+ 备注
+
+
-
-
-
- 经办人
-
-
-
-
- {{ selectedOperator ? selectedOperator.label : '请选择经办人' }}
-
- ▼
+
+
+
+ 附件
+
+
+
+
+
+ {{ getFileIcon(file.name) }}
+
+
+ {{ file.name }}
+ {{ formatFileSize(file.size) }}
+
+
+ ✕
+
-
-
-
- {{ item.label }}
- ✓
-
- 暂无数据
-
+
+ 选取文件
+
+
-
-
-
- 仓库/库区
-
-
-
-
-
- 仓库
-
-
-
- {{ selectedWarehouse ? selectedWarehouse.label : '请选择' }}
-
- ▼
-
-
-
-
- {{ item.label }}
- ✓
-
- 暂无数据
-
-
-
+
+
+
+
+ 入库清单({{ itemList.length }})
+
+
+
+
+ ✕
+
+
+
+ 📦
-
-
- 库区
-
-
-
- {{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}
-
- ▼
-
-
-
-
- {{ item.label }}
- ✓
-
- 暂无数据
-
-
-
+
+ {{ item.productName }}
+ {{ item.productBarCode || '-' }}
+
+
+
+
+ 入库数量
+ {{ item.inputCount }}{{ item.purchaseUnitName }}
-
-
-
- {{ t('sparepartInbound.noSelectedSparepart') }}
+
+ 请扫码或选择备件
+
@@ -229,104 +149,60 @@ n
@@ -568,7 +415,7 @@ onHide(() => {
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
-/* ====== 操作按钮区 ====== */
+/* 操作按钮区 */
.action-row {
display: flex;
gap: 20rpx;
@@ -588,501 +435,135 @@ onHide(() => {
font-size: 28rpx;
font-weight: 600;
- .btn-icon-wrap {
- width: 44rpx;
- height: 44rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .btn-icon {
- font-size: 36rpx;
- color: #fff;
- }
-
- .btn-text {
- font-size: 28rpx;
- font-weight: 600;
- color: #fff;
- }
+ .btn-icon-wrap { width: 44rpx; height: 44rpx; display: flex; align-items: center; justify-content: center; }
+ .btn-icon { font-size: 36rpx; color: #fff; }
+ .btn-text { font-size: 28rpx; font-weight: 600; color: #fff; }
}
-/* ====== 已选备件区域 ====== */
-.sparepart-section {
- padding: 0;
-}
+/* 备件列表 */
+.sparepart-section { padding: 0; }
.section-title-bar {
- display: flex;
- align-items: center;
- gap: 10rpx;
+ display: flex; align-items: center; gap: 10rpx;
padding: 24rpx 24rpx 18rpx;
}
-
.section-bar-line {
- width: 6rpx;
- height: 32rpx;
- border-radius: 3rpx;
- background: #2563eb;
- flex-shrink: 0;
-}
-
-.section-title {
- font-size: 30rpx;
- font-weight: 700;
- color: #1a1a1a;
-}
-
-.sparepart-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin: 0 24rpx 20rpx;
- box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
-}
-
-/* ====== 卡片上半部分:图片 + 信息 + 删除 ====== */
-.card-top {
- display: flex;
- position: relative;
-}
-
-.card-image-wrap {
- width: 160rpx;
- height: 160rpx;
- border-radius: 12rpx;
- overflow: hidden;
- background: #f8fafc;
- border: 1rpx solid #f0f0f0;
- flex-shrink: 0;
- margin-right: 20rpx;
+ width: 6rpx; height: 32rpx; border-radius: 3rpx;
+ background: #2563eb; flex-shrink: 0;
}
+.section-title { font-size: 30rpx; font-weight: 700; color: #1a1a1a; }
+.required { color: #ef4444; margin-right: 2rpx; }
-.card-image {
- width: 100%;
- height: 100%;
-}
-
-.card-image-empty {
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- background: #f8fafc;
-}
+.item-list { padding: 0 24rpx; }
-.empty-img-icon {
- font-size: 56rpx;
- opacity: 0.3;
+.item-card {
+ position: relative; display: flex; align-items: center; justify-content: space-between;
+ background: #fff; border-radius: 14rpx; padding: 20rpx; margin-bottom: 16rpx;
+ box-shadow: 0 2rpx 12rpx rgba(15, 23, 42, 0.04);
}
+.item-delete-top { position: absolute; top: 10rpx; right: 10rpx; width: 40rpx; height: 40rpx; border-radius: 20rpx; background: #fee2e2; display: flex; align-items: center; justify-content: center; z-index: 1; .delete-icon { font-size: 20rpx; color: #dc2626; } }
-/* 右侧信息区 */
-.card-info-right {
- flex: 1;
- min-width: 0;
+.item-left { display: flex; align-items: center; flex: 1; min-width: 0; padding-right: 36rpx; }
+.item-image-wrap {
+ width: 80rpx; height: 80rpx; border-radius: 10rpx; overflow: hidden;
+ background: #f8fafc; border: 1rpx solid #f0f0f0; flex-shrink: 0;
+ display: flex; align-items: center; justify-content: center;
}
-
-.info-item {
- display: flex;
- align-items: center;
- margin-bottom: 8rpx;
+.item-image { width: 100%; height: 100%; }
+.item-image-empty { font-size: 36rpx; opacity: 0.3; }
+.item-info { margin-left: 16rpx; flex: 1; min-width: 0; }
+.item-name {
+ font-size: 28rpx; font-weight: 600; color: #1a1a1a; display: block;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
+.item-spec { font-size: 24rpx; color: #9ca3af; margin-top: 6rpx; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.info-label {
- font-size: 26rpx;
- color: #6b7280;
- flex-shrink: 0;
-}
+.item-right { display: flex; flex-direction: column; align-items: flex-end; margin-left: 16rpx; flex-shrink: 0; padding-right: 50rpx; }
+.item-qty-row { display: flex; flex-direction: column; align-items: flex-end; }
+.item-qty-label { font-size: 22rpx; color: #9ca3af; }
+.item-qty-value { font-size: 28rpx; font-weight: 700; color: #2563eb; margin-top: 4rpx; }
-.info-name {
- font-size: 30rpx;
- font-weight: 700;
- color: #0f172a;
-}
+.empty-wrap { padding: 160rpx 0; text-align: center; }
+.empty-text { font-size: 28rpx; color: #94a3b8; }
-.info-value {
- font-size: 26rpx;
- color: #374151;
+/* 入库信息区 */
+.form-section { padding-bottom: 16rpx; }
- &.stock-highlight {
- color: #2563eb;
- font-weight: 500;
- }
-}
-
-/* 删除按钮 */
-.card-delete {
- position: absolute;
- top: -8rpx;
- right: -8rpx;
- width: 48rpx;
- height: 48rpx;
- border-radius: 24rpx;
- background: #fee2e2;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.delete-icon {
- font-size: 22rpx;
- color: #dc2626;
-}
-
-/* ====== 卡片下半部分:详细信息 ====== */
-.card-bottom {
- margin-top: 20rpx;
- padding-top: 20rpx;
- border-top: 1rpx solid #f0f0f0;
-}
-
-.detail-row {
- display: flex;
- align-items: center;
- margin-bottom: 14rpx;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- &.two-col {
- justify-content: space-between;
- }
-}
-
-.detail-col {
- display: flex;
- align-items: center;
- flex: 1;
-}
-
-.detail-label {
- font-size: 24rpx;
- color: #9ca3af;
- flex-shrink: 0;
-}
-
-.detail-value {
- font-size: 24rpx;
- color: #4b5563;
-}
-
-/* ====== 入库数量 ====== */
-.inbound-qty-section {
- margin-top: 4rpx;
-}
-
-.qty-input-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin: 0 24rpx;
- box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
-}
-
-.form-field {
- display: flex;
- flex-direction: column;
- gap: 12rpx;
-}
-
-.form-label {
- font-size: 26rpx;
- color: #4b5563;
- font-weight: 500;
-}
-
-.form-input {
- width: 100%;
- height: 88rpx;
- padding: 0 24rpx;
- font-size: 28rpx;
- color: #374151;
- background: #f8fafc;
- border-radius: 14rpx;
- box-sizing: border-box;
-}
-
-.form-suffix-text {
- display: block;
- margin-top: 10rpx;
- font-size: 24rpx;
- color: #9ca3af;
-}
-
-/* ====== 折算后库存数量(内嵌在入库数量卡片) ====== */
-.convert-row {
- margin-top: 20rpx;
- padding-top: 20rpx;
- border-top: 1rpx solid #f0f0f0;
- display: flex;
- align-items: center;
-}
-
-.convert-label-inline {
- font-size: 26rpx;
- color: #6b7280;
-}
-
-.convert-value-inline {
- font-size: 36rpx;
- font-weight: 700;
- color: #1f2937;
- margin-left: auto;
-}
-
-.convert-unit-inline {
- font-size: 24rpx;
- color: #64748b;
- margin-left: 8rpx;
-}
-
-.convert-formula-inline {
- margin-top: 10rpx;
- font-size: 22rpx;
- color: #9ca3af;
-}
-
-/* ====== 供应商/经办人 选择行 ====== */
-.select-row-card {
+.form-row-card {
position: relative;
- display: flex;
- align-items: center;
- justify-content: space-between;
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin: 0 24rpx 20rpx;
+ display: flex; align-items: center; justify-content: space-between;
+ background: #fff; border-radius: 16rpx; padding: 26rpx 24rpx; margin: 0 24rpx 16rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
-
- .dropdown-panel {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- z-index: 200;
- margin-top: 4rpx;
- background: #fff;
- border: 1rpx solid #e0e0e0;
- border-radius: 12rpx;
- box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
- overflow: hidden;
- }
-}
-
-.select-row-left {
- display: flex;
- align-items: center;
- flex-shrink: 0;
-}
-
-.select-row-icon {
- font-size: 36rpx;
- color: #3b82f6;
- margin-right: 16rpx;
- flex-shrink: 0;
-}
-
-.select-row-label {
- font-size: 28rpx;
- color: #374151;
+ font-size: 28rpx; color: #374151;
+ .placeholder { color: #9ca3af; }
}
+.form-row-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; }
-.full-dropdown {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- font-size: 28rpx;
- color: #374151;
-
- .placeholder {
- color: #9ca3af;
- }
-}
-
-.select-row-dropdown {
- flex: 1;
- min-width: 0;
- margin-left: 24rpx;
-}
-
-/* ====== 仓库/库区 两行布局 ====== */
-.warehouse-area-rows {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 16rpx;
-}
-
-.warehouse-area-row {
- display: flex;
- align-items: center;
-}
-
-.warehouse-area-label {
- width: 80rpx;
- font-size: 26rpx;
- color: #6b7280;
+/* 经办人下拉 */
+.dropdown-panel {
+ position: absolute; top: 100%; left: 0; right: 0; z-index: 200; margin-top: 4rpx;
+ background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx;
+ box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1); overflow: hidden;
+}
+.dropdown-scroll { height: 360rpx; }
+.dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0;
+ &:last-child { border-bottom: 0; }
+ &.active { background: #f0f5ff; }
+}
+.dropdown-item-text { font-size: 27rpx; color: #333; }
+.dropdown-check { font-size: 28rpx; color: #2563eb; font-weight: 700; }
+.dropdown-empty { padding: 32rpx; text-align: center; color: #999; font-size: 26rpx; }
+
+/* 备注 */
+.remark-card { padding: 20rpx 24rpx; }
+.remark-textarea { width: 100%; min-height: 120rpx; font-size: 27rpx; color: #374151; line-height: 1.6; box-sizing: border-box; }
+.remark-placeholder { color: #bbb; }
+
+/* 附件 */
+.attachment-area { padding: 0 24rpx 16rpx; }
+.attachment-list { display: flex; flex-direction: column; gap: 12rpx; }
+
+.attachment-file-item {
+ display: flex; align-items: center; gap: 16rpx;
+ background: #fff; border-radius: 12rpx; padding: 18rpx 20rpx;
+ box-shadow: 0 2rpx 8rpx rgba(15, 23, 42, 0.03);
+}
+.file-icon {
+ width: 72rpx; height: 72rpx; border-radius: 10rpx;
+ background: #f1f5f9; display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
-
-.warehouse-area-dropdown {
- flex: 1;
- min-width: 0;
- position: relative;
-
- .dropdown-panel {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- z-index: 100;
- margin-top: 4rpx;
- background: #fff;
- border: 1rpx solid #e0e0e0;
- border-radius: 12rpx;
- box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
- overflow: hidden;
- }
-}
-
-.warehouse-area-card {
- align-items: flex-start;
- flex-wrap: wrap;
-}
-
-/* ====== 下拉选择器 ====== */
-.select-dropdown {
- position: relative;
- width: 100%;
-}
-
-.dropdown-input {
- display: flex;
- align-items: center;
- height: 64rpx;
- padding: 0 20rpx;
- border: 1rpx solid #e0e0e0;
- border-radius: 10rpx;
- background: #f9fafb;
-}
-
-.dropdown-value {
- flex: 1;
- font-size: 27rpx;
- color: #333;
-
- &.placeholder {
- color: #bbb;
- }
+.file-icon-text { font-size: 36rpx; }
+.file-info { flex: 1; min-width: 0; }
+.file-name {
+ font-size: 26rpx; color: #1f2937; display: block;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
-
-.dropdown-arrow {
- font-size: 20rpx;
- color: #999;
+.file-size { font-size: 22rpx; color: #9ca3af; margin-top: 4rpx; display: block; }
+.file-delete {
+ width: 48rpx; height: 48rpx; border-radius: 24rpx;
+ background: #fee2e2; display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
+.delete-icon-sm { font-size: 22rpx; color: #dc2626; }
-.dropdown-panel {
- position: absolute;
- top: 68rpx;
- left: 0;
- right: 0;
- z-index: 200;
- background: #fff;
- border: 1rpx solid #e0e0e0;
+.attachment-add-file {
+ display: flex; align-items: center; justify-content: center; gap: 10rpx;
+ height: 88rpx; background: #fff; border: 2rpx dashed #cbd5e1;
border-radius: 12rpx;
- box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
- overflow: hidden;
-}
-
-.dropdown-scroll {
- height: 360rpx;
-}
-
-.dropdown-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx 24rpx;
- border-bottom: 1rpx solid #f0f0f0;
-
- &:last-child {
- border-bottom: 0;
- }
-
- &.active {
- background: #f0f5ff;
- }
-}
-
-.dropdown-item-text {
- font-size: 27rpx;
- color: #333;
-}
-
-.dropdown-check {
- font-size: 28rpx;
- color: #2563eb;
- font-weight: 700;
}
+.add-icon { font-size: 32rpx; }
+.add-text { font-size: 26rpx; color: #6b7280; }
-.dropdown-empty {
- padding: 32rpx;
- text-align: center;
- color: #999;
- font-size: 26rpx;
-}
-
-/* ====== 空状态 ====== */
-.empty-wrap {
- padding: 160rpx 24rpx;
- text-align: center;
-}
-
-.empty-text {
- font-size: 28rpx;
- color: #94a3b8;
-}
-
-/* ====== 底部操作栏 ====== */
+/* 底部操作栏 */
.bottom-actions {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- gap: 18rpx;
+ position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx;
padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom));
- background: #ffffff;
- box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06);
- z-index: 99;
-}
-
-.bottom-btn {
- flex: 1;
- height: 84rpx;
- line-height: 84rpx;
- text-align: center;
- border-radius: 16rpx;
- font-size: 30rpx;
- font-weight: 600;
-
- &:active {
- opacity: 0.85;
- }
+ background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06); z-index: 99;
}
-
-.cancel-btn {
- background: #eef2f7;
- color: #475569;
-}
-
-.confirm-btn {
- background: #1f4b79;
- color: #ffffff;
+.bottom-btn { flex: 1; height: 84rpx; line-height: 84rpx; text-align: center; border-radius: 16rpx; font-size: 30rpx; font-weight: 600;
+ &:active { opacity: 0.85; }
}
+.cancel-btn { background: #eef2f7; color: #475569; }
+.confirm-btn { background: #1f4b79; color: #ffffff; }
diff --git a/src/pages_function/pages/sparepartInbound/index.vue b/src/pages_function/pages/sparepartInbound/index.vue
index bdc7497..179d30a 100644
--- a/src/pages_function/pages/sparepartInbound/index.vue
+++ b/src/pages_function/pages/sparepartInbound/index.vue
@@ -71,10 +71,14 @@
{{ textValue(item.auditUserName) }}
-
+
+
+ 提交审核
+
+
- {{ t('sparepartInbound.approve') }}
- {{ t('sparepartInbound.reject') }}
+ 审核通过
+ 审核驳回
@@ -94,6 +98,43 @@
+
+
+
+
+
+
+
+
+
+ *审核人
+
+ {{ selectedAuditor ? selectedAuditor.label : '请选择' }}
+ ▼
+
+
+
+ {{ u.label }}
+ ✓
+
+
+
+
+
+
+
+ 备注
+
+
+
+
+
+
@@ -102,7 +143,8 @@ import { computed, nextTick, ref } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
-import { getSparepartInboundPage, auditSparepartInbound } from '@/api/mes/sparepartInbound'
+import { getSparepartInboundPage, auditSparepartInbound, submitSparepartInbound } from '@/api/mes/sparepartInbound'
+import { getSimpleUserList } from '@/api/mes/moldget'
const { t } = useI18n()
@@ -156,15 +198,15 @@ function formatDateTime(value) {
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
}
-const STATUS_MAP = { 0: '待入库', 10: '待审核', 20: '已通过', 1: '已驳回' }
+const STATUS_MAP = { 0: '待入库', 10: '待审核', 20: '已入库', 1: '已驳回' }
function statusText(s) {
const num = Number(s)
return STATUS_MAP[num] || textValue(num)
}
function statusClass(s) {
const num = Number(s)
- if (num === 0) return 'text-warning'
- if (num === 10) return 'text-primary'
+ if (num === 0) return 'text-primary'
+ if (num === 10) return 'text-warning'
if (num === 20) return 'text-success'
if (num === 1) return 'text-danger'
return ''
@@ -292,6 +334,75 @@ async function handleReject(item) {
})
}
+// 提交审核弹框
+const showAuditModal = ref(false)
+const currentAuditItem = ref(null)
+const auditorOptions = ref([])
+const selectedAuditor = ref(null)
+const showAuditorDropdown = ref(false)
+const auditRemark = ref('')
+
+function openSubmitAudit(item) {
+ currentAuditItem.value = item
+ selectedAuditor.value = null
+ auditRemark.value = ''
+ showAuditModal.value = true
+ loadAuditorOptions()
+}
+
+function closeAuditModal() {
+ showAuditModal.value = false
+ currentAuditItem.value = null
+ showAuditorDropdown.value = false
+}
+
+function toggleAuditorDropdown() {
+ showAuditorDropdown.value = !showAuditorDropdown.value
+}
+
+function selectAuditor(u) {
+ selectedAuditor.value = u
+ showAuditorDropdown.value = false
+}
+
+async function loadAuditorOptions() {
+ if (auditorOptions.value.length) return
+ try {
+ const res = await getSimpleUserList()
+ const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
+ auditorOptions.value = data.map((u) => ({
+ value: u.id || u.userId,
+ label: u.nickname || u.userName || u.name || String(u.id || '')
+ }))
+ } catch (e) {
+ console.error('loadAuditorOptions error', e)
+ }
+}
+
+async function confirmSubmitAudit() {
+ if (!selectedAuditor.value) {
+ uni.showToast({ title: '请选择审核人', icon: 'none' })
+ return
+ }
+ if (!currentAuditItem.value?.id) return
+
+ try {
+ uni.showLoading({ title: '提交中...', mask: true })
+ await submitSparepartInbound({
+ id: currentAuditItem.value.id,
+ auditUserId: selectedAuditor.value.value,
+ remark: auditRemark.value || undefined
+ })
+ uni.hideLoading()
+ uni.showToast({ title: '提交审核成功', icon: 'success' })
+ closeAuditModal()
+ fetchList(true)
+ } catch (e) {
+ uni.hideLoading()
+ uni.showToast({ title: '提交失败', icon: 'none' })
+ }
+}
+
function onScroll(event) {
showGoTop.value = (event?.detail?.scrollTop || 0) > 600
}
@@ -505,6 +616,11 @@ onUnload(() => {
color: #dc2626;
}
+ &.submit-btn {
+ background: #dbeafe;
+ color: #1d4ed8;
+ }
+
&:active {
opacity: 0.8;
}
@@ -591,4 +707,60 @@ onUnload(() => {
line-height: 1;
margin-top: -4rpx;
}
+
+/* ====== 提交审核弹框 ====== */
+.modal-overlay {
+ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0, 0, 0, 0.45); z-index: 999;
+ display: flex; align-items: center; justify-content: center;
+}
+.modal-card {
+ width: 640rpx; background: #fff; border-radius: 20rpx;
+ box-shadow: 0 16rpx 48rpx rgba(0,0,0,0.15);
+}
+.modal-header {
+ display: flex; align-items: center; justify-content: space-between;
+ padding: 32rpx 32rpx 20rpx; border-bottom: 1rpx solid #f0f0f0;
+}
+.modal-title { font-size: 32rpx; font-weight: 700; color: #1a1a1a; }
+.modal-close { font-size: 32rpx; color: #999; padding: 8rpx; }
+.modal-body { padding: 28rpx 32rpx; }
+.modal-field { margin-bottom: 28rpx;
+ &:last-child { margin-bottom: 0; }
+}
+.modal-label { font-size: 28rpx; color: #374151; font-weight: 500; margin-bottom: 14rpx; display: block; }
+.modal-label .required { color: #ef4444; }
+.modal-dropdown {
+ position: relative; display: flex; align-items: center; justify-content: space-between;
+ height: 80rpx; padding: 0 24rpx;
+ background: #f8fafc; border: 1rpx solid #e0e0e0; border-radius: 12rpx;
+ font-size: 28rpx; color: #374151;
+ .placeholder { color: #bbb; }
+ .dropdown-arrow { font-size: 20rpx; color: #999; }
+}
+.modal-textarea {
+ width: 100%; min-height: 140rpx; padding: 20rpx;
+ background: #f8fafc; border: 1rpx solid #e0e0e0;
+ border-radius: 12rpx; font-size: 27rpx; color: #374151;
+ box-sizing: border-box;
+}
+.textarea-placeholder { color: #bbb; }
+.dropdown-panel {
+ position: absolute; top: 100%; left: 0; right: 0; z-index: 200; margin-top: 4rpx;
+ background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx;
+ box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.1); overflow: hidden;
+}
+.dropdown-scroll { height: 300rpx; }
+.dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0;
+ &:last-child { border-bottom: 0; }
+ &.active { background: #f0f5ff; }
+}
+.dropdown-item-text { font-size: 27rpx; color: #333; }
+.dropdown-check { font-size: 28rpx; color: #2563eb; font-weight: 700; }
+.modal-footer { display: flex; gap: 18rpx; padding: 24rpx 32rpx; border-top: 1rpx solid #f0f0f0; }
+.modal-btn { flex: 1; height: 80rpx; line-height: 80rpx; text-align: center; border-radius: 14rpx; font-size: 30rpx; font-weight: 600;
+ &:active { opacity: 0.85; }
+ &.cancel-btn { background: #f0f0f0; color: #6b7280; }
+ &.confirm-btn { background: #1f4b79; color: #fff; }
+}
diff --git a/src/pages_function/pages/sparepartInbound/sparepartConfirm.vue b/src/pages_function/pages/sparepartInbound/sparepartConfirm.vue
new file mode 100644
index 0000000..8a0ec6f
--- /dev/null
+++ b/src/pages_function/pages/sparepartInbound/sparepartConfirm.vue
@@ -0,0 +1,492 @@
+
+
+
+
+
+
+
+
+ 已选备件
+
+
+
+
+
+
+ 📦
+
+
+
+
+ 备件名称:
+ {{ textValue(sparepart.name) }}
+
+
+ 规格:
+ {{ textValue(sparepart.standard || sparepart.deviceSpec) }}
+
+
+ 当前库存:
+ {{ sparepart.count != null ? sparepart.count : 0 }}{{ textUnit(sparepart.unitName || sparepart.minStockUnitName || '个') }}
+
+
+
+
+
+
+ 采购单位:
+ {{ textValue(sparepart.purchaseUnitName) }}
+
+
+ 库存单位:
+ {{ textValue(sparepart.unitName || sparepart.minStockUnitName || '个') }}
+
+
+
+ 换算关系:
+ 1{{ textValue(sparepart.purchaseUnitName) }}={{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }}
+
+
+
+
+
+
+
+ 入库数量
+
+
+
+ 入库数量
+
+ 单位:{{ textValue(sparepart.purchaseUnitName) }}
+
+
+ 折算后库存数量:
+ {{ calculatedStock }}
+ {{ stockUnitLabel(sparepart) }}
+
+
+ {{ inboundQty || 0 }}{{ textValue(sparepart.purchaseUnitName) }} × {{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(sparepart) }}
+
+
+
+
+
+
+ 供应商
+
+
+
+
+ {{ defaultSupplierName || '未配置默认供应商' }}
+
+
+
+
+
+
+
+ 仓库/库区
+
+
+
+
+ 仓库
+
+
+
+ {{ selectedWarehouse ? selectedWarehouse.label : '请选择' }}
+
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+ 暂无数据
+
+
+
+
+
+ 库区
+
+
+
+ {{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}
+
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+ 暂无数据
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确认
+
+
+
+
+
+
+
diff --git a/src/pages_function/pages/sparepartInbound/sparepartSelect.vue b/src/pages_function/pages/sparepartInbound/sparepartSelect.vue
index 7730636..c451d98 100644
--- a/src/pages_function/pages/sparepartInbound/sparepartSelect.vue
+++ b/src/pages_function/pages/sparepartInbound/sparepartSelect.vue
@@ -143,9 +143,15 @@ function handleConfirm() {
return
}
const item = sparepartList.value.find((d) => d.id === selectedId.value)
- // 存入 globalData 后再返回,目标页 onShow 中读取
- getApp().globalData._sparepartSelectResult = item || null
- uni.navigateBack()
+ if (!item) return
+ // 存入 globalData 后跳转到对应确认页
+ getApp().globalData._sparepartBeforeConfirm = item
+ const from = getApp().globalData._sparepartSelectFrom || 'inbound'
+ getApp().globalData._sparepartSelectFrom = null
+ const url = from === 'outbound'
+ ? '/pages_function/pages/sparepartOutbound/sparepartConfirm'
+ : '/pages_function/pages/sparepartInbound/sparepartConfirm'
+ uni.navigateTo({ url })
}
onShow(async () => {
diff --git a/src/pages_function/pages/sparepartOutbound/create.vue b/src/pages_function/pages/sparepartOutbound/create.vue
index d105571..44831d6 100644
--- a/src/pages_function/pages/sparepartOutbound/create.vue
+++ b/src/pages_function/pages/sparepartOutbound/create.vue
@@ -5,213 +5,107 @@
-
-
-
+
扫备件码
-
-
-
+
选择备件
-
-
+
+
+
- 已选备件
+ *出库时间
-
-
-
-
-
- 📦
-
-
-
-
- 备件名称:
- {{ textValue(selectedSparepart.name) }}
-
-
- 规格:
- {{ textValue(selectedSparepart.standard || selectedSparepart.deviceSpec) }}
-
-
- 当前库存:
- {{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
-
-
+
+
+ {{ outboundDate || '请选择出库时间' }}
+ ▼
-
-
-
- 采购单位:
- {{ textValue(selectedSparepart.purchaseUnitName) }}
-
-
- 库存单位:
- {{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}
+
+
+
+
+
+ *经办人
+
+
+ {{ selectedOperator ? selectedOperator.label : '请选择经办人' }}
+ ▼
+
+
+
+ {{ item.label }}
+ ✓
-
-
- 换算关系:
- 1{{ textValue(selectedSparepart.purchaseUnitName) }}={{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }}
-
+ 暂无数据
+
-
+
- 出库用途
+ 备注
-
-
-
- 维修领用
-
-
-
- 保养领用
-
-
-
- 其他出库
-
+
-
-
+
+
- 关联信息
+ 附件
-
-
-
-
- 关联设备
-
-
- {{ selectedDevice ? selectedDevice.label : '请选择' }}
- ▼
-
-
-
-
- {{ item.label }}
- ✓
-
- 加载中...
- {{ selectedPurpose === 'maintain' ? '暂无保养设备' : '暂无设备数据' }}
-
-
+
+
+
+ {{ getFileIcon(file.name) }}
+
+ {{ file.name }}
+ {{ formatFileSize(file.size) }}
+ ✕
-
-
- {{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}
-
-
- {{ currentOrder ? currentOrder.label : '请选择' }}
- ▼
-
-
-
-
- {{ item.label }}
- ✓
-
- {{ selectedPurpose === 'repair' ? '暂无维修单' : '暂无保养单' }}
-
-
-
+
+ 选取文件
+
-
-
-
-
- 出库数量
-
-
-
- 出库数量
-
- 单位:{{ textValue(selectedSparepart.purchaseUnitName) }}
-
-
- 折算后库存数量:
- {{ calculatedStock }}
- {{ stockUnitLabel(selectedSparepart) }}
-
-
- {{ outboundQty || 0 }}{{ textValue(selectedSparepart.purchaseUnitName) }} × {{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(selectedSparepart) }}
-
-
-
-
-
-
- 经办人
-
-
-
-
- {{ selectedOperator ? selectedOperator.label : '请选择经办人' }}
-
- ▼
+
+
+
+
+ 出库清单({{ itemList.length }})
+
+
+
+ ✕
+
+
+
+ 📦
+
+
+ {{ item.productName }}
+ {{ item.productBarCode || '-' }}
+
-
-
-
- {{ item.label }}
- ✓
-
- 暂无数据
-
+
+
+ 出库数量
+ {{ item.inputCount }}{{ item.purchaseUnitName }}
+
-
-
-
-
- 请扫码或选择备件
+ 请扫码或选择备件
@@ -223,627 +117,197 @@
diff --git a/src/pages_function/pages/sparepartOutbound/index.vue b/src/pages_function/pages/sparepartOutbound/index.vue
index 985b2a6..d81d705 100644
--- a/src/pages_function/pages/sparepartOutbound/index.vue
+++ b/src/pages_function/pages/sparepartOutbound/index.vue
@@ -2,82 +2,40 @@
-
-
+
-
- {{ currentStatusLabel }}
-
-
+ {{ currentStatusLabel }}
{{ t('functionCommon.reset') }}
-
-
+
-
+
-
- {{ t('sparepartOutbound.sparepartInfo') }}
- {{ textValue(item.productNames) }}
-
-
- {{ t('sparepartOutbound.outboundTime') }}
- {{ formatDateTime(item.outTime || item.createTime) }}
-
-
- {{ t('sparepartOutbound.creator') }}
- {{ textValue(item.creatorName || item.creator) }}
-
-
- {{ t('sparepartOutbound.quantity') }}
- {{ textValue(item.totalCount) }}
-
-
- {{ t('sparepartOutbound.reviewer') }}
- {{ textValue(item.auditUserName) }}
-
+ {{ t('sparepartOutbound.sparepartInfo') }}{{ textValue(item.productNames) }}
+ {{ t('sparepartOutbound.outboundTime') }}{{ formatDateTime(item.outTime || item.createTime) }}
+ {{ t('sparepartOutbound.creator') }}{{ textValue(item.creatorName || item.creator) }}
+ {{ t('sparepartOutbound.quantity') }}{{ textValue(item.totalCount) }}
+ {{ t('sparepartOutbound.reviewer') }}{{ textValue(item.auditUserName) }}
+
+
+ 提交审核
-
- {{ t('sparepartOutbound.approve') }}
- {{ t('sparepartOutbound.reject') }}
+ 审核通过
+ 审核驳回
-
{{ t('functionCommon.loading') }}
{{ t('sparepartOutbound.empty') }}
{{ t('functionCommon.loadingMore') }}
@@ -85,46 +43,65 @@
-
-
-
-
-
-
-
- +
+
+
+ +
+
+
+
+
+
+
+
+ *审核人
+
+ {{ selectedAuditor ? selectedAuditor.label : '请选择' }}
+ ▼
+
+
+
+ {{ u.label }}
+ ✓
+
+
+
+
+
+
+ 备注
+
+
+
+
+
diff --git a/src/pages_function/pages/sparepartOutbound/sparepartConfirm.vue b/src/pages_function/pages/sparepartOutbound/sparepartConfirm.vue
new file mode 100644
index 0000000..be4321a
--- /dev/null
+++ b/src/pages_function/pages/sparepartOutbound/sparepartConfirm.vue
@@ -0,0 +1,590 @@
+
+
+
+
+
+
+
+ 已选备件
+
+
+
+
+
+ 📦
+
+
+ 备件名称:{{ textValue(sparepart.name) }}
+ 规格:{{ textValue(sparepart.standard || sparepart.deviceSpec) }}
+ 当前库存:{{ sparepart.count != null ? sparepart.count : 0 }}{{ textUnit(sparepart.unitName || sparepart.minStockUnitName || '个') }}
+
+
+
+
+ 采购单位:{{ textValue(sparepart.purchaseUnitName) }}
+ 库存单位:{{ textValue(sparepart.unitName || sparepart.minStockUnitName || '个') }}
+
+ 换算关系:1{{ textValue(sparepart.purchaseUnitName) }}={{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }}
+
+
+
+
+
+
+ 出库用途
+
+
+
+
+ 维修领用
+
+
+
+ 保养领用
+
+
+
+ 其他出库
+
+
+
+
+
+ 关联信息
+
+
+
+
+ 关联设备
+
+
+ {{ selectedDevice ? selectedDevice.label : '请选择' }}
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+ 加载中...
+ {{ selectedPurpose === 'maintain' ? '暂无保养设备' : '暂无设备数据' }}
+
+
+
+
+
+ {{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}
+
+
+ {{ currentOrder ? currentOrder.label : '请选择' }}
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+ {{ selectedPurpose === 'repair' ? '暂无维修单' : '暂无保养单' }}
+
+
+
+
+
+
+
+
+
+ 出库数量
+
+
+
+ 出库数量
+
+ 单位:{{ textValue(sparepart.purchaseUnitName) }}
+
+
+ 出库后剩余库存:
+ {{ remainingStock }}
+ {{ stockUnitLabel(sparepart) }}
+
+ {{ currentStockText }}
+ {{ outboundQty || 0 }}{{ textValue(sparepart.purchaseUnitName) }} × {{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(sparepart) }}
+
+
+ ⚠
+
+ 当前库存不足,不能出库
+ 当前库存:{{ stockCount }}{{ stockUnitLabel(sparepart) }}({{ stockPackText }})
+ 本次出库:{{ calculatedStock }}{{ stockUnitLabel(sparepart) }}({{ outboundPackText }})
+
+
+
+
+
+
+ 供应商
+
+
+
+ {{ defaultSupplierName || '未配置默认供应商' }}
+
+
+
+
+
+ 仓库/库区
+
+
+
+
+ 仓库
+
+
+ {{ selectedWarehouse ? selectedWarehouse.label : '请选择' }}
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+
+
+
+
+
+ 库区
+
+
+ {{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}
+ ▼
+
+
+
+
+ {{ item.label }}
+ ✓
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确认
+
+
+
+
+
+
+
From a2c7ba01ae6c2f3f16d03a0fadee0cdad8c6f0d1 Mon Sep 17 00:00:00 2001
From: ck-chenkang
Date: Wed, 17 Jun 2026 16:39:54 +0800
Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=8A=BD=E5=8F=96=E9=80=9A?=
=?UTF-8?q?=E7=94=A8=E6=89=AB=E7=A0=81=E8=BE=93=E5=85=A5=E7=9B=91=E5=90=AC?=
=?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E5=8F=B0=E8=B4=A6?=
=?UTF-8?q?=E6=89=AB=E7=A0=81=E4=BD=93=E9=AA=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/hooks/useScannerInput.js | 203 ++++++++++++++++++
.../pages/equipmentLedger/index.vue | 46 ++--
2 files changed, 236 insertions(+), 13 deletions(-)
create mode 100644 src/hooks/useScannerInput.js
diff --git a/src/hooks/useScannerInput.js b/src/hooks/useScannerInput.js
new file mode 100644
index 0000000..4de89c9
--- /dev/null
+++ b/src/hooks/useScannerInput.js
@@ -0,0 +1,203 @@
+import { onHide, onShow, onUnload } from '@dcloudio/uni-app'
+
+const androidKeyCodeMap = {
+ 7: '0',
+ 8: '1',
+ 9: '2',
+ 10: '3',
+ 11: '4',
+ 12: '5',
+ 13: '6',
+ 14: '7',
+ 15: '8',
+ 16: '9',
+ 29: 'A',
+ 30: 'B',
+ 31: 'C',
+ 32: 'D',
+ 33: 'E',
+ 34: 'F',
+ 35: 'G',
+ 36: 'H',
+ 37: 'I',
+ 38: 'J',
+ 39: 'K',
+ 40: 'L',
+ 41: 'M',
+ 42: 'N',
+ 43: 'O',
+ 44: 'P',
+ 45: 'Q',
+ 46: 'R',
+ 47: 'S',
+ 48: 'T',
+ 49: 'U',
+ 50: 'V',
+ 51: 'W',
+ 52: 'X',
+ 53: 'Y',
+ 54: 'Z',
+ 55: ',',
+ 56: '.',
+ 69: '-',
+ 70: '=',
+ 71: '[',
+ 72: ']',
+ 73: '\\',
+ 74: ';',
+ 75: "'",
+ 76: '/',
+ 81: '+'
+}
+
+export function useScannerInput(options = {}) {
+ const minLength = Number(options.minLength ?? 3)
+ const commitDelay = Number(options.commitDelay ?? 120)
+ const uppercase = options.uppercase !== false
+ const ignoreInputTarget = options.ignoreInputTarget !== false
+ const hideKeyboardOnScan = options.hideKeyboardOnScan !== false
+ const hideKeyboardOnLeave = options.hideKeyboardOnLeave !== false
+ const onScan = typeof options.onScan === 'function' ? options.onScan : () => {}
+
+ let scannerBuffer = ''
+ let scannerTimer = null
+ let scannerListening = false
+ let plusKeyListening = false
+
+ function getScannerEventTarget() {
+ if (typeof document !== 'undefined') return document
+ if (typeof window !== 'undefined') return window
+ return null
+ }
+
+ function registerScannerListener() {
+ if (scannerListening) return
+ const target = getScannerEventTarget()
+ if (target?.addEventListener) {
+ target.addEventListener('keydown', handleScannerKeydown)
+ scannerListening = true
+ }
+ registerPlusKeyListener()
+ }
+
+ function unregisterScannerListener() {
+ const target = getScannerEventTarget()
+ if (scannerListening && target?.removeEventListener) {
+ target.removeEventListener('keydown', handleScannerKeydown)
+ }
+ scannerListening = false
+ unregisterPlusKeyListener()
+ clearScannerBuffer()
+ if (hideKeyboardOnLeave) hideSoftKeyboard()
+ }
+
+ function registerPlusKeyListener() {
+ if (plusKeyListening) return
+ if (typeof plus === 'undefined' || !plus?.key?.addEventListener) return
+ plus.key.addEventListener('keydown', handlePlusScannerKeydown)
+ plusKeyListening = true
+ }
+
+ function unregisterPlusKeyListener() {
+ if (!plusKeyListening) return
+ if (typeof plus !== 'undefined' && plus?.key?.removeEventListener) {
+ plus.key.removeEventListener('keydown', handlePlusScannerKeydown)
+ }
+ plusKeyListening = false
+ }
+
+ function handlePlusScannerKeydown(event) {
+ handleScannerKeydown({ ...event, isPlusKeyEvent: true })
+ }
+
+ function handleScannerKeydown(event) {
+ if (ignoreInputTarget) {
+ const targetTag = String(event?.target?.tagName || '').toLowerCase()
+ if (targetTag === 'input' || targetTag === 'textarea') return
+ }
+
+ const key = normalizeScannerKey(event)
+ if (!key) return
+ if (key === 'Enter') {
+ commitScannerBuffer()
+ return
+ }
+
+ scannerBuffer += key
+ if (scannerTimer) clearTimeout(scannerTimer)
+ scannerTimer = setTimeout(() => {
+ commitScannerBuffer()
+ }, commitDelay)
+ }
+
+ function normalizeScannerKey(event) {
+ if (event?.ctrlKey || event?.altKey || event?.metaKey) return ''
+ if (isAndroidPlusKeyEvent(event)) return normalizeAndroidKeyCode(event)
+
+ const key = event?.key
+ if (key === 'Enter' || key === 'Tab') return 'Enter'
+ if (typeof key === 'string' && key.length === 1) return uppercase ? key.toUpperCase() : key
+
+ const code = Number(event?.keyCode || event?.which || 0)
+ if (code === 13 || code === 9) return 'Enter'
+ if (code >= 48 && code <= 90) {
+ const value = String.fromCharCode(code)
+ return uppercase ? value.toUpperCase() : value
+ }
+ if (code >= 96 && code <= 105) return String(code - 96)
+ if (code === 189 || code === 109) return '-'
+ if (code === 190 || code === 110) return '.'
+ return ''
+ }
+
+ function isAndroidPlusKeyEvent(event) {
+ return Boolean(event?.isPlusKeyEvent)
+ }
+
+ function normalizeAndroidKeyCode(event) {
+ const code = Number(event?.keyCode || event?.which || 0)
+ if (code === 66 || code === 61 || code === 160) return 'Enter'
+ const value = androidKeyCodeMap[code] || ''
+ return uppercase ? value.toUpperCase() : value.toLowerCase()
+ }
+
+ function clearScannerBuffer() {
+ scannerBuffer = ''
+ if (scannerTimer) {
+ clearTimeout(scannerTimer)
+ scannerTimer = null
+ }
+ }
+
+ async function commitScannerBuffer() {
+ const value = scannerBuffer.trim()
+ clearScannerBuffer()
+ if (value.length < minLength) return
+ if (hideKeyboardOnScan) hideSoftKeyboard()
+ await onScan(value)
+ }
+
+ function hideSoftKeyboard() {
+ try {
+ uni.hideKeyboard()
+ } catch (e) {}
+ }
+
+ onShow(() => {
+ registerScannerListener()
+ })
+
+ onHide(() => {
+ unregisterScannerListener()
+ })
+
+ onUnload(() => {
+ unregisterScannerListener()
+ })
+
+ return {
+ clearScannerBuffer,
+ registerScannerListener,
+ unregisterScannerListener
+ }
+}
diff --git a/src/pages_function/pages/equipmentLedger/index.vue b/src/pages_function/pages/equipmentLedger/index.vue
index a3662a1..d8ea078 100644
--- a/src/pages_function/pages/equipmentLedger/index.vue
+++ b/src/pages_function/pages/equipmentLedger/index.vue
@@ -7,14 +7,14 @@
{{ selectedLineLabel }}
-
+
@@ -82,7 +82,7 @@