|
|
|
|
@ -1,26 +1,73 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="page">
|
|
|
|
|
<NavBar title="样品库存查询" />
|
|
|
|
|
<view class="card">
|
|
|
|
|
<text class="label">扫码对象</text>
|
|
|
|
|
<text class="value">{{ typeText }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="card">
|
|
|
|
|
<text class="label">编码</text>
|
|
|
|
|
<text class="value">{{ detail.code || '-' }}</text>
|
|
|
|
|
|
|
|
|
|
<view class="search-panel">
|
|
|
|
|
<view class="scan-row">
|
|
|
|
|
<input v-model="locationSearchCode" class="input" placeholder="扫描或输入库位标签" @confirm="searchLocation" />
|
|
|
|
|
<view class="scan-btn" @click="openLocationScanner">扫码</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-if="locationFilter.id" class="filter-row">
|
|
|
|
|
<text class="filter-text">库位:{{ locationFilter.name || locationFilter.code }}</text>
|
|
|
|
|
<text class="clear-text" @click="clearLocationFilter">清空</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="card">
|
|
|
|
|
<text class="label">名称</text>
|
|
|
|
|
<text class="value">{{ detail.name || '-' }}</text>
|
|
|
|
|
|
|
|
|
|
<view class="summary">
|
|
|
|
|
<view class="summary-row">
|
|
|
|
|
<text class="summary-label">查询对象</text>
|
|
|
|
|
<text class="summary-value">{{ typeText }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="summary-row">
|
|
|
|
|
<text class="summary-label">编码</text>
|
|
|
|
|
<text class="summary-value">{{ detail.code || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="summary-row">
|
|
|
|
|
<text class="summary-label">名称</text>
|
|
|
|
|
<text class="summary-value">{{ detail.name || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="tip">库存明细以 Web 端样品库存查询为准;PDA 当前用于扫码识别和现场作业。</view>
|
|
|
|
|
|
|
|
|
|
<scroll-view scroll-y class="list-scroll" @scrolltolower="loadMore" :lower-threshold="80">
|
|
|
|
|
<view class="list-wrap">
|
|
|
|
|
<view v-for="item in list" :key="item.id" class="stock-card">
|
|
|
|
|
<view class="card-title">{{ item.productName || item.name || '-' }}</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">样品码</text>
|
|
|
|
|
<text class="info-value">{{ item.barCode || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">仓库</text>
|
|
|
|
|
<text class="info-value">{{ item.warehouseName || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">库区</text>
|
|
|
|
|
<text class="info-value">{{ item.areaName || '-' }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">库位</text>
|
|
|
|
|
<text class="info-value">{{ formatLocation(item) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="info-row">
|
|
|
|
|
<text class="info-label">库存</text>
|
|
|
|
|
<text class="info-value count-value">{{ formatCount(item.count, item.unitName) }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-if="loading && pageNo === 1" class="state-text">加载中...</view>
|
|
|
|
|
<view v-else-if="!list.length" class="state-text">暂无库位库存</view>
|
|
|
|
|
<view v-else-if="loadingMore" class="state-text">加载更多...</view>
|
|
|
|
|
<view v-else-if="finished" class="state-text">没有更多了</view>
|
|
|
|
|
</view>
|
|
|
|
|
</scroll-view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { reactive, computed } from 'vue'
|
|
|
|
|
import { computed, reactive, ref } from 'vue'
|
|
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
|
|
import { getBitWmsLocationStockPage, scanBitWmsCode } from '@/api/erp/bitwms'
|
|
|
|
|
|
|
|
|
|
const detail = reactive({
|
|
|
|
|
productId: undefined,
|
|
|
|
|
@ -28,21 +75,149 @@ const detail = reactive({
|
|
|
|
|
code: '',
|
|
|
|
|
name: ''
|
|
|
|
|
})
|
|
|
|
|
const locationFilter = reactive({
|
|
|
|
|
id: undefined,
|
|
|
|
|
code: '',
|
|
|
|
|
name: ''
|
|
|
|
|
})
|
|
|
|
|
const locationSearchCode = ref('')
|
|
|
|
|
const list = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const loadingMore = ref(false)
|
|
|
|
|
const finished = ref(false)
|
|
|
|
|
const pageNo = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
|
|
|
|
const typeText = computed(() => detail.productId ? '样品' : detail.locationId ? '库位' : '未识别')
|
|
|
|
|
const unwrap = (res) => res?.data || res
|
|
|
|
|
const typeText = computed(() => {
|
|
|
|
|
if (detail.productId && locationFilter.id) return '样品指定库位库存'
|
|
|
|
|
if (detail.productId) return '样品库位库存'
|
|
|
|
|
if (locationFilter.id) return '库位库存'
|
|
|
|
|
return '全部样品库位库存'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
onLoad((options = {}) => {
|
|
|
|
|
detail.productId = options.productId
|
|
|
|
|
detail.locationId = options.locationId
|
|
|
|
|
onLoad(async (options = {}) => {
|
|
|
|
|
detail.productId = options.productId ? Number(options.productId) : undefined
|
|
|
|
|
detail.locationId = options.locationId ? Number(options.locationId) : undefined
|
|
|
|
|
detail.code = decodeURIComponent(options.code || '')
|
|
|
|
|
detail.name = decodeURIComponent(options.name || '')
|
|
|
|
|
if (detail.locationId) {
|
|
|
|
|
locationFilter.id = detail.locationId
|
|
|
|
|
locationFilter.code = detail.code
|
|
|
|
|
locationFilter.name = detail.name
|
|
|
|
|
locationSearchCode.value = detail.code
|
|
|
|
|
}
|
|
|
|
|
await fetchList(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function normalizePageData(res) {
|
|
|
|
|
const root = unwrap(res) || {}
|
|
|
|
|
return {
|
|
|
|
|
list: Array.isArray(root.list) ? root.list : [],
|
|
|
|
|
total: Number(root.total || 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildQueryParams() {
|
|
|
|
|
return {
|
|
|
|
|
pageNo: pageNo.value,
|
|
|
|
|
pageSize: pageSize.value,
|
|
|
|
|
productId: detail.productId || undefined,
|
|
|
|
|
locationId: locationFilter.id || undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openLocationScanner() {
|
|
|
|
|
uni.scanCode({
|
|
|
|
|
scanType: ['qrCode', 'barCode'],
|
|
|
|
|
success: (res) => {
|
|
|
|
|
locationSearchCode.value = String(res.result || '').trim()
|
|
|
|
|
searchLocation()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function searchLocation() {
|
|
|
|
|
const code = locationSearchCode.value.trim()
|
|
|
|
|
if (!code) {
|
|
|
|
|
clearLocationFilter()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const data = unwrap(await scanBitWmsCode(code))
|
|
|
|
|
if (data?.type !== 'LOCATION') return uni.showToast({ title: '请扫描库位标签', icon: 'none' })
|
|
|
|
|
locationFilter.id = data.id
|
|
|
|
|
locationFilter.code = data.code
|
|
|
|
|
locationFilter.name = data.name
|
|
|
|
|
locationSearchCode.value = data.code || code
|
|
|
|
|
await fetchList(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function clearLocationFilter() {
|
|
|
|
|
locationFilter.id = undefined
|
|
|
|
|
locationFilter.code = ''
|
|
|
|
|
locationFilter.name = ''
|
|
|
|
|
locationSearchCode.value = ''
|
|
|
|
|
await fetchList(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function fetchList(reset = false) {
|
|
|
|
|
if (reset) {
|
|
|
|
|
pageNo.value = 1
|
|
|
|
|
finished.value = false
|
|
|
|
|
}
|
|
|
|
|
if (finished.value && !reset) return
|
|
|
|
|
if (pageNo.value === 1) loading.value = true
|
|
|
|
|
else loadingMore.value = true
|
|
|
|
|
try {
|
|
|
|
|
const page = normalizePageData(await getBitWmsLocationStockPage(buildQueryParams()))
|
|
|
|
|
total.value = page.total
|
|
|
|
|
list.value = reset ? page.list : [...list.value, ...page.list]
|
|
|
|
|
finished.value = list.value.length >= total.value || page.list.length < pageSize.value
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
loadingMore.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadMore() {
|
|
|
|
|
if (loading.value || loadingMore.value || finished.value) return
|
|
|
|
|
pageNo.value += 1
|
|
|
|
|
await fetchList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatLocation(item) {
|
|
|
|
|
const code = item.locationCode || '-'
|
|
|
|
|
const name = item.locationName || '-'
|
|
|
|
|
return `${code} / ${name}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatCount(count, unitName) {
|
|
|
|
|
const value = count === undefined || count === null || count === '' ? '-' : count
|
|
|
|
|
return unitName ? `${value} ${unitName}` : String(value)
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.page { min-height: 100vh; background: #f5f6f8; padding: 24rpx; }
|
|
|
|
|
.card { background: #fff; border-radius: 16rpx; padding: 26rpx; margin-bottom: 18rpx; display: flex; justify-content: space-between; box-shadow: 0 4rpx 16rpx rgba(15,23,42,.04); }
|
|
|
|
|
.label { color: #64748b; font-size: 26rpx; }
|
|
|
|
|
.value { color: #0f172a; font-size: 30rpx; font-weight: 600; max-width: 460rpx; text-align: right; }
|
|
|
|
|
.tip { margin-top: 26rpx; color: #64748b; font-size: 26rpx; line-height: 1.6; }
|
|
|
|
|
.page { min-height: 100vh; background: #f5f6f8; padding: 24rpx; box-sizing: border-box; }
|
|
|
|
|
.search-panel { background: #fff; border-radius: 16rpx; padding: 22rpx; margin-bottom: 20rpx; box-shadow: 0 4rpx 16rpx rgba(15,23,42,.04); }
|
|
|
|
|
.scan-row { display: flex; gap: 14rpx; }
|
|
|
|
|
.input { flex: 1; height: 82rpx; padding: 0 22rpx; background: #f8fafc; border-radius: 12rpx; font-size: 28rpx; }
|
|
|
|
|
.scan-btn { width: 130rpx; height: 82rpx; line-height: 82rpx; text-align: center; border-radius: 12rpx; background: #22486e; color: #fff; font-size: 28rpx; }
|
|
|
|
|
.filter-row { display: flex; justify-content: space-between; align-items: center; gap: 20rpx; padding-top: 18rpx; }
|
|
|
|
|
.filter-text { color: #1f2937; font-size: 26rpx; }
|
|
|
|
|
.clear-text { color: #22486e; font-size: 26rpx; font-weight: 600; }
|
|
|
|
|
.summary { background: #fff; border-radius: 16rpx; padding: 22rpx; margin-bottom: 20rpx; box-shadow: 0 4rpx 16rpx rgba(15,23,42,.04); }
|
|
|
|
|
.summary-row { display: flex; justify-content: space-between; gap: 20rpx; min-height: 52rpx; align-items: center; }
|
|
|
|
|
.summary-label { color: #64748b; font-size: 26rpx; }
|
|
|
|
|
.summary-value { color: #0f172a; font-size: 28rpx; font-weight: 600; max-width: 470rpx; text-align: right; }
|
|
|
|
|
.list-scroll { height: calc(100vh - 240rpx); }
|
|
|
|
|
.list-wrap { padding-bottom: 30rpx; }
|
|
|
|
|
.stock-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 18rpx; box-shadow: 0 4rpx 16rpx rgba(15,23,42,.04); }
|
|
|
|
|
.card-title { color: #0f172a; font-size: 30rpx; font-weight: 700; margin-bottom: 18rpx; }
|
|
|
|
|
.info-row { display: flex; justify-content: space-between; gap: 20rpx; min-height: 46rpx; align-items: center; }
|
|
|
|
|
.info-label { color: #64748b; font-size: 26rpx; }
|
|
|
|
|
.info-value { color: #1f2937; font-size: 27rpx; max-width: 470rpx; text-align: right; }
|
|
|
|
|
.count-value { color: #22486e; font-weight: 700; }
|
|
|
|
|
.state-text { color: #64748b; font-size: 26rpx; text-align: center; padding: 26rpx 0; }
|
|
|
|
|
</style>
|
|
|
|
|
|