style:产品库存查询字段调整

master
黄伟杰 4 days ago
parent 9bc94b6816
commit cf34393ef0

@ -9,4 +9,11 @@ export function getProductInventoryPage(params = {}) {
...params
}
})
}
}
export function getProductDetail(id) {
return request({
url: '/admin-api/erp/product/get',
method: 'get',
params: { id }
})
}

@ -4,6 +4,29 @@
<scroll-view scroll-y class="detail-scroll">
<view v-if="detail" class="content-section">
<view class="detail-card">
<view class="info-row-top">
<view class="item-image-wrap">
<image v-if="detailImage" :src="detailImage" class="item-image" mode="aspectFill" />
<view v-else class="item-image-placeholder">📦</view>
</view>
<view class="item-info-col">
<view class="item-row">
<text class="item-label">{{ t('productInventory.productName') }}</text>
<text class="item-value">{{ textValue(detail.name || detail.productName) }}</text>
</view>
<view class="item-row">
<text class="item-label">{{ t('productInventory.barCode') }}</text>
<text class="item-value">{{ textValue(detail.barCode || detail.productBarCode) }}</text>
</view>
<view class="item-row">
<text class="item-label">{{ t('productInventory.category') }}</text>
<text class="item-value">{{ textValue(detail.categoryName || detail.subCategoryName) }}</text>
</view>
</view>
</view>
</view>
<view class="section-card">
<view class="section-header">
<view class="section-icon">
@ -13,18 +36,6 @@
</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">{{ t('productInventory.barCode') }}</text>
<text class="info-value strong">{{ textValue(detail.barCode || detail.productBarCode) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('productInventory.productName') }}</text>
<text class="info-value strong">{{ textValue(detail.name || detail.productName) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('productInventory.category') }}</text>
<text class="info-value">{{ textValue(detail.categoryName || detail.subCategoryName) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('productInventory.packagingRule') }}</text>
<text class="info-value">{{ textValue(detail.packagingRule) }}</text>
@ -69,6 +80,7 @@
</view>
</view>
</view>
</view>
<view v-else-if="loading" class="hint">{{ t('functionCommon.loading') }}</view>
@ -82,7 +94,7 @@ import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getProductInventoryPage } from '@/api/mes/productInventory'
import { getProductDetail, getProductInventoryPage } from '@/api/mes/productInventory'
const { t } = useI18n()
const detail = ref(null)
@ -90,6 +102,12 @@ const loading = ref(true)
const stockDisplayList = computed(() => formatStockDisplay(detail.value?.stockDisplay))
const areaStockDisplayList = computed(() => getAreaStockDisplayList(detail.value?.areaStocks))
const detailImage = computed(() => {
const images = detail.value?.images || detail.value?.image || detail.value?.picUrl
if (!images) return ''
if (Array.isArray(images) && images.length) return String(images[0])
return String(images).split(',')[0]?.trim() || ''
})
function textValue(value) {
if (value === 0) return '0'
@ -158,16 +176,48 @@ function normalizePageData(res) {
return Array.isArray(candidateList) ? candidateList : []
}
function applyStockItem(item) {
function normalizeDetailData(res) {
const root = res && res.data !== undefined ? res.data : res
return root?.data || root || {}
}
function applyStockItem(item, productDetail = {}) {
detail.value = {
name: item?.name || item?.productName,
barCode: item?.barCode || item?.productBarCode,
categoryName: item?.categoryName || item?.subCategoryName,
name: item?.name || item?.productName || productDetail?.name,
barCode: item?.barCode || item?.productBarCode || productDetail?.barCode,
categoryName: item?.categoryName || productDetail?.categoryName || productDetail?.subCategoryName,
standard: item?.standard || productDetail?.standard,
remark: item?.remark || productDetail?.remark,
images: productDetail?.images || item?.images,
image: productDetail?.image || item?.image,
picUrl: productDetail?.picUrl || item?.picUrl,
...productDetail,
...item,
stockDisplay: item?.stockDisplay || String(item?.count || '')
}
}
async function enrichProductDetail(productId) {
try {
const res = await getProductDetail(productId)
const product = normalizeDetailData(res)
if (detail.value) {
detail.value = {
...product,
...detail.value,
name: detail.value.name || detail.value.productName || product.name,
barCode: detail.value.barCode || detail.value.productBarCode || product.barCode,
categoryName: detail.value.categoryName || product.categoryName || product.subCategoryName,
standard: detail.value.standard || product.standard,
remark: detail.value.remark || product.remark,
images: detail.value.images || product.images,
image: detail.value.image || product.image,
picUrl: detail.value.picUrl || product.picUrl
}
}
} catch (e) {}
}
onLoad(async (options) => {
const id = options?.id || options?.productId
const warehouseId = options?.warehouseId
@ -182,22 +232,30 @@ onLoad(async (options) => {
if (cached && String(cached.productId || cached.id) === String(id)) {
applyStockItem(cached)
getApp().globalData._productInventoryDetail = null
await enrichProductDetail(id)
loading.value = false
return
}
const pageRes = await getProductInventoryPage({
pageNo: 1,
pageSize: 1,
productId: id,
warehouseId: warehouseId || undefined,
areaId: areaId || undefined
})
const [pageRes, productRes] = await Promise.all([
getProductInventoryPage({
pageNo: 1,
pageSize: 1,
productId: id,
warehouseId: warehouseId || undefined,
areaId: areaId || undefined
}),
getProductDetail(id)
])
const rows = normalizePageData(pageRes)
const product = normalizeDetailData(productRes)
if (rows[0]) {
applyStockItem(rows[0])
applyStockItem(rows[0], product)
} else {
detail.value = { stockDisplay: '-' }
detail.value = {
...product,
stockDisplay: '-'
}
}
} catch (e) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
@ -211,7 +269,17 @@ onLoad(async (options) => {
.page-container { min-height: 100vh; background: #f5f7fb; }
.detail-scroll { height: calc(100vh - 120rpx); }
.content-section { padding: 20rpx 24rpx 40rpx; }
.section-card { padding: 24rpx; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 20rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
.detail-card { padding: 28rpx; margin-bottom: 20rpx; background: #ffffff; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(15, 23, 42, 0.04); }
.info-row-top { display: flex; align-items: center; }
.item-image-wrap { width: 130rpx; height: 130rpx; border-radius: 12rpx; overflow: hidden; background: #f8fafc; border: 1rpx solid #f0f0f0; flex-shrink: 0; display: flex; align-items: center; justify-content: center; margin-right: 24rpx; }
.item-image { width: 100%; height: 100%; }
.item-image-placeholder { font-size: 48rpx; opacity: 0.2; }
.item-info-col { flex: 1; min-width: 0; }
.item-row { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding: 8rpx 0; }
.item-row:last-child { padding-bottom: 0; }
.item-label { font-size: 28rpx; color: #9ca3af; flex-shrink: 0; }
.item-value { font-size: 28rpx; color: #374151; text-align: right; word-break: break-all; }
.section-card { padding: 24rpx; margin-bottom: 20rpx; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 20rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
.section-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 22rpx; padding-bottom: 18rpx; border-bottom: 1rpx solid #f1f5f9; }
.section-icon { width: 40rpx; height: 40rpx; border-radius: 10rpx; background: #eff6ff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
@ -221,7 +289,6 @@ onLoad(async (options) => {
.info-row.block-row { flex-direction: column; gap: 12rpx; }
.info-label { width: 190rpx; font-size: 26rpx; color: #94a3b8; flex-shrink: 0; }
.info-value { flex: 1; text-align: right; font-size: 27rpx; color: #334155; line-height: 1.5; word-break: break-all; }
.info-value.strong { color: #0f172a; font-weight: 700; }
.info-value.highlight { color: #1f4b79; font-weight: 700; }
.tag-wrap { width: 100%; display: flex; flex-wrap: wrap; gap: 12rpx; justify-content: flex-end; }
.stock-tag { max-width: 100%; padding: 8rpx 16rpx; border-radius: 999rpx; color: #1d4ed8; background: #dbeafe; font-size: 24rpx; line-height: 1.35; word-break: break-all; }

@ -31,12 +31,12 @@
<view class="list-wrap">
<view v-for="item in list" :key="itemKey(item)" class="stock-card" hover-class="stock-card-hover" @click="openDetail(item)">
<view class="card-header">
<text class="product-code">{{ textValue(item.barCode || item.productBarCode) }}</text>
<text class="product-code">{{ textValue(item.name || item.productName) }}</text>
</view>
<view class="card-body">
<view class="row">
<text class="label">{{ t('productInventory.productName') }}</text>
<text class="value">{{ textValue(item.name || item.productName) }}</text>
<text class="label">{{ t('productInventory.barCode') }}</text>
<text class="value">{{ textValue(item.barCode || item.productBarCode) }}</text>
</view>
<view class="row">
<text class="label">{{ t('productInventory.stockDisplay') }}</text>
@ -46,14 +46,6 @@
<text class="label">{{ t('productInventory.count') }}</text>
<text class="value highlight">{{ formatStockCount(item.count) }}{{ textUnit(item.unitName) }}</text>
</view>
<view class="row">
<text class="label">{{ t('productInventory.latestInTime') }}</text>
<text class="value">{{ formatDateTime(getLatestInTime(item)) }}</text>
</view>
<view class="row">
<text class="label">{{ t('productInventory.latestOutTime') }}</text>
<text class="value">{{ formatDateTime(getLatestOutTime(item)) }}</text>
</view>
</view>
</view>
@ -121,37 +113,6 @@ function formatStockCount(value) {
return Number.isFinite(num) ? num.toLocaleString() : String(value)
}
function formatDateTime(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
const [year, month, day, hour = 0, minute = 0, second = 0] = value
const pad = (n) => String(n).padStart(2, '0')
return `${year}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
}
const text = String(value).trim()
if (!text) return '-'
const numeric = Number(text)
if (Number.isFinite(numeric)) {
const timestamp = text.length === 10 ? numeric * 1000 : numeric
const date = new Date(timestamp)
if (!Number.isNaN(date.getTime())) return formatDate(date)
}
const date = new Date(text)
return Number.isNaN(date.getTime()) ? text : formatDate(date)
}
function formatDate(date) {
const pad = (n) => String(n).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
function getLatestInTime(row) {
return row?.latestInTime ?? row?.lastInTime ?? row?.recentInTime ?? row?.latestStockInTime ?? row?.lastStockInTime
}
function getLatestOutTime(row) {
return row?.latestOutTime ?? row?.lastOutTime ?? row?.recentOutTime ?? row?.latestStockOutTime ?? row?.lastStockOutTime
}
function itemKey(item) {
return [item?.id, item?.productId, item?.warehouseId, item?.areaId].filter((value) => value !== undefined && value !== null).join('_')

Loading…
Cancel
Save