黄伟杰 2 days ago
commit c655469801

@ -71,27 +71,21 @@
</view> </view>
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text> <text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <view class="product-search-row">
<text :class="['dropdown-value', { placeholder: !selectedArea }]"> <input
{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }} v-model="areaScanInput"
</text> class="scan-input"
<text class="dropdown-arrow"></text> type="text"
</view> placeholder="扫码或输入库区码"
<view v-if="showAreaDropdown" class="dropdown-panel"> confirm-type="done"
<view class="dropdown-scroll"> @confirm="onAreaScanConfirm"
<view />
v-for="item in areaOptions" <picker mode="selector" :range="areaOptions" range-key="label" :value="getAreaIndex(selectedArea, areaOptions)" @change="onAreaChange">
:key="item.value" <view class="area-select-btn">
class="dropdown-item" <text class="select-btn-text">选择</text>
: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 v-if="!areaOptions.length" class="dropdown-empty"></view> </picker>
</view>
</view> </view>
</view> </view>
</view> </view>
@ -161,11 +155,11 @@ const inboundQty = ref(null)
const warehouseOptions = ref([]) const warehouseOptions = ref([])
const selectedWarehouse = ref(null) const selectedWarehouse = ref(null)
// //
const areaOptions = ref([]) const areaOptions = ref([])
const selectedArea = ref(null) const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false) const loadingAreas = ref(false)
const areaScanInput = ref('')
const materialImage = computed(() => { const materialImage = computed(() => {
const images = material.value.images const images = material.value.images
@ -295,17 +289,41 @@ async function loadWarehouses() {
} }
// //
function toggleAreaDropdown() { function getAreaIndex(selected, options) {
if (!selectedWarehouse.value) { if (!selected) return -1
uni.showToast({ title: '请先选择仓库', icon: 'none' }) const index = options.findIndex((item) => String(item.value) === String(selected.value))
return return index >= 0 ? index : -1
}
function onAreaChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < areaOptions.value.length) {
selectedArea.value = areaOptions.value[index]
areaScanInput.value = selectedArea.value.label
} }
showAreaDropdown.value = !showAreaDropdown.value
} }
function handleSelectArea(item) {
selectedArea.value = item function getAreaScanId(value) {
showAreaDropdown.value = false const text = String(value || '').trim()
if (!text) return ''
const match = text.match(/WAREHOUSE_AREA[-_:](\d+)/i)
if (match) return match[1]
const tail = text.match(/(\d+)$/)
return tail ? tail[1] : text
} }
async function onAreaScanConfirm() {
const areaId = getAreaScanId(areaScanInput.value)
if (!areaId) return
const area = areaOptions.value.find((item) => String(item.value) === String(areaId))
if (!area) {
uni.showToast({ title: '未找到该库区', icon: 'none' })
return
}
selectedArea.value = area
areaScanInput.value = area.label
}
async function loadAreas(warehouseId) { async function loadAreas(warehouseId) {
if (!warehouseId) return if (!warehouseId) return
loadingAreas.value = true loadingAreas.value = true
@ -363,9 +381,7 @@ async function loadStockCount() {
} }
} }
onHide(() => { onHide(() => {})
showAreaDropdown.value = false
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -446,6 +462,11 @@ onHide(() => {
.warehouse-area-label { width: 80rpx; font-size: 26rpx; color: #6b7280; flex-shrink: 0; } .warehouse-area-label { width: 80rpx; font-size: 26rpx; color: #6b7280; flex-shrink: 0; }
.warehouse-area-dropdown { flex: 1; min-width: 0; position: relative; } .warehouse-area-dropdown { flex: 1; min-width: 0; position: relative; }
.product-search-row { display: flex; align-items: center; gap: 16rpx; }
.scan-input { flex: 1; min-width: 0; height: 64rpx; padding: 0 20rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 10rpx; box-sizing: border-box; font-size: 27rpx; color: #333; }
.area-select-btn { flex-shrink: 0; width: 160rpx; min-height: 64rpx; padding: 0 18rpx; background: #1f4b79; border-radius: 10rpx; box-sizing: border-box; display: flex; align-items: center; justify-content: center; }
.select-btn-text { color: #ffffff; font-size: 26rpx; font-weight: 600; }
.dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; padding: 0 20rpx; border: 1rpx solid #e0e0e0; border-radius: 10rpx; background: #f9fafb; } .dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; 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-value { flex: 1; font-size: 27rpx; color: #333; &.placeholder { color: #bbb; } }
.dropdown-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; } .dropdown-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; }

@ -45,18 +45,21 @@
</view> </view>
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text> <text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <view class="product-search-row">
<text :class="['dropdown-value', { placeholder: !selectedArea }]">{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}</text> <input
<text class="dropdown-arrow"></text> v-model="areaScanInput"
</view> class="scan-input"
<view v-if="showAreaDropdown" class="dropdown-panel"> type="text"
<view class="dropdown-scroll"> placeholder="扫码或输入库区码"
<view v-for="item in areaOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedArea?.value === item.value }" @click.stop="handleSelectArea(item)"> confirm-type="done"
<text class="dropdown-item-text">{{ item.label }}</text> @confirm="onAreaScanConfirm"
<text v-if="selectedArea?.value === item.value" class="dropdown-check"></text> />
<picker mode="selector" :range="areaOptions" range-key="label" :value="getAreaIndex(selectedArea, areaOptions)" @change="onAreaChange">
<view class="area-select-btn">
<text class="select-btn-text">选择</text>
</view> </view>
</view> </picker>
</view> </view>
</view> </view>
</view> </view>
@ -125,8 +128,8 @@ const warehouseOptions = ref([])
const selectedWarehouse = ref(null) const selectedWarehouse = ref(null)
const areaOptions = ref([]) const areaOptions = ref([])
const selectedArea = ref(null) const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false) const loadingAreas = ref(false)
const areaScanInput = ref('')
const materialImage = computed(() => { const materialImage = computed(() => {
const images = material.value.images const images = material.value.images
@ -262,8 +265,42 @@ async function loadWarehouses() {
} catch (e) {} } catch (e) {}
} }
function toggleAreaDropdown() { if (!selectedWarehouse.value) { uni.showToast({ title: '请先选择仓库', icon: 'none' }); return }; showAreaDropdown.value = !showAreaDropdown.value } function getAreaIndex(selected, options) {
function handleSelectArea(item) { selectedArea.value = item; showAreaDropdown.value = false; loadStockCount() } if (!selected) return -1
const index = options.findIndex((item) => String(item.value) === String(selected.value))
return index >= 0 ? index : -1
}
function onAreaChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < areaOptions.value.length) {
selectedArea.value = areaOptions.value[index]
areaScanInput.value = selectedArea.value.label
loadStockCount()
}
}
function getAreaScanId(value) {
const text = String(value || '').trim()
if (!text) return ''
const match = text.match(/WAREHOUSE_AREA[-_:](\d+)/i)
if (match) return match[1]
const tail = text.match(/(\d+)$/)
return tail ? tail[1] : text
}
async function onAreaScanConfirm() {
const areaId = getAreaScanId(areaScanInput.value)
if (!areaId) return
const area = areaOptions.value.find((item) => String(item.value) === String(areaId))
if (!area) {
uni.showToast({ title: '未找到该库区', icon: 'none' })
return
}
selectedArea.value = area
areaScanInput.value = area.label
loadStockCount()
}
async function loadAreas(warehouseId) { async function loadAreas(warehouseId) {
if (!warehouseId) return if (!warehouseId) return
loadingAreas.value = true loadingAreas.value = true
@ -320,7 +357,7 @@ async function loadStockCount() {
} catch (e) { warehouseAreaStockCount.value = 0 } } catch (e) { warehouseAreaStockCount.value = 0 }
} }
onHide(() => { showAreaDropdown.value = false }) onHide(() => {})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -375,6 +412,12 @@ onHide(() => { showAreaDropdown.value = false })
.warehouse-area-dropdown { flex: 1; min-width: 0; position: relative; .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-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; }
} }
.product-search-row { display: flex; align-items: center; gap: 16rpx; }
.scan-input { flex: 1; min-width: 0; height: 64rpx; padding: 0 20rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 10rpx; box-sizing: border-box; font-size: 27rpx; color: #333; }
.area-select-btn { flex-shrink: 0; width: 160rpx; min-height: 64rpx; padding: 0 18rpx; background: #1f4b79; border-radius: 10rpx; box-sizing: border-box; display: flex; align-items: center; justify-content: center; }
.select-btn-text { color: #ffffff; font-size: 26rpx; font-weight: 600; }
.dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; padding: 0 20rpx; border: 1rpx solid #e0e0e0; border-radius: 10rpx; background: #f9fafb; } .dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; 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-value { flex: 1; font-size: 27rpx; color: #333; &.placeholder { color: #bbb; } }
.dropdown-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; } .dropdown-arrow { font-size: 20rpx; color: #999; flex-shrink: 0; }

@ -72,27 +72,21 @@
</view> </view>
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text> <text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <view class="product-search-row">
<text :class="['dropdown-value', { placeholder: !selectedArea }]"> <input
{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }} v-model="areaScanInput"
</text> class="scan-input"
<text class="dropdown-arrow"></text> type="text"
</view> placeholder="扫码或输入库区码"
<view v-if="showAreaDropdown" class="dropdown-panel"> confirm-type="done"
<view class="dropdown-scroll"> @confirm="onAreaScanConfirm"
<view />
v-for="item in areaOptions" <picker mode="selector" :range="areaOptions" range-key="label" :value="getAreaIndex(selectedArea, areaOptions)" @change="onAreaChange">
:key="item.value" <view class="area-select-btn">
class="dropdown-item" <text class="select-btn-text">选择</text>
: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 v-if="!areaOptions.length" class="dropdown-empty"></view> </picker>
</view>
</view> </view>
</view> </view>
</view> </view>
@ -164,11 +158,11 @@ const supplierName = ref('')
const warehouseOptions = ref([]) const warehouseOptions = ref([])
const selectedWarehouse = ref(null) const selectedWarehouse = ref(null)
// //
const areaOptions = ref([]) const areaOptions = ref([])
const selectedArea = ref(null) const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false) const loadingAreas = ref(false)
const areaScanInput = ref('')
const sparepartImage = computed(() => { const sparepartImage = computed(() => {
const images = sparepart.value.images const images = sparepart.value.images
@ -309,17 +303,41 @@ async function loadWarehouses() {
} }
// //
function toggleAreaDropdown() { function getAreaIndex(selected, options) {
if (!selectedWarehouse.value) { if (!selected) return -1
uni.showToast({ title: '请先选择仓库', icon: 'none' }) const index = options.findIndex((item) => String(item.value) === String(selected.value))
return return index >= 0 ? index : -1
}
function onAreaChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < areaOptions.value.length) {
selectedArea.value = areaOptions.value[index]
areaScanInput.value = selectedArea.value.label
} }
showAreaDropdown.value = !showAreaDropdown.value
} }
function handleSelectArea(item) {
selectedArea.value = item function getAreaScanId(value) {
showAreaDropdown.value = false const text = String(value || '').trim()
if (!text) return ''
const match = text.match(/WAREHOUSE_AREA[-_:](\d+)/i)
if (match) return match[1]
const tail = text.match(/(\d+)$/)
return tail ? tail[1] : text
}
async function onAreaScanConfirm() {
const areaId = getAreaScanId(areaScanInput.value)
if (!areaId) return
const area = areaOptions.value.find((item) => String(item.value) === String(areaId))
if (!area) {
uni.showToast({ title: '未找到该库区', icon: 'none' })
return
}
selectedArea.value = area
areaScanInput.value = area.label
} }
async function loadAreas(warehouseId) { async function loadAreas(warehouseId) {
if (!warehouseId) return if (!warehouseId) return
loadingAreas.value = true loadingAreas.value = true
@ -379,9 +397,7 @@ async function loadStockCount() {
} }
} }
onHide(() => { onHide(() => {})
showAreaDropdown.value = false
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -449,11 +465,6 @@ onHide(() => {
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04); box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
min-height: 0 !important; min-height: 0 !important;
height: auto !important; height: auto !important;
.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;
}
} }
.full-dropdown { display: flex; align-items: center; justify-content: space-between; width: 100%; font-size: 28rpx; color: #374151; .full-dropdown { display: flex; align-items: center; justify-content: space-between; width: 100%; font-size: 28rpx; color: #374151;
@ -465,26 +476,18 @@ onHide(() => {
.warehouse-area-rows { width: 100%; display: flex; flex-direction: column; gap: 16rpx; } .warehouse-area-rows { width: 100%; display: flex; flex-direction: column; gap: 16rpx; }
.warehouse-area-row { display: flex; align-items: center; } .warehouse-area-row { display: flex; align-items: center; }
.warehouse-area-label { width: 80rpx; font-size: 26rpx; color: #6b7280; flex-shrink: 0; } .warehouse-area-label { width: 80rpx; font-size: 26rpx; color: #6b7280; flex-shrink: 0; }
.warehouse-area-dropdown { flex: 1; min-width: 0; position: relative; .warehouse-area-dropdown { flex: 1; min-width: 0; }
.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;
}
}
.dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; padding: 0 20rpx; border: 1rpx solid #e0e0e0; border-radius: 10rpx; background: #f9fafb; } .dropdown-input { display: flex; align-items: center; min-height: 64rpx !important; height: 64rpx !important; 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-value { flex: 1; font-size: 27rpx; color: #333; }
.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; } .product-search-row { display: flex; align-items: center; gap: 16rpx; }
.dropdown-scroll { max-height: 360rpx; overflow-y: auto; } .scan-input { flex: 1; min-width: 0; height: 70rpx; padding: 0 22rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; font-size: 28rpx; color: #1f2937; }
.dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; .area-select-btn { flex-shrink: 0; width: 160rpx; min-height: 70rpx; padding: 0 18rpx; background: #1f4b79; border-radius: 14rpx; box-sizing: border-box; display: flex; align-items: center; justify-content: center; }
&:last-child { border-bottom: 0; } .select-btn-text { color: #ffffff; font-size: 26rpx; font-weight: 600; }
&.active { background: #f0f5ff; } .selected-tip { margin-top: 10rpx; padding: 14rpx 18rpx; border-radius: 12rpx; background: #f8fafc; color: #334155; font-size: 24rpx; }
}
.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 { .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;

@ -56,39 +56,24 @@
<view class="warehouse-area-rows"> <view class="warehouse-area-rows">
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">关联设备</text> <text class="warehouse-area-label">关联设备</text>
<view class="warehouse-area-dropdown" @click="toggleDeviceDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <picker mode="selector" :range="filteredDeviceOptions" range-key="label" :value="getDeviceIndex(selectedDevice, filteredDeviceOptions)" @change="onDeviceChange">
<text :class="['dropdown-value', { placeholder: !selectedDevice }]">{{ selectedDevice ? selectedDevice.label : '请选择' }}</text> <view class="dropdown-input">
<text class="dropdown-arrow"></text> <text :class="['dropdown-value', { placeholder: !selectedDevice }]">{{ selectedDevice ? selectedDevice.label : '请选择' }}</text>
</view> <text class="dropdown-arrow"></text>
<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' ? '暂无保养设备' : '暂无设备数据' }}</view>
</view> </view>
</view> </picker>
</view> </view>
</view> </view>
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">{{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}</text> <text class="warehouse-area-label">{{ selectedPurpose === 'repair' ? '维修单号' : '保养单号' }}</text>
<view class="warehouse-area-dropdown" @click="toggleOrderDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <picker mode="selector" :range="currentOrderOptions" range-key="label" :value="getOrderIndex(currentOrder, currentOrderOptions)" :disabled="!currentOrderOptions.length" @change="onOrderChange">
<text :class="['dropdown-value', { placeholder: !currentOrder }]">{{ currentOrder ? currentOrder.label : '请选择' }}</text> <view class="dropdown-input">
<text class="dropdown-arrow"></text> <text :class="['dropdown-value', { placeholder: !currentOrder }]">{{ currentOrder ? currentOrder.label : '请选择' }}</text>
</view> <text class="dropdown-arrow"></text>
<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' ? '' : '' }}</view>
</view> </view>
</view> </picker>
</view> </view>
</view> </view>
</view> </view>
@ -111,18 +96,21 @@
</view> </view>
<view class="warehouse-area-row"> <view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text> <text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown"> <view class="warehouse-area-dropdown">
<view class="dropdown-input"> <view class="product-search-row">
<text :class="['dropdown-value', { placeholder: !selectedArea }]">{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}</text> <input
<text class="dropdown-arrow"></text> v-model="areaScanInput"
</view> class="scan-input"
<view v-if="showAreaDropdown" class="dropdown-panel"> type="text"
<view class="dropdown-scroll"> placeholder="扫码或输入库区码"
<view v-for="item in areaOptions" :key="item.value" class="dropdown-item" :class="{ active: selectedArea?.value === item.value }" @click.stop="handleSelectArea(item)"> confirm-type="done"
<text class="dropdown-item-text">{{ item.label }}</text> @confirm="onAreaScanConfirm"
<text v-if="selectedArea?.value === item.value" class="dropdown-check"></text> />
<picker mode="selector" :range="areaOptions" range-key="label" :value="getAreaIndex(selectedArea, areaOptions)" @change="onAreaChange">
<view class="area-select-btn">
<text class="select-btn-text">选择</text>
</view> </view>
</view> </picker>
</view> </view>
</view> </view>
</view> </view>
@ -198,23 +186,25 @@ function setPurpose(value) {
selectedMaintainOrder.value = null selectedMaintainOrder.value = null
repairOrderOptions.value = [] repairOrderOptions.value = []
maintainOrderOptions.value = [] maintainOrderOptions.value = []
if (value === 'repair') loadRepairDeviceIds()
if (value === 'maintain') loadMaintainDeviceIds()
} }
const deviceOptions = ref([]) const deviceOptions = ref([])
const selectedDevice = ref(null) const selectedDevice = ref(null)
const showDeviceDropdown = ref(false)
const repairOrderOptions = ref([]) const repairOrderOptions = ref([])
const selectedRepairOrder = ref(null) const selectedRepairOrder = ref(null)
const maintainOrderOptions = ref([]) const maintainOrderOptions = ref([])
const selectedMaintainOrder = ref(null) const selectedMaintainOrder = ref(null)
const maintainDeviceIds = ref(new Set()) const maintainDeviceIds = ref(new Set())
const repairDeviceIds = ref(new Set())
const deviceLoading = ref(false) const deviceLoading = ref(false)
const filteredDeviceOptions = computed(() => { const filteredDeviceOptions = computed(() => {
if (selectedPurpose.value === 'maintain') return deviceOptions.value.filter(d => maintainDeviceIds.value.has(d.value)) if (selectedPurpose.value === 'maintain') return deviceOptions.value.filter(d => maintainDeviceIds.value.has(d.value))
if (selectedPurpose.value === 'repair') return deviceOptions.value.filter(d => repairDeviceIds.value.has(d.value))
return deviceOptions.value return deviceOptions.value
}) })
const showOrderDropdown = ref(false)
const currentOrder = computed(() => selectedPurpose.value === 'repair' ? selectedRepairOrder.value : selectedMaintainOrder.value) const currentOrder = computed(() => selectedPurpose.value === 'repair' ? selectedRepairOrder.value : selectedMaintainOrder.value)
const currentOrderOptions = computed(() => selectedPurpose.value === 'repair' ? repairOrderOptions.value : maintainOrderOptions.value) const currentOrderOptions = computed(() => selectedPurpose.value === 'repair' ? repairOrderOptions.value : maintainOrderOptions.value)
@ -222,8 +212,8 @@ const warehouseOptions = ref([])
const selectedWarehouse = ref(null) const selectedWarehouse = ref(null)
const areaOptions = ref([]) const areaOptions = ref([])
const selectedArea = ref(null) const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false) const loadingAreas = ref(false)
const areaScanInput = ref('')
const sparepartImage = computed(() => { const sparepartImage = computed(() => {
const images = sparepart.value.images const images = sparepart.value.images
@ -355,12 +345,39 @@ function handleConfirm() {
}, 800) }, 800)
} }
function toggleDeviceDropdown() { function getDeviceIndex(selected, options) {
showDeviceDropdown.value = !showDeviceDropdown.value if (!selected) return -1
if (showDeviceDropdown.value && selectedPurpose.value === 'maintain' && maintainDeviceIds.value.size === 0) { const index = options.findIndex((item) => String(item.value) === String(selected.value))
loadMaintainDeviceIds() return index >= 0 ? index : -1
}
function getOrderIndex(selected, options) {
if (!selected) return -1
const index = options.findIndex((item) => String(item.value) === String(selected.value))
return index >= 0 ? index : -1
}
async function onDeviceChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < filteredDeviceOptions.value.length) {
const item = filteredDeviceOptions.value[index]
selectedDevice.value = item
selectedRepairOrder.value = null; selectedMaintainOrder.value = null
repairOrderOptions.value = []; maintainOrderOptions.value = []
if (selectedPurpose.value === 'repair') loadRepairOrders(item.deviceCode)
else loadMaintainOrders(item.deviceCode)
}
}
function onOrderChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < currentOrderOptions.value.length) {
const item = currentOrderOptions.value[index]
if (selectedPurpose.value === 'repair') selectedRepairOrder.value = item
else selectedMaintainOrder.value = item
} }
} }
async function loadMaintainDeviceIds() { async function loadMaintainDeviceIds() {
deviceLoading.value = true deviceLoading.value = true
try { try {
@ -376,12 +393,27 @@ async function loadMaintainDeviceIds() {
} catch (e) { console.error('loadMaintainDeviceIds error', e) } } catch (e) { console.error('loadMaintainDeviceIds error', e) }
finally { deviceLoading.value = false } finally { deviceLoading.value = false }
} }
async function handleSelectDevice(item) { async function loadRepairDeviceIds() {
selectedDevice.value = item; showDeviceDropdown.value = false deviceLoading.value = true
selectedRepairOrder.value = null; selectedMaintainOrder.value = null try {
repairOrderOptions.value = []; maintainOrderOptions.value = [] // 100
if (selectedPurpose.value === 'repair') loadRepairOrders(item.deviceCode) const allRecords = []
else loadMaintainOrders(item.deviceCode) for (let page = 1; page <= 5; page++) {
const res = await getDvRepairPage({ pageNo: page, pageSize: 100 })
const data = res && res.data !== undefined ? res.data : res
const records = Array.isArray(data) ? data : (data?.list || data?.records || [])
allRecords.push(...records)
if (records.length < 100) break
}
const deviceCodes = new Set(allRecords.map(r => r.machineryCode || r.deviceCode || r.deviceName).filter(Boolean))
const ids = new Set()
for (const d of deviceOptions.value) {
if (deviceCodes.has(d.deviceCode) || deviceCodes.has(d.label) || deviceCodes.has(d.deviceName)) ids.add(d.value)
}
repairDeviceIds.value = ids
console.log('[loadRepairDeviceIds] 维修单设备编码:', [...deviceCodes], '匹配到的设备数:', ids.size)
} catch (e) { console.error('loadRepairDeviceIds error', e) }
finally { deviceLoading.value = false }
} }
async function loadDevices() { async function loadDevices() {
try { try {
@ -401,12 +433,6 @@ async function loadDevices() {
})) }))
} catch (e) { console.error('loadDevices error', e) } } 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) { async function loadMaintainOrders(deviceCode) {
const deviceId = selectedDevice.value?.value const deviceId = selectedDevice.value?.value
if (deviceId) await loadMaintainOrdersById(deviceId) if (deviceId) await loadMaintainOrdersById(deviceId)
@ -462,8 +488,43 @@ async function loadWarehouses() {
} catch (e) {} } catch (e) {}
} }
function toggleAreaDropdown() { if (!selectedWarehouse.value) { uni.showToast({ title: '请先选择仓库', icon: 'none' }); return }; showAreaDropdown.value = !showAreaDropdown.value } function getAreaIndex(selected, options) {
function handleSelectArea(item) { selectedArea.value = item; showAreaDropdown.value = false; loadStockCount() } if (!selected) return -1
const index = options.findIndex((item) => String(item.value) === String(selected.value))
return index >= 0 ? index : -1
}
function onAreaChange(event) {
const index = Number(event.detail.value)
if (index >= 0 && index < areaOptions.value.length) {
selectedArea.value = areaOptions.value[index]
areaScanInput.value = selectedArea.value.label
loadStockCount()
}
}
function getAreaScanId(value) {
const text = String(value || '').trim()
if (!text) return ''
const match = text.match(/WAREHOUSE_AREA[-_:](\d+)/i)
if (match) return match[1]
const tail = text.match(/(\d+)$/)
return tail ? tail[1] : text
}
async function onAreaScanConfirm() {
const areaId = getAreaScanId(areaScanInput.value)
if (!areaId) return
const area = areaOptions.value.find((item) => String(item.value) === String(areaId))
if (!area) {
uni.showToast({ title: '未找到该库区', icon: 'none' })
return
}
selectedArea.value = area
areaScanInput.value = area.label
loadStockCount()
}
async function loadAreas(warehouseId) { async function loadAreas(warehouseId) {
if (!warehouseId) return if (!warehouseId) return
loadingAreas.value = true loadingAreas.value = true
@ -489,7 +550,8 @@ onShow(async () => {
} catch (e) { console.error('获取备件详情失败:', e) } } catch (e) { console.error('获取备件详情失败:', e) }
} }
} }
loadDevices() await loadDevices()
loadRepairDeviceIds()
loadWarehouses() loadWarehouses()
}) })
@ -521,7 +583,7 @@ async function loadStockCount() {
} catch (e) { warehouseAreaStockCount.value = 0 } } catch (e) { warehouseAreaStockCount.value = 0 }
} }
onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false }) onHide(() => {})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -592,6 +654,11 @@ onHide(() => { showDeviceDropdown.value = false; showOrderDropdown.value = false
.dropdown-check { font-size: 28rpx; color: #2563eb; font-weight: 700; } .dropdown-check { font-size: 28rpx; color: #2563eb; font-weight: 700; }
.dropdown-empty { padding: 32rpx; text-align: center; color: #999; font-size: 26rpx; } .dropdown-empty { padding: 32rpx; text-align: center; color: #999; font-size: 26rpx; }
.product-search-row { display: flex; align-items: center; gap: 16rpx; }
.scan-input { flex: 1; min-width: 0; height: 70rpx; padding: 0 22rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; font-size: 28rpx; color: #1f2937; }
.area-select-btn { flex-shrink: 0; width: 160rpx; min-height: 70rpx; padding: 0 18rpx; background: #1f4b79; border-radius: 14rpx; box-sizing: border-box; display: flex; align-items: center; justify-content: center; }
.select-btn-text { color: #ffffff; font-size: 26rpx; font-weight: 600; }
.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-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; } } .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; } .cancel-btn { background: #eef2f7; color: #475569; }

Loading…
Cancel
Save