Files
klp-oa/klp-ui/src/views/wms/coil/info.vue

2221 lines
78 KiB
Vue
Raw Normal View History

<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>
</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 || '-',
...item
});
});
// 添加技术部改判记录
this.coilQualityRejudgeList.forEach(item => {
list.push({
type: '技术部改判',
before: '质量状态:' + (item.beforeQuality || '-'),
after: '质量状态:' + (item.afterQuality || '-'),
createTime: item.createTime || '-',
...item
});
});
// 按时间排序
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: 3;
}
.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>