86 lines
6.5 KiB
Vue
86 lines
6.5 KiB
Vue
<script setup lang="ts">
|
|
import type { InboundFileSummary, InboundInternalNote, InboundPalletLocation, InboundStatusHistory } from './types'
|
|
import { inboundHistoryActionLabel, inboundProcessStatusLabels, inboundStatusLabel } from '../status'
|
|
|
|
const props = withDefaults(defineProps<{
|
|
palletLocations: InboundPalletLocation[]
|
|
files: InboundFileSummary[]
|
|
internalNotes: InboundInternalNote[]
|
|
statusHistory: InboundStatusHistory[]
|
|
publicRemark: string | null
|
|
sections?: Array<'allocations' | 'files' | 'notes' | 'history' | 'remark'>
|
|
}>(), {
|
|
sections: () => ['allocations', 'files', 'notes', 'history', 'remark'],
|
|
})
|
|
|
|
function shows(section: 'allocations' | 'files' | 'notes' | 'history' | 'remark') {
|
|
return props.sections.includes(section)
|
|
}
|
|
|
|
function value(value: string | number | null | undefined) { return value === null || value === undefined || value === '' ? '—' : String(value) }
|
|
function number(value: number | null) { return value === null ? '—' : value.toFixed(3) }
|
|
function date(value: string | null) {
|
|
if (!value) return '—'
|
|
const parsed = new Date(value)
|
|
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleString('zh-CN', { hour12: false })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="history-panel">
|
|
<section v-if="shows('allocations')" class="history-section">
|
|
<div class="section-heading"><h3>托盘与库位</h3><span>{{ palletLocations.length }} 条分配</span></div>
|
|
<div v-if="palletLocations.length" class="allocation-list">
|
|
<div v-for="item in palletLocations" :key="`${item.allocationNo}-${item.palletId}-${item.locationId}`" class="allocation-row">
|
|
<div><strong>{{ value(item.palletCode) }}</strong><span>分配 {{ value(item.allocationNo) }}</span></div>
|
|
<div><span>{{ number(item.allocatedVolumeCbm) }} CBM</span><span>{{ value(item.allocatedPackageCount) }} 箱</span></div>
|
|
<div class="allocation-location">{{ value(item.locationCode) }}</div>
|
|
</div>
|
|
</div>
|
|
<p v-else class="muted-empty">暂无托盘库位分配记录</p>
|
|
</section>
|
|
<section v-if="shows('files')" class="history-section">
|
|
<div class="section-heading"><h3>文件记录</h3><span>{{ files.length }} 个文件</span></div>
|
|
<div v-if="files.length" class="simple-list"><div v-for="file in files" :key="`${file.fileId}-${file.fileVersion}`"><strong>{{ value(file.fileNameSnapshot) }}</strong><span>{{ value(file.fileType) }} · {{ date(file.uploadedAt) }}</span></div></div>
|
|
<p v-else class="muted-empty">暂无文件记录</p>
|
|
</section>
|
|
<section v-if="shows('notes')" class="history-section">
|
|
<div class="section-heading"><h3>内部备注</h3><span>{{ internalNotes.length }} 条</span></div>
|
|
<div v-if="internalNotes.length" class="simple-list"><div v-for="note in internalNotes" :key="String(note.id)"><strong>{{ value(note.createdByName || note.createdBy) }}</strong><span>{{ value(note.content) }} · {{ date(note.createdAt) }}</span></div></div>
|
|
<p v-else class="muted-empty">暂无内部备注</p>
|
|
</section>
|
|
<section v-if="shows('history')" class="history-section">
|
|
<div class="section-heading"><h3>状态历史</h3><span>{{ statusHistory.length }} 条</span></div>
|
|
<ol v-if="statusHistory.length" class="timeline"><li v-for="(history, index) in statusHistory" :key="`${history.occurredAt}-${index}`"><span class="timeline-dot" /><div><strong>{{ inboundHistoryActionLabel(history.action) }}</strong><span>{{ inboundStatusLabel(history.fromStatus, inboundProcessStatusLabels) }} → {{ inboundStatusLabel(history.toStatus, inboundProcessStatusLabels) }} · {{ value(history.operatorName || history.operatorId) }}</span><small>{{ date(history.occurredAt) }}<template v-if="history.reason"> · {{ history.reason }}</template></small></div></li></ol>
|
|
<p v-else class="muted-empty">暂无状态历史</p>
|
|
</section>
|
|
<section v-if="shows('remark') && publicRemark" class="history-section remark-section"><div class="section-heading"><h3>公开备注</h3></div><p>{{ publicRemark }}</p></section>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.history-panel { display: grid; gap: 24px; min-width: 0; }
|
|
.history-section { min-width: 0; padding-top: 16px; border-top: 1px solid var(--cc-border); }
|
|
.history-section:first-child { padding-top: 0; border-top: 0; }
|
|
.section-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
|
.section-heading h3 { margin: 0; color: #172033; font-size: 13px; }
|
|
.section-heading span { color: var(--cc-muted); font-size: 12px; }
|
|
.allocation-list, .simple-list { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 6px; background: #fff; }
|
|
.allocation-row { display: grid; grid-template-columns: 1fr auto minmax(100px, 0.8fr); gap: 12px; align-items: center; padding: 10px 12px; border-bottom: 1px solid #edf0f4; font-size: 12px; }
|
|
.allocation-row:last-child, .simple-list > div:last-child { border-bottom: 0; }
|
|
.allocation-row > div, .simple-list > div { min-width: 0; }
|
|
.allocation-row span, .simple-list span { display: block; margin-top: 3px; color: var(--cc-muted); }
|
|
.allocation-location { overflow-wrap: anywhere; color: var(--cc-primary); text-align: right; }
|
|
.simple-list > div { padding: 10px 12px; border-bottom: 1px solid #edf0f4; font-size: 12px; }
|
|
.simple-list strong, .simple-list span { overflow-wrap: anywhere; }
|
|
.muted-empty { margin: 0; padding: 18px 12px; border: 1px dashed var(--cc-border); border-radius: 6px; background: #f8fafc; color: var(--cc-muted); font-size: 12px; text-align: center; }
|
|
.timeline { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
|
|
.timeline li { display: grid; grid-template-columns: 16px 1fr; gap: 10px; min-width: 0; padding-bottom: 16px; }
|
|
.timeline-dot { width: 8px; height: 8px; margin-top: 5px; border: 2px solid var(--cc-primary); border-radius: 50%; background: #fff; box-shadow: 0 0 0 3px color-mix(in srgb, var(--cc-primary) 12%, transparent); }
|
|
.timeline li > div { min-width: 0; padding-bottom: 12px; border-bottom: 1px solid #edf0f4; }
|
|
.timeline strong, .timeline span, .timeline small { display: block; overflow-wrap: anywhere; }
|
|
.timeline span, .timeline small { margin-top: 3px; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
|
|
.remark-section p { margin: 0; padding: 12px; border-radius: 6px; background: #f8fafc; color: var(--cc-text); font-size: 13px; line-height: 1.6; white-space: pre-wrap; }
|
|
@media (max-width: 480px) { .allocation-row { grid-template-columns: 1fr auto; } .allocation-location { grid-column: 1 / -1; text-align: left; } }
|
|
</style>
|