You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_app/src/pages_function/pages/sparepartOutbound/sparepartConfirm.vue

591 lines
31 KiB
Vue

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="page-container">
<NavBar :title="'确认备件出库'" />
<view class="sparepart-section">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">å·²éå¤ä»?/text>
</view>
<view class="sparepart-card">
<view class="card-top">
<view class="card-image-wrap">
<image v-if="sparepartImage" :src="sparepartImage" class="card-image" mode="aspectFill" />
<view v-else class="card-image-empty"><text class="empty-img-icon">📦</text></view>
</view>
<view class="card-info-right">
<view class="info-item"><text class="info-label">备件å<C2B6><C3A5>ç§°ï¼?/text><text class="info-name">{{ textValue(sparepart.name) }}</text></view>
<view class="info-item"><text class="info-label">规格�/text><text class="info-value">{{ textValue(sparepart.standard || sparepart.deviceSpec) }}</text></view>
<view class="info-item"><text class="info-label">当å‰<C3A5>库存ï¼?/text><text class="info-value stock-highlight">{{ sparepart.count != null ? sparepart.count : 0 }}{{ textUnit(sparepart.unitName || sparepart.minStockUnitName || 'ä¸?) }}</text></view>
</view>
</view>
<view class="card-bottom">
<view class="detail-row two-col">
<view class="detail-col"><text class="detail-label">采购å<C2AD>•ä½<C3A4>ï¼?/text><text class="detail-value">{{ textValue(sparepart.purchaseUnitName) }}</text></view>
<view class="detail-col"><text class="detail-label">库存å<CB9C>•ä½<C3A4>ï¼?/text><text class="detail-value">{{ textValue(sparepart.unitName || sparepart.minStockUnitName || 'ä¸?) }}</text></view>
</view>
<view class="detail-row"><text class="detail-label">æ<>¢ç®—关系ï¼?/text><text class="detail-value">1{{ textValue(sparepart.purchaseUnitName) }}={{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }}</text></view>
</view>
</view>
<!-- 出库用�-->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view>
<text class="section-title">出库用�/text>
</view>
<view class="purpose-card">
<view class="purpose-item" :class="{ active: selectedPurpose === 'repair' }" @click="setPurpose('repair')">
<text class="iconfont icon-repair purpose-icon"></text>
<text class="purpose-text">维修领用</text>
</view>
<view class="purpose-item" :class="{ active: selectedPurpose === 'maintain' }" @click="setPurpose('maintain')">
<text class="iconfont icon-shield purpose-icon"></text>
<text class="purpose-text">ä¿<C3A4>养领用</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>
<!-- å…³è<C2B3>”ä¿¡æ<C2A1>¯ -->
<view v-if="selectedPurpose === 'repair' || selectedPurpose === 'maintain'" class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view><text class="section-title">å…³è<C2B3>”ä¿¡æ<C2A1>¯</text>
</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">å…³è<C2B3>”设备</text>
<view class="warehouse-area-dropdown" @click="toggleDeviceDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedDevice }]">{{ selectedDevice ? selectedDevice.label : '请选择' }}</text>
<text class="dropdown-arrow">â–?/text>
</view>
<view v-if="showDeviceDropdown" class="dropdown-panel">
<view class="dropdown-scroll">
<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="deviceLoading" class="dropdown-empty">加载�..</view>
<view v-else-if="!filteredDeviceOptions.length" class="dropdown-empty">{{ selectedPurpose === 'maintain' ? 'æšæ— ä¿<C3A4>养设备' : 'æšæ— è®¾å¤‡æ•°æ<C2B0>®' }}</view>
</view>
</view>
</view>
</view>
<view class="warehouse-area-row">
<text class="warehouse-area-label">{{ selectedPurpose === 'repair' ? 'ç»´ä¿®å<C2AE>•å<E280A2>·' : 'ä¿<C3A4>å…»å<C2BB>•å<E280A2>·' }}</text>
<view class="warehouse-area-dropdown" @click="toggleOrderDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !currentOrder }]">{{ currentOrder ? currentOrder.label : '请选择' }}</text>
<text class="dropdown-arrow">â–?/text>
</view>
<view v-if="showOrderDropdown" class="dropdown-panel">
<view class="dropdown-scroll">
<view v-for="item in currentOrderOptions" :key="item.value" class="dropdown-item" :class="{ active: currentOrder?.value === item.value }" @click.stop="handleSelectOrder(item)">
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="currentOrder?.value === item.value" class="dropdown-check">�/text>
</view>
<view v-if="!currentOrderOptions.length" class="dropdown-empty">{{ selectedPurpose === 'repair' ? 'æšæ— ç»´ä¿®å<C2AE>? : 'æšæ— ä¿<C3A4>å…»å<C2BB>? }}</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 出库数é‡<C3A9> -->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view><text class="section-title">出库数é‡<C3A9></text>
</view>
<view class="qty-input-card">
<view class="form-field">
<text class="form-label">出库数é‡<C3A9></text>
<input v-model="outboundQty" class="form-input" placeholder="请输� confirm-type="done" />
<text class="form-suffix-text">å<>•ä½<C3A4>:{{ textValue(sparepart.purchaseUnitName) }}</text>
</view>
<view class="convert-row">
<text class="convert-label-inline">出库å<E2809C>Žå‰©ä½™åº“存:</text>
<text :class="['convert-value-inline', { 'text-danger': stockExceeded }]">{{ remainingStock }}</text>
<text class="convert-unit-inline">{{ stockUnitLabel(sparepart) }}</text>
</view>
<view class="convert-helper">{{ currentStockText }}</view>
<view class="convert-formula-inline">{{ outboundQty || 0 }}{{ textValue(sparepart.purchaseUnitName) }} × {{ textValue(sparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(sparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(sparepart) }}</view>
<!-- 库存ä¸<C3A4>è¶³æ<C2B3><C3A6>示 -->
<view v-if="stockExceeded" class="stock-warning">
<text class="warning-icon">âš?/text>
<view class="warning-text">
<text class="warning-line">当å‰<C3A5>库存ä¸<C3A4>足,ä¸<C3A4>能出åº?/text>
<text class="warning-line">当å‰<C3A5>库存:{{ stockCount }}{{ stockUnitLabel(sparepart) }}({{ stockPackText }}ï¼?/text>
<text class="warning-line">本次出库:{{ calculatedStock }}{{ stockUnitLabel(sparepart) }}({{ outboundPackText }}�/text>
</view>
</view>
</view>
<!-- 供应�-->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view><text class="section-title">供应�/text>
</view>
<view class="select-row-card">
<view class="full-dropdown">
<text :class="{ placeholder: !defaultSupplierName }">{{ defaultSupplierName || '未é…<C3A9>置默认ä¾åº”商' }}</text>
</view>
</view>
<!-- 仓库/库区 -->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view><text class="section-title">仓库/库区</text>
</view>
<view 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="toggleWarehouseDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedWarehouse }]">{{ selectedWarehouse ? selectedWarehouse.label : '请选择' }}</text>
<text class="dropdown-arrow">â–?/text>
</view>
<view v-if="showWarehouseDropdown" class="dropdown-panel">
<view class="dropdown-scroll">
<view v-for="item in warehouseOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedWarehouse?.value === item.value }" @click.stop="handleSelectWarehouse(item)">
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedWarehouse?.value === item.value" class="dropdown-check">�/text>
</view>
</view>
</view>
</view>
</view>
<view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedArea }]">{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载�..' : '请选择') }}</text>
<text class="dropdown-arrow">â–?/text>
</view>
<view v-if="showAreaDropdown" class="dropdown-panel">
<view class="dropdown-scroll">
<view v-for="item in areaOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedArea?.value === item.value }" @click.stop="handleSelectArea(item)">
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedArea?.value === item.value" class="dropdown-check">�/text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="bottom-actions">
<view class="bottom-btn cancel-btn" @click="handleCancel">å<>消</view>
<view class="bottom-btn confirm-btn" @click="handleConfirm">确认</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onShow, onHide } from '@dcloudio/uni-app'
import NavBar from '@/components/common/NavBar.vue'
import { getDeviceLedgerList } from '@/api/mes/moldoperate'
import { getWarehouseSimpleList, getWarehouseAreaSimpleList } from '@/api/mes/moldget'
import { getEquipmentRepairListByDeviceId, getMaintenanceTicketPage } from '@/api/mes/equipment'
import { getDvRepairPage } from '@/api/mes/dvrepair'
import { getSparepartDetail, getSparepartStockCount } from '@/api/mes/sparepart'
const sparepart = ref({})
const outboundQty = ref(null)
const selectedPurpose = ref('repair')
function setPurpose(value) {
selectedPurpose.value = value
selectedDevice.value = null
selectedRepairOrder.value = null
selectedMaintainOrder.value = null
repairOrderOptions.value = []
maintainOrderOptions.value = []
}
const deviceOptions = ref([])
const selectedDevice = ref(null)
const showDeviceDropdown = ref(false)
const repairOrderOptions = ref([])
const selectedRepairOrder = ref(null)
const maintainOrderOptions = ref([])
const selectedMaintainOrder = ref(null)
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(() => selectedPurpose.value === 'repair' ? selectedRepairOrder.value : selectedMaintainOrder.value)
const currentOrderOptions = computed(() => selectedPurpose.value === 'repair' ? repairOrderOptions.value : maintainOrderOptions.value)
// 仓库/库区
const warehouseOptions = ref([])
const selectedWarehouse = ref(null)
const showWarehouseDropdown = ref(false)
const areaOptions = ref([])
const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false)
const sparepartImage = computed(() => {
const images = sparepart.value.images
if (!images) return ''
if (Array.isArray(images)) return String(images[0] || '')
return String(images).split(',')[0]?.trim() || ''
})
const stockCount = computed(() => sparepart.value.count ?? 0)
const calculatedStock = computed(() => {
const qty = Number(outboundQty.value) || 0
const ratio = Number(sparepart.value.purchaseUnitConvertQuantity) || 1
return qty * ratio
})
const remainingStock = computed(() => {
return Math.max(stockCount.value - calculatedStock.value, 0)
})
const stockExceeded = computed(() => {
return outboundQty.value > 0 && calculatedStock.value > stockCount.value
})
// 库存格å¼<C3A5>åŒï¼šX包Y根(带å<C2A6>•ä½<C3A4>)
function formatStockPack(count) {
const ratio = Number(sparepart.value.purchaseUnitConvertQuantity) || 1
if (ratio === 1) return ''
const packs = Math.floor(count / ratio)
const remain = count % ratio
const parts = []
if (packs > 0) parts.push(`${packs}${textValue(sparepart.value.purchaseUnitName)}`)
if (remain > 0) parts.push(`${remain}${stockUnitLabel(sparepart)}`)
return parts.join('') || ''
}
const currentStockText = computed(() => {
const text = formatStockPack(stockCount.value)
return text ? `当å‰<EFBFBD>库存ï¼?{text}` : ''
})
const stockPackText = computed(() => {
return formatStockPack(stockCount.value) || `${stockCount.value}${stockUnitLabel(sparepart)}`
})
const outboundPackText = computed(() => {
return formatStockPack(calculatedStock.value) || `${calculatedStock.value}${stockUnitLabel(sparepart)}`
})
const defaultSupplierName = computed(() => {
const suppliers = sparepart.value.suppliers
if (!suppliers || !suppliers.length) return ''
const defaultId = sparepart.value.defaultSupplierId
if (defaultId) {
const found = suppliers.find(s => s.supplierId === defaultId || s.id === defaultId)
if (found) return found.supplierName || ''
}
const firstDefault = suppliers.find(s => s.defaultStatus === 1)
return firstDefault?.supplierName || ''
})
function textValue(v) { if (v === 0) return '0'; if (v == null) return '-'; const s = String(v).trim(); return s || '-' }
function textUnit(v) { if (v === 0) return '0'; if (v == null) return ''; return String(v).trim() }
function stockUnitLabel(item) { return item.unitName || item.minStockUnitName || '� }
function handleCancel() { uni.navigateBack() }
// 用途映射:repair�, maintain�, other�
const PURPOSE_MAP = { repair: 1, maintain: 2, other: 3 }
function handleConfirm() {
if (!outboundQty.value || Number(outboundQty.value) <= 0) {
uni.showToast({ title: '请è¾å¥åºåºæ°é?, icon: 'none' }); return
}
if (stockExceeded.value) {
uni.showModal({
title: '库存ä¸<C3A4>è¶³',
content: `当å‰<EFBFBD>库存ï¼?{stockCount.value}${stockUnitLabel(sparepart)}ï¼?{stockPackText.value})\n本次出库ï¼?{calculatedStock.value}${stockUnitLabel(sparepart)}ï¼?{outboundPackText.value})`,
showCancel: false,
confirmText: '知é<C2A5>“äº?
})
return
}
if (selectedPurpose.value === 'repair' && !selectedDevice.value) {
uni.showToast({ title: 'è¯·éæ©å³è<EFBFBD>设å¤', icon: 'none' }); return
}
if (selectedPurpose.value === 'maintain' && !selectedDevice.value) {
uni.showToast({ title: 'è¯·éæ©å³è<EFBFBD>设å¤', icon: 'none' }); return
}
if (!selectedWarehouse.value) {
uni.showToast({ title: 'è¯·éæ©ä»åº', icon: 'none' }); return
}
const item = {
productId: sparepart.value.id,
productName: sparepart.value.name || '',
productBarCode: sparepart.value.barCode || '',
productUnitName: sparepart.value.unitName || sparepart.value.minStockUnitName || '',
purchaseUnitName: sparepart.value.purchaseUnitName || '',
purchaseUnitConvertQuantity: Number(sparepart.value.purchaseUnitConvertQuantity) || 1,
inputCount: Number(outboundQty.value),
count: Number(outboundQty.value) * (Number(sparepart.value.purchaseUnitConvertQuantity) || 1),
supplierId: sparepart.value.defaultSupplierId,
supplierName: defaultSupplierName.value,
purpose: selectedPurpose.value,
outUsageType: PURPOSE_MAP[selectedPurpose.value] || 3,
warehouseId: selectedWarehouse.value?.value,
warehouseName: selectedWarehouse.value?.label || '',
areaId: selectedArea.value?.value,
areaName: selectedArea.value?.label || '',
_sparepart: { ...sparepart.value }
}
if (selectedDevice.value) {
item.repairDeviceId = selectedDevice.value.value
item.deviceName = selectedDevice.value.deviceName || selectedDevice.value.label || ''
}
if (selectedPurpose.value === 'repair' && selectedRepairOrder.value) {
item.repairId = selectedRepairOrder.value.value
} else if (selectedPurpose.value === 'maintain' && selectedMaintainOrder.value) {
item.maintenanceId = selectedMaintainOrder.value.value
}
if (!getApp().globalData._sparepartOutboundItems) {
getApp().globalData._sparepartOutboundItems = []
}
getApp().globalData._sparepartOutboundItems.push(item)
uni.showToast({ title: '已添� ' + (sparepart.value.name || ''), icon: 'success', duration: 1200 })
setTimeout(() => {
const fromScan = getApp().globalData._sparepartFromScan
getApp().globalData._sparepartFromScan = false
uni.navigateBack({ delta: fromScan ? 1 : 2 })
}, 800)
}
// Device dropdown
function toggleDeviceDropdown() {
showDeviceDropdown.value = !showDeviceDropdown.value
if (showDeviceDropdown.value && selectedPurpose.value === 'maintain' && maintainDeviceIds.value.size === 0) {
loadMaintainDeviceIds()
}
}
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 || [])
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) {
selectedDevice.value = item; showDeviceDropdown.value = false
selectedRepairOrder.value = null; selectedMaintainOrder.value = null
repairOrderOptions.value = []; maintainOrderOptions.value = []
if (selectedPurpose.value === 'repair') loadRepairOrders(item.deviceCode)
else loadMaintainOrders(item.deviceCode)
}
async function loadDevices() {
try {
const res = await getDeviceLedgerList({ pageNo: 1, pageSize: 100 })
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?.list) list = data.data.list
else if (data?.data?.rows) list = data.data.rows
else if (data?.data?.records) list = data.data.records
else if (data?.list) list = data.list
else if (data?.rows) list = data.rows
deviceOptions.value = list.map((d) => ({
value: d.id, label: d.name || d.deviceName || d.deviceCode || String(d.id || ''),
deviceCode: d.deviceCode || d.code || '', deviceName: d.name || d.deviceName || ''
}))
} catch (e) { console.error('loadDevices error', e) }
}
function toggleOrderDropdown() { if (!selectedDevice.value) return; showOrderDropdown.value = !showOrderDropdown.value }
function handleSelectOrder(item) {
if (selectedPurpose.value === 'repair') selectedRepairOrder.value = item
else selectedMaintainOrder.value = item
showOrderDropdown.value = false
}
async function loadMaintainOrders(deviceCode) {
const deviceId = selectedDevice.value?.value
if (deviceId) await loadMaintainOrdersById(deviceId)
}
async function loadMaintainOrdersById(deviceId) {
if (!deviceId) return
try {
const deviceName = selectedDevice.value?.deviceName || selectedDevice.value?.label || ''
const res = await getMaintenanceTicketPage({ deviceName })
const data = res && res.data !== undefined ? res.data : res
const list = Array.isArray(data) ? data : (data?.list || data?.records || [])
maintainOrderOptions.value = list.map((m) => ({ value: m.id, label: m.planNo || String(m.id || ''), deviceId: m.deviceId }))
} catch (e) { console.error('loadMaintainOrdersById error', e) }
}
async function loadRepairOrders(deviceCode) {
if (!deviceCode) { const deviceId = selectedDevice.value?.value; if (deviceId) await loadRepairOrdersById(deviceId); return }
try {
const res = await getDvRepairPage({ machineryCode: deviceCode, pageNo: 1, pageSize: 100 })
const root = res && res.data !== undefined ? res.data : res
let list = []
if (Array.isArray(root)) list = root
else if (root && Array.isArray(root.data)) list = root.data
else if (root?.data?.list) list = root.data.list
else if (root?.data?.rows) list = root.data.rows
else if (root?.list) list = root.list
repairOrderOptions.value = list.map((r) => ({ value: r.id, label: r.repairCode || r.repairName || r.subjectName || String(r.id || '') }))
} catch (e) { console.error('loadRepairOrders error', e) }
}
async function loadRepairOrdersById(deviceId) {
if (!deviceId) return
try {
const res = await getEquipmentRepairListByDeviceId(deviceId)
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?.list) list = data.data.list
else if (data?.list) list = data.list
repairOrderOptions.value = list.map((r) => ({ value: r.id, label: r.repairName || String(r.id || '') }))
} catch (e) { console.error('loadRepairOrdersById error', e) }
}
// 仓库
function toggleWarehouseDropdown() { showWarehouseDropdown.value = !showWarehouseDropdown.value }
function handleSelectWarehouse(item) { selectedWarehouse.value = item; showWarehouseDropdown.value = false; selectedArea.value = null; areaOptions.value = []; loadAreas(item.value) }
async function loadWarehouses() {
try {
const res = await getWarehouseSimpleList()
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 || '') }))
// 默认选中"备件�
const defaultWh = warehouseOptions.value.find(w => (w.label || '').includes('å¤ä»ä»?))
if (defaultWh) { selectedWarehouse.value = defaultWh; loadAreas(defaultWh.value) }
} catch (e) {}
}
// 库区
function toggleAreaDropdown() { if (!selectedWarehouse.value) { uni.showToast({ title: '请先选择仓库', icon: 'none' }); return }; showAreaDropdown.value = !showAreaDropdown.value }
function handleSelectArea(item) { selectedArea.value = item; showAreaDropdown.value = false }
async function loadAreas(warehouseId) {
if (!warehouseId) return
loadingAreas.value = true
areaOptions.value = []
try {
const res = await getWarehouseAreaSimpleList(warehouseId)
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
areaOptions.value = data.map(a => ({ value: a.id, label: a.name || a.areaName || String(a.id || '') }))
// 默认选中"备件�
const defaultArea = areaOptions.value.find(a => (a.label || '').includes('备件�))
if (defaultArea) selectedArea.value = defaultArea
} catch (e) {} finally { loadingAreas.value = false }
}
onShow(async () => {
const selectResult = getApp().globalData?._sparepartBeforeConfirm
if (selectResult) {
sparepart.value = selectResult
outboundQty.value = null
getApp().globalData._sparepartBeforeConfirm = null
if (selectResult.id) {
try {
const res = await getSparepartDetail(selectResult.id)
const detail = res?.data || res
if (detail) { sparepart.value = { ...sparepart.value, ...detail }; loadStockCount() }
} catch (e) { console.error('获å<EFBFBD>å¤ä»è¯¦æƒå¤±è´¥:', e) }
}
}
loadDevices()
loadWarehouses()
})
async function loadStockCount() {
const id = sparepart.value.id
if (!id) return
try { const res = await getSparepartStockCount(id); sparepart.value.count = (res?.data ?? res) != null ? Number(res?.data ?? res) : 0 }
catch (e) { sparepart.value.count = 0 }
}
onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false })
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f6f8; padding-bottom: calc(120rpx + env(safe-area-inset-bottom)); }
.sparepart-section { padding: 0; }
.section-title-bar { 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; }
.card-image { width: 100%; height: 100%; }
.card-image-empty { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #f8fafc; }
.empty-img-icon { font-size: 56rpx; opacity: 0.3; }
.card-info-right { flex: 1; min-width: 0; }
.info-item { display: flex; align-items: center; margin-bottom: 8rpx; }
.info-label { font-size: 26rpx; color: #6b7280; flex-shrink: 0; }
.info-name { font-size: 30rpx; font-weight: 700; color: #0f172a; }
.info-value { font-size: 26rpx; color: #374151; &.stock-highlight { color: #2563eb; font-weight: 500; } }
.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; }
.purpose-card { display: flex; gap: 20rpx; margin: 0 24rpx 20rpx; }
.purpose-item { flex: 1; display: flex; align-items: center; justify-content: center; gap: 12rpx; height: 92rpx; background: #fff; border: 2rpx solid #e5e7eb; border-radius: 14rpx; font-size: 28rpx; color: #6b7280;
&.active { border-color: #2563eb; background: #eff6ff; color: #2563eb; font-weight: 600; }
}
.purpose-icon { font-size: 32rpx; }
.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; }
.convert-helper { margin-top: 8rpx; font-size: 24rpx; color: #9ca3af; }
.text-danger { color: #dc2626 !important; }
.stock-warning { display: flex; gap: 12rpx; margin-top: 20rpx; padding: 20rpx; background: #fef2f2; border: 1rpx solid #fecaca; border-radius: 12rpx; }
.warning-icon { font-size: 32rpx; flex-shrink: 0; }
.warning-text { flex: 1; display: flex; flex-direction: column; gap: 6rpx; }
.warning-line { font-size: 24rpx; color: #991b1b; line-height: 1.6; display: block; }
.select-row-card { position: relative; display: flex; align-items: center; justify-content: space-between; background: #fff; border-radius: 16rpx; padding: 24rpx; margin: 0 24rpx 20rpx; box-shadow: 0 4rpx 16rpx rgba(15,23,42,0.04); }
.full-dropdown { display: flex; align-items: center; justify-content: space-between; width: 100%; font-size: 28rpx; color: #374151; .placeholder { color: #9ca3af; } }
.warehouse-area-card { align-items: flex-start; }
.warehouse-area-rows { width: 100%; display: flex; flex-direction: column; gap: 16rpx; }
.warehouse-area-row { display: flex; align-items: center; }
.warehouse-area-label { width: 120rpx; font-size: 26rpx; color: #6b7280; 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: 200; margin-top: 4rpx; background: #fff; border: 1rpx solid #e0e0e0; border-radius: 12rpx; box-shadow: 0 8rpx 30rpx rgba(0,0,0,0.15); overflow: hidden; }
}
.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: 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 { max-height: 360rpx; overflow-y: auto; }
.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; }
.bottom-actions { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #fff; 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; } }
.cancel-btn { background: #eef2f7; color: #475569; }
.confirm-btn { background: #1f4b79; color: #ffffff; }
</style>