Files
klp-oa/klp-ui/src/views/wms/coil/info.vue
砂糖 b1a997fde8 feat(调拨): 添加调拨记录查看功能并优化显示
- 在卷材信息页面添加调拨记录弹窗,展示批量调拨和技术部改判记录
- 为卷材选择器添加数据类型和状态过滤条件
- 调整仓库表格的flex布局比例
- 在卷材操作面板添加查看调拨记录按钮
2026-04-30 10:28:09 +08:00

2224 lines
78 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="coil-info-page" v-loading="loading">
<div class="content-container">
<div class="section basic-info-section">
<div class="section-header">
<span class="section-icon">📦</span>
<span class="section-title">基本信息</span>
</div>
<div class="section-body">
<CoilInfoRender :coilInfo="coilInfo" :column="5" />
</div>
</div>
<div class="section trace-section">
<div class="section-header">
<span class="section-icon">🔄</span>
<span class="section-title">生命周期追踪</span>
</div>
<div class="section-body">
<div v-if="standardSteps && standardSteps.length > 0" class="trace-content">
<div v-for="(step, index) in standardSteps" :key="index" class="step-item">
<!-- 第一个步骤是入库创建 -->
<div v-if="index === 0 && step.action === '创建'" class="step-content step-content-inbound">
<!-- 入库钢卷卡片 -->
<div class="coil-card-wrapper inbound-coil-wrapper">
<div class="coil-card-header inbound-header">
<span class="card-label">📦 入库</span>
</div>
<div v-for="(coil, idx) in getInboundCoil(step, index)" :key="idx" class="coil-card-futuristic"
v-if="coil && coil.currentCoilNo !== '-'">
<div class="coil-futuristic-header">
<span class="coil-futuristic-no">{{ coil.currentCoilNo }}</span>
<span class="coil-futuristic-weight">{{ coil.netWeight ? coil.netWeight + ' t' : '-' }}</span>
</div>
<div class="coil-futuristic-body">
<!-- 钢卷图标 -->
<div class="coil-icon-container">
<svg class="coil-svg" viewBox="0 0 200 200">
<defs>
<linearGradient id="coilGradInbound1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:0.5" />
<stop offset="50%" style="stop-color:#2563eb;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#3b82f6;stop-opacity:0.5" />
</linearGradient>
<linearGradient id="coilGradInbound2" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#93c5fd;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#bfdbfe;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- 外圈 -->
<circle cx="100" cy="100" r="70" fill="none" stroke="url(#coilGradInbound1)"
stroke-width="2.5">
<animate attributeName="r" values="70;72;70" dur="2s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="60" fill="none" stroke="url(#coilGradInbound2)"
stroke-width="1.8" opacity="0.6">
<animate attributeName="stroke-dashoffset" values="0;377" dur="4s"
repeatCount="indefinite" />
</circle>
<!-- 内层钢卷 -->
<circle cx="100" cy="100" r="45" fill="url(#coilGradInbound1)" opacity="0.15">
<animate attributeName="opacity" values="0.15;0.3;0.15" dur="3s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="35" fill="url(#coilGradInbound2)" opacity="0.2" />
<circle cx="100" cy="100" r="25" fill="url(#coilGradInbound1)" opacity="0.25" />
<circle cx="100" cy="100" r="15" fill="white" opacity="0.4" />
<!-- 装饰线条 -->
<line x1="100" y1="100" x2="100" y2="30" stroke="url(#coilGradInbound1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="170" y2="100" stroke="url(#coilGradInbound1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="100" y2="170" stroke="url(#coilGradInbound1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="30" y2="100" stroke="url(#coilGradInbound1)" stroke-width="1"
opacity="0.4" />
<!-- 中心点 -->
<circle cx="100" cy="100" r="4" fill="#3b82f6">
<animate attributeName="r" values="4;6;4" dur="1.5s" repeatCount="indefinite" />
</circle>
</svg>
</div>
<!-- 属性标签 -->
<div class="coil-attr-tags">
<!-- 顶部 - 材质 -->
<div class="attr-tag attr-top">
<div class="attr-line attr-line-top"></div>
<div class="attr-content">
<span class="attr-label">材质</span>
<span class="attr-value">{{ coil.material || '-' }}</span>
</div>
</div>
<!-- 右侧 - 规格 -->
<div class="attr-tag attr-right">
<div class="attr-line attr-line-right"></div>
<div class="attr-content">
<span class="attr-label">规格</span>
<span class="attr-value">{{ coil.specification || '-' }}</span>
</div>
</div>
<!-- 左侧 - 质量状态 -->
<div class="attr-tag attr-left">
<div class="attr-line attr-line-left"></div>
<div class="attr-content">
<span class="attr-label">质量状态</span>
<span class="attr-value" :class="getFuturisticStatusClass(coil.qualityStatus)">{{
coil.qualityStatus }}</span>
</div>
</div>
<!-- 左上角 - 入场卷号 -->
<div class="attr-tag attr-top-left" v-if="coil.enterCoilNo && coil.enterCoilNo !== '-'">
<div class="attr-content attr-content-small">
<span class="attr-label">入场卷号</span>
<span class="attr-value">{{ coil.enterCoilNo }}</span>
</div>
</div>
<!-- 右上角 - 厂家原料号 -->
<div class="attr-tag attr-top-right" v-if="coil.supplierCoilNo && coil.supplierCoilNo !== '-'">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家原料号</span>
<span class="attr-value">{{ coil.supplierCoilNo }}</span>
</div>
</div>
<!-- 左下角 - 班组 -->
<div class="attr-tag attr-bottom-left">
<div class="attr-content attr-content-small">
<span class="attr-label">班组</span>
<span class="attr-value">{{ coil.team || '-' }}</span>
</div>
</div>
<!-- 右下角 - 厂家 -->
<div class="attr-tag attr-bottom-right">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家</span>
<span class="attr-value">{{ coil.manufacturer || '-' }}</span>
</div>
</div>
<!-- 备注信息在底部 -->
<div class="attr-tag attr-remark">
<div class="attr-content attr-content-remark">
<span class="attr-label">备注</span>
<span class="attr-value">{{ coil.remark || '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 底部信息 -->
<div class="coil-futuristic-footer">
<div class="footer-item">
<span class="footer-label">镀层</span>
<span class="footer-value">{{ coil.zincLayer || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">表面处理</span>
<span class="footer-value">{{ coil.surfaceTreatmentDesc || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">逻辑库位</span>
<span class="footer-value">{{ coil.warehouseName || '-' }}</span>
</div>
</div>
</div>
<div
v-if="getInboundCoil(step, index).length === 0 || getInboundCoil(step, index).every(c => !c || c.currentCoilNo === '-')"
class="empty-coil">
<span>无钢卷信息</span>
</div>
</div>
<!-- 中间入库信息 -->
<div class="action-center">
<div class="action-arrow action-arrow-inbound">
<i class="el-icon-download"></i>
</div>
<div class="action-info">
<el-tag size="mini" type="primary" class="action-tag">入库</el-tag>
<span class="action-operator">{{ step.operation }}</span>
<span class="action-time">{{ step.time }}</span>
</div>
</div>
</div>
<!-- 其他步骤是变更 -->
<div v-if="index !== 0 && step.action !== '创建'" class="step-content">
<!-- 左侧旧钢卷卡片 -->
<div class="coil-card-wrapper old-coil-wrapper">
<div class="coil-card-header">
<span class="card-label">变更前</span>
</div>
<div v-for="(coil, idx) in step.oldCoilInfoList" :key="idx" class="coil-card-futuristic"
v-if="coil && coil.currentCoilNo !== '-'">
<div class="coil-futuristic-header">
<span class="coil-futuristic-no">{{ coil.currentCoilNo }}</span>
<span class="coil-futuristic-weight">{{ coil.netWeight ? coil.netWeight + ' t' : '-' }}</span>
</div>
<div class="coil-futuristic-body">
<!-- 钢卷图标 -->
<div class="coil-icon-container">
<svg class="coil-svg" viewBox="0 0 200 200">
<defs>
<linearGradient id="coilGrad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#6366f1;stop-opacity:0.5" />
<stop offset="50%" style="stop-color:#8b5cf6;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#6366f1;stop-opacity:0.5" />
</linearGradient>
<linearGradient id="coilGrad2" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#a78bfa;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#c084fc;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- 外圈 -->
<circle cx="100" cy="100" r="70" fill="none" stroke="url(#coilGrad1)" stroke-width="2.5">
<animate attributeName="r" values="70;72;70" dur="2s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="60" fill="none" stroke="url(#coilGrad2)" stroke-width="1.8"
opacity="0.6">
<animate attributeName="stroke-dashoffset" values="0;377" dur="4s"
repeatCount="indefinite" />
</circle>
<!-- 内层钢卷 -->
<circle cx="100" cy="100" r="45" fill="url(#coilGrad1)" opacity="0.15">
<animate attributeName="opacity" values="0.15;0.3;0.15" dur="3s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="35" fill="url(#coilGrad2)" opacity="0.2" />
<circle cx="100" cy="100" r="25" fill="url(#coilGrad1)" opacity="0.25" />
<circle cx="100" cy="100" r="15" fill="white" opacity="0.4" />
<!-- 装饰线条 -->
<line x1="100" y1="100" x2="100" y2="30" stroke="url(#coilGrad1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="170" y2="100" stroke="url(#coilGrad1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="100" y2="170" stroke="url(#coilGrad1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="30" y2="100" stroke="url(#coilGrad1)" stroke-width="1"
opacity="0.4" />
<!-- 中心点 -->
<circle cx="100" cy="100" r="4" fill="#6366f1">
<animate attributeName="r" values="4;6;4" dur="1.5s" repeatCount="indefinite" />
</circle>
</svg>
</div>
<!-- 属性标签 -->
<div class="coil-attr-tags">
<!-- 顶部 - 材质 -->
<div class="attr-tag attr-top">
<div class="attr-line attr-line-top"></div>
<div class="attr-content">
<span class="attr-label">材质</span>
<span class="attr-value">{{ coil.material || '-' }}</span>
</div>
</div>
<!-- 右侧 - 规格 -->
<div class="attr-tag attr-right">
<div class="attr-line attr-line-right"></div>
<div class="attr-content">
<span class="attr-label">规格</span>
<span class="attr-value">{{ coil.specification || '-' }}</span>
</div>
</div>
<!-- 左侧 - 质量状态 -->
<div class="attr-tag attr-left">
<div class="attr-line attr-line-left"></div>
<div class="attr-content">
<span class="attr-label">状态</span>
<span class="attr-value" :class="getFuturisticStatusClass(coil.qualityStatus)">{{
coil.qualityStatus || '-' }}</span>
</div>
</div>
<!-- 左上角 - 入场卷号 -->
<div class="attr-tag attr-top-left">
<div class="attr-content attr-content-small">
<span class="attr-label">入场卷号</span>
<span class="attr-value">{{ coil.enterCoilNo || '-' }}</span>
</div>
</div>
<!-- 右上角 - 厂家原料号 -->
<div class="attr-tag attr-top-right">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家原料号</span>
<span class="attr-value">{{ coil.supplierCoilNo || '-' }}</span>
</div>
</div>
<!-- 左下角 - 班组 -->
<div class="attr-tag attr-bottom-left">
<div class="attr-content attr-content-small">
<span class="attr-label">班组</span>
<span class="attr-value">{{ coil.team || '-' }}</span>
</div>
</div>
<!-- 右下角 - 厂家 -->
<div class="attr-tag attr-bottom-right">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家</span>
<span class="attr-value">{{ coil.manufacturer || '-' }}</span>
</div>
</div>
<!-- 备注信息在底部 -->
<div class="attr-tag attr-remark">
<div class="attr-content attr-content-remark">
<span class="attr-label">备注</span>
<span class="attr-value">{{ coil.remark || '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 底部信息 -->
<div class="coil-futuristic-footer">
<div class="footer-item">
<span class="footer-label">镀层</span>
<span class="footer-value">{{ coil.zincLayer || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">表面处理</span>
<span class="footer-value">{{ coil.surfaceTreatmentDesc || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">逻辑库位</span>
<span class="footer-value">{{ coil.warehouseName || '-' }}</span>
</div>
</div>
</div>
<div
v-if="step.oldCoilInfoList.length === 0 || step.oldCoilInfoList.every(c => !c || c.currentCoilNo === '-')"
class="empty-coil">
<span>无钢卷信息</span>
</div>
</div>
<!-- 中间操作信息 -->
<div class="action-center">
<div class="action-arrow">
<i class="el-icon-right"></i>
</div>
<div class="action-info">
<el-tag size="mini" type="info" class="action-tag">{{ step.action }}</el-tag>
<span class="action-operator">{{ step.operation }}</span>
<span class="action-time">{{ step.time }}</span>
</div>
</div>
<!-- 右侧新钢卷卡片 -->
<div class="coil-card-wrapper new-coil-wrapper">
<div class="coil-card-header">
<span class="card-label">变更后</span>
</div>
<div v-for="(coil, idx) in step.newCoilInfoList" :key="idx" class="coil-card-futuristic"
v-if="coil && coil.currentCoilNo !== '-'">
<div class="coil-futuristic-header">
<span class="coil-futuristic-no">{{ coil.currentCoilNo }}</span>
<span class="coil-futuristic-weight">{{ coil.netWeight ? coil.netWeight + ' t' : '-' }}</span>
</div>
<div class="coil-futuristic-body">
<!-- 钢卷图标 -->
<div class="coil-icon-container">
<svg class="coil-svg" viewBox="0 0 200 200">
<defs>
<linearGradient id="coilGrad3" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#10b981;stop-opacity:0.5" />
<stop offset="50%" style="stop-color:#34d399;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#10b981;stop-opacity:0.5" />
</linearGradient>
<linearGradient id="coilGrad4" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#a7f3d0;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#fbcfe8;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- 外圈 -->
<circle cx="100" cy="100" r="70" fill="none" stroke="url(#coilGrad3)" stroke-width="2.5">
<animate attributeName="r" values="70;72;70" dur="2s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="60" fill="none" stroke="url(#coilGrad4)" stroke-width="1.8"
opacity="0.6" stroke-dasharray="50 50">
<animate attributeName="stroke-dashoffset" values="0;100" dur="3s"
repeatCount="indefinite" />
</circle>
<!-- 内层钢卷 -->
<circle cx="100" cy="100" r="45" fill="url(#coilGrad3)" opacity="0.15">
<animate attributeName="opacity" values="0.15;0.3;0.15" dur="3s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="35" fill="url(#coilGrad4)" opacity="0.2" />
<circle cx="100" cy="100" r="25" fill="url(#coilGrad3)" opacity="0.25" />
<circle cx="100" cy="100" r="15" fill="white" opacity="0.4" />
<!-- 装饰线条 -->
<line x1="100" y1="100" x2="100" y2="30" stroke="url(#coilGrad3)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="170" y2="100" stroke="url(#coilGrad3)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="100" y2="170" stroke="url(#coilGrad3)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="30" y2="100" stroke="url(#coilGrad3)" stroke-width="1"
opacity="0.4" />
<!-- 中心点 -->
<circle cx="100" cy="100" r="4" fill="#10b981">
<animate attributeName="r" values="4;6;4" dur="1.5s" repeatCount="indefinite" />
</circle>
</svg>
</div>
<!-- 属性标签 -->
<div class="coil-attr-tags">
<!-- 顶部 - 材质 -->
<div class="attr-tag attr-top">
<div class="attr-line attr-line-top"></div>
<div class="attr-content">
<span class="attr-label">材质</span>
<span class="attr-value">{{ coil.material || '-' }}</span>
</div>
</div>
<!-- 右侧 - 规格 -->
<div class="attr-tag attr-right">
<div class="attr-line attr-line-right"></div>
<div class="attr-content">
<span class="attr-label">规格</span>
<span class="attr-value">{{ coil.specification || '-' }}</span>
</div>
</div>
<!-- 左侧 - 质量状态 -->
<div class="attr-tag attr-left">
<div class="attr-line attr-line-left"></div>
<div class="attr-content">
<span class="attr-label">状态</span>
<span class="attr-value" :class="getFuturisticStatusClass(coil.qualityStatus || '-')">{{
coil.qualityStatus || '-' }}</span>
</div>
</div>
<!-- 左上角 - 入场卷号 -->
<div class="attr-tag attr-top-left">
<div class="attr-content attr-content-small">
<span class="attr-label">入场卷号</span>
<span class="attr-value">{{ coil.enterCoilNo || '-' }}</span>
</div>
</div>
<!-- 右上角 - 厂家原料号 -->
<div class="attr-tag attr-top-right">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家原料号</span>
<span class="attr-value">{{ coil.supplierCoilNo || '-' }}</span>
</div>
</div>
<!-- 左下角 - 班组 -->
<div class="attr-tag attr-bottom-left">
<div class="attr-content attr-content-small">
<span class="attr-label">班组</span>
<span class="attr-value">{{ coil.team || '-' }}</span>
</div>
</div>
<!-- 右下角 - 厂家 -->
<div class="attr-tag attr-bottom-right">
<div class="attr-content attr-content-small">
<span class="attr-label">厂家</span>
<span class="attr-value">{{ coil.manufacturer || '-' }}</span>
</div>
</div>
<!-- 备注信息在底部 -->
<div class="attr-tag attr-remark">
<div class="attr-content attr-content-remark">
<span class="attr-label">备注</span>
<span class="attr-value">{{ coil.remark || '-' }}</span>
</div>
</div>
</div>
</div>
<!-- 底部信息 -->
<div class="coil-futuristic-footer">
<div class="footer-item">
<span class="footer-label">镀层</span>
<span class="footer-value">{{ coil.zincLayer || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">表面处理</span>
<span class="footer-value">{{ coil.surfaceTreatmentDesc || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">逻辑库位</span>
<span class="footer-value">{{ coil.warehouseName || '-' }}</span>
</div>
</div>
</div>
<div
v-if="step.newCoilInfoList.length === 0 || step.newCoilInfoList.every(c => !c || c.currentCoilNo === '-')"
class="empty-coil">
<span>无钢卷信息</span>
</div>
</div>
</div>
</div>
<!-- 发货记录 -->
<div class="step-item">
<div class="step-content">
<div class="coil-card-wrapper inbound-coil-wrapper">
<div class="coil-card-header inbound-header">
<span class="card-label">🚚 发货</span>
</div>
<div class="coil-card-futuristic">
<div class="coil-futuristic-body">
<!-- 钢卷图标 -->
<div class="coil-icon-container">
<svg class="coil-svg" viewBox="0 0 200 200">
<defs>
<linearGradient id="coilGradShip1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#f59e0b;stop-opacity:0.5" />
<stop offset="50%" style="stop-color:#ef4444;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#f59e0b;stop-opacity:0.5" />
</linearGradient>
<linearGradient id="coilGradShip2" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#fcd34d;stop-opacity:0.4" />
<stop offset="100%" style="stop-color:#fca5a5;stop-opacity:0.3" />
</linearGradient>
</defs>
<!-- 外圈 -->
<circle cx="100" cy="100" r="70" fill="none" stroke="url(#coilGradShip1)" stroke-width="2.5">
<animate attributeName="r" values="70;72;70" dur="2s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="60" fill="none" stroke="url(#coilGradShip2)" stroke-width="1.8"
opacity="0.6">
<animate attributeName="stroke-dashoffset" values="0;377" dur="4s"
repeatCount="indefinite" />
</circle>
<!-- 内层钢卷 -->
<circle cx="100" cy="100" r="45" fill="url(#coilGradShip1)" opacity="0.15">
<animate attributeName="opacity" values="0.15;0.3;0.15" dur="3s" repeatCount="indefinite" />
</circle>
<circle cx="100" cy="100" r="35" fill="url(#coilGradShip2)" opacity="0.2" />
<circle cx="100" cy="100" r="25" fill="url(#coilGradShip1)" opacity="0.25" />
<circle cx="100" cy="100" r="15" fill="white" opacity="0.4" />
<!-- 装饰线条 -->
<line x1="100" y1="100" x2="100" y2="30" stroke="url(#coilGradShip1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="170" y2="100" stroke="url(#coilGradShip1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="100" y2="170" stroke="url(#coilGradShip1)" stroke-width="1"
opacity="0.4" />
<line x1="100" y1="100" x2="30" y2="100" stroke="url(#coilGradShip1)" stroke-width="1"
opacity="0.4" />
<!-- 中心点 -->
<circle cx="100" cy="100" r="4" fill="#f59e0b">
<animate attributeName="r" values="4;6;4" dur="1.5s" repeatCount="indefinite" />
</circle>
</svg>
</div>
<!-- 属性标签 - 显示发货状态 -->
<div class="coil-attr-tags">
<!-- 顶部 - 发货状态 -->
<div class="attr-tag attr-top">
<div class="attr-line attr-line-top"></div>
<div class="attr-content">
<span class="attr-label">发货状态</span>
<span class="attr-value"
:class="coilInfo.status === 1 ? 'futuristic-status-success' : 'futuristic-status-warning'">
{{ coilInfo.status === 1 ? '已发货' : '未发货' }}
</span>
</div>
</div>
</div>
</div>
<!-- 底部信息 -->
<div class="coil-futuristic-footer" v-if="coilInfo.status === 1">
<div class="footer-item">
<span class="footer-label">发货人</span>
<span class="footer-value">{{ coilInfo.exportByName || '-' }}</span>
</div>
<div class="footer-item">
<span class="footer-label">发货时间</span>
<span class="footer-value">{{ formatTime(coilInfo.exportTime) }}</span>
</div>
</div>
<div class="coil-futuristic-footer" v-else>
<div class="footer-item" style="flex: 1;">
<span class="footer-label">状态</span>
<span class="footer-value">等待发货</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<i class="el-icon-document"></i>
<span>未找到相关钢卷记录</span>
</div>
</div>
</div>
<div class="section sales-section">
<div class="section-header">
<span class="section-icon">💰</span>
<span class="section-title">销售信息</span>
</div>
<div class="section-body" v-if="salesInfo.orderId">
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="合同号">{{ salesInfo.contractCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ salesInfo.customer || '-' }}</el-descriptions-item>
<el-descriptions-item label="销售人员">{{ salesInfo.salesman || '-' }}</el-descriptions-item>
<el-descriptions-item label="签订时间">{{ salesInfo.signTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="交货时间">{{ salesInfo.deliveryTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="订单总额">{{ salesInfo.orderAmount || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 技术附件 -->
<h4>技术附件</h4>
<FileList :oss-ids="salesInfo.techAnnex" />
<!-- 排产函附件 -->
<h4>排产函</h4>
<FileList :oss-ids="salesInfo.productionSchedule" />
</div>
<div v-else class="empty-state">
<i class="el-icon-document"></i>
<span>未找到相关销售记录</span>
</div>
</div>
<div class="section combined-section">
<div class="section-header">
<span class="section-icon">📋</span>
<span class="section-title">移库记录 & 调拨记录</span>
</div>
<div class="section-body tables-row">
<!-- 移库记录 - 占3份 -->
<div class="table-wrapper warehouse-table">
<div class="table-title">移库记录</div>
<div class="table-container">
<el-table :data="warehouseTranferList" size="small" border stripe style="width: 100%">
<el-table-column prop="createTime" label="操作时间"></el-table-column>
<el-table-column prop="operationType" label="操作类型">
<template slot-scope="scope">
<span v-if="scope.row.operationType === 1">收货</span>
<span v-else-if="scope.row.operationType === 2">加工</span>
<span v-else-if="scope.row.operationType === 3">调拨</span>
<span v-else-if="scope.row.operationType === 4">发货</span>
</template>
</el-table-column>
<el-table-column prop="inOutType" label="出入库">
<template slot-scope="scope">
{{ scope.row.inOutType === 1 ? '入库' : '出库' }}
</template>
</el-table-column>
<el-table-column prop="warehouse.actualWarehouseName" label="库位">
<template slot-scope="scope">
{{ scope.row.warehouse.actualWarehouseName || '-' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip></el-table-column>
</el-table>
</div>
</div>
<!-- 调拨记录 - 占2份 -->
<div class="table-wrapper transfer-table">
<div class="table-title">调拨记录</div>
<div class="table-container">
<el-table :data="tranferList" size="small" border stripe style="width: 100%">
<el-table-column prop="type" label="类型" min-width="100"></el-table-column>
<el-table-column label="变更前">
<template slot-scope="scope">
{{ scope.row.before }}
</template>
</el-table-column>
<el-table-column label="变更后">
<template slot-scope="scope">
{{ scope.row.after }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="时间"></el-table-column>
<el-table-column prop="remark" label="备注" show-overflow-tooltip></el-table-column>
</el-table>
</div>
</div>
</div>
</div>
<div class="section abnormal-section">
<div class="section-header">
<span class="section-icon"></span>
<span class="section-title">异常信息</span>
</div>
<div class="section-body">
<abnormal-table ref="abnormalTable" :list="abmornalList" :editable="false" :show-coil="false" />
</div>
</div>
<!-- 生产工艺数据图表 -->
<div v-if="isColdHardCoil" class="section production-section">
<div class="section-header">
<span class="section-icon">📈</span>
<span class="section-title">生产工艺数据</span>
<el-tag v-if="perfLoading" size="mini" type="info" style="margin-left:8px">加载中</el-tag>
<span v-if="hasPerfData" class="perf-count">({{ perfSegCount }} )</span>
</div>
<div class="section-body">
<div v-if="hasPerfData" class="charts-wrap">
<div ref="chartSpeed" class="chart-box" />
<div ref="chartMillSpeed" class="chart-box" />
<div ref="chartTension" class="chart-box" />
<div ref="chartPorSpeed" class="chart-box" />
<div ref="chartPorTens" class="chart-box" />
<div ref="chartTrim" class="chart-box" />
<div ref="chartTemp" class="chart-box" />
<div ref="chartMesh" class="chart-box" />
<div ref="chartElong" class="chart-box" />
</div>
<el-empty v-else-if="!perfLoading" description="暂无生产工艺数据" :image-size="56" style="margin-top: 24px" />
</div>
</div>
</div>
</div>
</template>
<script>
import { getMaterialCoil, listMaterialCoil, getMaterialCoilTrace } from '@/api/wms/coil';
import { getCoilWarehouseOperationLogByCoilId } from '@/api/wms/coilWarehouseOperationLog';
import { listCoilAbnormal } from '@/api/wms/coilAbnormal'
// 查询批量调拨的记录
import { listTransferOrderItem } from '@/api/wms/transferOrderItem'
// 查询技术部改判调拨的记录
import { listCoilQualityRejudge } from '@/api/wms/coilQualityRejudge'
// 引入 ECharts 和 L2 时序数据 API
import * as echarts from 'echarts'
import { getTimingSegByEncoilId } from '@/api/l2/timing'
import AbnormalTable from '@/views/wms/coil/components/AbnormalTable.vue';
import FileList from "@/components/FileList";
export default {
name: 'CoilInfo',
components: {
AbnormalTable,
FileList
},
data() {
return {
loading: false,
coilInfoLoading: false,
traceLoading: false,
traceResult: null,
coilDetails: {},
loadingCoilDetails: false,
coilInfo: {},
coilList: [],
wipList: [],
annealList: [],
standardSteps: [],
coilId: '',
warehouseTranferList: [],
abmornalList: [],
transferOrderItemList: [], // 批量调拨记录
coilQualityRejudgeList: [], // 技术部改判记录
tranferList: [], // 合并后的调拨记录
// 生产数据相关
perfLoading: false,
perfSeries: null,
perfSegCount: 0
}
},
computed: {
salesInfo() {
return this.coilInfo.orderList?.[0] || {};
},
// 判断是否为冷硬卷
isColdHardCoil() {
return this.coilInfo.itemName && this.coilInfo.itemName.includes('冷硬卷');
},
hasPerfData() {
return this.perfSeries && this.perfSegCount > 0;
}
},
async created() {
this.chartInstances = [];
this.resizeHandler = null;
this.coilId = this.$route.params.coilId;
this.loading = true;
await this.getCoilInfo();
await this.getTraceInfo();
await this.getAbnormalList();
await this.getWarehouseTransferList();
await this.getTransferOrderItemList();
await this.getCoilQualityRejudgeList();
this.mergeTransferList();
// 如果是冷硬卷,加载生产数据
if (this.isColdHardCoil) {
await this.loadProductionData();
}
this.loading = false;
},
beforeDestroy() {
this.disposeCharts();
},
methods: {
async getCoilInfo() {
const res = await getMaterialCoil(this.coilId);
this.coilInfo = res.data;
},
async getTraceInfo() {
const res = await getMaterialCoilTrace({
coilId: this.coilId,
currentCoilNo: this.coilInfo.currentCoilNo
});
this.traceResult = res.data;
this.standardSteps = this.traceResult.steps?.map(step => this.formatStep(step)) || [];
this.fetchCoilDetails();
},
async getCoilList() {
const res = await listMaterialCoil({ enterCoilNo: this.coilInfo.enterCoilNo, pageNum: 1, pageSize: 100 });
this.coilList = res.rows;
},
async getAbnormalList() {
const res = await listCoilAbnormal({
coilId: this.coilId
});
this.abmornalList = res.rows || [];
},
async getWarehouseTransferList() {
const res = await getCoilWarehouseOperationLogByCoilId({
coilId: this.coilId
});
this.warehouseTranferList = res.data || [];
},
// 获取批量调拨记录
async getTransferOrderItemList() {
const res = await listTransferOrderItem({
coilId: this.coilId,
pageNum: 1,
pageSize: 100
});
this.transferOrderItemList = res.rows || [];
},
// 获取技术部改判记录
async getCoilQualityRejudgeList() {
const res = await listCoilQualityRejudge({
coilId: this.coilId,
pageNum: 1,
pageSize: 100
});
this.coilQualityRejudgeList = res.rows || [];
},
// 合并调拨记录
mergeTransferList() {
const list = [];
// 添加批量调拨记录
this.transferOrderItemList.forEach(item => {
list.push({
type: '批量调拨',
before: '逻辑库:' + (item.warehouseNameBefore || '-'),
after: '逻辑库:' + (item.warehouseNameAfter || '-'),
createTime: item.createTime || '-',
remark: item.remark || '-',
...item
});
});
// 添加技术部改判记录
this.coilQualityRejudgeList.forEach(item => {
list.push({
...item,
type: '技术部改判',
before: '质量状态:' + (item.beforeQuality || '-'),
after: '质量状态:' + (item.afterQuality || '-'),
createTime: item.createTime || '-',
remark: item.rejudgeReason,
});
});
// 按时间排序
list.sort((a, b) => {
const timeA = new Date(a.createTime || 0).getTime();
const timeB = new Date(b.createTime || 0).getTime();
return timeB - timeA;
});
this.tranferList = list;
},
formatTime(timeStamp) {
if (!timeStamp) return '-';
const date = new Date(timeStamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
},
parseChangedFields(changedFieldsStr) {
if (!changedFieldsStr) return [];
const items = changedFieldsStr.split(';').filter(item => item.trim());
return items.map(item => {
const [fieldPart, valuePart] = item.split(':').map(part => part.trim());
if (!valuePart) return { field: fieldPart, oldVal: '-', newVal: '-' };
const [oldVal, newVal] = valuePart.split('→').map(val => {
const trimmedVal = val.trim();
return trimmedVal === 'null' ? '-' : trimmedVal;
});
return {
field: fieldPart,
oldVal: oldVal || '-',
newVal: newVal || '-'
};
});
},
formatStep(originalStep) {
const standardStep = {
action: '',
time: '-',
operation: '-',
oldCoilIds: [],
newCoilIds: [],
changedFields: '',
oldCoilInfoList: [],
newCoilInfoList: [],
deletedCoilInfo: null,
restoredCoilInfo: null,
original: originalStep
};
if (originalStep.action === '新增') {
standardStep.action = '创建';
} else if (originalStep.action === '更新') {
if (originalStep.operation === '信息更新') {
standardStep.action = '更新';
} else if (originalStep.operation === '分卷') {
standardStep.action = '分卷';
} else if (originalStep.operation === '合卷') {
standardStep.action = '合卷';
} else {
standardStep.action = '更新';
}
} else if (originalStep.action === '回滚') {
standardStep.action = '回滚';
} else {
standardStep.action = originalStep.action || '-';
}
if (originalStep.update_time || originalStep.create_time) {
standardStep.time = this.formatTime(originalStep.update_time || originalStep.create_time);
} else if (originalStep.rollback_time) {
standardStep.time = this.formatTime(originalStep.rollback_time);
}
standardStep.operation = originalStep.operator_nickname || originalStep.operator || originalStep.create_by || '-';
switch (standardStep.action) {
case '创建':
standardStep.oldCoilIds = [];
break;
case '更新':
standardStep.oldCoilIds = originalStep.old_coil_id ? [originalStep.old_coil_id.trim()] : [];
break;
case '退火':
standardStep.oldCoilIds = originalStep.old_coil_id ? [originalStep.old_coil_id.trim()] : [];
break;
case '分卷':
standardStep.oldCoilIds = originalStep.old_coil_id ? [originalStep.old_coil_id.trim()] : [];
break;
case '合卷':
standardStep.oldCoilIds = originalStep.parent_coil_ids
? originalStep.parent_coil_ids.split(',').map(id => id.trim()).filter(Boolean)
: [];
break;
case '回滚':
standardStep.oldCoilIds = originalStep.deleted_coil_id ? [originalStep.deleted_coil_id.trim()] : [];
break;
default:
standardStep.oldCoilIds = [];
}
switch (standardStep.action) {
case '创建':
standardStep.newCoilIds = originalStep.current_coil_id ? [originalStep.current_coil_id.trim()] : [];
break;
case '更新':
standardStep.newCoilIds = originalStep.new_coil_id ? [originalStep.new_coil_id.trim()] : [];
break;
case '退火':
standardStep.newCoilIds = originalStep.new_coil_id ? [originalStep.new_coil_id.trim()] : [];
break;
case '分卷':
standardStep.newCoilIds = originalStep.child_coil_ids
? originalStep.child_coil_ids.split(',').map(id => id.trim()).filter(Boolean)
: [];
break;
case '合卷':
standardStep.newCoilIds = originalStep.new_coil_id ? [originalStep.new_coil_id.trim()] : [];
break;
case '回滚':
standardStep.newCoilIds = originalStep.restored_coil_id ? [originalStep.restored_coil_id.trim()] : [];
break;
default:
standardStep.newCoilIds = [];
}
if (standardStep.action === '更新') {
standardStep.changedFields = originalStep.changed_fields || '';
}
if (standardStep.action === '创建') {
listMaterialCoil({ currentCoilNo: originalStep.current_coil_no, enterCoilNo: originalStep.current_coil_no }).then(res => {
standardStep.newCoilInfoList = res.rows || [];
});
}
standardStep.newCoilInfoList = this.mapCoilInfoList(standardStep.newCoilIds);
if (standardStep.action === '回滚') {
standardStep.deletedCoilInfo = standardStep.oldCoilInfoList[0] || null;
standardStep.restoredCoilInfo = standardStep.newCoilInfoList[0] || null;
}
return standardStep;
},
mapCoilInfoList(coilIds) {
if (!coilIds || !coilIds.length) return [];
return coilIds.map(coilId => {
const coil = this.coilDetails[coilId] || {};
// 确保 coil 不为 null 或 undefined
const safeCoil = coil || {};
return {
...safeCoil,
enterCoilNo: safeCoil.enterCoilNo || '-',
currentCoilNo: safeCoil.currentCoilNo || '-',
materialType: safeCoil.materialType || '-',
itemName: safeCoil.itemName || '-',
specification: safeCoil.specification || '-',
material: safeCoil.material || '-',
netWeight: safeCoil.netWeight || 0,
warehouseName: safeCoil.warehouseName || '-',
actualWarehouseName: safeCoil.actualWarehouseName || '-',
manufacturer: safeCoil.manufacturer || '-',
zincLayer: safeCoil.zincLayer || '-',
qualityStatus: safeCoil.qualityStatus || '-',
createTime: safeCoil.createTime || '-',
status: safeCoil.status || 0,
exportByName: safeCoil.exportByName || '-',
exportTime: safeCoil.exportTime || '-',
};
}).filter(coil => coil); // 过滤掉 null/undefined 的项
},
collectCoilIds() {
if (!this.standardSteps.length) return [];
const coilIds = new Set();
this.standardSteps.forEach(step => {
step.oldCoilIds.forEach(id => id && coilIds.add(id));
step.newCoilIds.forEach(id => id && coilIds.add(id));
});
return [...coilIds];
},
async fetchCoilDetails() {
const coilIds = this.collectCoilIds().filter(id => id);
if (coilIds.length === 0) {
this.coilDetails = {};
return;
}
if (this.loadingCoilDetails) return;
this.loadingCoilDetails = true;
try {
const res = await listMaterialCoil({ coilIds: coilIds.join(',') });
if (res && res.code === 200 && res.rows) {
const coilList = res.rows;
const coilIdMap = {};
coilList.forEach(coil => {
if (coil.coilId) coilIdMap[coil.coilId] = coil;
});
this.coilDetails = coilIdMap;
this.standardSteps.forEach(step => {
step.oldCoilInfoList = this.mapCoilInfoList(step.oldCoilIds);
step.newCoilInfoList = this.mapCoilInfoList(step.newCoilIds);
if (step.action === '回滚') {
step.deletedCoilInfo = step.oldCoilInfoList[0] || null;
step.restoredCoilInfo = step.newCoilInfoList[0] || null;
}
});
} else {
this.$message.warning('获取钢卷详情失败');
}
} catch (error) {
console.error('获取钢卷详情接口异常:', error);
this.$message.error('获取钢卷详情异常,请刷新重试');
} finally {
this.loadingCoilDetails = false;
}
},
getMiniStatusClass(status) {
if (!status) return '';
const statusLower = status.toLowerCase();
if (statusLower.includes('合格')) return 'mini-status-success';
if (statusLower.includes('不合格')) return 'mini-status-danger';
if (statusLower.includes('待检')) return 'mini-status-warning';
return 'mini-status-default';
},
getFuturisticStatusClass(status) {
if (!status) return '';
const statusLower = status.toLowerCase();
if (statusLower.includes('合格')) return 'futuristic-status-success';
if (statusLower.includes('不合格')) return 'futuristic-status-danger';
if (statusLower.includes('待检')) return 'futuristic-status-warning';
return 'futuristic-status-default';
},
getInboundCoil(step, index) {
// 如果只有一个步骤用当前步骤的newCoilInfoList
if (this.standardSteps.length === 1) {
return step.newCoilInfoList;
}
// 如果有多个步骤用第二个步骤的oldCoilInfoList
if (index === 0 && this.standardSteps[1]) {
return this.standardSteps[1].oldCoilInfoList;
}
return step.newCoilInfoList;
},
// ECharts 辅助函数
makeLine(name, data) {
return { name, type: 'line', smooth: true, symbol: 'none', data };
},
baseOption(title, xData, series, yName) {
return {
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
tooltip: { trigger: 'axis' },
legend: { top: 4, right: 8, textStyle: { fontSize: 11 } },
grid: { top: 36, bottom: 28, left: 8, right: 8, containLabel: true },
xAxis: {
type: 'category',
data: xData,
name: 'pos(m)',
nameTextStyle: { fontSize: 10 },
axisLabel: { fontSize: 10 }
},
yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 } },
series
};
},
// 加载生产数据
async loadProductionData() {
const encoilId = this.coilInfo.currentCoilNo;
if (!encoilId) return;
this.perfLoading = true;
try {
const res = await getTimingSegByEncoilId(encoilId);
const series = res?.data?.series || null;
const rows = res?.data?.rows || [];
this.perfSegCount = rows.length;
this.perfSeries = series;
if (series && rows.length) {
await this.$nextTick();
this.renderCharts(series);
}
} catch (error) {
console.error('获取生产数据异常:', error);
this.perfSeries = null;
this.perfSegCount = 0;
} finally {
this.perfLoading = false;
}
},
// 销毁图表
disposeCharts() {
if (this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
}
if (this.chartInstances && this.chartInstances.length) {
this.chartInstances.forEach(c => { if (c) c.dispose(); });
this.chartInstances = [];
}
},
// 渲染图表
renderCharts(series) {
this.disposeCharts();
if (!this.$refs.chartSpeed || !this.$refs.chartMillSpeed || !this.$refs.chartTension) {
return;
}
const pick = key => (series[key] || []).map(v => v == null ? null : Number(v).toFixed(2));
const xData = (series.startpos || []).map(v => v == null ? '' : Number(v).toFixed(1));
const c1 = echarts.init(this.$refs.chartSpeed);
c1.setOption(this.baseOption(
'速度趋势 (m/min)', xData,
[
this.makeLine('轧制速度 plspeed', pick('plspeed')),
this.makeLine('剪切速度 trimspeed', pick('trimspeed'))
],
'm/min'
));
const c2 = echarts.init(this.$refs.chartMillSpeed);
c2.setOption(this.baseOption(
'轧机速度 (m/min)', xData,
[
this.makeLine('入口速度 millentryspeed', pick('millentryspeed')),
this.makeLine('出口速度 millexitspeed', pick('millexitspeed'))
],
'm/min'
));
const c3 = echarts.init(this.$refs.chartTension);
c3.setOption(this.baseOption(
'张力趋势 (N)', xData,
[
this.makeLine('出口张力 pltens', pick('pltens')),
this.makeLine('入口张力 enltens', pick('enltens')),
this.makeLine('cxltens', pick('cxltens'))
],
'N'
));
let c4, c5, c6, c7, c8, c9;
if (this.$refs.chartPorSpeed) {
c4 = echarts.init(this.$refs.chartPorSpeed);
c4.setOption(this.baseOption(
'开卷机速度 (m/min)', xData,
[
this.makeLine('开卷速度 porspeed', pick('porspeed')),
this.makeLine('最大 porspeedmax', pick('porspeedmax')),
this.makeLine('最小 porspeedmin', pick('porspeedmin'))
],
'm/min'
));
}
if (this.$refs.chartPorTens) {
c5 = echarts.init(this.$refs.chartPorTens);
c5.setOption(this.baseOption(
'开卷机张力 (N)', xData,
[
this.makeLine('开卷张力 portens', pick('portens')),
this.makeLine('最大 portensmax', pick('portensmax')),
this.makeLine('最小 portensmin', pick('portensmin'))
],
'N'
));
}
if (this.$refs.chartTrim) {
c6 = echarts.init(this.$refs.chartTrim);
c6.setOption(this.baseOption(
'剪切参数', xData,
[
this.makeLine('剪切张力 trimtens', pick('trimtens')),
this.makeLine('剪切宽度 trimwidth', pick('trimwidth')),
this.makeLine('trtens', pick('trtens'))
],
''
));
}
if (this.$refs.chartTemp) {
c7 = echarts.init(this.$refs.chartTemp);
c7.setOption(this.baseOption(
'温度趋势 (℃)', xData,
[
this.makeLine('1号测温 tk1temp', pick('tk1temp')),
this.makeLine('2号测温 tk2temp', pick('tk2temp')),
this.makeLine('3号测温 tk3temp', pick('tk3temp')),
this.makeLine('4号测温 tk4temp', pick('tk4temp')),
this.makeLine('漂洗温度 rinsetemp', pick('rinsetemp'))
],
'℃'
));
}
if (this.$refs.chartMesh) {
c8 = echarts.init(this.$refs.chartMesh);
c8.setOption(this.baseOption(
'网纹辊参数', xData,
[
this.makeLine('网纹辊1 tlmesh1', pick('tlmesh1')),
this.makeLine('网纹辊2 tlmesh2', pick('tlmesh2')),
this.makeLine('网纹辊3 tlmesh3', pick('tlmesh3'))
],
''
));
}
if (this.$refs.chartElong) {
c9 = echarts.init(this.$refs.chartElong);
c9.setOption(this.baseOption(
'延伸率', xData,
[
this.makeLine('延伸率 tlelong', pick('tlelong')),
this.makeLine('总张力 tltens', pick('tltens')),
this.makeLine('teltens', pick('teltens'))
],
''
));
}
this.chartInstances = [c1, c2, c3, c4, c5, c6, c7, c8, c9].filter(c => c);
this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize(); });
window.addEventListener('resize', this.resizeHandler);
},
},
}
</script>
<style scoped>
.coil-info-page {
background: linear-gradient(180deg, #f0f4f8 0%, #e2e8f0 50%, #f7fafc 100%);
min-height: 100vh;
padding: 8px 12px;
}
.page-header {
margin-bottom: 16px;
}
.header-title {
font-size: 20px;
font-weight: 700;
color: #1e293b;
text-shadow: 0 0 10px rgba(59, 130, 246, 0.1);
}
.header-divider {
height: 2px;
width: 60px;
background: linear-gradient(90deg, #3b82f6 0%, #60a5fa 50%, #3b82f6 100%);
margin-top: 8px;
box-shadow: 0 0 8px rgba(59, 130, 246, 0.3);
}
.content-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.section {
border: 1px solid rgba(59, 130, 246, 0.2);
background: rgba(255, 255, 255, 0.9);
overflow: hidden;
position: relative;
box-shadow:
0 0 0 1px rgba(59, 130, 246, 0.1) inset,
0 4px 16px rgba(59, 130, 246, 0.08);
}
.section-header {
display: flex;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid rgba(59, 130, 246, 0.15);
background: linear-gradient(90deg, rgba(59, 130, 246, 0.05) 0%, rgba(59, 130, 246, 0.02) 100%);
}
.section-icon {
font-size: 16px;
margin-right: 8px;
filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.3));
}
.section-title {
font-size: 15px;
font-weight: 700;
color: #1e293b;
letter-spacing: 1px;
background: linear-gradient(90deg, #3b82f6, #60a5fa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.section-body {
padding: 12px 16px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.8) 0%, rgba(248, 250, 252, 0.6) 100%);
}
.trace-content {
display: flex;
flex-direction: column;
gap: 10px;
}
.step-item {
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
padding: 12px 14px;
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow:
0 2px 8px rgba(59, 130, 246, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
position: relative;
overflow: hidden;
}
.step-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.4), transparent);
}
.step-content {
display: flex;
align-items: stretch;
gap: 10px;
}
.coil-card-wrapper {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.coil-card-header {
padding: 6px 12px;
background: linear-gradient(145deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.04) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
border-bottom: none;
}
.card-label {
font-size: 13px;
font-weight: 700;
color: #1e293b;
letter-spacing: 0.5px;
}
.coil-card {
padding: 10px;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
border: 1px solid rgba(59, 130, 246, 0.15);
box-shadow:
0 2px 6px rgba(59, 130, 246, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.old-coil-wrapper .coil-card-header {
background: linear-gradient(145deg, rgba(245, 158, 11, 0.08) 0%, rgba(245, 158, 11, 0.04) 100%);
border-color: rgba(245, 158, 11, 0.25);
}
.new-coil-wrapper .coil-card-header {
background: linear-gradient(145deg, rgba(16, 185, 129, 0.08) 0%, rgba(16, 185, 129, 0.04) 100%);
border-color: rgba(16, 185, 129, 0.25);
}
.inbound-header {
background: linear-gradient(145deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%) !important;
border-color: rgba(59, 130, 246, 0.3) !important;
}
.step-content-inbound {
display: flex;
align-items: stretch;
gap: 10px;
}
.inbound-coil-wrapper {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.action-arrow-inbound {
color: #3b82f6 !important;
font-size: 28px;
margin-bottom: 10px;
text-shadow: 0 0 12px rgba(59, 130, 246, 0.6);
animation: bounce-in 2s ease-in-out infinite !important;
}
@keyframes bounce-in {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
.empty-coil {
display: flex;
align-items: center;
justify-content: center;
padding: 24px 16px;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
border: 1px solid rgba(59, 130, 246, 0.15);
color: #64748b;
font-size: 13px;
}
.action-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 100px;
padding: 12px 8px;
background: linear-gradient(145deg, rgba(59, 130, 246, 0.06) 0%, rgba(59, 130, 246, 0.03) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow:
0 2px 6px rgba(59, 130, 246, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.action-arrow {
color: #3b82f6;
font-size: 28px;
margin-bottom: 10px;
text-shadow: 0 0 12px rgba(59, 130, 246, 0.6);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.action-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.action-tag {
font-size: 12px;
font-weight: 700;
}
.action-operator {
font-size: 12px;
font-weight: 600;
color: #1e293b;
}
.action-time {
font-size: 11px;
color: #64748b;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px 0;
color: #64748b;
font-size: 14px;
}
.empty-state i {
font-size: 48px;
margin-bottom: 12px;
color: #94a3b8;
text-shadow: 0 0 8px rgba(59, 130, 246, 0.3);
}
::v-deep .el-descriptions--small .el-descriptions__cell {
padding: 8px 12px;
background: linear-gradient(145deg, rgba(255, 255, 255, 0.7) 0%, rgba(248, 250, 252, 0.85) 100%);
border-color: rgba(59, 130, 246, 0.2) !important;
}
::v-deep .el-descriptions__label {
background: linear-gradient(145deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.04) 100%) !important;
color: #1e293b !important;
font-weight: 700;
letter-spacing: 0.5px;
}
::v-deep .el-descriptions__content {
color: #1e293b !important;
}
::v-deep .el-table {
background: transparent !important;
}
::v-deep .el-table th,
::v-deep .el-table td {
padding: 8px 0;
background: transparent !important;
border-color: rgba(59, 130, 246, 0.2) !important;
color: #1e293b !important;
}
::v-deep .el-table th {
background: linear-gradient(180deg, rgba(59, 130, 246, 0.08) 0%, rgba(59, 130, 246, 0.04) 100%) !important;
color: #1e293b !important;
font-weight: 700;
letter-spacing: 0.5px;
}
::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
background: rgba(59, 130, 246, 0.03) !important;
}
::v-deep .el-table--border,
::v-deep .el-table--group {
border-color: rgba(59, 130, 246, 0.25) !important;
}
::v-deep .el-tag {
background: linear-gradient(145deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%) !important;
border: 1px solid rgba(59, 130, 246, 0.3) !important;
color: #1e293b !important;
box-shadow: 0 0 8px rgba(59, 130, 246, 0.2);
font-weight: 600;
}
/* CoilInfo组件样式优化 */
.coil-card ::v-deep .el-descriptions--small .el-descriptions__cell {
padding: 8px 10px;
font-size: 12px;
}
.coil-card ::v-deep .el-descriptions__label {
background: linear-gradient(145deg, rgba(241, 245, 249, 0.9) 0%, rgba(226, 232, 240, 1) 100%) !important;
color: #475569 !important;
font-weight: 600;
font-size: 12px;
}
.coil-card ::v-deep .el-descriptions__content {
color: #1e293b !important;
font-size: 12px;
}
/* 科技感钢卷卡片样式 */
.coil-card-futuristic {
padding: 8px;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f0f4ff 100%);
border: 1px solid rgba(59, 130, 246, 0.25);
box-shadow:
0 2px 12px rgba(59, 130, 246, 0.1),
0 0 20px rgba(59, 130, 246, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
position: relative;
overflow: hidden;
}
/* 发光边框效果 */
.coil-card-futuristic::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(59, 130, 246, 0.08) 0%, transparent 70%);
animation: rotateGlow 10s linear infinite;
pointer-events: none;
}
@keyframes rotateGlow {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.coil-futuristic-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
.coil-futuristic-no {
font-size: 16px;
font-weight: 700;
color: #1e293b;
text-shadow: 0 0 8px rgba(59, 130, 246, 0.3);
letter-spacing: 1px;
font-family: 'Courier New', monospace;
}
.coil-futuristic-weight {
font-size: 14px;
font-weight: 700;
color: #d97706;
padding: 4px 10px;
background: linear-gradient(145deg, rgba(245, 158, 11, 0.12) 0%, rgba(245, 158, 11, 0.06) 100%);
border: 1px solid rgba(245, 158, 11, 0.3);
text-shadow: 0 0 4px rgba(245, 158, 11, 0.3);
box-shadow: 0 0 8px rgba(245, 158, 11, 0.15);
}
.coil-futuristic-body {
position: relative;
height: 180px;
margin-bottom: 8px;
z-index: 1;
}
/* SVG钢卷图标容器 */
.coil-icon-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 80px;
}
.coil-svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.3));
}
/* 属性标签容器 */
.coil-attr-tags {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
/* 通用属性标签样式 */
.attr-tag {
position: absolute;
display: flex;
align-items: center;
pointer-events: auto;
}
.attr-content {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 249, 255, 0.95) 100%);
border: 1px solid rgba(59, 130, 246, 0.3);
padding: 5px 8px;
backdrop-filter: blur(4px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.12);
min-width: 70px;
}
.attr-label {
display: block;
font-size: 10px;
color: #3b82f6;
text-transform: uppercase;
letter-spacing: 0.8px;
margin-bottom: 2px;
font-weight: 700;
}
.attr-value {
display: block;
font-size: 12px;
color: #1e293b;
font-weight: 600;
word-break: break-all;
}
/* 属性连接线 */
.attr-line {
position: absolute;
background: linear-gradient(90deg, transparent, rgba(59, 130, 246, 0.9));
animation: linePulse 2s ease-in-out infinite;
}
@keyframes linePulse {
0%, 100% {
opacity: 0.4;
}
50% {
opacity: 0.8;
}
}
/* 顶部属性标签 */
.attr-top {
top: 0;
left: 50%;
transform: translateX(-50%);
flex-direction: column;
}
.attr-top .attr-line-top {
width: 2px;
height: 16px;
background: linear-gradient(180deg, transparent, rgba(59, 130, 246, 0.8));
margin-bottom: 5px;
}
.attr-top .attr-content {
text-align: center;
}
/* 右侧属性标签 */
.attr-right {
right: 0;
top: 50%;
transform: translateY(-50%);
flex-direction: row-reverse;
}
.attr-right .attr-line-right {
width: 16px;
height: 2px;
background: linear-gradient(90deg, rgba(59, 130, 246, 0.8), transparent);
margin-left: 5px;
}
/* 底部属性标签 */
.attr-bottom {
bottom: 0;
left: 50%;
transform: translateX(-50%);
flex-direction: column-reverse;
}
.attr-bottom .attr-line-bottom {
width: 2px;
height: 16px;
background: linear-gradient(0deg, transparent, rgba(59, 130, 246, 0.8));
margin-top: 5px;
}
.attr-bottom .attr-content {
text-align: center;
}
/* 左侧属性标签 */
.attr-left {
left: 0;
top: 50%;
transform: translateY(-50%);
flex-direction: row;
}
.attr-left .attr-line-left {
width: 16px;
height: 2px;
background: linear-gradient(270deg, rgba(59, 130, 246, 0.8), transparent);
margin-right: 5px;
}
/* 左上角标签 */
.attr-top-left {
position: absolute;
top: 6px;
left: 6px;
}
/* 右上角标签 */
.attr-top-right {
position: absolute;
top: 6px;
right: 6px;
}
/* 左下角标签 */
.attr-bottom-left {
position: absolute;
bottom: 6px;
left: 6px;
}
/* 右下角标签 */
.attr-bottom-right {
position: absolute;
bottom: 6px;
right: 6px;
}
/* 小尺寸标签样式 */
.attr-content-small {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 249, 255, 0.95) 100%);
border: 1px solid rgba(59, 130, 246, 0.3);
padding: 4px 6px;
backdrop-filter: blur(4px);
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.1);
min-width: 60px;
}
.attr-content-small .attr-label {
font-size: 9px;
letter-spacing: 0.6px;
}
.attr-content-small .attr-value {
font-size: 11px;
}
/* 备注标签 */
.attr-remark {
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
}
.attr-content-remark {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 249, 255, 1) 100%);
border: 1px solid rgba(59, 130, 246, 0.35);
padding: 5px 8px;
backdrop-filter: blur(4px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.12);
max-width: 160px;
text-align: center;
}
.attr-content-remark .attr-value {
word-break: break-word;
font-size: 11px;
line-height: 1.3;
}
/* 状态标签颜色 */
.futuristic-status-success {
color: #22c55e !important;
text-shadow: 0 0 6px rgba(34, 197, 94, 0.5) !important;
}
.futuristic-status-danger {
color: #ef4444 !important;
text-shadow: 0 0 6px rgba(239, 68, 68, 0.5) !important;
}
.futuristic-status-warning {
color: #f59e0b !important;
text-shadow: 0 0 6px rgba(245, 158, 11, 0.5) !important;
}
.futuristic-status-default {
color: #64748b !important;
text-shadow: 0 0 6px rgba(100, 116, 139, 0.4) !important;
}
/* 底部信息 */
.coil-futuristic-footer {
display: flex;
justify-content: space-around;
padding-top: 8px;
border-top: 1px solid rgba(59, 130, 246, 0.2);
position: relative;
z-index: 1;
}
.footer-item {
text-align: center;
}
.footer-label {
display: block;
font-size: 10px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.6px;
margin-bottom: 2px;
font-weight: 700;
}
.footer-value {
display: block;
font-size: 12px;
color: #475569;
font-weight: 600;
}
/* 简化版钢卷卡片样式(保留用于其他地方使用)
.coil-card-mini {
padding: 16px;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
border: 1px solid rgba(180, 180, 180, 0.4);
border-radius: 0 0 8px 8px;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.coil-mini-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.coil-mini-no {
font-size: 18px;
font-weight: 700;
color: #1e293b;
letter-spacing: 0.5px;
}
.coil-mini-weight {
font-size: 16px;
font-weight: 600;
color: #f59e0b;
padding: 4px 12px;
background: linear-gradient(145deg, #fef3c7 0%, #fde68a 100%);
border-radius: 6px;
}
.coil-mini-sub {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.coil-mini-item {
font-size: 13px;
color: #64748b;
line-height: 1.4;
}
.coil-mini-status {
padding-top: 12px;
border-top: 1px dashed rgba(180, 180, 180, 0.3);
}
.mini-status-success {
display: inline-block;
padding: 4px 12px;
font-size: 13px;
font-weight: 600;
color: #22c55e;
background: linear-gradient(145deg, #dcfce7 0%, #bbf7d0 100%);
border-radius: 6px;
}
.mini-status-danger {
display: inline-block;
padding: 4px 12px;
font-size: 13px;
font-weight: 600;
color: #ef4444;
background: linear-gradient(145deg, #fee2e2 0%, #fecaca 100%);
border-radius: 6px;
}
.mini-status-warning {
display: inline-block;
padding: 4px 12px;
font-size: 13px;
font-weight: 600;
color: #f59e0b;
background: linear-gradient(145deg, #fef3c7 0%, #fde68a 100%);
border-radius: 6px;
}
.mini-status-default {
display: inline-block;
padding: 4px 12px;
font-size: 13px;
font-weight: 600;
color: #64748b;
background: linear-gradient(145deg, #f1f5f9 0%, #e2e8f0 100%);
border-radius: 6px;
}
/* 表格行布局 */
.tables-row {
display: flex;
gap: 12px;
min-height: 320px;
}
.table-wrapper {
display: flex;
flex-direction: column;
}
.table-wrapper.warehouse-table {
flex: 2;
}
.table-wrapper.transfer-table {
flex: 2;
}
.table-title {
padding: 6px 12px;
margin-bottom: 10px;
background: linear-gradient(145deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
font-size: 13px;
font-weight: 700;
color: #1e293b;
letter-spacing: 0.6px;
}
.table-container {
flex: 1;
overflow: hidden;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow:
0 2px 8px rgba(59, 130, 246, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
/* 确保表格高度一致 */
.table-container ::v-deep .el-table {
height: 100%;
}
.table-container ::v-deep .el-table__body-wrapper {
flex: 1;
overflow-y: auto;
}
/* 附件区域样式 */
.annex-section {
margin-top: 12px;
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 50%, #f1f5f9 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow:
0 2px 8px rgba(59, 130, 246, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.annex-title {
display: flex;
align-items: center;
padding: 8px 14px;
background: linear-gradient(145deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
border-bottom: 1px solid rgba(59, 130, 246, 0.2);
}
.annex-icon {
font-size: 14px;
margin-right: 8px;
filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.4));
}
.annex-title span:last-child {
font-size: 13px;
font-weight: 700;
color: #1e293b;
letter-spacing: 0.6px;
}
.annex-content {
padding: 12px 14px;
}
/* 附件区域的h4标题样式 */
.sales-section h4 {
margin: 12px 0 8px 0;
padding: 6px 10px;
font-size: 13px;
font-weight: 700;
color: #1e293b;
background: linear-gradient(145deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%);
border: 1px solid rgba(59, 130, 246, 0.2);
letter-spacing: 0.6px;
}
/* 生产数据样式 */
.perf-count {
font-weight: normal;
color: #909399;
font-size: 12px;
margin-left: 4px;
}
.charts-wrap {
display: flex;
flex-direction: column;
gap: 12px;
}
.chart-box {
width: 100%;
height: 200px;
}
</style>