feat: 物料入库优化

master
zhongwenkai 4 days ago
parent c3a488c59e
commit 50a2d67583

@ -178,7 +178,7 @@ async function handleScanCode(code) {
return
}
try {
const apiRes = await getSparepartDetail(materialId)
const apiRes = await getProductDetail(materialId)
const detail = apiRes?.data || apiRes
if (detail && detail.id) {
getApp().globalData._materialFromScan = true

@ -14,12 +14,24 @@
@confirm="handleSearch"
/>
</view>
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-box">
<view class="status-box-wrapper">
<view class="status-box" @click="toggleStatusDropdown">
<text class="status-box-text">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
<view v-if="showStatusDropdown" class="status-dropdown-panel">
<view
v-for="(item, idx) in statusOptions"
:key="idx"
class="status-dropdown-item"
:class="{ active: selectedStatus === item.value }"
@click.stop="selectStatus(item, idx)"
>
<text class="status-dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedStatus === item.value" class="status-dropdown-check"></text>
</view>
</view>
</view>
<view class="reset-filter-btn" @click="resetFilters"></view>
</view>
@ -140,26 +152,23 @@
<script setup>
import { computed, ref } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { onShow, onUnload, onHide } from '@dcloudio/uni-app'
import NavBar from '@/components/common/NavBar.vue'
import { getMaterialInboundPage, auditMaterialInbound, submitMaterialInbound } from '@/api/mes/materialInbound'
import { getSimpleUserList } from '@/api/mes/moldget'
const selectedStatus = ref('')
const searchKeyword = ref('')
const showStatusDropdown = ref(false)
const statusOptions = computed(() => [
{ label: '全部', value: '' },
{ label: '待入库', value: '0' },
{ label: '待审核', value: '10' },
{ label: '已入库', value: '20' }
{ label: '已入库', value: '20' },
{ label: '已驳回', value: '1' }
])
const statusLabels = computed(() => statusOptions.value.map((item) => item.label))
const statusIndex = computed(() => {
const index = statusOptions.value.findIndex((item) => item.value === selectedStatus.value)
return index >= 0 ? index : 0
})
const currentStatusLabel = computed(() => {
const current = statusOptions.value.find((item) => item.value === selectedStatus.value)
return current ? current.label : '全部'
@ -234,7 +243,7 @@ async function fetchList(reset) {
pageNo: pageNo.value,
pageSize: pageSize.value,
no: searchKeyword.value.trim() || undefined,
status: selectedStatus.value || undefined
statusList: selectedStatus.value !== '' ? [Number(selectedStatus.value)] : undefined
}
const res = await getMaterialInboundPage(params)
const page = normalizePageData(res)
@ -249,9 +258,13 @@ async function fetchList(reset) {
}
}
function onStatusChange(event) {
const index = Number(event?.detail?.value || 0)
selectedStatus.value = statusOptions.value[index]?.value ?? ''
function toggleStatusDropdown() {
showStatusDropdown.value = !showStatusDropdown.value
}
function selectStatus(item, _idx) {
selectedStatus.value = item.value
showStatusDropdown.value = false
fetchList(true)
}
@ -429,6 +442,10 @@ onShow(() => {
onUnload(() => {
clearSearchTimer()
})
onHide(() => {
showStatusDropdown.value = false
})
</script>
<style lang="scss" scoped>
@ -464,8 +481,12 @@ onUnload(() => {
color: #374151;
}
.status-box {
.status-box-wrapper {
position: relative;
flex-shrink: 0;
}
.status-box {
display: flex;
align-items: center;
justify-content: space-between;
@ -477,6 +498,46 @@ onUnload(() => {
border-radius: 12rpx;
}
.status-dropdown-panel {
position: absolute;
top: 74rpx;
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.12);
overflow: hidden;
}
.status-dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: 0;
}
&.active {
background: #f0f5ff;
}
}
.status-dropdown-item-text {
font-size: 26rpx;
color: #374151;
}
.status-dropdown-check {
font-size: 26rpx;
color: #2563eb;
font-weight: 700;
}
.status-box-text {
font-size: 24rpx;
color: #374151;

@ -63,27 +63,9 @@
<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="warehouse-area-dropdown">
<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 v-if="!warehouseOptions.length" class="dropdown-empty"></view>
</view>
<text class="dropdown-value">{{ selectedWarehouse ? selectedWarehouse.label : '原料仓' }}</text>
</view>
</view>
</view>
@ -174,10 +156,9 @@ import { getProductDetail, getSparepartStockCount } from '@/api/mes/sparepart'
const material = ref({})
const inboundQty = ref(null)
//
//
const warehouseOptions = ref([])
const selectedWarehouse = ref(null)
const showWarehouseDropdown = ref(false)
//
const areaOptions = ref([])
@ -236,10 +217,6 @@ function handleConfirm() {
uni.showToast({ title: '请输入入库数量', icon: 'none' })
return
}
if (!selectedWarehouse.value) {
uni.showToast({ title: '请选择仓库', icon: 'none' })
return
}
if (!selectedArea.value) {
uni.showToast({ title: '请选择库区', icon: 'none' })
return
@ -276,17 +253,7 @@ function handleConfirm() {
}, 800)
}
//
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()
@ -295,6 +262,12 @@ async function loadWarehouses() {
value: w.id,
label: w.name || String(w.id || '')
}))
// ""
const rawWarehouse = warehouseOptions.value.find(w => w.label === '原料仓')
if (rawWarehouse) {
selectedWarehouse.value = rawWarehouse
loadAreas(rawWarehouse.value)
}
} catch (e) {
console.error('loadWarehouses error', e)
}
@ -338,7 +311,7 @@ onShow(async () => {
getApp().globalData._materialBeforeConfirm = null
if (selectResult.id) {
try {
const res = await getSparepartDetail(selectResult.id)
const res = await getProductDetail(selectResult.id)
const detail = res?.data || res
if (detail) {
material.value = { ...material.value, ...detail }
@ -365,7 +338,6 @@ async function loadStockCount() {
}
onHide(() => {
showWarehouseDropdown.value = false
showAreaDropdown.value = false
})
</script>

@ -14,12 +14,18 @@
@confirm="handleSearch"
/>
</view>
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-box">
<view class="status-box-wrapper">
<view class="status-box" @click="toggleStatusDropdown">
<text class="status-box-text">{{ currentStatusLabel }}</text>
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
<view v-if="showStatusDropdown" class="status-dropdown-panel">
<view v-for="(item, idx) in statusOptions" :key="idx" class="status-dropdown-item" :class="{ active: selectedStatus === item.value }" @click.stop="selectStatus(item, idx)">
<text class="status-dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedStatus === item.value" class="status-dropdown-check"></text>
</view>
</view>
</view>
<view class="reset-filter-btn" @click="resetFilters">{{ t('functionCommon.reset') }}</view>
</view>
@ -140,7 +146,7 @@
<script setup>
import { computed, nextTick, ref } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { onShow, onUnload, onHide } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getSparepartInboundPage, auditSparepartInbound, submitSparepartInbound } from '@/api/mes/sparepartInbound'
@ -150,19 +156,16 @@ const { t } = useI18n()
const selectedStatus = ref('')
const searchKeyword = ref('')
const showStatusDropdown = ref(false)
const statusOptions = computed(() => [
{ label: t('functionCommon.all'), value: '' },
{ label: t('sparepartInbound.tabPending'), value: '0' },
{ label: t('sparepartInbound.tabAuditing'), value: '10' },
{ label: t('sparepartInbound.approve'), value: '20' }
{ label: t('sparepartInbound.approve'), value: '20' },
{ label: '已驳回', value: '1' }
])
const statusLabels = computed(() => statusOptions.value.map((item) => item.label))
const statusIndex = computed(() => {
const index = statusOptions.value.findIndex((item) => item.value === selectedStatus.value)
return index >= 0 ? index : 0
})
const currentStatusLabel = computed(() => {
const current = statusOptions.value.find((item) => item.value === selectedStatus.value)
return current ? current.label : t('functionCommon.all')
@ -237,7 +240,7 @@ async function fetchList(reset) {
pageNo: pageNo.value,
pageSize: pageSize.value,
no: searchKeyword.value.trim() || undefined,
status: selectedStatus.value || undefined
statusList: selectedStatus.value !== '' ? [Number(selectedStatus.value)] : undefined
}
const res = await getSparepartInboundPage(params)
const page = normalizePageData(res)
@ -252,9 +255,13 @@ async function fetchList(reset) {
}
}
function onStatusChange(event) {
const index = Number(event?.detail?.value || 0)
selectedStatus.value = statusOptions.value[index]?.value ?? ''
function toggleStatusDropdown() {
showStatusDropdown.value = !showStatusDropdown.value
}
function selectStatus(item, _idx) {
selectedStatus.value = item.value
showStatusDropdown.value = false
fetchList(true)
}
@ -431,6 +438,10 @@ onShow(() => {
onUnload(() => {
clearSearchTimer()
})
onHide(() => {
showStatusDropdown.value = false
})
</script>
<style lang="scss" scoped>
@ -466,8 +477,12 @@ onUnload(() => {
color: #374151;
}
.status-box {
.status-box-wrapper {
position: relative;
flex-shrink: 0;
}
.status-box {
display: flex;
align-items: center;
justify-content: space-between;
@ -479,6 +494,46 @@ onUnload(() => {
border-radius: 12rpx;
}
.status-dropdown-panel {
position: absolute;
top: 74rpx;
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.12);
overflow: hidden;
}
.status-dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: 0;
}
&.active {
background: #f0f5ff;
}
}
.status-dropdown-item-text {
font-size: 26rpx;
color: #374151;
}
.status-dropdown-check {
font-size: 26rpx;
color: #2563eb;
font-weight: 700;
}
.status-box-text {
font-size: 24rpx;
color: #374151;

@ -6,9 +6,15 @@
<view class="keyword-box">
<input v-model="searchKeyword" class="keyword-input" :placeholder="t('sparepartOutbound.searchPlaceholder')" confirm-type="search" @input="handleKeywordInput" @confirm="handleSearch" />
</view>
<picker mode="selector" :range="statusLabels" :value="statusIndex" @change="onStatusChange">
<view class="status-box"><text class="status-box-text">{{ currentStatusLabel }}</text><uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons></view>
</picker>
<view class="status-box-wrapper">
<view class="status-box" @click="toggleStatusDropdown"><text class="status-box-text">{{ currentStatusLabel }}</text><uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons></view>
<view v-if="showStatusDropdown" class="status-dropdown-panel">
<view v-for="(item, idx) in statusOptions" :key="idx" class="status-dropdown-item" :class="{ active: selectedStatus === item.value }" @click.stop="selectStatus(item, idx)">
<text class="status-dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedStatus === item.value" class="status-dropdown-check"></text>
</view>
</view>
</view>
<view class="reset-filter-btn" @click="resetFilters">{{ t('functionCommon.reset') }}</view>
</view>
@ -83,7 +89,7 @@
<script setup>
import { computed, ref } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
import { onShow, onUnload, onHide } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getSparepartOutboundPage, auditSparepartOutbound, submitSparepartOutbound } from '@/api/mes/sparepartOutbound'
@ -93,14 +99,14 @@ const { t } = useI18n()
const selectedStatus = ref('')
const searchKeyword = ref('')
const showStatusDropdown = ref(false)
const statusOptions = computed(() => [
{ label: t('functionCommon.all'), value: '' },
{ label: t('sparepartOutbound.tabPending'), value: '0' },
{ label: t('sparepartOutbound.tabAuditing'), value: '10' },
{ label: t('sparepartOutbound.approve'), value: '20' }
{ label: t('sparepartOutbound.approve'), value: '20' },
{ label: '已驳回', value: '1' }
])
const statusLabels = computed(() => statusOptions.value.map(i => i.label))
const statusIndex = computed(() => { const idx = statusOptions.value.findIndex(i => i.value === selectedStatus.value); return idx >= 0 ? idx : 0 })
const currentStatusLabel = computed(() => { const cur = statusOptions.value.find(i => i.value === selectedStatus.value); return cur ? cur.label : t('functionCommon.all') })
const list = ref([])
const loading = ref(false)
@ -142,7 +148,7 @@ async function fetchList(reset) {
if (reset) { pageNo.value = 1; finished.value = false }
if (pageNo.value === 1) loading.value = true; else loadingMore.value = true
try {
const params = { pageNo: pageNo.value, pageSize: pageSize.value, no: searchKeyword.value.trim() || undefined, status: selectedStatus.value || undefined }
const params = { pageNo: pageNo.value, pageSize: pageSize.value, no: searchKeyword.value.trim() || undefined, statusList: selectedStatus.value !== '' ? [Number(selectedStatus.value)] : undefined }
const res = await getSparepartOutboundPage(params)
const page = normalizePageData(res)
list.value = reset ? page.list : [...list.value, ...page.list]
@ -151,7 +157,8 @@ async function fetchList(reset) {
finally { loading.value = false; loadingMore.value = false }
}
function onStatusChange(e) { const idx = Number(e?.detail?.value || 0); selectedStatus.value = statusOptions.value[idx]?.value ?? ''; fetchList(true) }
function toggleStatusDropdown() { showStatusDropdown.value = !showStatusDropdown.value }
function selectStatus(item, _idx) { selectedStatus.value = item.value; showStatusDropdown.value = false; fetchList(true) }
function handleSearch() { clearSearchTimer(); uni.hideKeyboard(); fetchList(true) }
function handleKeywordInput() { clearSearchTimer(); searchTimer = setTimeout(() => fetchList(true), 300) }
function resetFilters() { clearSearchTimer(); searchKeyword.value = ''; selectedStatus.value = ''; fetchList(true) }
@ -198,6 +205,7 @@ function clearSearchTimer() { if (searchTimer) { clearTimeout(searchTimer); sear
onShow(() => { fetchList(true) })
onUnload(() => { clearSearchTimer() })
onHide(() => { showStatusDropdown.value = false })
</script>
<style lang="scss" scoped>
@ -205,7 +213,12 @@ onUnload(() => { clearSearchTimer() })
.filter-bar { display: flex; align-items: center; gap: 12rpx; padding: 18rpx 24rpx; background: #fff; }
.keyword-box { flex: 1; min-width: 0; height: 66rpx; padding: 0 28rpx; background: #f4f5f7; border: 1rpx solid #e5e7eb; border-radius: 12rpx; display: flex; align-items: center; }
.keyword-input { width: 100%; font-size: 24rpx; color: #374151; }
.status-box { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; height: 66rpx; padding: 0 28rpx; min-width: 160rpx; background: #fff; border: 1rpx solid #e5e7eb; border-radius: 12rpx; }
.status-box-wrapper { position: relative; flex-shrink: 0; }
.status-box { display: flex; align-items: center; justify-content: space-between; height: 66rpx; padding: 0 28rpx; min-width: 160rpx; background: #fff; border: 1rpx solid #e5e7eb; border-radius: 12rpx; }
.status-dropdown-panel { position: absolute; top: 74rpx; 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.12); overflow: hidden; }
.status-dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 18rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; &:last-child { border-bottom: 0; } &.active { background: #f0f5ff; } }
.status-dropdown-item-text { font-size: 26rpx; color: #374151; }
.status-dropdown-check { font-size: 26rpx; color: #2563eb; font-weight: 700; }
.status-box-text { font-size: 24rpx; color: #374151; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.reset-filter-btn { height: 66rpx; line-height: 66rpx; padding: 0 28rpx; font-size: 24rpx; color: #4b5563; background: #fff; border: 1rpx solid #e5e7eb; border-radius: 12rpx; flex-shrink: 0; }

Loading…
Cancel
Save