Merge branch 'master' of https://git.ngsk.tech/linweidong/besure_app
commit
ba1bdb3164
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<NavBar :title="'物料盘点详情'" />
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<view v-if="detail" class="section-card">
|
||||
<view class="section-header">
|
||||
<view class="section-icon">
|
||||
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
|
||||
</view>
|
||||
<text class="section-title">盘点信息</text>
|
||||
</view>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">单据号</text>
|
||||
<text class="info-value">{{ textValue(detail.no) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">盘点时间</text>
|
||||
<text class="info-value">{{ formatDateTime(detail.checkTime) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">仓库</text>
|
||||
<text class="info-value">{{ textValue(getWarehouseNames()) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">来源方式</text>
|
||||
<text class="info-value">{{ detail.sourceType === 1 ? '按库存' : '按产品' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">盘点状态</text>
|
||||
<text :class="['info-value', 'status-tag', checkStatusClass(detail.checkStatus)]">{{ checkStatusText(detail.checkStatus) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">创建人</text>
|
||||
<text class="info-value">{{ textValue(detail.creatorName || detail.creator) }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="detail.remark">
|
||||
<text class="info-label">备注</text>
|
||||
<text class="info-value">{{ textValue(detail.remark) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-card">
|
||||
<view class="section-header list-header">
|
||||
<view class="section-title-wrap">
|
||||
<view class="section-icon">
|
||||
<uni-icons type="list" size="24" color="#1f7cff"></uni-icons>
|
||||
</view>
|
||||
<text class="section-title">盘点项</text>
|
||||
</view>
|
||||
<text v-if="items.length" class="item-count">{{ items.length }} 项</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="hint">加载中...</view>
|
||||
<view v-else-if="items.length" class="item-list">
|
||||
<view v-for="(item, idx) in items" :key="idx" class="item-card">
|
||||
<view class="item-header">
|
||||
<view class="item-index">{{ idx + 1 }}</view>
|
||||
<view class="item-title-wrap">
|
||||
<text class="item-name">{{ textValue(item.productName) }}</text>
|
||||
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-grid">
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">仓库</text>
|
||||
<text class="cell-value">{{ textValue(item.warehouseName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">库区</text>
|
||||
<text class="cell-value">{{ textValue(item.areaName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">账面数量</text>
|
||||
<text class="cell-value highlight">{{ textValue(item.stockCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">实盘数量</text>
|
||||
<text class="cell-value highlight">{{ textValue(item.actualCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">差异</text>
|
||||
<text :class="['cell-value', 'diff-value', diffClass(item)]">{{ getDiffText(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="hint">暂无盘点项</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import NavBar from '@/components/common/NavBar.vue'
|
||||
import { getMaterialCheckDetail } from '@/api/mes/materialCheck'
|
||||
|
||||
const detail = ref(null)
|
||||
const items = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
function textValue(v) {
|
||||
if (v === 0) return '0'
|
||||
if (v == null) return '-'
|
||||
const s = String(v).trim()
|
||||
return s || '-'
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '-'
|
||||
if (Array.isArray(value) && value.length >= 3) {
|
||||
const [y, m, d, h = 0, M = 0, s = 0] = value
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${y}-${p(m)}-${p(d)} ${p(h)}:${p(M)}:${p(s)}`
|
||||
}
|
||||
const d = new Date(Number(value))
|
||||
if (Number.isNaN(d.getTime())) return String(value)
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
||||
}
|
||||
|
||||
function getWarehouseNames() {
|
||||
return [...new Set(items.value.map(i => i.warehouseName).filter(Boolean))].join('、')
|
||||
}
|
||||
|
||||
function getDiff(item) {
|
||||
const a = item.actualCount != null ? Number(item.actualCount) : null
|
||||
if (a == null) return null
|
||||
return a - (Number(item.stockCount) || 0)
|
||||
}
|
||||
|
||||
function getDiffText(item) {
|
||||
const d = getDiff(item)
|
||||
if (d == null) return '-'
|
||||
return `${d > 0 ? '+' : ''}${d}`
|
||||
}
|
||||
|
||||
function diffClass(item) {
|
||||
const d = getDiff(item)
|
||||
if (d > 0) return 'positive'
|
||||
if (d < 0) return 'negative'
|
||||
return 'zero'
|
||||
}
|
||||
|
||||
function checkStatusText(s) {
|
||||
const m = { 0: '未盘点', 1: '已盘点', 2: '已提交', 10: '待审核', 20: '已审核' }
|
||||
return m[s] || '-'
|
||||
}
|
||||
|
||||
function checkStatusClass(s) {
|
||||
const m = { 0: 'status-pending', 1: 'status-done', 2: 'status-submitted', 10: 'status-auditing', 20: 'status-passed' }
|
||||
return m[s] || ''
|
||||
}
|
||||
|
||||
async function loadDetail(id) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getMaterialCheckDetail(id)
|
||||
const data = res?.data || res
|
||||
detail.value = data
|
||||
items.value = Array.isArray(data?.items) ? data.items : []
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载详情失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const id = options?.id
|
||||
if (!id) { uni.showToast({ title: '缺少单据ID', icon: 'none' }); return }
|
||||
loadDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||
.detail-scroll { height: calc(100vh - 120rpx); }
|
||||
.content-section { padding: 20rpx 24rpx 28rpx; }
|
||||
.section-card { background: #ffffff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1rpx solid #eef2f7; 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; }
|
||||
.section-title-wrap { display: flex; align-items: center; gap: 12rpx; min-width: 0; }
|
||||
.list-header { justify-content: space-between; gap: 16rpx; }
|
||||
.item-count { font-size: 24rpx; color: #1f4b79; font-weight: 600; }
|
||||
.info-list { display: flex; flex-direction: column; gap: 14rpx; }
|
||||
.info-row { display: flex; justify-content: space-between; gap: 20rpx; }
|
||||
.info-label { width: 150rpx; font-size: 25rpx; color: #94a3b8; flex-shrink: 0; }
|
||||
.info-value { flex: 1; text-align: right; font-size: 27rpx; color: #334155; line-height: 1.5; }
|
||||
.status-tag { font-weight: 600; }
|
||||
.status-pending { color: #f59e0b; }
|
||||
.status-done { color: #3b82f6; }
|
||||
.status-submitted { color: #8b5cf6; }
|
||||
.status-auditing { color: #f59e0b; }
|
||||
.status-passed { color: #16a34a; }
|
||||
.item-list { display: flex; flex-direction: column; gap: 18rpx; }
|
||||
.item-card { padding: 20rpx; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 18rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
|
||||
.item-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 18rpx; }
|
||||
.item-index { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #1f4b79; color: #fff; font-size: 26rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.item-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
|
||||
.item-name { font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.item-code { font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 18rpx; background: #f8fafc; border: 1rpx solid #eef2f7; border-radius: 16rpx; }
|
||||
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.cell-label { font-size: 22rpx; color: #9ca3af; }
|
||||
.cell-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.cell-value.highlight { color: #1f4b79; font-weight: 600; }
|
||||
.diff-value.positive { color: #16a34a; font-weight: 700; }
|
||||
.diff-value.negative { color: #dc2626; font-weight: 700; }
|
||||
.diff-value.zero { color: #64748b; font-weight: 700; }
|
||||
.hint { padding: 60rpx 0; text-align: center; color: #94a3b8; font-size: 26rpx; }
|
||||
</style>
|
||||
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<NavBar :title="'备件盘点详情'" />
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<!-- 盘点信息 -->
|
||||
<view v-if="detail" class="section-card">
|
||||
<view class="section-header">
|
||||
<view class="section-icon">
|
||||
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
|
||||
</view>
|
||||
<text class="section-title">盘点信息</text>
|
||||
</view>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">单据号</text>
|
||||
<text class="info-value">{{ textValue(detail.no) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">盘点时间</text>
|
||||
<text class="info-value">{{ formatDateTime(detail.checkTime) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">仓库</text>
|
||||
<text class="info-value">{{ textValue(getWarehouseNames()) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">来源方式</text>
|
||||
<text class="info-value">{{ detail.sourceType === 1 ? '按库存' : '按产品' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">盘点状态</text>
|
||||
<text :class="['info-value', 'status-tag', checkStatusClass(detail.checkStatus)]">{{ checkStatusText(detail.checkStatus) }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">创建人</text>
|
||||
<text class="info-value">{{ textValue(detail.creatorName || detail.creator) }}</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="detail.remark">
|
||||
<text class="info-label">备注</text>
|
||||
<text class="info-value">{{ textValue(detail.remark) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 盘点项列表 -->
|
||||
<view class="section-card">
|
||||
<view class="section-header list-header">
|
||||
<view class="section-title-wrap">
|
||||
<view class="section-icon">
|
||||
<uni-icons type="list" size="24" color="#1f7cff"></uni-icons>
|
||||
</view>
|
||||
<text class="section-title">盘点项</text>
|
||||
</view>
|
||||
<text v-if="items.length" class="item-count">{{ items.length }} 项</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="hint">加载中...</view>
|
||||
<view v-else-if="items.length" class="item-list">
|
||||
<view v-for="(item, idx) in items" :key="idx" class="item-card">
|
||||
<view class="item-header">
|
||||
<view class="item-index">{{ idx + 1 }}</view>
|
||||
<view class="item-title-wrap">
|
||||
<text class="item-name">{{ textValue(item.productName) }}</text>
|
||||
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-grid">
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">仓库</text>
|
||||
<text class="cell-value">{{ textValue(item.warehouseName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">库区</text>
|
||||
<text class="cell-value">{{ textValue(item.areaName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">账面数量</text>
|
||||
<text class="cell-value highlight">{{ textValue(item.stockCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">实盘数量</text>
|
||||
<text class="cell-value highlight">{{ textValue(item.actualCount) }} {{ textValue(item.productUnitName || item.unitName) }}</text>
|
||||
</view>
|
||||
<view class="info-cell">
|
||||
<text class="cell-label">差异</text>
|
||||
<text :class="['cell-value', 'diff-value', diffClass(item)]">{{ getDiffText(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="hint">暂无盘点项</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import NavBar from '@/components/common/NavBar.vue'
|
||||
import { getSparepartCheckDetail } from '@/api/mes/sparepartCheck'
|
||||
|
||||
const detail = ref(null)
|
||||
const items = ref([])
|
||||
const loading = ref(false)
|
||||
|
||||
function textValue(v) {
|
||||
if (v === 0) return '0'
|
||||
if (v == null) return '-'
|
||||
const s = String(v).trim()
|
||||
return s || '-'
|
||||
}
|
||||
|
||||
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 date = new Date(Number(value))
|
||||
if (Number.isNaN(date.getTime())) return String(value)
|
||||
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 getWarehouseNames() {
|
||||
return [...new Set(items.value.map((item) => item.warehouseName).filter(Boolean))].join('、')
|
||||
}
|
||||
|
||||
function getDiff(item) {
|
||||
const actual = item.actualCount != null ? Number(item.actualCount) : null
|
||||
if (actual == null) return null
|
||||
return actual - (Number(item.stockCount) || 0)
|
||||
}
|
||||
|
||||
function getDiffText(item) {
|
||||
const diff = getDiff(item)
|
||||
if (diff == null) return '-'
|
||||
return `${diff > 0 ? '+' : ''}${diff}`
|
||||
}
|
||||
|
||||
function diffClass(item) {
|
||||
const diff = getDiff(item)
|
||||
if (diff > 0) return 'positive'
|
||||
if (diff < 0) return 'negative'
|
||||
return 'zero'
|
||||
}
|
||||
|
||||
function checkStatusText(status) {
|
||||
const map = { 0: '未盘点', 1: '已盘点', 2: '已提交', 10: '待审核', 20: '已审核' }
|
||||
return map[status] || '-'
|
||||
}
|
||||
|
||||
function checkStatusClass(status) {
|
||||
const map = { 0: 'status-pending', 1: 'status-done', 2: 'status-submitted', 10: 'status-auditing', 20: 'status-passed' }
|
||||
return map[status] || ''
|
||||
}
|
||||
|
||||
async function loadDetail(id) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getSparepartCheckDetail(id)
|
||||
const data = res?.data || res
|
||||
detail.value = data
|
||||
items.value = Array.isArray(data?.items) ? data.items : []
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载详情失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const id = options?.id
|
||||
if (!id) {
|
||||
uni.showToast({ title: '缺少单据ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
loadDetail(id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||
.detail-scroll { height: calc(100vh - 120rpx); }
|
||||
.content-section { padding: 20rpx 24rpx 28rpx; }
|
||||
.section-card { background: #ffffff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1rpx solid #eef2f7; 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; }
|
||||
.section-title-wrap { display: flex; align-items: center; gap: 12rpx; min-width: 0; }
|
||||
.list-header { justify-content: space-between; gap: 16rpx; }
|
||||
.item-count { font-size: 24rpx; color: #1f4b79; font-weight: 600; }
|
||||
.info-list { display: flex; flex-direction: column; gap: 14rpx; }
|
||||
.info-row { display: flex; justify-content: space-between; gap: 20rpx; }
|
||||
.info-label { width: 150rpx; font-size: 25rpx; color: #94a3b8; flex-shrink: 0; }
|
||||
.info-value { flex: 1; text-align: right; font-size: 27rpx; color: #334155; line-height: 1.5; }
|
||||
.status-tag { font-weight: 600; }
|
||||
.status-pending { color: #f59e0b; }
|
||||
.status-done { color: #3b82f6; }
|
||||
.status-submitted { color: #8b5cf6; }
|
||||
.status-auditing { color: #f59e0b; }
|
||||
.status-passed { color: #16a34a; }
|
||||
.item-list { display: flex; flex-direction: column; gap: 18rpx; }
|
||||
.item-card { padding: 20rpx; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 18rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
|
||||
.item-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 18rpx; }
|
||||
.item-index { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #1f4b79; color: #fff; font-size: 26rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.item-title-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
|
||||
.item-name { font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.item-code { font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; padding: 18rpx; background: #f8fafc; border: 1rpx solid #eef2f7; border-radius: 16rpx; }
|
||||
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||
.cell-label { font-size: 22rpx; color: #9ca3af; }
|
||||
.cell-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.cell-value.highlight { color: #1f4b79; font-weight: 600; }
|
||||
.diff-value.positive { color: #16a34a; font-weight: 700; }
|
||||
.diff-value.negative { color: #dc2626; font-weight: 700; }
|
||||
.diff-value.zero { color: #64748b; font-weight: 700; }
|
||||
.hint { padding: 60rpx 0; text-align: center; color: #94a3b8; font-size: 26rpx; }
|
||||
</style>
|
||||
Loading…
Reference in New Issue