style: 备件出入库优化

master
zhongwenkai 6 days ago
parent bac605c5ab
commit 51dc76aa10

@ -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 }
})
}

@ -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 }
})
}

@ -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({

@ -1,4 +1,4 @@
<template>
n<template>
<view class="page-container">
<NavBar :title="t('sparepartInbound.createTitle')" />
@ -51,7 +51,7 @@
</view>
<view class="info-item">
<text class="info-label">当前库存</text>
<text class="info-value stock-highlight">{{ textValue(selectedSparepart.stock) }}{{ textUnit(selectedSparepart.minStockUnitName) }}</text>
<text class="info-value stock-highlight">{{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}</text>
</view>
</view>
</view>
@ -65,7 +65,7 @@
</view>
<view class="detail-col">
<text class="detail-label">库存单位</text>
<text class="detail-value">{{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName) }}</text>
<text class="detail-value">{{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}</text>
</view>
</view>
<view class="detail-row">
@ -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 {

@ -48,7 +48,7 @@
</view>
<view class="info-item">
<text class="info-label">当前库存</text>
<text class="info-value stock-highlight">{{ textValue(selectedSparepart.stock) }}{{ textUnit(selectedSparepart.minStockUnitName) }}</text>
<text class="info-value stock-highlight">{{ selectedSparepart.count != null ? selectedSparepart.count : 0 }}{{ textUnit(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}</text>
</view>
</view>
</view>
@ -60,7 +60,7 @@
</view>
<view class="detail-col">
<text class="detail-label">库存单位</text>
<text class="detail-value">{{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName) }}</text>
<text class="detail-value">{{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName || '个') }}</text>
</view>
</view>
<view class="detail-row">
@ -92,6 +92,14 @@
<text class="iconfont icon-shield purpose-icon"></text>
<text class="purpose-text">保养领用</text>
</view>
<view
class="purpose-item"
:class="{ active: selectedPurpose === 'other' }"
@click="setPurpose('other')"
>
<text class="iconfont icon-box purpose-icon"></text>
<text class="purpose-text">其他出库</text>
</view>
</view>
<!-- 关联信息维修领用/保养领用 -->
@ -101,6 +109,7 @@
</view>
<view v-if="selectedPurpose === 'repair' || selectedPurpose === 'maintain'" class="select-row-card warehouse-area-card">
<view class="warehouse-area-rows">
<!-- 关联设备 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">关联设备</text>
<view class="warehouse-area-dropdown" @click="toggleDeviceDropdown">
@ -110,15 +119,17 @@
</view>
<view v-if="showDeviceDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view v-for="item in deviceOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedDevice?.value === item.value }" @click.stop="handleSelectDevice(item)">
<view v-for="item in filteredDeviceOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedDevice?.value === item.value }" @click.stop="handleSelectDevice(item)">
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedDevice?.value === item.value" class="dropdown-check"></text>
</view>
<view v-if="!deviceOptions.length" class="dropdown-empty"></view>
<view v-if="deviceLoading" class="dropdown-empty">...</view>
<view v-else-if="!filteredDeviceOptions.length" class="dropdown-empty">{{ selectedPurpose === 'maintain' ? '暂无保养设备' : '暂无设备数据' }}</view>
</scroll-view>
</view>
</view>
</view>
<!-- 维修单号/保养单号 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">{{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}</text>
<view class="warehouse-area-dropdown" @click="toggleOrderDropdown">
@ -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; }

Loading…
Cancel
Save