Files
klp-oa/klp-ui/src/views/wms/coil/info/components/ChangeLogSection.vue
2026-07-13 10:39:34 +08:00

487 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="section change-log-section">
<div class="section-header">
<span class="section-icon">&#128221;</span>
<span class="section-title">修改日志</span>
</div>
<div class="section-body">
<el-table
v-if="changeLogs.length"
:data="changeLogs"
border
stripe
size="small"
v-loading="loading"
:header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: 600 }"
style="width: 100%"
>
<el-table-column prop="operationDesc" label="操作类型" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
<span class="operation-badge">{{ scope.row.operationDesc }}</span>
</template>
</el-table-column>
<el-table-column label="修改信息" min-width="420">
<template slot-scope="scope">
<div class="change-log-line">
<div class="change-log-line-boxes">
<div
v-for="(item, idx) in scope.row.previewItems"
:key="scope.row.logId + '-' + idx"
class="mini-change-item"
:title="previewText(item)"
>
<span class="mini-change-label">{{ item.label }}</span>
<span class="mini-change-box old">{{ item.oldText }}</span>
<span class="mini-change-arrow"></span>
<span class="mini-change-box new">{{ item.newText }}</span>
</div>
</div>
<el-button
v-if="scope.row.moreCount > 0"
type="text"
size="mini"
class="more-btn"
@click="openMoreDialog(scope.row)"
>
更多 {{ scope.row.moreCount }}
</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="createBy" label="修改人" width="120" show-overflow-tooltip />
<el-table-column label="修改时间" width="170" show-overflow-tooltip>
<template slot-scope="scope">
{{ formatDateTime(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
<el-empty
v-else-if="!loading"
description="暂无修改日志"
:image-size="60"
/>
<el-dialog
title="全部修改信息"
:visible.sync="dialogVisible"
width="960px"
append-to-body
>
<div v-if="activeLog" class="change-log-dialog">
<div class="change-log-dialog-meta">
<span>操作类型{{ activeLog.operationDesc }}</span>
<span>修改人{{ activeLog.createBy }}</span>
<span>修改时间{{ formatDateTime(activeLog.createTime) }}</span>
</div>
<div class="change-log-grid all-items">
<div
v-for="(item, idx) in activeLog.summaryItems"
:key="'dialog-' + activeLog.logId + '-' + idx"
class="change-log-card"
>
<div class="change-log-card-header">
<span class="change-field-name">{{ item.label }}</span>
</div>
<div class="change-log-card-body">
<div class="change-value-box old">
<span class="change-value-label">原值</span>
<span class="change-value-text">{{ item.oldText }}</span>
</div>
<span class="change-arrow"></span>
<div class="change-value-box new">
<span class="change-value-label">新值</span>
<span class="change-value-text">{{ item.newText }}</span>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</div>
</template>
<script>
import { getCoilChangeLogByCoilId } from '@/api/wms/coilChangeLog'
const CHANGE_FIELD_LABELS = {
currentCoilNo: '当前钢卷号',
enterCoilNo: '入场钢卷号',
supplierCoilNo: '厂家原料号',
itemName: '物料名称',
specification: '规格',
material: '材质',
netWeight: '净重',
grossWeight: '毛重',
warehouseName: '库位',
actualWarehouseName: '实际库区',
qualityStatus: '质量状态',
manufacturer: '厂家',
zincLayer: '镀层质量',
status: '状态',
remark: '备注',
saleName: '销售员',
actualLength: '实际长度',
actualWidth: '实际宽度',
actualThickness: '实际厚度',
length: '长度',
theoreticalLength: '理论长度',
theoreticalThickness: '理论厚度',
trimmingRequirement: '净边料要求',
packagingRequirement: '包装要求',
contractNo: '合同号',
chromePlateCoilNo: '镀铬卷号',
productionStartTime: '生产开始时间',
productionEndTime: '生产结束时间',
productionDuration: '生产时长'
}
const HIDDEN_CHANGE_FIELDS = new Set([
'warehouseId',
'actualWarehouseId',
'itemId',
'parentCoilId',
'parentCoilIds',
'parentCoilNos',
'hasMergeSplit',
'exclusiveStatus'
])
const PREVIEW_ITEM_LIMIT = 3
export default {
name: 'ChangeLogSection',
props: {
coilId: { type: [String, Number], default: '' }
},
data() {
return {
loading: false,
changeLogs: [],
dialogVisible: false,
activeLog: null
}
},
watch: {
coilId: {
immediate: true,
handler(val) {
if (val) {
this.loadChangeLogs()
} else {
this.changeLogs = []
}
}
}
},
methods: {
formatDateTime(val) {
if (!val) return '-'
const date = new Date(val)
if (isNaN(date.getTime())) return String(val)
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
const hh = String(date.getHours()).padStart(2, '0')
const mm = String(date.getMinutes()).padStart(2, '0')
const ss = String(date.getSeconds()).padStart(2, '0')
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
},
getFieldLabel(field) {
return CHANGE_FIELD_LABELS[field] || field
},
shouldHideField(field, diffMap) {
if (HIDDEN_CHANGE_FIELDS.has(field)) return true
if (field.endsWith('Id') && diffMap[`${field.slice(0, -2)}Name`]) return true
return false
},
normalizeDisplayValue(field, val) {
if (val === null || val === undefined || val === '') return '-'
if (typeof val === 'number' && /Time$/.test(field)) {
return this.formatDateTime(val)
}
if (typeof val === 'string' && /^\d{13}$/.test(val) && /Time$/.test(field)) {
return this.formatDateTime(Number(val))
}
if (Array.isArray(val)) return val.length ? val.join(', ') : '-'
if (typeof val === 'object') {
try {
return JSON.stringify(val)
} catch (e) {
return String(val)
}
}
return String(val)
},
buildSummaryItem(field, oldVal, newVal) {
const oldText = this.normalizeDisplayValue(field, oldVal)
const newText = this.normalizeDisplayValue(field, newVal)
if (oldText === newText) return null
return {
field,
label: this.getFieldLabel(field),
oldText,
newText
}
},
buildSummaryItems(log) {
const items = []
if (log.changedFields) {
try {
const diffMap = JSON.parse(log.changedFields)
Object.keys(diffMap || {}).forEach(key => {
if (this.shouldHideField(key, diffMap)) return
const item = diffMap[key] || {}
const summaryItem = this.buildSummaryItem(key, item.old, item.new)
if (summaryItem) items.push(summaryItem)
})
} catch (e) {
items.push({
field: 'raw',
label: '修改内容',
oldText: '-',
newText: log.changedFields
})
}
}
if (!items.length && log.oldCoilNo !== log.newCoilNo) {
items.push(this.buildSummaryItem('currentCoilNo', log.oldCoilNo, log.newCoilNo))
}
if (!items.length && log.operationDesc) {
items.push({
field: 'operationDesc',
label: '修改内容',
oldText: '-',
newText: log.operationDesc
})
}
return items.length ? items : [{
field: 'empty',
label: '修改内容',
oldText: '-',
newText: '-'
}]
},
previewText(item) {
if (!item) return '-'
return `${item.label}${item.oldText}${item.newText}`
},
openMoreDialog(row) {
this.activeLog = row
this.dialogVisible = true
},
async loadChangeLogs() {
if (!this.coilId) return
this.loading = true
try {
const res = await getCoilChangeLogByCoilId(this.coilId)
const rows = (res && res.data) || []
this.changeLogs = rows.map(item => ({
...item,
createBy: item.createBy || '-',
operationDesc: item.operationDesc || '-',
summaryItems: this.buildSummaryItems(item)
})).map(item => {
const previewItems = (item.summaryItems || []).slice(0, PREVIEW_ITEM_LIMIT)
return {
...item,
previewItems,
moreCount: Math.max(item.summaryItems.length - previewItems.length, 0)
}
})
} catch (e) {
console.error('获取钢卷修改日志失败:', e)
this.changeLogs = []
this.activeLog = null
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.operation-badge {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 8px;
border-radius: 11px;
background: rgba(59, 130, 246, 0.1);
border: 1px solid rgba(59, 130, 246, 0.18);
color: #2563eb;
font-size: 11px;
font-weight: 600;
}
.change-log-line {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.change-log-line-boxes {
flex: 1;
min-width: 0;
overflow: hidden;
display: flex;
align-items: center;
gap: 6px;
}
.mini-change-item {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fff;
min-width: 0;
}
.mini-change-label {
color: #475569;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.mini-change-box {
display: inline-block;
max-width: 140px;
padding: 0 6px;
border-radius: 4px;
border: 1px solid transparent;
font-size: 11px;
line-height: 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #1e293b;
}
.mini-change-box.old {
background: #fff7ed;
border-color: #fed7aa;
}
.mini-change-box.new {
background: #ecfdf5;
border-color: #a7f3d0;
}
.mini-change-arrow {
color: #94a3b8;
font-size: 12px;
font-weight: 700;
}
.change-log-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 6px;
padding: 0;
}
.change-log-grid.all-items {
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.more-btn {
padding: 0;
font-size: 12px;
white-space: nowrap;
}
.change-log-card {
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fff;
overflow: hidden;
}
.change-log-card-header {
display: flex;
align-items: center;
min-height: 26px;
padding: 0 8px;
background: #f8fafc;
border-bottom: 1px solid #edf2f7;
}
.change-field-name {
font-size: 11px;
font-weight: 600;
color: #475569;
}
.change-log-card-body {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
min-height: 44px;
}
.change-value-box {
display: flex;
flex: 1;
flex-direction: column;
min-width: 0;
padding: 6px 8px;
border-radius: 4px;
border: 1px solid #ebeef5;
background: #fff;
}
.change-value-box.old {
background: #fff7ed;
border-color: #fed7aa;
}
.change-value-box.new {
background: #ecfdf5;
border-color: #a7f3d0;
}
.change-value-label {
margin-bottom: 2px;
font-size: 10px;
color: #94a3b8;
line-height: 1.2;
}
.change-value-text {
color: #1e293b;
font-size: 11px;
line-height: 1.35;
word-break: break-all;
}
.change-arrow {
flex: 0 0 auto;
color: #94a3b8;
font-size: 14px;
font-weight: 700;
}
.change-log-dialog-meta {
display: flex;
flex-wrap: wrap;
gap: 12px 20px;
margin-bottom: 10px;
color: #606266;
font-size: 12px;
}
@media (max-width: 768px) {
.change-log-grid {
grid-template-columns: 1fr;
}
}
</style>