SafetyDialog.vue 13.7 KB
<template>
  <el-dialog
    :model-value="visible"
    @update:model-value="$emit('update:visible', $event)"
    title=""
    width="calc(100vw - 40px)"
    :style="{ maxWidth: '1400px' }"
    top="3vh"
    destroy-on-close
    class="safety-dialog"
  >
    <template #header>
      <div class="dialog-header">
        <span class="title-text">{{ device?.name || '能耗设备1' }}</span>
        <div class="header-right">
          <el-icon :size="16" style="cursor:pointer;color:#409eff;"><Refresh /></el-icon>
          <el-icon :size="16" style="cursor:pointer;color:#409eff;margin-left:6px;"><FullScreen /></el-icon>
          <el-icon :size="16" style="cursor:pointer;color:#409eff;margin-left:6px;" @click="$emit('update:visible', false)"><Close /></el-icon>
        </div>
      </div>
    </template>

    <div class="safety-body">
      <!-- 设备运行状态 -->
      <div class="panel">
        <div class="panel-title">设备运行状态:</div>
        <div class="status-chart-area">
          <div class="status-bar-chart">
            <svg viewBox="0 0 700 100">
              <g font-size="10" fill="#999" text-anchor="middle">
                <text x="50" y="85">04:00</text><text x="180" y="85">08:00</text><text x="310" y="85">12:00</text><text x="440" y="85">16:00</text><text x="570" y="85">20:00</text><text x="650" y="85">25:00</text>
              </g>
              <rect x="200" y="15" width="90" height="55" rx="3" fill="#999" opacity="0.7"/>
            </svg>
          </div>
          <el-date-picker v-model="runDate" type="date" size="small" placeholder="2026-04-28" />
        </div>
      </div>

      <!-- 运行状态能耗产量复合 -->
      <div class="panel">
        <div class="panel-title">运行状态能耗产量复合</div>
        <div class="composite-row">
          <div class="composite-left">
            <div class="pie-wrap">
              <svg viewBox="0 0 120 120"><circle cx="60" cy="60" r="48" fill="#e8f4fd" stroke="#ddd"/>
                <circle cx="60" cy="60" r="38" fill="none" stroke="#409eff" stroke-width="12"
                  stroke-dasharray="239 239" transform="rotate(-90 60 60)"/>
                <text x="60" y="58" text-anchor="middle" font-size="11" fill="#333">正常</text>
                <text x="60" y="72" text-anchor="middle" font-size="9" fill="#666">100%</text>
              </svg>
            </div>
            <div class="composite-info">
              <div class="ci-row"><span class="ci-dot b"></span>本月耗电</div>
              <div class="ci-row"><span class="ci-dot g"></span>日产量</div>
            </div>
          </div>
          <div class="composite-right">
            <div>待机电能范围:<b>0.00A ≤ I ≤ 0.00A</b></div>
            <div>运行电能范围:<b>I ≥ 0.00A</b></div>
          </div>
        </div>
      </div>

      <!-- 运行时长统计 + 健康度/能效统计 -->
      <div class="two-col-row">
        <div class="panel flex-1">
          <div class="panel-title">运行时长统计:</div>
          <div class="bar-legend">
            <span class="leg"><i class="dot o"/>停机</span>
            <span class="leg"><i class="dot g"/>待机</span>
            <span class="leg"><i class="dot b"/>运行</span>
            <span class="leg"><i class="dot gy"/>离线</span>
          </div>
          <div class="runtime-bar-chart">
            <svg viewBox="0 0 600 160">
              <g font-size="9" fill="#999" text-anchor="end">
                <text x="24" y="18">24</text><text x="24" y="46">18</text><text x="24" y="74">12</text>
                <text x="24" y="102">6</text><text x="24" y="130">0</text>
              </g>
              <line x1="30" y1="126" x2="580" y2="126" stroke="#ddd"/>
              <rect x="36" y="86" width="14" height="40" fill="#999" rx="1"/><text x="43" y="141" text-anchor="middle" font-size="7">03-13</text>
              <rect x="54" y="106" width="14" height="20" fill="#999" rx="1"/><text x="61" y="141" text-anchor="middle" font-size="7">03-14</text>
              <rect x="72" y="126" width="14" height="0" rx="1"/><text x="79" y="141" text-anchor="middle" font-size="7">03-15</text>
              <rect x="90" y="126" width="14" height="0" rx="1"/><text x="97" y="141" text-anchor="middle" font-size="7">03-16</text>
              <rect x="108" y="126" width="14" height="0" rx="1"/><text x="115" y="141" text-anchor="middle" font-size="7">03-17</text>
              <rect x="126" y="126" width="14" height="0" rx="1"/><text x="133" y="141" text-anchor="middle" font-size="7">03-18</text>
              <rect x="144" y="126" width="14" height="0" rx="1"/><text x="151" y="141" text-anchor="middle" font-size="7">03-19</text>
              <rect x="162" y="126" width="14" height="0" rx="1"/><text x="169" y="141" text-anchor="middle" font-size="7">03-20</text>
              <rect x="180" y="126" width="14" height="0" rx="1"/><text x="187" y="141" text-anchor="middle" font-size="7">03-21</text>
              <rect x="198" y="126" width="14" height="0" rx="1"/><text x="205" y="141" text-anchor="middle" font-size="7">03-22</text>
              <rect x="216" y="116" width="14" height="10" fill="#999" rx="1"/><text x="223" y="141" text-anchor="middle" font-size="7">03-23</text>
              <rect x="234" y="56" width="14" height="70" fill="#999" rx="1"/><text x="241" y="141" text-anchor="middle" font-size="7">03-24</text>
              <rect x="252" y="46" width="14" height="80" fill="#999" rx="1"/><text x="259" y="141" text-anchor="middle" font-size="7">03-25</text>
              <rect x="270" y="46" width="14" height="80" fill="#999" rx="1"/><text x="277" y="141" text-anchor="middle" font-size="7">03-26</text>
              <rect x="288" y="76" width="14" height="50" fill="#999" rx="1"/><text x="295" y="141" text-anchor="middle" font-size="7">03-27</text>
              <rect x="306" y="96" width="14" height="30" fill="#999" rx="1"/><text x="313" y="141" text-anchor="middle" font-size="7">03-28</text>
              <line x1="330" y1="10" x2="330" y2="130" stroke="#eee" stroke-dasharray="3,3"/>
              <text x="380" y="75" text-anchor="middle" font-size="11" fill="#666" opacity="0.6">2026-04-13 — 2026-04-28</text>
            </svg>
          </div>
        </div>

        <div class="right-stats">
          <!-- 健康度 -->
          <div class="stat-card">
            <div class="sc-header">健康度:</div>
            <div class="sc-content">
              <div class="score-circle">
                <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="42" fill="none" stroke="#eee" stroke-width="8"/>
                  <circle cx="50" cy="50" r="42" fill="none" stroke="#e6a23c" stroke-width="8" stroke-dasharray="2 262" transform="rotate(-90 50 50)"/>
                  <text x="50" y="53" text-anchor="middle" font-size="18" font-weight="bold" fill="#333">0.00</text>
                </svg>
              </div>
              <div class="sc-detail">
                <div class="sd-row"><span class="sd-label">总评分:</span><span class="sd-val">0.00</span></div>
                <div class="sd-row"><span class="sd-label">月最大需量</span></div>
                <div class="sd-big"><b>0.00Kw</b></div>
              </div>
              <div class="sc-bars">
                <div v-for="(item, i) in healthBars" :key="i" :class="['hb-item', 'hb-'+item.color]">
                  {{ item.pct }}% {{ item.label }}
                </div>
              </div>
            </div>
          </div>

          <!-- 能效统计 -->
          <div class="stat-card">
            <div class="sc-header">能效统计:</div>
            <div class="sc-content">
              <div class="score-circle small">
                <svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="42" fill="none" stroke="#eee" stroke-width="8"/>
                  <text x="50" y="53" text-anchor="middle" font-size="18" font-weight="bold" fill="#333">0</text>
                </svg>
              </div>
              <div class="ef-bars">
                <div v-for="(item, i) in effBars" :key="i" class="eb-item">
                  <div class="eb-bar-wrap"><div :class="['eb-fill', 'fill-' + item.color]" :style="{width:item.val+'%'}"></div></div>
                  <div class="eb-label">{{ item.val }}<br/>{{ item.label }}</div>
                </div>
              </div>
              <div class="ef-bottom">日能效曲线</div>
            </div>
          </div>
        </div>
      </div>

      <!-- 产量分析 + 日投入产出 -->
      <div class="two-col-row">
        <div class="panel flex-1">
          <div class="panel-title">产量分析:</div>
          <div class="prod-bars">
            <div v-for="(item, i) in prodData" :key="i" class="prod-item">
              <div :class="['pb-dot', item.color]"></div>
              <div>{{ item.label }}</div>
              <div class="pb-val">{{ item.val }}</div>
            </div>
          </div>
          <div class="prod-line">日产量曲线</div>
        </div>
        <div class="panel flex-1">
          <div class="panel-title">日投入产出、日生产效率</div>
          <div class="io-legend">
            <span class="leg"><i class="dot b"/>收入产出比</span>
            <span class="leg"><i class="dot g"/>生产效率</span>
          </div>
        </div>
      </div>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref } from 'vue'
import { Refresh, FullScreen, Close } from '@element-plus/icons-vue'

defineProps({ visible: Boolean, device: Object })
defineEmits(['update:visible'])

const runDate = ref('')
const healthBars = [
  { pct: 0.00, label: '自愈率', color: 'orange' },
  { pct: 0.00, label: '电平平衡率', color: 'green' },
  { pct: 0.00, label: '总故障数', color: 'red' },
  { pct: 0.00, label: '电压不平衡', color: 'blue' }
]
const effBars = [
  { val: 0.00, label: '日累计运行时长', color: 'orange' },
  { val: 0.00, label: '日累计能耗', color: 'green' },
  { val: 0.00, label: '月累计运行时长', color: 'red' },
  { val: 0.00, label: '月累计能耗', color: 'blue' }
]
const prodData = [
  { label: '日累计产量', val: '0.00', color: 'o' },
  { label: '月累计产量', val: '0.00', color: 'g' },
  { label: '日投入产出', val: '0.00', color: 'r' },
  { label: '日生产效率', val: '0.00', color: 'b' }
]
</script>

<style scoped>
.safety-dialog :deep(.el-dialog) { max-height: 92vh; display:flex;flex-direction:column; }
.safety-dialog :deep(.el-dialog__header){padding:10px 20px;border-bottom:1px solid #e8e8e8;margin:0;flex-shrink:0; }
.safety-dialog :deep(.el-dialog__body){overflow-y:auto;flex:1;padding:12px;}
.dialog-header{display:flex;align-items:center;justify-content:space-between;}
.title-text{font-size:15px;font-weight:bold;color:#333;}
.header-right{display:flex;align-items:center;}

.safety-body .panel{background:#fff;border:1px solid #eee;border-radius:6px;padding:14px;margin-bottom:12px;}
.panel-title{font-size:13px;font-weight:bold;color:#333;margin-bottom:10px;}
.flex-1{flex:1;}
.two-col-row{display:flex;gap:12px;}

.status-chart-area{display:flex;align-items:center;justify-content:space-between;gap:12px;}
.status-bar-chart{flex:1;}
.status-bar-chart svg{width:100%;height:auto;}

.composite-row{display:flex;justify-content:space-between;align-items:center;gap:16px;}
.composite-left{display:flex;align-items:center;gap:16px;}
.pie-wrap svg{width:120px;height:120px;}
.composite-info{font-size:12px;color:#666;display:flex;gap:16px;}
.ci-row{display:flex;align-items:center;gap:4px;}
.ci-dot{display:inline-block;width:10px;height:10px;border-radius:2px;}
.ci-dot.b{background:#409eff;}.ci-dot.g{background:#67c23a;}
.composite-right{font-size:12px;color:#666;line-height:2;}

.bar-legend{display:flex;gap:14px;font-size:11px;color:#666;margin-bottom:6px;}
.leg{display:flex;align-items:center;gap:3px;}
.dot{display:inline-block;width:10px;height:10px;border-radius:2px;}
.dot.o{background:#f56c6c;}.dot.g{background:#67c23a;}.dot.b{background:#409eff;}.dot.gy{background:#909399;}
.runtime-bar-chart{overflow-x:auto;}

.right-stats{width:280px;display:flex;flex-direction:column;gap:12px;}
.stat-card{background:#fff;border:1px solid #eee;border-radius:6px;padding:12px;}
.sc-header{font-size:13px;font-weight:bold;color:#333;margin-bottom:8px;}
.sc-content{display:flex;align-items:flex-start;gap:10px;}
.score-circle svg{width:90px;height:90px;flex-shrink:0;}
.score-circle.small svg{width:76px;height:76px;}
.sc-detail{flex:1;font-size:11px;color:#666;}
.sd-row{margin-bottom:2px;}
.sd-val{color:#e6a23c;font-weight:bold;font-size:13px;}
.sd-big{margin-top:4px;color:#409eff;font-size:16px;}
.sc-bars,.ef-bars{flex:1;display:flex;flex-direction:column;gap:4px;}
.hb-item{font-size:10px;padding:3px 6px;border-radius:3px;text-align:center;}
.hb-orange{background:#fff3e0;color:#e65100;}
.hb-green{background:#e8f5e9;color:#2e7d32;}
.hb-red{background:#ffebee;color:#c62828;}
.hb-blue{background:#e3f2fd;color:#1565c0;}
.eb-item{display:flex;align-items:center;gap:6px;font-size:10px;}
.eb-bar-wrap{width:50px;height:8px;background:#f0f0f0;border-radius:2px;overflow:hidden;}
.eb-fill{height:100%;border-radius:2px;}
.fill-orange{background:#f56c6c;}.fill-green{background:#67c23a;}
.fill-red{background:#f56c6c;}.fill-blue{background:#409eff;}
.eb-label{color:#666;white-space:nowrap;}
.ef-bottom{text-align:center;font-size:11px;color:#999;margin-top:4px;}

.prod-bars{display:flex;gap:24px;margin-bottom:8px;}
.pb-dot{width:12px;height:12px;border-radius:2px;margin-right:4px;}
.pb-dot.o{background:#f56c6c;}.pb-dot.g{background:#67c23a;}.pb-dot.r{background:#e74c3c;}.pb-dot.b{background:#409eff;}
.prod-item{display:flex;align-items:center;gap:4px;font-size:12px;color:#666;}
.pb-val{font-weight:bold;color:#333;margin-left:4px;}
.prod-line{text-align:center;font-size:11px;color:#999;}
.io-legend{display:flex;gap:14px;font-size:11px;color:#666;}
</style>