1. 将接口从get请求改为post请求,适配后端参数变更 2. 重新设计生产工艺数据页面,拆分为趋势参数、厚度曲线、带钢板形、板形曲线四个标签页 3. 新增趋势参数树形侧边栏,支持分类展开选择查看不同参数曲线 4. 新增厚度、板形3D热力图、板形曲线图表展示 5. 优化图表渲染逻辑和样式,统一数据处理与加载状态
2659 lines
91 KiB
Vue
2659 lines
91 KiB
Vue
<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" :coil-info="coilInfo" />
|
||
</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 || segLoading || realtimeLoading" size="mini" type="info" style="margin-left:8px">加载中…</el-tag>
|
||
<span v-if="hasPerfData" class="perf-count">({{ perfSegCount }} 段)</span>
|
||
</div>
|
||
<div class="section-body">
|
||
<el-tabs v-model="perfActiveTab" size="small" class="perf-tabs" @tab-click="handlePerfTabSwitch">
|
||
|
||
<!-- 趋势参数:左树形目录 + 右单图 -->
|
||
<el-tab-pane label="趋势参数" name="trend">
|
||
<div v-if="!hasPerfData && !segLoading" class="no-data-hint">暂无生产数据</div>
|
||
<div v-else-if="segLoading" class="no-data-hint">加载中…</div>
|
||
<div v-else class="trend-layout">
|
||
<div class="trend-tree">
|
||
<div v-for="group in trendGroups" :key="group.label" class="tree-group">
|
||
<div class="tree-group-label" @click="toggleTrendGroup(group.label)">
|
||
<i :class="expandedGroups[group.label] ? 'el-icon-caret-bottom' : 'el-icon-caret-right'" />
|
||
{{ group.label }}
|
||
</div>
|
||
<div v-show="expandedGroups[group.label]" class="tree-children">
|
||
<div
|
||
v-for="item in group.children"
|
||
:key="item.col"
|
||
class="tree-item"
|
||
:class="{ active: selectedTrendParam && selectedTrendParam.col === item.col }"
|
||
@click="selectTrendParam(item)"
|
||
>{{ item.label }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="trend-chart-area">
|
||
<div v-if="!selectedTrendParam" class="no-data-hint">← 点击左侧参数查看曲线</div>
|
||
<div ref="trendSingleChart" :style="{ display: selectedTrendParam ? 'block' : 'none', height: '100%', width: '100%' }" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- 厚度曲线 -->
|
||
<el-tab-pane label="厚度曲线" name="thickness">
|
||
<div v-if="!gaugeRows || !gaugeRows.length" class="no-data-hint">暂无厚度数据</div>
|
||
<div v-else class="charts-scroll charts-grid">
|
||
<div ref="chartGauge1" class="chart-box" />
|
||
<div ref="chartGauge2" class="chart-box" />
|
||
<div ref="chartGauge3" class="chart-box" />
|
||
<div ref="chartGauge4" class="chart-box" />
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- 带钢板形 3D 热力图 -->
|
||
<el-tab-pane label="带钢板形" name="flatness3d">
|
||
<div v-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||
<div v-else class="charts-scroll">
|
||
<div ref="chartFlatness3d" class="chart-box chart-box-tall" />
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<!-- 板形曲线 -->
|
||
<el-tab-pane label="板形曲线" name="flatness">
|
||
<div v-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||
<div v-else class="charts-scroll charts-grid">
|
||
<div ref="chartFlatDev" class="chart-box" />
|
||
<div ref="chartTilt" class="chart-box" />
|
||
<div ref="chartWrBend" class="chart-box" />
|
||
<div ref="chartIrBend" class="chart-box" />
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</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 'echarts-gl'
|
||
import { getTimingSegByEncoilId, getTimingPlanDetailByHotcoilId, getTimingRealtimeData } from '@/api/l2/timing'
|
||
|
||
import AbnormalTable from '@/views/wms/coil/components/AbnormalTable.vue';
|
||
import FileList from "@/components/FileList";
|
||
|
||
const TREND_GROUPS = [
|
||
{
|
||
label: '张力',
|
||
children: [
|
||
{ label: '开卷张力', col: 'PORTENS' },
|
||
{ label: '入口活套张力', col: 'ENLTENS' },
|
||
{ label: '拉矫张力', col: 'TLTENS' },
|
||
{ label: '酸洗张力', col: 'PLTENS' },
|
||
{ label: '出口活套张力', col: 'CXLTENS' },
|
||
{ label: '圆盘剪张力', col: 'TRIMTENS' }
|
||
]
|
||
},
|
||
{
|
||
label: '速度',
|
||
children: [
|
||
{ label: '开卷速度', col: 'PORSPEED' },
|
||
{ label: '酸洗速度', col: 'PLSPEED' },
|
||
{ label: '圆盘剪速度', col: 'TRIMSPEED' },
|
||
{ label: '轧机入口速度', col: 'MILLENTRYSPEED' },
|
||
{ label: '轧机出口速度', col: 'MILLEXITSPEED' }
|
||
]
|
||
},
|
||
{
|
||
label: '拉矫机',
|
||
children: [
|
||
{ label: '1#插入量', col: 'TLMESH1' },
|
||
{ label: '2#插入量', col: 'TLMESH2' },
|
||
{ label: '3#插入量', col: 'TLMESH3' },
|
||
{ label: '延伸率', col: 'TLELONG' }
|
||
]
|
||
},
|
||
{
|
||
label: '酸洗段',
|
||
children: [
|
||
{ label: '1#温度', col: 'TK1TEMP' },
|
||
{ label: '2#温度', col: 'TK2TEMP' },
|
||
{ label: '3#温度', col: 'TK3TEMP' },
|
||
{ label: '漂洗温度', col: 'RINSETEMP' }
|
||
]
|
||
}
|
||
]
|
||
|
||
const GAUGE_COLS = [
|
||
{ col: 'THICK0', title: '入口测厚仪 [mm]' },
|
||
{ col: 'THICK1', title: '1架出口厚度 [mm]' },
|
||
{ col: 'THICK4', title: '末架出口厚度 [mm]' },
|
||
{ col: 'EXIT_SPEED', title: '轧制速度 [m/min]' }
|
||
]
|
||
|
||
const SHAPE_SCALAR_COLS = [
|
||
{ col: 'ABSDEVIATION', title: '总板形偏差 [IU]' },
|
||
{ col: 'TILT', title: '末架倾斜量 [mm]' },
|
||
{ col: 'WRBEND', title: '工作辊弯辊力 [kN]' },
|
||
{ col: 'IRBEND', title: '中间辊弯辊力 [kN]' }
|
||
]
|
||
|
||
function calcYRange(vals) {
|
||
const nums = vals.filter(v => v != null && isFinite(Number(v))).map(Number)
|
||
if (!nums.length) return {}
|
||
const min = Math.min(...nums)
|
||
const max = Math.max(...nums)
|
||
if (min === max) {
|
||
const base = Math.abs(min) || 1
|
||
return { min: parseFloat((min - base * 0.2).toFixed(4)), max: parseFloat((max + base * 0.2).toFixed(4)) }
|
||
}
|
||
const pad = (max - min) * 0.15
|
||
return {
|
||
min: parseFloat((min - pad).toFixed(4)),
|
||
max: parseFloat((max + pad).toFixed(4))
|
||
}
|
||
}
|
||
|
||
function makeLine(title, xData, yData) {
|
||
const range = calcYRange(yData)
|
||
return {
|
||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
|
||
tooltip: { trigger: 'axis' },
|
||
grid: { top: 36, bottom: 28, left: 8, right: 16, containLabel: true },
|
||
xAxis: {
|
||
type: 'category', data: xData,
|
||
name: 'pos(m)', nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 }
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
min: range.min,
|
||
max: range.max,
|
||
nameTextStyle: { fontSize: 10 },
|
||
axisLabel: { fontSize: 10 }
|
||
},
|
||
dataZoom: [
|
||
{ type: 'inside', xAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true },
|
||
{ type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: false, moveOnMouseMove: true }
|
||
],
|
||
series: [{
|
||
name: title, type: 'line', smooth: false, symbol: 'none',
|
||
lineStyle: { width: 1 }, data: yData
|
||
}]
|
||
}
|
||
}
|
||
|
||
function getRowVal(row, col) {
|
||
const v = row[col] !== undefined ? row[col] : row[col.toLowerCase()]
|
||
return v == null ? null : Number(v)
|
||
}
|
||
|
||
function xLocData(rows) {
|
||
return rows.map(r => {
|
||
const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation
|
||
return v == null ? '' : Number(v).toFixed(1)
|
||
})
|
||
}
|
||
|
||
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,
|
||
// 新增图表数据状态
|
||
segLoading: false,
|
||
realtimeLoading: false,
|
||
segData: null,
|
||
gaugeRows: null,
|
||
shapeRows: null,
|
||
perfActiveTab: 'trend',
|
||
// 趋势参数树状态
|
||
trendGroups: TREND_GROUPS,
|
||
expandedGroups: { '张力': true, '速度': true, '拉矫机': true, '酸洗段': true },
|
||
selectedTrendParam: null,
|
||
trendChartInst: null,
|
||
_trendResizeFn: null,
|
||
chartInstances: [],
|
||
resizeHandler: null
|
||
}
|
||
},
|
||
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;
|
||
},
|
||
// 加载生产数据
|
||
async loadProductionData() {
|
||
const hotCoilId = this.coilInfo.enterCoilNo;
|
||
if (!hotCoilId) return;
|
||
|
||
this.perfLoading = true;
|
||
try {
|
||
const detail = await getTimingPlanDetailByHotcoilId(hotCoilId);
|
||
const encoilId = detail?.data?.firstRow?.coilid || '';
|
||
// V_VBDA_GAUGE / V_VBDA_SHAPE 使用 EXCOILID 作为 MATID,对应入场卷号
|
||
const excoilId = this.coilInfo.enterCoilNo;
|
||
|
||
const [segRes, realtimeRes] = await Promise.all([
|
||
encoilId ? getTimingSegByEncoilId(encoilId) : Promise.resolve({ data: null }),
|
||
encoilId ? getTimingRealtimeData(encoilId) : Promise.resolve({ data: null })
|
||
]);
|
||
|
||
const series = segRes?.data?.series || null;
|
||
const rows = segRes?.data?.rows || [];
|
||
this.perfSegCount = rows.length;
|
||
this.perfSeries = series;
|
||
this.segData = series;
|
||
|
||
const g = realtimeRes?.data?.gauge?.result;
|
||
const s = realtimeRes?.data?.shape?.result;
|
||
this.gaugeRows = Array.isArray(g) ? g : null;
|
||
this.shapeRows = Array.isArray(s) ? s : null;
|
||
|
||
if (series && rows.length) {
|
||
await this.$nextTick();
|
||
this.selectTrendParam(TREND_GROUPS[0].children[0]);
|
||
// 数据加载完成后,根据当前选中的标签页渲染对应图表
|
||
this.renderCurrentTab();
|
||
} else if (this.gaugeRows?.length || this.shapeRows?.length) {
|
||
await this.$nextTick();
|
||
this.renderCurrentTab();
|
||
}
|
||
} catch (error) {
|
||
console.error('获取生产数据异常:', error);
|
||
this.perfSeries = null;
|
||
this.perfSegCount = 0;
|
||
this.segData = null;
|
||
this.gaugeRows = null;
|
||
this.shapeRows = null;
|
||
} finally {
|
||
this.perfLoading = false;
|
||
}
|
||
},
|
||
// 销毁图表
|
||
disposeCharts() {
|
||
this.disposeSideCharts();
|
||
if (this._trendResizeFn) {
|
||
window.removeEventListener('resize', this._trendResizeFn);
|
||
this._trendResizeFn = null;
|
||
}
|
||
if (this.trendChartInst && !this.trendChartInst.isDisposed()) {
|
||
this.trendChartInst.dispose();
|
||
this.trendChartInst = null;
|
||
}
|
||
},
|
||
disposeSideCharts() {
|
||
if (this.resizeHandler) {
|
||
window.removeEventListener('resize', this.resizeHandler);
|
||
this.resizeHandler = null;
|
||
}
|
||
this.chartInstances.forEach(c => { if (c && !c.isDisposed()) c.dispose(); });
|
||
this.chartInstances = [];
|
||
},
|
||
// 趋势参数树操作
|
||
toggleTrendGroup(label) {
|
||
this.$set(this.expandedGroups, label, !this.expandedGroups[label]);
|
||
},
|
||
selectTrendParam(item) {
|
||
this.selectedTrendParam = item;
|
||
this.$nextTick(() => this.renderTrendSingleChart());
|
||
},
|
||
renderTrendSingleChart() {
|
||
if (!this.selectedTrendParam || !this.segData) return;
|
||
const el = this.$refs.trendSingleChart;
|
||
if (!el) return;
|
||
|
||
if (!this.trendChartInst || this.trendChartInst.isDisposed()) {
|
||
this.trendChartInst = echarts.init(el);
|
||
const resizeFn = () => this.trendChartInst && !this.trendChartInst.isDisposed() && this.trendChartInst.resize();
|
||
window.addEventListener('resize', resizeFn);
|
||
this._trendResizeFn = resizeFn;
|
||
}
|
||
const x = this.segX();
|
||
const yData = this.seg(this.selectedTrendParam.col);
|
||
this.trendChartInst.setOption(makeLine(this.selectedTrendParam.label, x, yData), true);
|
||
},
|
||
// 标签页切换
|
||
handlePerfTabSwitch() {
|
||
this.$nextTick(() => {
|
||
if (this.perfActiveTab === 'trend') {
|
||
if (this.selectedTrendParam && this.segData) {
|
||
this.renderTrendSingleChart();
|
||
}
|
||
} else {
|
||
this.renderCurrentTab();
|
||
}
|
||
});
|
||
},
|
||
renderCurrentTab() {
|
||
this.disposeSideCharts();
|
||
if (this.perfActiveTab === 'thickness' && this.gaugeRows?.length) this.renderGaugeCharts();
|
||
if (this.perfActiveTab === 'flatness3d' && this.shapeRows?.length) this.renderFlatness3d();
|
||
if (this.perfActiveTab === 'flatness' && this.shapeRows?.length) this.renderFlatnessCharts();
|
||
},
|
||
// SEG 数据辅助
|
||
seg(col) {
|
||
const s = this.segData;
|
||
const arr = s[col] !== undefined ? s[col] : (s[col.toLowerCase()] || []);
|
||
return arr.map(v => v == null ? null : Number(Number(v).toFixed(3)));
|
||
},
|
||
segX() {
|
||
const s = this.segData;
|
||
const arr = s['STARTPOS'] !== undefined ? s['STARTPOS'] : (s['startpos'] || []);
|
||
return arr.map(v => v == null ? '' : Number(v).toFixed(1));
|
||
},
|
||
// 图表初始化
|
||
makeChart(ref, option) {
|
||
const el = this.$refs[ref];
|
||
if (!el) return null;
|
||
const chart = echarts.init(el);
|
||
chart.setOption(option);
|
||
return chart;
|
||
},
|
||
setupResize() {
|
||
this.resizeHandler = () => this.chartInstances.forEach(c => {
|
||
if (c && !c.isDisposed()) c.resize();
|
||
});
|
||
window.addEventListener('resize', this.resizeHandler);
|
||
},
|
||
// 厚度曲线
|
||
renderGaugeCharts() {
|
||
const rows = this.gaugeRows;
|
||
if (!rows || !rows.length) return;
|
||
const xData = xLocData(rows);
|
||
const refs = ['chartGauge1', 'chartGauge2', 'chartGauge3', 'chartGauge4'];
|
||
const charts = refs.map((ref, i) => {
|
||
const { col, title } = GAUGE_COLS[i];
|
||
const yData = rows.map(r => {
|
||
const v = getRowVal(r, col);
|
||
return v == null ? null : parseFloat(v.toFixed(4));
|
||
});
|
||
return this.makeChart(ref, makeLine(title, xData, yData));
|
||
});
|
||
this.chartInstances = charts.filter(Boolean);
|
||
this.setupResize();
|
||
},
|
||
// 带钢板形 3D 热力图
|
||
renderFlatness3d() {
|
||
const rows = this.shapeRows;
|
||
if (!rows || !rows.length) return;
|
||
const firstRow = rows[0];
|
||
const high = parseInt(getRowVal(firstRow, 'HIGHZONEID')) || 26;
|
||
const low = parseInt(getRowVal(firstRow, 'LOWZONEID')) || 1;
|
||
const numZones = Math.min(Math.max(high - low + 1, 1), 26);
|
||
const zoneCols = Array.from({ length: numZones }, (_, i) =>
|
||
`VALUES${String(low + i).padStart(2, '0')}`
|
||
);
|
||
const step = Math.max(1, Math.floor(rows.length / 200));
|
||
const sampled = rows.filter((_, i) => i % step === 0);
|
||
const numX = sampled.length;
|
||
|
||
const xLabels = sampled.map(r => {
|
||
const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation;
|
||
return v == null ? '' : Number(v).toFixed(0);
|
||
});
|
||
|
||
let minV = Infinity, maxV = -Infinity;
|
||
sampled.forEach(row => {
|
||
zoneCols.forEach(col => {
|
||
const v = getRowVal(row, col);
|
||
if (v != null) {
|
||
if (v < minV) minV = v;
|
||
if (v > maxV) maxV = v;
|
||
}
|
||
});
|
||
});
|
||
if (!isFinite(minV)) { minV = -30; maxV = 30; }
|
||
const absMax = Math.max(Math.abs(minV), Math.abs(maxV));
|
||
|
||
const channelLines = zoneCols.map((col, yi) => ({
|
||
type: 'line3D',
|
||
coordinateSystem: 'cartesian3D',
|
||
data: sampled.map((row, xi) => {
|
||
const v = getRowVal(row, col);
|
||
return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))];
|
||
}).filter(Boolean),
|
||
lineStyle: { width: 2, opacity: 1 }
|
||
}));
|
||
|
||
const xStride = Math.max(1, Math.floor(numX / 60));
|
||
const crossLines = [];
|
||
for (let xi = 0; xi < numX; xi += xStride) {
|
||
const pts = zoneCols.map((col, yi) => {
|
||
const v = getRowVal(sampled[xi], col);
|
||
return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))];
|
||
}).filter(Boolean);
|
||
if (pts.length > 1) {
|
||
crossLines.push({
|
||
type: 'line3D',
|
||
coordinateSystem: 'cartesian3D',
|
||
data: pts,
|
||
lineStyle: { width: 1.5, opacity: 1 }
|
||
});
|
||
}
|
||
}
|
||
|
||
const series = [...channelLines, ...crossLines];
|
||
|
||
const option = {
|
||
title: { text: '实测平直度 [IU]', textStyle: { fontSize: 13, fontWeight: 'normal' }, top: 6, left: 10 },
|
||
tooltip: {},
|
||
visualMap: {
|
||
show: true,
|
||
dimension: 2,
|
||
min: -absMax,
|
||
max: absMax,
|
||
calculable: true,
|
||
orient: 'vertical',
|
||
right: 10,
|
||
top: 'center',
|
||
textStyle: { fontSize: 10 },
|
||
inRange: {
|
||
color: ['#8B0000','#CC2200','#E84C00','#F46D43',
|
||
'#FDAE61','#FEE08B',
|
||
'#66BD63','#1A9850','#006837',
|
||
'#3288BD','#5E4FA2','#762A83']
|
||
}
|
||
},
|
||
grid3D: {
|
||
boxWidth: 200,
|
||
boxHeight: 60,
|
||
boxDepth: 80,
|
||
viewControl: {
|
||
projection: 'orthographic',
|
||
autoRotate: false,
|
||
rotateSensitivity: 1,
|
||
zoomSensitivity: 1
|
||
},
|
||
light: {
|
||
main: { intensity: 1.2, shadow: false },
|
||
ambient: { intensity: 0.3 }
|
||
}
|
||
},
|
||
xAxis3D: {
|
||
type: 'value',
|
||
name: '位置',
|
||
min: 0,
|
||
max: numX - 1,
|
||
nameTextStyle: { fontSize: 10 },
|
||
axisLabel: {
|
||
fontSize: 9,
|
||
formatter: v => xLabels[Math.round(v)] || ''
|
||
}
|
||
},
|
||
yAxis3D: {
|
||
type: 'value',
|
||
name: '通道',
|
||
min: 0,
|
||
max: numZones - 1,
|
||
nameTextStyle: { fontSize: 10 },
|
||
axisLabel: {
|
||
fontSize: 9,
|
||
formatter: v => String(low + Math.round(v))
|
||
}
|
||
},
|
||
zAxis3D: {
|
||
type: 'value',
|
||
name: 'IU',
|
||
nameTextStyle: { fontSize: 10 },
|
||
axisLabel: { fontSize: 9 }
|
||
},
|
||
series
|
||
};
|
||
|
||
const el = this.$refs.chartFlatness3d;
|
||
if (!el) return;
|
||
const chart = echarts.init(el);
|
||
chart.setOption(option);
|
||
this.chartInstances = [chart];
|
||
this.setupResize();
|
||
},
|
||
// 板形曲线
|
||
renderFlatnessCharts() {
|
||
const rows = this.shapeRows;
|
||
if (!rows || !rows.length) return;
|
||
const xData = xLocData(rows);
|
||
const refs = ['chartFlatDev', 'chartTilt', 'chartWrBend', 'chartIrBend'];
|
||
const charts = refs.map((ref, i) => {
|
||
const { col, title } = SHAPE_SCALAR_COLS[i];
|
||
const yData = rows.map(r => {
|
||
const v = getRowVal(r, col);
|
||
return v == null ? null : parseFloat(v.toFixed(3));
|
||
});
|
||
return this.makeChart(ref, makeLine(title, xData, yData));
|
||
});
|
||
this.chartInstances = charts.filter(Boolean);
|
||
this.setupResize();
|
||
},
|
||
},
|
||
}
|
||
</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;
|
||
}
|
||
|
||
.production-section {
|
||
min-height: 600px;
|
||
flex: 1;
|
||
}
|
||
|
||
.production-section .section-body {
|
||
padding: 0;
|
||
height: calc(100% - 48px);
|
||
min-height: 550px;
|
||
}
|
||
|
||
.perf-tabs {
|
||
height: 100%;
|
||
min-height: 550px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
::v-deep .el-tabs__header {
|
||
flex-shrink: 0;
|
||
}
|
||
::v-deep .el-tabs__content {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
min-height: 0;
|
||
padding: 0;
|
||
}
|
||
::v-deep .el-tab-pane {
|
||
height: 100%;
|
||
min-height: 500px;
|
||
}
|
||
}
|
||
|
||
.trend-layout {
|
||
display: flex;
|
||
height: 100%;
|
||
gap: 0;
|
||
}
|
||
|
||
.trend-tree {
|
||
width: 140px;
|
||
flex-shrink: 0;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
border-right: 1px solid #ebeef5;
|
||
padding: 4px 0;
|
||
max-height: calc(100vh - 400px);
|
||
|
||
&::-webkit-scrollbar {
|
||
width: 4px;
|
||
height: 4px;
|
||
}
|
||
&::-webkit-scrollbar-thumb {
|
||
background: #dcdfe6;
|
||
border-radius: 2px;
|
||
}
|
||
&::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
}
|
||
|
||
.tree-group {
|
||
user-select: none;
|
||
}
|
||
|
||
.tree-group-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 5px 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
cursor: pointer;
|
||
|
||
&:hover { background: #f5f7fa; }
|
||
|
||
i { font-size: 10px; color: #909399; }
|
||
}
|
||
|
||
.tree-children { padding-left: 4px; }
|
||
|
||
.tree-item {
|
||
padding: 4px 8px 4px 18px;
|
||
font-size: 12px;
|
||
color: #606266;
|
||
cursor: pointer;
|
||
border-radius: 3px;
|
||
margin: 1px 4px;
|
||
|
||
&:hover { background: #ecf5ff; color: #409eff; }
|
||
|
||
&.active {
|
||
background: #ecf5ff;
|
||
color: #409eff;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.trend-chart-area {
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 500px;
|
||
padding: 4px 4px 4px 8px;
|
||
display: flex;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.charts-scroll {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
padding: 8px;
|
||
box-sizing: border-box;
|
||
|
||
&::-webkit-scrollbar { width: 4px; }
|
||
&::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 2px; }
|
||
}
|
||
|
||
.charts-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
align-content: start;
|
||
min-height: 100%;
|
||
padding: 4px;
|
||
|
||
.chart-box { margin-bottom: 0; }
|
||
}
|
||
|
||
.chart-box {
|
||
width: 100%;
|
||
height: 260px;
|
||
margin-bottom: 12px;
|
||
min-height: 200px;
|
||
}
|
||
|
||
.chart-box-tall {
|
||
height: 480px;
|
||
min-height: 400px;
|
||
}
|
||
|
||
.no-data-hint {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 120px;
|
||
color: #c0c4cc;
|
||
font-size: 13px;
|
||
}
|
||
</style>
|