|
|
|
|
@ -0,0 +1,306 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="page-container">
|
|
|
|
|
<NavBar :title="'备件详情'" />
|
|
|
|
|
|
|
|
|
|
<view class="content-section" v-if="detail">
|
|
|
|
|
<!-- 标题 -->
|
|
|
|
|
<view class="page-title-bar">
|
|
|
|
|
<view class="page-title-bar-line"></view>
|
|
|
|
|
<text class="page-title">备件基本信息</text>
|
|
|
|
|
</view>
|
|
|
|
|
<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">物料名称</text><text class="item-value">{{ textValue(detail.name) }}</text></view>
|
|
|
|
|
<view class="item-row"><text class="item-label">物料编码</text><text class="item-value">{{ textValue(detail.barCode) }}</text></view>
|
|
|
|
|
<view class="item-row"><text class="item-label">物料小类</text><text class="item-value">{{ textValue(detail.categoryName) }}</text></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<!-- 其余信息 -->
|
|
|
|
|
<view class="info-list">
|
|
|
|
|
<view class="info-row"><text class="info-label">仓库</text><text class="info-value">{{ textValue(warehouseName) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">库区</text><text class="info-value">{{ textValue(areaName) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">库存展示</text><text class="info-value highlight">{{ textValue(detail.stockDisplay) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">基本数量</text><text class="info-value">{{ textValue(stockCount) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">单位</text><text class="info-value">{{ textValue(stockUnit) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">包装/换算规则</text><text class="info-value">{{ textValue(packagingRule) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">最近入库</text><text class="info-value">{{ formatDateTime(recentInTime) }}</text></view>
|
|
|
|
|
<view class="info-row"><text class="info-label">最近出库</text><text class="info-value">{{ formatDateTime(recentOutTime) }}</text></view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-else-if="loading" class="hint">加载中...</view>
|
|
|
|
|
<view v-else class="hint">加载失败</view>
|
|
|
|
|
</view>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
|
|
import { getSparepartInventoryPage, getSparepartDetail } from '@/api/mes/sparepart'
|
|
|
|
|
|
|
|
|
|
const detail = ref(null)
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
const stockCount = ref(0)
|
|
|
|
|
const stockUnit = ref('-')
|
|
|
|
|
const packagingRule = ref('-')
|
|
|
|
|
const warehouseName = ref('-')
|
|
|
|
|
const areaName = ref('-')
|
|
|
|
|
const recentInTime = ref('')
|
|
|
|
|
const recentOutTime = ref('')
|
|
|
|
|
|
|
|
|
|
const detailImage = computed(() => {
|
|
|
|
|
const images = detail.value?.images
|
|
|
|
|
if (!images) return ''
|
|
|
|
|
if (Array.isArray(images) && images.length) return String(images[0])
|
|
|
|
|
return String(images).split(',')[0]?.trim() || ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function textValue(v) {
|
|
|
|
|
if (v === 0) return '0'
|
|
|
|
|
if (v == null) return '-'
|
|
|
|
|
const s = String(v).trim()
|
|
|
|
|
return s || '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatStockDisplay() {
|
|
|
|
|
// 优先用 stockData 的 stockDisplay,兜底用 count+unit
|
|
|
|
|
if (stockCount.value > 0) {
|
|
|
|
|
const su = stockUnit.value && stockUnit.value !== '-' ? stockUnit.value : ''
|
|
|
|
|
return su ? `${stockCount.value}${su}` : String(stockCount.value)
|
|
|
|
|
}
|
|
|
|
|
return '-'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatDateTime(value) {
|
|
|
|
|
if (!value) return '-'
|
|
|
|
|
// 时间戳
|
|
|
|
|
const n = Number(value)
|
|
|
|
|
if (Number.isFinite(n) && n > 0) {
|
|
|
|
|
const d = new Date(n < 1e12 ? n * 1000 : n)
|
|
|
|
|
if (!Number.isNaN(d.getTime())) {
|
|
|
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return String(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onLoad(async (options) => {
|
|
|
|
|
const id = options?.id
|
|
|
|
|
const productId = options?.productId
|
|
|
|
|
const whId = options?.warehouseId
|
|
|
|
|
const arId = options?.areaId
|
|
|
|
|
if (!id && !productId) {
|
|
|
|
|
loading.value = false
|
|
|
|
|
uni.showToast({ title: '缺少备件信息', icon: 'none' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
// 优先从列表页传来的完整数据
|
|
|
|
|
const cached = getApp().globalData._sparepartInventoryDetail
|
|
|
|
|
if (cached && String(cached.productId) === String(id || productId)) {
|
|
|
|
|
// 映射字段名:列表页 productName -> 详情页 name
|
|
|
|
|
detail.value = {
|
|
|
|
|
name: cached.productName,
|
|
|
|
|
barCode: cached.barCode,
|
|
|
|
|
categoryName: cached.categoryName,
|
|
|
|
|
standard: cached.standard,
|
|
|
|
|
...cached
|
|
|
|
|
}
|
|
|
|
|
stockCount.value = Number(cached.count ?? 0)
|
|
|
|
|
stockUnit.value = cached.unitName || '-'
|
|
|
|
|
detail.value.stockDisplay = cached.stockDisplay || String(cached.count || '')
|
|
|
|
|
packagingRule.value = cached.packagingRule || '-'
|
|
|
|
|
warehouseName.value = cached.warehouseName || '-'
|
|
|
|
|
areaName.value = cached.areaName || '-'
|
|
|
|
|
recentInTime.value = cached.recentInTime || ''
|
|
|
|
|
recentOutTime.value = cached.recentOutTime || ''
|
|
|
|
|
// 异步加载图片(stock/page 不含 images)
|
|
|
|
|
if (!cached.images) {
|
|
|
|
|
getSparepartDetail(cached.productId).then(res => {
|
|
|
|
|
const pd = res?.data || res
|
|
|
|
|
if (pd?.images) detail.value = { ...detail.value, images: pd.images }
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
getApp().globalData._sparepartInventoryDetail = null
|
|
|
|
|
loading.value = false
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 兜底:并行调 stock/page + product/get 获取完整数据
|
|
|
|
|
const [pageRes, prodRes] = await Promise.all([
|
|
|
|
|
getSparepartInventoryPage({
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 1,
|
|
|
|
|
productId: id || productId,
|
|
|
|
|
warehouseId: whId || undefined,
|
|
|
|
|
areaId: arId || undefined
|
|
|
|
|
}),
|
|
|
|
|
getSparepartDetail(id || productId)
|
|
|
|
|
])
|
|
|
|
|
const root = pageRes?.data || pageRes
|
|
|
|
|
const list = root?.list || root?.rows || root?.records || []
|
|
|
|
|
const item = list[0]
|
|
|
|
|
const pd = prodRes?.data || prodRes
|
|
|
|
|
if (item) {
|
|
|
|
|
detail.value = {
|
|
|
|
|
name: item.productName,
|
|
|
|
|
...item,
|
|
|
|
|
stockDisplay: item.stockDisplay || String(item.count || ''),
|
|
|
|
|
images: pd?.images || item.images // product/get 专供图片
|
|
|
|
|
}
|
|
|
|
|
stockCount.value = Number(item.count ?? 0)
|
|
|
|
|
stockUnit.value = item.unitName || item.purchaseUnitName || '-'
|
|
|
|
|
packagingRule.value = item.packagingRule || '-'
|
|
|
|
|
warehouseName.value = item.warehouseName || '-'
|
|
|
|
|
areaName.value = item.areaName || '-'
|
|
|
|
|
recentInTime.value = item.recentInTime || ''
|
|
|
|
|
recentOutTime.value = item.recentOutTime || ''
|
|
|
|
|
} else {
|
|
|
|
|
detail.value = { name: '-', barCode: '-', categoryName: '-', standard: '-', stockDisplay: '-', images: pd?.images }
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('获取备件详情失败:', e)
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.page-container {
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
background: #f3f4f6;
|
|
|
|
|
padding-bottom: 40rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content-section {
|
|
|
|
|
padding: 24rpx 28rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 页面标题 */
|
|
|
|
|
.page-title-bar {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
padding: 0 0 20rpx;
|
|
|
|
|
}
|
|
|
|
|
.page-title-bar-line {
|
|
|
|
|
width: 6rpx;
|
|
|
|
|
height: 32rpx;
|
|
|
|
|
border-radius: 3rpx;
|
|
|
|
|
background: #2563eb;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
.page-title {
|
|
|
|
|
font-size: 30rpx;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
color: #1a1a1a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 详情卡片 */
|
|
|
|
|
.detail-card {
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
padding: 28rpx;
|
|
|
|
|
box-shadow: 0 2rpx 12rpx rgba(15, 23, 42, 0.04);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 图片 + 前三行 */
|
|
|
|
|
.info-row-top {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding-bottom: 20rpx;
|
|
|
|
|
border-bottom: 1rpx solid #f3f4f6;
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
&: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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
padding: 16rpx 0;
|
|
|
|
|
border-bottom: 1rpx solid #f3f4f6;
|
|
|
|
|
&:last-child { border-bottom: 0; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-value {
|
|
|
|
|
flex: 1;
|
|
|
|
|
text-align: right;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #374151;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
|
|
|
|
|
&.highlight {
|
|
|
|
|
color: #1f4b79;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hint {
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 160rpx 0;
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
}
|
|
|
|
|
</style>
|