Merge branch 'master' of https://git.ngsk.tech/linweidong/besure_app
commit
f63086c8e3
@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<NavBar :title="'物料入库详情'" />
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="detail-scroll">
|
||||||
|
<view class="content-section">
|
||||||
|
<view class="section-card">
|
||||||
|
<view class="section-header">
|
||||||
|
<view class="section-icon"><uni-icons type="compose" size="24" color="#1f7cff"></uni-icons></view>
|
||||||
|
<text class="section-title">入库信息</text>
|
||||||
|
<text :class="['status-tag', statusClass(detail.status)]">{{ statusText(detail.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="loading" class="loading-card">加载中...</view>
|
||||||
|
<template v-else>
|
||||||
|
<view class="readonly-grid">
|
||||||
|
<view class="readonly-item"><text class="readonly-label">单据号</text><text class="readonly-value">{{ textValue(detail.no) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">入库类型</text><text class="readonly-value">{{ textValue(detail.inType) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">入库时间</text><text class="readonly-value">{{ formatDateTime(detail.inTime || detail.createTime) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">经办人</text><text class="readonly-value">{{ textValue(detail.stockUserName) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">入库数量</text><text class="readonly-value highlight">{{ textValue(detail.totalCount) }}{{ detail.totalCount != null ? ' 个' : '' }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">审核人</text><text class="readonly-value">{{ textValue(detail.auditUserName) }}</text></view>
|
||||||
|
</view>
|
||||||
|
<view class="form-field"><text class="form-label">备注</text><view class="readonly-box">{{ textValue(detail.remark) }}</view></view>
|
||||||
|
<view v-if="attachmentList.length" class="form-field">
|
||||||
|
<text class="form-label">附件</text>
|
||||||
|
<view class="attachment-list">
|
||||||
|
<view v-for="(file, idx) in attachmentList" :key="idx" class="attachment-item">
|
||||||
|
<uni-icons type="paperclip" size="16" color="#64748b"></uni-icons>
|
||||||
|
<text class="attachment-name">{{ textValue(getAttachmentName(file, idx)) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</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 class="count-badge">{{ itemList.length }} 项</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="itemList.length" class="summary-strip">
|
||||||
|
<view class="summary-item"><text class="summary-value">{{ itemList.length }}</text><text class="summary-label">物料</text></view>
|
||||||
|
<view class="summary-item"><text class="summary-value">{{ totalPieceCount }} 个</text><text class="summary-label">总数量</text></view>
|
||||||
|
</view>
|
||||||
|
<view v-if="itemList.length" class="item-list">
|
||||||
|
<view v-for="(item, idx) in itemList" :key="item.id || idx" class="item-card">
|
||||||
|
<view class="item-header"><text class="item-name">{{ textValue(item.productName) }}</text><text class="item-code">{{ textValue(item.productBarCode) }}</text></view>
|
||||||
|
<view class="info-grid">
|
||||||
|
<view class="info-cell"><text class="info-label">仓库</text><text class="info-value">{{ textValue(item.warehouseName || warehouseMap[item.warehouseId]) }}</text></view>
|
||||||
|
<view class="info-cell"><text class="info-label">库区</text><text class="info-value">{{ textValue(item.areaName) }}</text></view>
|
||||||
|
<view class="info-cell"><text class="info-label">数量</text><text class="info-value">{{ textValue(item.count) }}{{ getUnitData(item).unitName && ' ' + getUnitData(item).unitName }}</text></view>
|
||||||
|
<view class="info-cell"><text class="info-label">换算关系</text><text class="info-value">{{ textValue(getUnitData(item).packagingRule) }}</text></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-card">{{ loading ? '加载中...' : '暂无入库清单' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
<view class="action-bar"><view class="action-btn back-btn" @click="handleBack">返回</view></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import NavBar from '@/components/common/NavBar.vue'
|
||||||
|
import { getMaterialInboundDetail } from '@/api/mes/materialInbound'
|
||||||
|
import { getProductDetail } from '@/api/mes/sparepart'
|
||||||
|
import { getWarehouseSimpleList } from '@/api/mes/moldget'
|
||||||
|
|
||||||
|
const detailId = ref(null)
|
||||||
|
const detail = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
const productMap = ref({})
|
||||||
|
const warehouseMap = ref({})
|
||||||
|
|
||||||
|
const itemList = computed(() => { const l = detail.value?.items || detail.value?.itemList || detail.value?.stockInItems || []; return Array.isArray(l) ? l : [] })
|
||||||
|
const attachmentList = computed(() => { const r = detail.value?.fileUrl || detail.value?.attachments || detail.value?.attachmentList; if (!r) return []; if (Array.isArray(r)) return r; return typeof r === 'object' ? [r] : String(r).split(',').map(s => s.trim()).filter(Boolean) })
|
||||||
|
const totalPieceCount = computed(() => itemList.value.reduce((s, i) => s + (Number(i.count) || 0), 0))
|
||||||
|
|
||||||
|
function getUnitData(item) {
|
||||||
|
const p = productMap.value[item.productId]
|
||||||
|
const puN = p?.purchaseUnitName || item.purchaseUnitName || ''
|
||||||
|
const puQ = p?.purchaseUnitConvertQuantity || item.purchaseUnitConvertQuantity
|
||||||
|
const uN = p?.unitName || p?.productUnitName || item.unitName || item.productUnitName || '个'
|
||||||
|
let pr = p?.packagingRule || item.packagingRule || ''
|
||||||
|
if (!pr && puN && puQ) pr = `1${puN}=${puQ}${uN}`
|
||||||
|
return { packagingRule: pr || '-', unitName: uN }
|
||||||
|
}
|
||||||
|
function textValue(v) { if (v === 0) return '0'; if (v == null) return '-'; return String(v).trim() || '-' }
|
||||||
|
function formatDateTime(v) { if (!v) return '-'; const r = typeof v === 'string' && /^\d+$/.test(v) ? Number(v) : v; const d = new Date(r); if (Number.isNaN(d.getTime())) return String(v); const p = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}` }
|
||||||
|
function statusText(s) { const m = { 0: '待入库', 10: '待审核', 20: '已入库', 1: '已驳回' }; return m[Number(s)] || textValue(s) }
|
||||||
|
function statusClass(s) { const m = { 0: 'text-primary', 10: 'text-warning', 20: 'text-success', 1: 'text-danger' }; return m[Number(s)] || '' }
|
||||||
|
function getAttachmentName(f, i) { if (f?.name) return f.name; if (f?.fileName) return f.fileName; const t = String(f?.url || f?.fileUrl || f || ''); const n = t.split('/').pop(); return n || `附件${i+1}` }
|
||||||
|
|
||||||
|
async function loadDetail() {
|
||||||
|
if (!detailId.value) return; loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getMaterialInboundDetail(detailId.value)
|
||||||
|
detail.value = res?.data !== undefined ? res.data : res
|
||||||
|
try { const wR = await getWarehouseSimpleList(); const wD = Array.isArray(wR) ? wR : (Array.isArray(wR?.data) ? wR.data : []); const m = {}; wD.forEach(w => { m[w.id] = w.name || String(w.id) }); warehouseMap.value = m } catch(e){}
|
||||||
|
const its = itemList.value
|
||||||
|
if (its.length) { const ids = [...new Set(its.map(i => i.productId).filter(Boolean))]; const m2 = {}; await Promise.all(ids.map(async id => { try { const r = await getProductDetail(id); const d = r?.data || r; if (d) m2[id] = d } catch(e){} })); productMap.value = m2 }
|
||||||
|
} catch(e) { uni.showToast({ title: '加载详情失败', icon: 'none' }) }
|
||||||
|
finally { loading.value = false }
|
||||||
|
}
|
||||||
|
function handleBack() { uni.navigateBack() }
|
||||||
|
onLoad((o) => { detailId.value = o?.id || null; loadDetail() })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||||
|
.detail-scroll { height: calc(100vh - 172rpx); }
|
||||||
|
.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; }
|
||||||
|
.count-badge { flex-shrink: 0; padding: 6rpx 16rpx; border-radius: 999rpx; background: #eff6ff; color: #1f7cff; font-size: 22rpx; font-weight: 600; }
|
||||||
|
.status-tag { margin-left: auto; flex-shrink: 0; padding: 8rpx 18rpx; border-radius: 999rpx; font-size: 22rpx; line-height: 1; background: #e2e8f0; color: #64748b; }
|
||||||
|
.status-tag.text-success { color: #15803d; background: #dcfce7; }
|
||||||
|
.status-tag.text-danger { color: #dc2626; background: #fee2e2; }
|
||||||
|
.status-tag.text-warning { color: #d97706; background: #fef3c7; }
|
||||||
|
.status-tag.text-primary { color: #1d4ed8; background: #dbeafe; }
|
||||||
|
.loading-card, .empty-card { min-height: 180rpx; border: 2rpx dashed #d7dde8; border-radius: 18rpx; background: #f8fafc; display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 27rpx; }
|
||||||
|
.readonly-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 14rpx; overflow: hidden; }
|
||||||
|
.readonly-item { min-width: 0; display: flex; flex-direction: column; gap: 8rpx; padding: 18rpx 20rpx; border-right: 1rpx solid #f1f5f9; border-bottom: 1rpx solid #f1f5f9; }
|
||||||
|
.readonly-item:nth-child(2n) { border-right: 0; }
|
||||||
|
.readonly-label { font-size: 23rpx; color: #8a94a6; }
|
||||||
|
.readonly-value { font-size: 27rpx; color: #334155; line-height: 1.35; word-break: break-all; }
|
||||||
|
.readonly-value.highlight { color: #1f4b79; font-weight: 700; }
|
||||||
|
.form-field { display: flex; flex-direction: column; gap: 12rpx; margin-top: 24rpx; }
|
||||||
|
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
|
||||||
|
.readonly-box { min-height: 70rpx; padding: 18rpx 24rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; font-size: 28rpx; color: #374151; line-height: 1.45; }
|
||||||
|
.attachment-list { display: flex; flex-direction: column; gap: 10rpx; }
|
||||||
|
.attachment-item { display: flex; align-items: center; gap: 10rpx; padding: 16rpx 18rpx; background: #f8fafc; border-radius: 12rpx; }
|
||||||
|
.attachment-name { flex: 1; min-width: 0; font-size: 24rpx; color: #334155; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.summary-strip { display: grid; grid-template-columns: repeat(2, 1fr); background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
|
||||||
|
.summary-item { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6rpx; padding: 16rpx 8rpx; border-right: 1rpx solid #eef2f7; }
|
||||||
|
.summary-item:last-child { border-right: 0; }
|
||||||
|
.summary-value { max-width: 100%; font-size: 30rpx; font-weight: 700; color: #1f4b79; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.summary-label { font-size: 22rpx; color: #8a94a6; }
|
||||||
|
.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: 12rpx; margin-bottom: 14rpx; }
|
||||||
|
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.item-code { flex-shrink: 0; max-width: 240rpx; font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
||||||
|
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||||
|
.info-label { font-size: 22rpx; color: #9ca3af; }
|
||||||
|
.info-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15,23,42,0.06); z-index: 99; }
|
||||||
|
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
|
||||||
|
.back-btn { background: #1f4b79; color: #ffffff; }
|
||||||
|
</style>
|
||||||
@ -1,235 +1,125 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page-container">
|
<view class="page-container">
|
||||||
<NavBar title="物料出库详情" />
|
<NavBar :title="'物料出库详情'" />
|
||||||
|
<scroll-view scroll-y class="detail-scroll">
|
||||||
<view v-if="loading" class="hint">加载中...</view>
|
<view class="content-section">
|
||||||
<view v-else-if="!detail.id" class="hint">暂无详情</view>
|
<view class="section-card">
|
||||||
<view v-else class="content">
|
<view class="section-header"><view class="section-icon"><uni-icons type="compose" size="24" color="#1f7cff"></uni-icons></view><text class="section-title">出库信息</text><text :class="['status-tag', statusClass(detail.status)]">{{ statusText(detail.status) }}</text></view>
|
||||||
<view class="summary-card">
|
<view v-if="loading" class="loading-card">加载中...</view>
|
||||||
<view class="summary-header">
|
<template v-else>
|
||||||
<text class="summary-no">{{ textValue(detail.no) }}</text>
|
<view class="readonly-grid">
|
||||||
<text :class="['status-tag', statusClass(detail.status)]">{{ statusText(detail.status) }}</text>
|
<view class="readonly-item"><text class="readonly-label">单据号</text><text class="readonly-value">{{ textValue(detail.no) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">出库类型</text><text class="readonly-value">{{ textValue(detail.outType) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">出库时间</text><text class="readonly-value">{{ formatDateTime(detail.outTime || detail.createTime) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">经办人</text><text class="readonly-value">{{ textValue(detail.responserName || detail.stockUserName || detail.creatorName || detail.creator) }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">出库数量</text><text class="readonly-value highlight">{{ textValue(detail.totalCount) }}{{ detail.totalCount != null ? ' 个' : '' }}</text></view>
|
||||||
|
<view class="readonly-item"><text class="readonly-label">审核人</text><text class="readonly-value">{{ textValue(detail.auditUserName) }}</text></view>
|
||||||
|
</view>
|
||||||
|
<view class="form-field"><text class="form-label">备注</text><view class="readonly-box">{{ textValue(detail.remark) }}</view></view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<view class="row"><text class="label">出库时间</text><text class="value">{{ formatDateTime(detail.outTime || detail.createTime) }}</text></view>
|
<view class="section-card">
|
||||||
<view class="row"><text class="label">经办人</text><text class="value">{{ textValue(detail.responserName || detail.stockUserName || detail.creatorName || detail.creator) }}</text></view>
|
<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 class="count-badge">{{ items.length }} 项</text></view>
|
||||||
<view class="row"><text class="label">审核人</text><text class="value">{{ textValue(detail.auditUserName) }}</text></view>
|
<view v-if="items.length" class="summary-strip"><view class="summary-item"><text class="summary-value">{{ items.length }}</text><text class="summary-label">物料</text></view><view class="summary-item"><text class="summary-value">{{ totalCount }} 个</text><text class="summary-label">总数量</text></view></view>
|
||||||
<view class="row"><text class="label">总数量</text><text class="value highlight">{{ textValue(detail.totalCount) }}</text></view>
|
<view v-if="items.length" class="item-list"><view v-for="(item, idx) in items" :key="item.id || idx" class="item-card">
|
||||||
<view class="row"><text class="label">备注</text><text class="value">{{ textValue(detail.remark) }}</text></view>
|
<view class="item-header"><text class="item-name">{{ textValue(item.productName || item.name) }}</text><text class="item-code">{{ textValue(item.productBarCode || item.barCode) }}</text></view>
|
||||||
</view>
|
<view class="info-grid">
|
||||||
|
<view class="info-cell"><text class="info-label">仓库</text><text class="info-value">{{ textValue(item.warehouseName || warehouseMap[item.warehouseId]) }}</text></view>
|
||||||
<view class="section-title-bar">
|
<view class="info-cell"><text class="info-label">库区</text><text class="info-value">{{ textValue(item.areaName) }}</text></view>
|
||||||
<view class="section-bar-line"></view>
|
<view class="info-cell"><text class="info-label">数量</text><text class="info-value">{{ textValue(item.count) }}{{ getUnitData(item).unitName && ' ' + getUnitData(item).unitName }}</text></view>
|
||||||
<text class="section-title">出库明细</text>
|
<view class="info-cell"><text class="info-label">换算关系</text><text class="info-value">{{ textValue(getUnitData(item).packagingRule) }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="items.length" class="item-list">
|
</view></view>
|
||||||
<view v-for="(item, index) in items" :key="item.id || index" class="item-card">
|
<view v-else class="empty-card">{{ loading ? '加载中...' : '暂无出库清单' }}</view>
|
||||||
<view class="item-title">{{ textValue(item.productName || item.name) }}</view>
|
|
||||||
<view class="row"><text class="label">物料编码</text><text class="value">{{ textValue(item.productBarCode || item.barCode) }}</text></view>
|
|
||||||
<view class="row"><text class="label">仓库/库区</text><text class="value">{{ warehouseAreaText(item) }}</text></view>
|
|
||||||
<view class="row"><text class="label">出库数量</text><text class="value highlight">{{ textValue(item.inputCount || item.count) }}{{ textUnit(item.purchaseUnitName || item.productUnitName) }}</text></view>
|
|
||||||
<view class="row"><text class="label">库存数量</text><text class="value">{{ textValue(item.count) }}{{ textUnit(item.productUnitName) }}</text></view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="hint">暂无明细</view>
|
</scroll-view>
|
||||||
</view>
|
<view class="action-bar"><view class="action-btn back-btn" @click="handleBack">返回</view></view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import NavBar from '@/components/common/NavBar.vue'
|
import NavBar from '@/components/common/NavBar.vue'
|
||||||
import { getMaterialOutbound } from '@/api/mes/materialOutbound'
|
import { getMaterialOutbound } from '@/api/mes/materialOutbound'
|
||||||
|
import { getProductDetail } from '@/api/mes/sparepart'
|
||||||
const loading = ref(false)
|
import { getWarehouseSimpleList } from '@/api/mes/moldget'
|
||||||
const detail = ref({})
|
|
||||||
|
const id = ref(null); const detail = ref({}); const loading = ref(false)
|
||||||
const items = computed(() => {
|
const productMap = ref({}); const warehouseMap = ref({})
|
||||||
const source = detail.value.items || detail.value.itemList || detail.value.details || detail.value.detailList || []
|
|
||||||
return Array.isArray(source) ? source : []
|
const items = computed(() => { const l = detail.value?.items || detail.value?.itemList || detail.value?.stockOutItems || []; return Array.isArray(l) ? l : [] })
|
||||||
})
|
const totalCount = computed(() => items.value.reduce((s,i) => s + (Number(i.count)||0), 0))
|
||||||
|
|
||||||
function textValue(value) {
|
function getUnitData(item) {
|
||||||
if (value === 0) return '0'
|
const p = productMap.value[item.productId]
|
||||||
if (value == null) return '-'
|
const puN = p?.purchaseUnitName || item.purchaseUnitName || ''
|
||||||
const text = String(value).trim()
|
const puQ = p?.purchaseUnitConvertQuantity || item.purchaseUnitConvertQuantity
|
||||||
return text || '-'
|
const uN = p?.unitName || p?.productUnitName || item.unitName || item.productUnitName || '个'
|
||||||
}
|
let pr = p?.packagingRule || item.packagingRule || ''
|
||||||
|
if (!pr && puN && puQ) pr = `1${puN}=${puQ}${uN}`
|
||||||
function textUnit(value) {
|
return { packagingRule: pr || '-', unitName: uN }
|
||||||
if (value === 0) return '0'
|
}
|
||||||
if (value == null) return ''
|
function textValue(v) { if (v === 0) return '0'; if (v == null) return '-'; return String(v).trim() || '-' }
|
||||||
return String(value).trim()
|
function formatDateTime(v) { if (!v) return '-'; const r = typeof v === 'string' && /^\d+$/.test(v) ? Number(v) : v; const d = new Date(r); if (Number.isNaN(d.getTime())) return String(v); const p = n => String(n).padStart(2,'0'); return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}` }
|
||||||
}
|
function statusText(s) { const m = { 0: '待出库', 10: '待审核', 20: '已出库', 1: '已驳回' }; return m[Number(s)] || textValue(s) }
|
||||||
|
function statusClass(s) { const m = { 0: 'text-primary', 10: 'text-warning', 20: 'text-success', 1: 'text-danger' }; return m[Number(s)] || '' }
|
||||||
function formatDateTime(value) {
|
|
||||||
if (!value) return '-'
|
async function loadDetail() {
|
||||||
if (Array.isArray(value) && value.length >= 3) {
|
if (!id.value) return; loading.value = true
|
||||||
const [year, month, day] = value
|
|
||||||
const pad = (n) => String(n).padStart(2, '0')
|
|
||||||
return `${year}-${pad(month)}-${pad(day)}`
|
|
||||||
}
|
|
||||||
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())}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const STATUS_MAP = {
|
|
||||||
0: '待出库',
|
|
||||||
1: '已驳回',
|
|
||||||
10: '待审核',
|
|
||||||
20: '已出库'
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusText(status) {
|
|
||||||
return STATUS_MAP[Number(status)] || textValue(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusClass(status) {
|
|
||||||
const value = Number(status)
|
|
||||||
if (value === 0) return 'pending'
|
|
||||||
if (value === 10) return 'auditing'
|
|
||||||
if (value === 20) return 'success'
|
|
||||||
if (value === 1) return 'danger'
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function warehouseAreaText(item) {
|
|
||||||
const warehouse = item.warehouseName || item.warehouse?.name || ''
|
|
||||||
const area = item.areaName || item.area?.name || ''
|
|
||||||
return [warehouse, area].filter(Boolean).join(' / ') || '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadDetail(id) {
|
|
||||||
if (!id) return
|
|
||||||
loading.value = true
|
|
||||||
try {
|
try {
|
||||||
const res = await getMaterialOutbound(id)
|
const res = await getMaterialOutbound(id.value); detail.value = res?.data !== undefined ? res.data : res
|
||||||
detail.value = res?.data || res || {}
|
try { const wR = await getWarehouseSimpleList(); const wD = Array.isArray(wR) ? wR : (Array.isArray(wR?.data) ? wR.data : []); const m = {}; wD.forEach(w => { m[w.id] = w.name || String(w.id) }); warehouseMap.value = m } catch(e){}
|
||||||
} catch (error) {
|
const its = items.value
|
||||||
detail.value = {}
|
if (its.length) { const ids = [...new Set(its.map(i => i.productId).filter(Boolean))]; const m2 = {}; await Promise.all(ids.map(async pid => { try { const r = await getProductDetail(pid); const d = r?.data || r; if (d) m2[pid] = d } catch(e){} })); productMap.value = m2 }
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
} catch(e) { uni.showToast({ title: '加载详情失败', icon: 'none' }) } finally { loading.value = false }
|
||||||
} finally {
|
}
|
||||||
loading.value = false
|
function handleBack() { uni.navigateBack() }
|
||||||
}
|
onLoad((o) => { id.value = o?.id || null; loadDetail() })
|
||||||
}
|
|
||||||
|
|
||||||
onLoad((options) => {
|
|
||||||
loadDetail(options?.id)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.page-container {
|
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||||
min-height: 100vh;
|
.detail-scroll { height: calc(100vh - 172rpx); }
|
||||||
background: #f4f5f7;
|
.content-section { padding: 20rpx 24rpx 28rpx; }
|
||||||
padding-bottom: 32rpx;
|
.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; }
|
||||||
.content {
|
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
|
||||||
padding: 20rpx 24rpx 32rpx;
|
.section-title-wrap { display: flex; align-items: center; gap: 12rpx; min-width: 0; }
|
||||||
}
|
.list-header { justify-content: space-between; gap: 16rpx; }
|
||||||
|
.count-badge { flex-shrink: 0; padding: 6rpx 16rpx; border-radius: 999rpx; background: #eff6ff; color: #1f7cff; font-size: 22rpx; font-weight: 600; }
|
||||||
.summary-card,
|
.status-tag { margin-left: auto; flex-shrink: 0; padding: 8rpx 18rpx; border-radius: 999rpx; font-size: 22rpx; line-height: 1; background: #e2e8f0; color: #64748b; }
|
||||||
.item-card {
|
.status-tag.text-success { color: #15803d; background: #dcfce7; }
|
||||||
background: #ffffff;
|
.status-tag.text-danger { color: #dc2626; background: #fee2e2; }
|
||||||
border-radius: 14rpx;
|
.status-tag.text-warning { color: #d97706; background: #fef3c7; }
|
||||||
padding: 24rpx;
|
.status-tag.text-primary { color: #1d4ed8; background: #dbeafe; }
|
||||||
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
|
.loading-card, .empty-card { min-height: 180rpx; border: 2rpx dashed #d7dde8; border-radius: 18rpx; background: #f8fafc; display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 27rpx; }
|
||||||
}
|
.readonly-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 14rpx; overflow: hidden; }
|
||||||
|
.readonly-item { min-width: 0; display: flex; flex-direction: column; gap: 8rpx; padding: 18rpx 20rpx; border-right: 1rpx solid #f1f5f9; border-bottom: 1rpx solid #f1f5f9; }
|
||||||
.summary-header {
|
.readonly-item:nth-child(2n) { border-right: 0; }
|
||||||
display: flex;
|
.readonly-label { font-size: 23rpx; color: #8a94a6; }
|
||||||
align-items: center;
|
.readonly-value { font-size: 27rpx; color: #334155; line-height: 1.35; word-break: break-all; }
|
||||||
justify-content: space-between;
|
.readonly-value.highlight { color: #1f4b79; font-weight: 700; }
|
||||||
padding-bottom: 18rpx;
|
.form-field { display: flex; flex-direction: column; gap: 12rpx; margin-top: 24rpx; }
|
||||||
margin-bottom: 14rpx;
|
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
|
||||||
border-bottom: 1rpx solid #eef2f7;
|
.readonly-box { min-height: 70rpx; padding: 18rpx 24rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; font-size: 28rpx; color: #374151; line-height: 1.45; }
|
||||||
}
|
.summary-strip { display: grid; grid-template-columns: repeat(2, 1fr); background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
|
||||||
|
.summary-item { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6rpx; padding: 16rpx 8rpx; border-right: 1rpx solid #eef2f7; }
|
||||||
.summary-no {
|
.summary-item:last-child { border-right: 0; }
|
||||||
font-size: 32rpx;
|
.summary-value { max-width: 100%; font-size: 30rpx; font-weight: 700; color: #1f4b79; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
font-weight: 700;
|
.summary-label { font-size: 22rpx; color: #8a94a6; }
|
||||||
color: #111827;
|
.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: 12rpx; margin-bottom: 14rpx; }
|
||||||
.status-tag {
|
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
padding: 6rpx 16rpx;
|
.item-code { flex-shrink: 0; max-width: 240rpx; font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
border-radius: 999rpx;
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
||||||
font-size: 24rpx;
|
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||||
color: #2563eb;
|
.info-label { font-size: 22rpx; color: #9ca3af; }
|
||||||
background: #eff6ff;
|
.info-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
}
|
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15,23,42,0.06); z-index: 99; }
|
||||||
|
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
|
||||||
.status-tag.auditing { color: #d97706; background: #fffbeb; }
|
.back-btn { background: #1f4b79; color: #ffffff; }
|
||||||
.status-tag.success { color: #16a34a; background: #f0fdf4; }
|
|
||||||
.status-tag.danger { color: #dc2626; background: #fef2f2; }
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 18rpx;
|
|
||||||
padding: 10rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
width: 150rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #374151;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
color: #1f4b79;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10rpx;
|
|
||||||
padding: 28rpx 0 18rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-bar-line {
|
|
||||||
width: 6rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
background: #1f4b79;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hint {
|
|
||||||
padding: 96rpx 24rpx;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<NavBar :title="'备件出库详情'" />
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="detail-scroll">
|
||||||
|
<view class="content-section">
|
||||||
|
<!-- 出库信息卡片 -->
|
||||||
|
<view class="section-card">
|
||||||
|
<view class="section-header">
|
||||||
|
<view class="section-icon">
|
||||||
|
<uni-icons type="compose" size="24" color="#1f7cff"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<text class="section-title">出库信息</text>
|
||||||
|
<text :class="['status-tag', statusClass(detail.status)]">{{ statusText(detail.status) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="loading" class="loading-card">加载中...</view>
|
||||||
|
<template v-else>
|
||||||
|
<view class="readonly-grid">
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">单据号</text>
|
||||||
|
<text class="readonly-value">{{ textValue(detail.no) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">出库类型</text>
|
||||||
|
<text class="readonly-value">{{ textValue(detail.outType) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">出库时间</text>
|
||||||
|
<text class="readonly-value">{{ formatDateTime(detail.outTime || detail.createTime) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">经办人</text>
|
||||||
|
<text class="readonly-value">{{ textValue(detail.stockUserName) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">出库数量</text>
|
||||||
|
<text class="readonly-value highlight">{{ textValue(detail.totalCount) }}{{ detail.totalCount != null ? ' 个' : '' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="readonly-item">
|
||||||
|
<text class="readonly-label">审核人</text>
|
||||||
|
<text class="readonly-value">{{ textValue(detail.auditUserName) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-field">
|
||||||
|
<text class="form-label">备注</text>
|
||||||
|
<view class="readonly-box">{{ textValue(detail.remark) }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="attachmentList.length" class="form-field">
|
||||||
|
<text class="form-label">附件</text>
|
||||||
|
<view class="attachment-list">
|
||||||
|
<view v-for="(file, idx) in attachmentList" :key="idx" class="attachment-item">
|
||||||
|
<uni-icons type="paperclip" size="16" color="#64748b"></uni-icons>
|
||||||
|
<text class="attachment-name">{{ textValue(getAttachmentName(file, idx)) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</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 class="count-badge">{{ itemList.length }} 项</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="itemList.length" class="summary-strip">
|
||||||
|
<view class="summary-item">
|
||||||
|
<text class="summary-value">{{ itemList.length }}</text>
|
||||||
|
<text class="summary-label">备件</text>
|
||||||
|
</view>
|
||||||
|
<view class="summary-item">
|
||||||
|
<text class="summary-value">{{ totalPieceCount }} 个</text>
|
||||||
|
<text class="summary-label">总数量</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="itemList.length" class="item-list">
|
||||||
|
<view v-for="(item, idx) in itemList" :key="item.id || idx" class="item-card">
|
||||||
|
<view class="item-header">
|
||||||
|
<text class="item-name">{{ textValue(item.productName) }}</text>
|
||||||
|
<text class="item-code">{{ textValue(item.productBarCode) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-grid">
|
||||||
|
<view class="info-cell">
|
||||||
|
<text class="info-label">仓库</text>
|
||||||
|
<text class="info-value">{{ textValue(item.warehouseName || warehouseMap[item.warehouseId]) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-cell">
|
||||||
|
<text class="info-label">库区</text>
|
||||||
|
<text class="info-value">{{ textValue(item.areaName) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-cell">
|
||||||
|
<text class="info-label">数量</text>
|
||||||
|
<text class="info-value">{{ textValue(item.count) }}{{ getSparepartUnitData(item).unitName && ' ' + getSparepartUnitData(item).unitName }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-cell">
|
||||||
|
<text class="info-label">换算关系</text>
|
||||||
|
<text class="info-value">{{ textValue(getSparepartUnitData(item).packagingRule) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="empty-card">{{ loading ? '加载中...' : '暂无出库清单' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="action-bar">
|
||||||
|
<view class="action-btn back-btn" @click="handleBack">返回</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import NavBar from '@/components/common/NavBar.vue'
|
||||||
|
import { getSparepartOutboundDetail } from '@/api/mes/sparepartOutbound'
|
||||||
|
import { getSparepartDetail } from '@/api/mes/sparepart'
|
||||||
|
import { getWarehouseSimpleList } from '@/api/mes/moldget'
|
||||||
|
|
||||||
|
const detailId = ref(null)
|
||||||
|
const detail = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
const sparepartMap = ref({})
|
||||||
|
const warehouseMap = ref({})
|
||||||
|
|
||||||
|
const itemList = computed(() => {
|
||||||
|
const list = detail.value?.items || detail.value?.itemList || detail.value?.stockOutItems || []
|
||||||
|
return Array.isArray(list) ? list : []
|
||||||
|
})
|
||||||
|
const attachmentList = computed(() => {
|
||||||
|
const raw = detail.value?.fileUrl || detail.value?.attachments || detail.value?.attachmentList
|
||||||
|
if (!raw) return []
|
||||||
|
if (Array.isArray(raw)) return raw
|
||||||
|
return typeof raw === 'object' ? [raw] : String(raw).split(',').map(s => s.trim()).filter(Boolean)
|
||||||
|
})
|
||||||
|
const totalPieceCount = computed(() => itemList.value.reduce((sum, item) => sum + (Number(item.count) || 0), 0))
|
||||||
|
|
||||||
|
function getSparepartUnitData(item) {
|
||||||
|
const sp = sparepartMap.value[item.productId]
|
||||||
|
const puName = sp?.purchaseUnitName || item.purchaseUnitName || ''
|
||||||
|
const puQty = sp?.purchaseUnitConvertQuantity || item.purchaseUnitConvertQuantity
|
||||||
|
const unitName = sp?.unitName || sp?.productUnitName || item.unitName || item.productUnitName || '个'
|
||||||
|
let packagingRule = sp?.packagingRule || item.packagingRule || ''
|
||||||
|
if (!packagingRule && puName && puQty) {
|
||||||
|
packagingRule = `1${puName}=${puQty}${unitName}`
|
||||||
|
}
|
||||||
|
return { packagingRule: packagingRule || '-', unitName: unitName }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 '-'
|
||||||
|
const raw = typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : value
|
||||||
|
const date = new Date(raw)
|
||||||
|
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())}`
|
||||||
|
}
|
||||||
|
function statusText(s) {
|
||||||
|
const map = { 0: '待出库', 10: '待审核', 20: '已出库', 1: '已驳回' }
|
||||||
|
return map[Number(s)] || textValue(s)
|
||||||
|
}
|
||||||
|
function statusClass(s) {
|
||||||
|
const map = { 0: 'text-primary', 10: 'text-warning', 20: 'text-success', 1: 'text-danger' }
|
||||||
|
return map[Number(s)] || ''
|
||||||
|
}
|
||||||
|
function getAttachmentName(file, index) {
|
||||||
|
if (file?.name) return file.name
|
||||||
|
if (file?.fileName) return file.fileName
|
||||||
|
const text = String(file?.url || file?.fileUrl || file || '')
|
||||||
|
const name = text.split('/').pop()
|
||||||
|
return name || `附件${index + 1}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDetail() {
|
||||||
|
if (!detailId.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getSparepartOutboundDetail(detailId.value)
|
||||||
|
detail.value = res?.data !== undefined ? res.data : res
|
||||||
|
// 加载仓库列表做名称映射
|
||||||
|
try {
|
||||||
|
const wRes = await getWarehouseSimpleList()
|
||||||
|
const wData = Array.isArray(wRes) ? wRes : (Array.isArray(wRes?.data) ? wRes.data : [])
|
||||||
|
const wMap = {}
|
||||||
|
wData.forEach(w => { wMap[w.id] = w.name || String(w.id) })
|
||||||
|
warehouseMap.value = wMap
|
||||||
|
} catch (e) {}
|
||||||
|
// 加载备件详情获取换算关系
|
||||||
|
const items = itemList.value
|
||||||
|
if (items.length) {
|
||||||
|
const ids = [...new Set(items.map(i => i.productId).filter(Boolean))]
|
||||||
|
const map = {}
|
||||||
|
await Promise.all(ids.map(async (id) => {
|
||||||
|
try { const r = await getSparepartDetail(id); const d = r?.data || r; if (d) map[id] = d } catch (e) {}
|
||||||
|
}))
|
||||||
|
sparepartMap.value = map
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载详情失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handleBack() { uni.navigateBack() }
|
||||||
|
|
||||||
|
onLoad((options) => { detailId.value = options?.id || null; loadDetail() })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.page-container { min-height: 100vh; background: #f5f7fb; }
|
||||||
|
.detail-scroll { height: calc(100vh - 172rpx); }
|
||||||
|
.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; }
|
||||||
|
.count-badge { flex-shrink: 0; padding: 6rpx 16rpx; border-radius: 999rpx; background: #eff6ff; color: #1f7cff; font-size: 22rpx; font-weight: 600; }
|
||||||
|
.status-tag { margin-left: auto; flex-shrink: 0; padding: 8rpx 18rpx; border-radius: 999rpx; font-size: 22rpx; line-height: 1; background: #e2e8f0; color: #64748b; }
|
||||||
|
.status-tag.text-success { color: #15803d; background: #dcfce7; }
|
||||||
|
.status-tag.text-danger { color: #dc2626; background: #fee2e2; }
|
||||||
|
.status-tag.text-warning { color: #d97706; background: #fef3c7; }
|
||||||
|
.status-tag.text-primary { color: #1d4ed8; background: #dbeafe; }
|
||||||
|
.loading-card, .empty-card { min-height: 180rpx; border: 2rpx dashed #d7dde8; border-radius: 18rpx; background: #f8fafc; display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 27rpx; }
|
||||||
|
.readonly-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 14rpx; overflow: hidden; }
|
||||||
|
.readonly-item { min-width: 0; display: flex; flex-direction: column; gap: 8rpx; padding: 18rpx 20rpx; border-right: 1rpx solid #f1f5f9; border-bottom: 1rpx solid #f1f5f9; }
|
||||||
|
.readonly-item:nth-child(2n) { border-right: 0; }
|
||||||
|
.readonly-label { font-size: 23rpx; color: #8a94a6; }
|
||||||
|
.readonly-value { font-size: 27rpx; color: #334155; line-height: 1.35; word-break: break-all; }
|
||||||
|
.readonly-value.highlight { color: #1f4b79; font-weight: 700; }
|
||||||
|
.form-field { display: flex; flex-direction: column; gap: 12rpx; margin-top: 24rpx; }
|
||||||
|
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
|
||||||
|
.readonly-box { min-height: 70rpx; padding: 18rpx 24rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; font-size: 28rpx; color: #374151; line-height: 1.45; }
|
||||||
|
.attachment-list { display: flex; flex-direction: column; gap: 10rpx; }
|
||||||
|
.attachment-item { display: flex; align-items: center; gap: 10rpx; padding: 16rpx 18rpx; background: #f8fafc; border-radius: 12rpx; }
|
||||||
|
.attachment-name { flex: 1; min-width: 0; font-size: 24rpx; color: #334155; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.summary-strip { display: grid; grid-template-columns: repeat(2, 1fr); background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; overflow: hidden; margin-bottom: 18rpx; }
|
||||||
|
.summary-item { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 6rpx; padding: 16rpx 8rpx; border-right: 1rpx solid #eef2f7; }
|
||||||
|
.summary-item:last-child { border-right: 0; }
|
||||||
|
.summary-value { max-width: 100%; font-size: 30rpx; font-weight: 700; color: #1f4b79; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.summary-label { font-size: 22rpx; color: #8a94a6; }
|
||||||
|
.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: 12rpx; margin-bottom: 14rpx; }
|
||||||
|
.item-name { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.item-code { flex-shrink: 0; max-width: 240rpx; font-size: 24rpx; color: #8a94a6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12rpx; }
|
||||||
|
.info-cell { min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
|
||||||
|
.info-label { font-size: 22rpx; color: #9ca3af; }
|
||||||
|
.info-value { font-size: 26rpx; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06); z-index: 99; }
|
||||||
|
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
|
||||||
|
.back-btn { background: #1f4b79; color: #ffffff; }
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue