Files
FTC25009/pda/pages/warehouse/stock/index.vue

389 lines
9.0 KiB
Vue

<template>
<view class="page-container">
<view class="query-card">
<view class="section-header">
<text class="section-title">库存查询</text>
<text class="section-subtitle">托盘码</text>
</view>
<view class="input-row">
<input
class="tray-input"
v-model="trayCode"
placeholder="请扫描或输入托盘码"
confirm-type="search"
@confirm="queryStock"
/>
<button class="icon-button" @click="scanTray">
<uni-icons type="scan" size="22" color="#1890ff"></uni-icons>
</button>
</view>
<button class="primary-button" :disabled="loading" @click="queryStock">
{{ loading ? '查询中...' : '查询库存' }}
</button>
</view>
<view v-if="searched" class="result-section">
<view v-if="materialList.length > 0">
<view class="summary-card">
<view class="summary-main">
<text class="summary-tray">{{ currentTrayCode }}</text>
<text class="summary-location">{{ warehouseName }} / {{ cargoSpaceName }}</text>
</view>
<view class="summary-grid">
<view class="summary-item">
<text class="summary-value">{{ materialCount }}</text>
<text class="summary-label">物料种类</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-value">{{ totalQuantity }}</text>
<text class="summary-label">库存总量</text>
</view>
<view class="summary-divider"></view>
<view class="summary-item">
<text class="summary-value">{{ availableQuantity }}</text>
<text class="summary-label">可用库存</text>
</view>
</view>
</view>
<view class="material-list">
<view class="material-card" v-for="(item, index) in materialList" :key="item.stockDetailId || index">
<view class="material-header">
<text class="material-name">{{ formatValue(item.materialName || item.productName) }}</text>
<text class="material-code">{{ formatValue(item.materialCode || item.productId) }}</text>
</view>
<view class="material-body">
<view class="info-item">
<text class="info-label">批次</text>
<text class="info-value">{{ formatValue(item.lotNo) }}</text>
</view>
<view class="info-item">
<text class="info-label">库存数量</text>
<text class="info-value">{{ formatNumber(item.quantity) }}</text>
</view>
<view class="info-item">
<text class="info-label">占用数量</text>
<text class="info-value">{{ formatNumber(item.outQuantity) }}</text>
</view>
<view class="info-item">
<text class="info-label">可用数量</text>
<text class="info-value strong">{{ formatNumber(item.availableQuantity) }}</text>
</view>
</view>
</view>
</view>
</view>
<view v-else class="empty-state">
<uni-icons type="info" size="30" color="#bfbfbf"></uni-icons>
<text class="empty-text">未查询到托盘库存</text>
</view>
</view>
</view>
</template>
<script>
import { getTrayStock } from '@/api/warehouse/stock'
export default {
data() {
return {
trayCode: '',
currentTrayCode: '',
loading: false,
searched: false,
materialList: []
}
},
computed: {
materialCount() {
var map = {}
this.materialList.forEach(item => {
var key = item.materialCode || item.productId || item.stockDetailId
if (key) map[key] = true
})
return Object.keys(map).length
},
totalQuantity() {
return this.sumBy('quantity')
},
availableQuantity() {
return this.sumBy('availableQuantity')
},
warehouseName() {
return this.formatValue(this.materialList[0] && this.materialList[0].warehouseName)
},
cargoSpaceName() {
return this.formatValue(this.materialList[0] && this.materialList[0].cargoSpaceName)
}
},
methods: {
scanTray() {
uni.scanCode({
success: res => {
this.trayCode = res.result || ''
this.queryStock()
}
})
},
queryStock() {
var code = (this.trayCode || '').trim()
if (!code) {
this.$modal.msg('请扫描或输入托盘码')
return
}
this.loading = true
getTrayStock(code).then(res => {
this.currentTrayCode = code
this.materialList = res.data || []
this.searched = true
}).catch(() => {
this.materialList = []
this.searched = true
}).then(() => {
this.loading = false
})
},
sumBy(field) {
var total = 0
this.materialList.forEach(item => {
total += this.toNumber(item[field])
})
return this.formatNumber(total)
},
toNumber(value) {
var number = Number(value)
return isNaN(number) ? 0 : number
},
formatNumber(value) {
var number = this.toNumber(value)
return Number.isInteger(number) ? String(number) : String(Number(number.toFixed(3)))
},
formatValue(value) {
return value === undefined || value === null || value === '' ? '-' : value
}
}
}
</script>
<style lang="scss" scoped>
page {
background-color: #f5f7fa;
min-height: 100%;
}
.page-container {
padding: 24rpx;
}
.query-card,
.summary-card,
.material-card,
.empty-state {
background: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.query-card {
padding: 28rpx;
margin-bottom: 28rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 22rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.section-subtitle {
font-size: 24rpx;
color: #999;
}
.input-row {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.tray-input {
flex: 1;
height: 82rpx;
padding: 0 24rpx;
border: 1rpx solid #e5e6eb;
border-radius: 12rpx;
background: #fafafa;
font-size: 28rpx;
color: #333;
}
.icon-button {
width: 82rpx;
height: 82rpx;
padding: 0;
margin-left: 18rpx;
border-radius: 12rpx;
background: #e6f7ff;
display: flex;
align-items: center;
justify-content: center;
}
.icon-button::after,
.primary-button::after {
border: none;
}
.primary-button {
height: 84rpx;
line-height: 84rpx;
border-radius: 12rpx;
background: #1890ff;
color: #fff;
font-size: 30rpx;
}
.primary-button[disabled] {
background: #91caff;
color: #fff;
}
.result-section {
margin-bottom: 28rpx;
}
.summary-card {
padding: 30rpx 28rpx;
margin-bottom: 24rpx;
}
.summary-main {
display: flex;
flex-direction: column;
padding-bottom: 24rpx;
margin-bottom: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.summary-tray {
font-size: 34rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.summary-location {
font-size: 24rpx;
color: #999;
}
.summary-grid {
display: flex;
align-items: center;
justify-content: space-between;
}
.summary-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.summary-divider {
width: 1rpx;
height: 54rpx;
background: #f0f0f0;
}
.summary-value {
font-size: 36rpx;
font-weight: 600;
color: #1890ff;
margin-bottom: 6rpx;
}
.summary-label {
font-size: 22rpx;
color: #999;
}
.material-card {
padding: 28rpx;
margin-bottom: 20rpx;
}
.material-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding-bottom: 18rpx;
margin-bottom: 16rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.material-name {
flex: 1;
font-size: 30rpx;
font-weight: 500;
color: #333;
margin-right: 20rpx;
}
.material-code {
font-size: 24rpx;
color: #999;
}
.material-body {
display: flex;
flex-direction: column;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 9rpx 0;
}
.info-label {
font-size: 26rpx;
color: #999;
}
.info-value {
font-size: 26rpx;
color: #333;
}
.info-value.strong {
color: #1890ff;
font-weight: 600;
}
.empty-state {
padding: 86rpx 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 16rpx;
}
</style>