dedsudiyu 1 gün önce
ebeveyn
işleme
4fffddb4e7

+ 62 - 5
src/components/AlarmInfo.vue

@@ -6,10 +6,22 @@
     <div class="panel-header">
       <div class="panel-header-icon">&#x26A0;&#xFE0F;</div>
       <span class="panel-title">实验室实时风险预警</span>
-      <div class="header-count" style="margin-left:auto">
-        <span class="count-label">本月</span>
-        <span class="count-value">{{ totalCount }}</span>
-        <span class="count-label">次</span>
+      <div class="header-right">
+        <!--<div class="header-count">-->
+          <!--<span class="count-label">本月</span>-->
+          <!--<span class="count-value">{{ totalCount }}</span>-->
+          <!--<span class="count-label">次</span>-->
+        <!--</div>-->
+        <div class="header-badge emergency">
+          <span class="badge-icon">🚨</span>
+          <span class="badge-val">{{ emergencyCount }}</span>
+          <span class="badge-lbl">应急</span>
+        </div>
+        <div class="header-badge violation">
+          <span class="badge-icon">☣️</span>
+          <span class="badge-val">{{ violationCount }}</span>
+          <span class="badge-lbl">违规带离</span>
+        </div>
       </div>
     </div>
     <!-- Scrolling warning list -->
@@ -43,6 +55,8 @@ export default {
   data() {
     return {
       totalCount: 0,
+      emergencyCount: 3,
+      violationCount: 1,
       warningList: [],
       pollTimer: null
     }
@@ -180,7 +194,15 @@ export default {
   color: $cyan;
 }
 
-/* ===== Header count ===== */
+/* ===== Header right group ===== */
+.header-right {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-left: auto;
+  flex-shrink: 0;
+}
+
 .header-count {
   display: flex;
   align-items: center;
@@ -199,6 +221,41 @@ export default {
   color: #f59e0b;
 }
 
+/* ===== Header badges ===== */
+.header-badge {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 18px;
+  border-radius: 8px;
+  flex-shrink: 0;
+
+  &.emergency {
+    background: rgba(239,68,68,0.1);
+    border: 1px solid rgba(239,68,68,0.35);
+    .badge-val { color: #ef4444; }
+  }
+
+  &.violation {
+    background: rgba(168,85,247,0.1);
+    border: 1px solid rgba(168,85,247,0.35);
+    .badge-val { color: #a855f7; }
+  }
+}
+
+.badge-icon { font-size: 26px; }
+
+.badge-val {
+  font-size: 40px;
+  font-weight: 700;
+  line-height: 1;
+}
+
+.badge-lbl {
+  font-size: 23px;
+  color: $text-dim;
+}
+
 /* ===== Warning scroll ===== */
 .warn-scroll-wrap {
   overflow: hidden;

+ 316 - 0
src/components/ChemicalsStats.vue

@@ -0,0 +1,316 @@
+<template>
+  <div class="panel chemicals-stats">
+    <div class="border-beam"></div>
+    <svg class="pc tl" viewBox="0 0 14 14"><path d="M0 14 V0 H14" fill="none" stroke="rgba(30,144,255,0.85)" stroke-width="1.5"/></svg>
+    <svg class="pc tr" viewBox="0 0 14 14"><path d="M0 14 V0 H14" fill="none" stroke="rgba(30,144,255,0.85)" stroke-width="1.5"/></svg>
+    <svg class="pc bl" viewBox="0 0 14 14"><path d="M0 14 V0 H14" fill="none" stroke="rgba(30,144,255,0.85)" stroke-width="1.5"/></svg>
+    <svg class="pc br" viewBox="0 0 14 14"><path d="M0 14 V0 H14" fill="none" stroke="rgba(30,144,255,0.85)" stroke-width="1.5"/></svg>
+
+    <div class="panel-header">
+      <div class="panel-header-icon">⚗️</div>
+      <span class="panel-title">化学品库存动态统计</span>
+    </div>
+
+    <div style="padding:12px 20px 15px;display:flex;gap:15px;height:calc(100% - 82px)">
+      <!-- 左侧: 饼图 -->
+      <div style="flex:1;min-width:0;display:flex;flex-direction:column">
+        <div ref="chemPieChart" style="flex:1;min-height:0"></div>
+      </div>
+      <!-- 右侧: 存量统计 -->
+      <div style="flex:0 0 380px;display:flex;flex-direction:column;gap:14px;justify-content:center">
+        <!-- 总量 -->
+        <div class="chem-stat-card total">
+          <div class="csc-label-a">存量化学品总量</div>
+          <div class="csc-value-a">{{ totalAmount.toLocaleString() }}<span class="csc-unit">L</span></div>
+        </div>
+        <!-- 管控 -->
+        <div class="chem-stat-card ctrl">
+          <div style="display:flex;justify-content:space-between;align-items:flex-start">
+            <div>
+              <div class="csc-label">管控类化学品</div>
+              <div class="csc-value">{{ ctrlAmount }}<span class="csc-unit">L</span></div>
+            </div>
+            <div class="csc-pct-ring">
+              <svg viewBox="0 0 60 60" width="80" height="80">
+                <circle cx="30" cy="30" r="24" fill="none" stroke="rgba(239,68,68,0.12)" stroke-width="6"/>
+                <circle cx="30" cy="30" r="24" fill="none" stroke="#ef4444" stroke-width="6"
+                  :stroke-dasharray="ctrlDash"
+                  stroke-linecap="round" transform="rotate(-90 30 30)"
+                  style="filter:drop-shadow(0 0 4px rgba(239,68,68,0.7))"/>
+                <text x="30" y="35" text-anchor="middle" fill="#ef4444" font-size="11" font-weight="700" font-family="Arial">{{ ctrlPct }}%</text>
+              </svg>
+            </div>
+          </div>
+          <div class="csc-sub">占总量 {{ ctrlPct }}% · 共 {{ ctrlCategories }} 类目</div>
+        </div>
+        <!-- 非管控 -->
+        <div class="chem-stat-card free">
+          <div style="display:flex;justify-content:space-between;align-items:flex-start">
+            <div>
+              <div class="csc-label">非管控类化学品</div>
+              <div class="csc-value" style="color:#00d8ff">{{ freeAmount }}<span class="csc-unit">L</span></div>
+            </div>
+            <div>
+              <svg viewBox="0 0 60 60" width="80" height="80">
+                <circle cx="30" cy="30" r="24" fill="none" stroke="rgba(0,216,255,0.12)" stroke-width="6"/>
+                <circle cx="30" cy="30" r="24" fill="none" stroke="#00d8ff" stroke-width="6"
+                  :stroke-dasharray="freeDash"
+                  stroke-linecap="round" transform="rotate(-90 30 30)"
+                  style="filter:drop-shadow(0 0 4px rgba(0,216,255,0.7))"/>
+                <text x="30" y="35" text-anchor="middle" fill="#00d8ff" font-size="11" font-weight="700" font-family="Arial">{{ freePct }}%</text>
+              </svg>
+            </div>
+          </div>
+          <div class="csc-sub" style="color:rgba(0,216,255,0.55)">占总量 {{ freePct }}% · 共 {{ freeCategories }} 类目</div>
+        </div>
+        <!-- 类目总数 -->
+        <div class="chem-stat-card cat">
+          <div class="csc-label">存量化学品总类目数</div>
+          <div style="display:flex;align-items:baseline;gap:12px;margin-top:8px">
+            <div class="csc-value" style="color:#ffd740">{{ ctrlCategories + freeCategories }}<span class="csc-unit">类</span></div>
+            <div style="display:flex;gap:6px;align-items:center">
+              <span style="font-size:22px;color:rgba(239,68,68,0.8)">管控 {{ ctrlCategories }}类</span>
+              <span style="font-size:20px;color:rgba(168,204,232,0.3)">|</span>
+              <span style="font-size:22px;color:rgba(0,216,255,0.8)">非管控 {{ freeCategories }}类</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+const C = 2 * Math.PI * 24 // circumference r=24 ~150.8
+
+export default {
+  name: 'ChemicalsStats',
+  data() {
+    return {
+      totalAmount: 1229,
+      ctrlAmount: 269,
+      freeAmount: 960,
+      ctrlCategories: 5,
+      freeCategories: 5,
+      chart: null
+    }
+  },
+  computed: {
+    ctrlPct() {
+      return Math.round(this.ctrlAmount / (this.totalAmount || 1) * 1000) / 10
+    },
+    freePct() {
+      return Math.round(this.freeAmount / (this.totalAmount || 1) * 1000) / 10
+    },
+    ctrlDash() {
+      const arc = Math.round(this.ctrlPct / 100 * C * 10) / 10
+      return `${arc} ${C}`
+    },
+    freeDash() {
+      const arc = Math.round(this.freePct / 100 * C * 10) / 10
+      return `${arc} ${C}`
+    }
+  },
+  mounted() {
+    this.$nextTick(() => this.initChart())
+  },
+  beforeDestroy() {
+    if (this.chart) this.chart.dispose()
+    window.removeEventListener('resize', this.handleResize)
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$refs.chemPieChart, null, { renderer: 'canvas', devicePixelRatio: 2 })
+      this.chart.setOption({
+        backgroundColor: 'transparent',
+        tooltip: {
+          trigger: 'item',
+          formatter: '{b}: {c}L ({d}%)',
+          backgroundColor: 'rgba(3,14,42,0.92)',
+          borderColor: 'rgba(30,144,255,0.3)',
+          textStyle: { color: '#a8cce8', fontSize: 22 }
+        },
+        graphic: [
+          // { type: 'text', left: 'center', top: '36%', style: { text: String(this.totalAmount), fill: '#ffd740', font: 'bold 44px Arial', textAlign: 'center' } },
+          // { type: 'text', left: 'center', top: '55%', style: { text: '化学品总量(L)', fill: 'rgba(168,204,232,0.65)', font: '20px Arial', textAlign: 'center' } }
+        ],
+        legend: {
+          show:false,
+          orient: 'horizontal', bottom: '2%', icon: 'circle',
+          itemWidth: 24, itemHeight: 24, itemGap: 30,
+          textStyle: { color: '#a8cce8', fontSize: 22 }
+        },
+        series: [{
+          type: 'pie', radius: ['32%', '62%'], center: ['50%', '52%'],
+          avoidLabelOverlap: true,
+          label: {
+            show: true, position: 'outside',
+            formatter: params => `{name|${params.name}}\n{val|${params.value}L  ${params.percent}%}`,
+            rich: {
+              name: { fontSize: 24, color: '#a8cce8', lineHeight: 30 },
+              val:  { fontSize: 22, color: '#ffd740', lineHeight: 26 }
+            },
+            distanceToLabelLine: 10
+          },
+          labelLine: { show: true, length: 25, length2: 35, lineStyle: { color: 'rgba(168,204,232,0.5)' } },
+          data: [
+            { value: this.ctrlAmount,  name: '管控类',   itemStyle: { color: '#ef4444', shadowBlur: 10, shadowColor: 'rgba(239,68,68,0.6)' } },
+            { value: this.freeAmount,  name: '非管控类', itemStyle: { color: '#00d8ff', shadowBlur: 10, shadowColor: 'rgba(0,216,255,0.6)' } }
+          ],
+          emphasis: { label: { fontSize: 26, fontWeight: 'bold' }, scale: true, scaleSize: 8 }
+        }]
+      })
+      window.addEventListener('resize', this.handleResize)
+    },
+    handleResize() {
+      if (this.chart) this.chart.resize()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.chemicals-stats {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  border-radius: 15px;
+  overflow: hidden;
+  background: $bg-panel;
+  border: 1px solid $border;
+}
+
+/* ---- Border beam ---- */
+.border-beam {
+  position: absolute;
+  inset: 0;
+  pointer-events: none;
+  border-radius: inherit;
+  overflow: hidden;
+  z-index: 2;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0; left: -100%;
+    width: 40%; height: 3px;
+    background: linear-gradient(90deg, transparent, rgba(30,144,255,0.9), rgba(0,216,255,0.7), transparent);
+    animation: beamTop 5s linear infinite;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    bottom: 0; right: -100%;
+    width: 40%; height: 3px;
+    background: linear-gradient(90deg, transparent, rgba(0,216,255,0.7), rgba(30,144,255,0.9), transparent);
+    animation: beamBottom 5s linear infinite 2.5s;
+  }
+}
+
+@keyframes beamTop    { from { left: -40%; }  to { left: 100%; } }
+@keyframes beamBottom { from { right: -40%; } to { right: 100%; } }
+
+/* ---- Corner ornaments ---- */
+.pc {
+  position: absolute;
+  width: 35px; height: 35px;
+  z-index: 3; pointer-events: none;
+  &.tl { top: 0; left: 0; }
+  &.tr { top: 0; right: 0; transform: scaleX(-1); }
+  &.bl { bottom: 0; left: 0; transform: scaleY(-1); }
+  &.br { bottom: 0; right: 0; transform: scale(-1); }
+}
+
+/* ---- Panel header ---- */
+.panel-header {
+  display: flex;
+  align-items: center;
+  gap: 25px;
+  padding: 20px 30px 18px;
+  border-bottom: 1px solid $border;
+  background: linear-gradient(90deg, rgba(0,60,160,0.18), transparent);
+  flex-shrink: 0;
+}
+
+.panel-header-icon {
+  width: 65px; height: 65px;
+  border-radius: 12px;
+  flex-shrink: 0;
+  display: flex; align-items: center; justify-content: center;
+  font-size: 32px;
+  background: linear-gradient(135deg, rgba(30,144,255,0.25), rgba(0,216,255,0.15));
+  border: 1px solid rgba(30,144,255,0.35);
+  animation: iconGlow 3s ease-in-out infinite;
+}
+
+@keyframes iconGlow {
+  0%, 100% { box-shadow: 0 0 15px rgba(30,144,255,0.3); }
+  50%       { box-shadow: 0 0 35px rgba(30,144,255,0.7), 0 0 12px rgba(0,216,255,0.3); }
+}
+
+.panel-title {
+  font-size: 30px;
+  font-weight: 600;
+  letter-spacing: 2px;
+  color: $cyan;
+}
+
+/* ---- Stat cards ---- */
+.chem-stat-card {
+  border-radius: 10px;
+  padding: 16px 20px;
+  border: 1px solid $border;
+  background: $bg-card;
+
+  &.total { border-color: rgba(0,216,255,0.3);  background: rgba(0,216,255,0.05); }
+  &.ctrl  { border-color: rgba(239,68,68,0.3);  background: rgba(239,68,68,0.05); }
+  &.free  { border-color: rgba(0,216,255,0.3);  background: rgba(0,216,255,0.05); }
+  &.cat   { border-color: rgba(255,215,64,0.2); background: rgba(255,215,64,0.04); }
+}
+
+.csc-label {
+  font-size: 24px;
+  color: $text-dim;
+  margin-bottom: 6px;
+  letter-spacing: 1px;
+}
+.csc-label-a{
+  font-size: 24px;
+  color: $text-dim;
+  margin-bottom: 6px;
+  letter-spacing: 1px;
+  text-align: center;
+}
+
+.csc-value {
+  font-size: 44px;
+  font-weight: 700;
+  color: #ef4444;
+  line-height: 1.1;
+}
+.csc-value-a{
+  font-size: 44px;
+  font-weight: 700;
+  color: #fff;
+  line-height: 1.1;
+  text-align: center;
+}
+
+.csc-unit {
+  font-size: 24px;
+  font-weight: 400;
+  color: $text-dim;
+  margin-left: 4px;
+}
+
+.csc-sub {
+  font-size: 22px;
+  color: rgba(239,68,68,0.55);
+  margin-top: 8px;
+}
+
+.csc-pct-ring { flex-shrink: 0; }
+</style>

+ 82 - 87
src/components/EquipmentStats.vue

@@ -5,14 +5,15 @@
       <div class="panel-header-icon">🔬</div>
       <span class="panel-title">实验室设备分类及使用统计</span>
     </div>
-    <!-- 4:2:4 vertical layout -->
+    <!-- 4:6 vertical layout -->
     <div class="equip-layout">
-      <!-- Top: ring/donut chart - equipment classification -->
+      <!-- Top flex:4: ring chart - device classification -->
       <div class="equip-section equip-top">
         <div ref="ringChart" class="ring-chart"></div>
       </div>
-      <!-- Middle: 3 stat items -->
-      <div class="equip-section equip-mid">
+      <!-- Bottom flex:6: stats row + two pie charts -->
+      <div class="equip-section equip-bottom">
+        <!-- 3 stat items -->
         <div class="equip-mid-row">
           <div class="equip-stat-item">
             <div class="ev">{{ totalEquipment.toLocaleString() }}</div>
@@ -27,10 +28,17 @@
             <div class="el">设备使用率</div>
           </div>
         </div>
-      </div>
-      <!-- Bottom: pie chart - usage status -->
-      <div class="equip-section equip-bottom">
-        <div ref="pieChart" class="pie-chart"></div>
+        <!-- Two pie charts side by side -->
+        <div class="equip-pie-row">
+          <div class="equip-pie-col">
+            <div class="pie-col-title">设备状态统计</div>
+            <div ref="pieStatusChart" class="pie-chart"></div>
+          </div>
+          <div class="equip-pie-col">
+            <div class="pie-col-title">使用状态统计</div>
+            <div ref="pieUsageChart" class="pie-chart"></div>
+          </div>
+        </div>
       </div>
     </div>
   </div>
@@ -58,7 +66,8 @@ export default {
       categories: [],
       usageStatus: [],
       ringChart: null,
-      pieChart: null,
+      pieStatusChart: null,
+      pieUsageChart: null,
       pollTimer: null
     }
   },
@@ -69,7 +78,8 @@ export default {
   beforeDestroy() {
     if (this.pollTimer) clearInterval(this.pollTimer)
     if (this.ringChart) this.ringChart.dispose()
-    if (this.pieChart) this.pieChart.dispose()
+    if (this.pieStatusChart) this.pieStatusChart.dispose()
+    if (this.pieUsageChart) this.pieUsageChart.dispose()
     window.removeEventListener('resize', this.handleResize)
   },
   methods: {
@@ -96,16 +106,19 @@ export default {
         // 保持默认值
       }
       if (this.ringChart) { this.ringChart.dispose(); this.ringChart = null }
-      if (this.pieChart)  { this.pieChart.dispose();  this.pieChart  = null }
+      if (this.pieStatusChart) { this.pieStatusChart.dispose(); this.pieStatusChart = null }
+      if (this.pieUsageChart)  { this.pieUsageChart.dispose();  this.pieUsageChart  = null }
       this.$nextTick(() => {
         this.initRingChart()
-        this.initPieChart()
+        this.initPieStatusChart()
+        this.initPieUsageChart()
         window.addEventListener('resize', this.handleResize)
       })
     },
     handleResize() {
       if (this.ringChart) this.ringChart.resize()
-      if (this.pieChart) this.pieChart.resize()
+      if (this.pieStatusChart) this.pieStatusChart.resize()
+      if (this.pieUsageChart) this.pieUsageChart.resize()
     },
     /** 初始化环形图 - 设备分类 */
     initRingChart() {
@@ -182,83 +195,41 @@ export default {
       }
       this.ringChart.setOption(option)
     },
-    /** 初始化饼图 - 设备使用状态 */
-    initPieChart() {
-      this.pieChart = echarts.init(this.$refs.pieChart, null, { renderer: 'canvas', devicePixelRatio: 2 })
-
-      // Build count map for legend formatter
+    /** 初始化饼图 - 设备状态统计 */
+    initPieStatusChart() {
+      this.pieStatusChart = echarts.init(this.$refs.pieStatusChart, null, { renderer: 'canvas', devicePixelRatio: 2 })
+      this.pieStatusChart.setOption(this._buildPieOption(this.usageStatus))
+    },
+    /** 初始化饼图 - 使用状态统计 */
+    initPieUsageChart() {
+      this.pieUsageChart = echarts.init(this.$refs.pieUsageChart, null, { renderer: 'canvas', devicePixelRatio: 2 })
+      this.pieUsageChart.setOption(this._buildPieOption(this.usageStatus))
+    },
+    _buildPieOption(list) {
       const countMap = {}
-      this.usageStatus.forEach(s => { countMap[s.name] = s.value })
-
-      const pieData = this.usageStatus.map(s => ({
-        value: s.value,
-        name: s.name,
-        itemStyle: {
-          color: s.color,
-          shadowBlur: 10,
-          shadowColor: s.color.replace(')', ',0.5)').replace('rgb', 'rgba')
-        }
+      list.forEach(s => { countMap[s.name] = s.value })
+      const pieData = list.map(s => ({
+        value: s.value, name: s.name,
+        itemStyle: { color: s.color, shadowBlur: 10, shadowColor: s.color }
       }))
-
-      const option = {
+      return {
         backgroundColor: 'transparent',
-        tooltip: {
-          trigger: 'item',
-          formatter: '{b}: {c}台 ({d}%)',
-          ...TOOLTIP_CFG
-        },
+        tooltip: { trigger: 'item', formatter: '{b}: {c}台 ({d}%)', ...TOOLTIP_CFG },
         legend: {
-          show: true,
-          orient: 'vertical',
-          right: '2%',
-          top: 'middle',
-          icon: 'circle',
-          itemWidth: 36,
-          itemHeight: 36,
-          itemGap: 40,
-          textStyle: { color: '#a8cce8', fontSize: 28 },
-          formatter: function(name) {
-            return name + ' ' + countMap[name] + '台'
-          },
-          rich: {
-            nm: { fontSize: 28, color: '#a8cce8', width: 90 },
-            vl: { fontSize: 34, fontWeight: 700, color: '#fff' }
-          }
+          orient: 'vertical', right: '2%', top: 'middle',
+          icon: 'circle', itemWidth: 28, itemHeight: 28, itemGap: 28,
+          textStyle: { color: '#a8cce8', fontSize: 22 },
+          formatter: name => name + ' ' + countMap[name] + '台'
         },
         series: [{
-          type: 'pie',
-          radius: ['36%', '62%'],
-          center: ['38%', '52%'],
-          itemStyle: {
-            borderRadius: 5,
-            borderColor: 'rgba(3,14,31,0.5)',
-            borderWidth: 2
-          },
-          label: {
-            show: true,
-            formatter: '{b}\n{c}台',
-            fontSize: 24,
-            color: '#a8cce8',
-            lineHeight: 36,
-            distanceToLabelLine: 6
-          },
-          labelLine: {
-            length: 18,
-            length2: 14,
-            lineStyle: { color: 'rgba(30,144,255,0.4)', width: 2 }
-          },
+          type: 'pie', radius: ['36%', '62%'], center: ['38%', '52%'],
+          itemStyle: { borderRadius: 5, borderColor: 'rgba(3,14,31,0.5)', borderWidth: 2 },
+          label: { show: true, formatter: '{b}\n{c}台', fontSize: 20, color: '#a8cce8', lineHeight: 30 },
+          labelLine: { length: 14, length2: 10, lineStyle: { color: 'rgba(30,144,255,0.4)', width: 2 } },
           data: pieData,
-          emphasis: {
-            scale: true,
-            scaleSize: 6,
-            itemStyle: {
-              shadowBlur: 25,
-              shadowColor: 'rgba(30,144,255,0.6)'
-            }
-          }
+          emphasis: { scale: true, scaleSize: 6, itemStyle: { shadowBlur: 25, shadowColor: 'rgba(30,144,255,0.6)' } }
         }]
       }
-      this.pieChart.setOption(option)
     }
   }
 }
@@ -269,7 +240,7 @@ export default {
   position: relative;
   display: flex;
   flex-direction: column;
-  height: 100%;
+  height:1150px;
 }
 
 /* ---- Border beam ---- */
@@ -362,14 +333,38 @@ export default {
   flex: 4;
 }
 
-.equip-mid {
-  flex: 2;
+.equip-bottom {
+  flex: 6;
   display: flex;
-  align-items: center;
+  flex-direction: column;
+  gap: 10px;
 }
 
-.equip-bottom {
-  flex: 4;
+.equip-pie-row {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  gap: 12px;
+}
+
+.equip-pie-col {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+
+  .pie-col-title {
+    text-align: center;
+    font-size: 22px;
+    font-weight: 600;
+    letter-spacing: 3px;
+    color: $cyan;
+    padding: 5px 0 7px;
+    border-bottom: 1px solid rgba(0, 216, 255, 0.22);
+    text-shadow: 0 0 10px rgba(0, 216, 255, 0.45);
+    flex-shrink: 0;
+  }
 }
 
 .ring-chart,

+ 5 - 1
src/router/index.js

@@ -31,7 +31,11 @@ const router = new VueRouter({
 router.beforeEach((to, from, next) => {
   const token = localStorage.getItem('token')
   if (to.meta.requiresAuth && !token) {
-    next('/login')
+    if (to.path !== '/login') {
+      next('/login')
+    } else {
+      next()
+    }
   } else {
     next()
   }

+ 4 - 1
src/views/Screen.vue

@@ -14,6 +14,7 @@
       <div class="panel-col">
         <EnvMonitorStats />
         <EquipmentStats />
+        <ChemicalsStats />
       </div>
       <!-- 3区:实时监控 -->
       <div class="center-col">
@@ -42,6 +43,7 @@ import SafetyCompliance from '@/components/SafetyCompliance.vue'
 import PersonnelTrend from '@/components/PersonnelTrend.vue'
 import EnvMonitorStats from '@/components/EnvMonitorStats.vue'
 import EquipmentStats from '@/components/EquipmentStats.vue'
+import ChemicalsStats from '@/components/ChemicalsStats.vue'
 import SecurityMonitor from '@/components/SecurityMonitor.vue'
 import LabEnvironment from '@/components/LabEnvironment.vue'
 import AlarmInfo from '@/components/AlarmInfo.vue'
@@ -59,6 +61,7 @@ export default {
     PersonnelTrend,
     EnvMonitorStats,
     EquipmentStats,
+    ChemicalsStats,
     SecurityMonitor,
     LabEnvironment,
     AlarmInfo,
@@ -239,7 +242,7 @@ export default {
 /* 第2列:设备统计(666) + 设备分类(1916) */
 .panel-col:nth-child(2) {
   > :nth-child(1) { flex: 666; min-height: 0; }
-  > :nth-child(2) { flex: 1916; min-height: 0; }
+  /*> :nth-child(2) { flex: 1916; min-height: 0; }*/
 }
 
 /* 第4列:环境感知(1807) + 风险预警(775) */