|
@@ -0,0 +1,570 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="panel lab-stats">
|
|
|
|
|
+ <!-- Border beam animation -->
|
|
|
|
|
+ <div class="border-beam"></div>
|
|
|
|
|
+ <!-- Corner ornaments -->
|
|
|
|
|
+ <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>
|
|
|
|
|
+ <!-- Panel header -->
|
|
|
|
|
+ <div class="panel-header">
|
|
|
|
|
+ <div class="panel-header-icon">🏛️</div>
|
|
|
|
|
+ <span class="panel-title">实验室基本情况统计</span>
|
|
|
|
|
+ <div class="status-dot green"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Upper section: left gauge + right donut -->
|
|
|
|
|
+ <div class="upper-section">
|
|
|
|
|
+ <!-- Left: SVG gauge showing total -->
|
|
|
|
|
+ <div class="gauge-side">
|
|
|
|
|
+ <svg width="320" height="320" viewBox="0 0 320 320">
|
|
|
|
|
+ <defs>
|
|
|
|
|
+ <linearGradient id="arcG1" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
|
|
|
+ <stop offset="0%" stop-color="#1e90ff"/>
|
|
|
|
|
+ <stop offset="100%" stop-color="#00d8ff"/>
|
|
|
|
|
+ </linearGradient>
|
|
|
|
|
+ <filter id="glow1">
|
|
|
|
|
+ <feGaussianBlur stdDeviation="4" result="blur"/>
|
|
|
|
|
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
|
|
|
+ </filter>
|
|
|
|
|
+ </defs>
|
|
|
|
|
+ <!-- Outermost decorative ring -->
|
|
|
|
|
+ <circle cx="160" cy="160" r="148" fill="none" stroke="rgba(30,144,255,0.08)" stroke-width="1" stroke-dasharray="6 6"/>
|
|
|
|
|
+ <!-- Outer track -->
|
|
|
|
|
+ <circle cx="160" cy="160" r="136" fill="none" stroke="rgba(30,144,255,0.14)" stroke-width="3"/>
|
|
|
|
|
+ <!-- Main arc 300deg -->
|
|
|
|
|
+ <circle cx="160" cy="160" r="136" fill="none" stroke="url(#arcG1)" stroke-width="8"
|
|
|
|
|
+ stroke-dasharray="711 143" stroke-linecap="round" transform="rotate(-240 160 160)" filter="url(#glow1)"/>
|
|
|
|
|
+ <!-- Inner track -->
|
|
|
|
|
+ <circle cx="160" cy="160" r="110" fill="rgba(3,14,31,0.7)" stroke="rgba(0,216,255,0.15)" stroke-width="2"/>
|
|
|
|
|
+ <!-- Four-level color arcs -->
|
|
|
|
|
+ <circle cx="160" cy="160" r="88" fill="none" stroke="#cc0000" stroke-width="12"
|
|
|
|
|
+ :stroke-dasharray="levelArcs[0].dash" stroke-linecap="round" transform="rotate(-90 160 160)" opacity="0.9"/>
|
|
|
|
|
+ <circle cx="160" cy="160" r="88" fill="none" stroke="#ff8000" stroke-width="12"
|
|
|
|
|
+ :stroke-dasharray="levelArcs[1].dash" stroke-linecap="round" :transform="'rotate(' + levelArcs[1].rotate + ' 160 160)'" opacity="0.9"/>
|
|
|
|
|
+ <circle cx="160" cy="160" r="88" fill="none" stroke="#ffcc00" stroke-width="12"
|
|
|
|
|
+ :stroke-dasharray="levelArcs[2].dash" stroke-linecap="round" :transform="'rotate(' + levelArcs[2].rotate + ' 160 160)'" opacity="0.9"/>
|
|
|
|
|
+ <circle cx="160" cy="160" r="88" fill="none" stroke="#0066cc" stroke-width="12"
|
|
|
|
|
+ :stroke-dasharray="levelArcs[3].dash" stroke-linecap="round" :transform="'rotate(' + levelArcs[3].rotate + ' 160 160)'" opacity="0.9"/>
|
|
|
|
|
+ <!-- Endpoint glow -->
|
|
|
|
|
+ <circle cx="160" cy="24" r="6" fill="#1e90ff" opacity="0.9"/>
|
|
|
|
|
+ <!-- Center number -->
|
|
|
|
|
+ <text x="160" y="145" text-anchor="middle" fill="#ffd740" font-size="72" font-weight="900" font-family="Arial,sans-serif" letter-spacing="-2">{{ total }}</text>
|
|
|
|
|
+ <text x="160" y="190" text-anchor="middle" fill="rgba(168,204,232,0.75)" font-size="26" font-family="Arial,sans-serif" letter-spacing="4">间</text>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ <!-- Labels -->
|
|
|
|
|
+ <div class="gauge-label">
|
|
|
|
|
+ <div class="gauge-label-main">实验室总数</div>
|
|
|
|
|
+ <div class="gauge-label-sub">TOTAL LABORATORIES</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- Color bar showing proportions -->
|
|
|
|
|
+ <div class="color-bar-wrap">
|
|
|
|
|
+ <div class="color-bar">
|
|
|
|
|
+ <div v-for="level in levels" :key="level.name" :style="{ flex: level.value, background: level.color, boxShadow: '0 0 10px ' + level.color + '80' }"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="color-bar-labels">
|
|
|
|
|
+ <span v-for="level in levels" :key="'lbl-' + level.name" :style="{ flex: level.value }">{{ level.name.replace('级', '') }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Right: ECharts donut + level detail list -->
|
|
|
|
|
+ <div class="donut-side">
|
|
|
|
|
+ <div ref="donutChart" class="donut-chart"></div>
|
|
|
|
|
+ <div class="level-list">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="level in levels"
|
|
|
|
|
+ :key="'detail-' + level.name"
|
|
|
|
|
+ class="level-item"
|
|
|
|
|
+ :style="{ background: level.color + '12', borderLeftColor: level.color }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="level-item-left">
|
|
|
|
|
+ <span class="level-dot" :style="{ background: level.color, boxShadow: '0 0 8px ' + level.color + 'cc' }"></span>
|
|
|
|
|
+ {{ level.label }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span class="level-item-val" :style="{ color: levelValColor(level.color) }">{{ level.value }}间</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Lower section: 3 status badges -->
|
|
|
|
|
+ <div class="lower-section">
|
|
|
|
|
+ <div class="status-row">
|
|
|
|
|
+ <div class="status-badge active">
|
|
|
|
|
+ <div class="val">{{ status.active }}</div>
|
|
|
|
|
+ <div class="lbl">使用(间)</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="status-badge warning">
|
|
|
|
|
+ <div class="val">{{ status.warning }}</div>
|
|
|
|
|
+ <div class="lbl">异常(间)</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="status-badge idle">
|
|
|
|
|
+ <div class="val">{{ status.idle }}</div>
|
|
|
|
|
+ <div class="lbl">空闲(间)</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import * as echarts from 'echarts'
|
|
|
|
|
+import { getLabStats } from '@/api/screen'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'EventStats',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ total: 0,
|
|
|
|
|
+ levels: [],
|
|
|
|
|
+ status: { active: 0, warning: 0, idle: 0 },
|
|
|
|
|
+ chart: null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ /** 计算SVG仪表盘中四个分级色弧的dasharray和旋转角度 */
|
|
|
|
|
+ levelArcs() {
|
|
|
|
|
+ if (!this.levels.length) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ { dash: '0 553', rotate: -90 },
|
|
|
|
|
+ { dash: '0 553', rotate: -90 },
|
|
|
|
|
+ { dash: '0 553', rotate: -90 },
|
|
|
|
|
+ { dash: '0 553', rotate: -90 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ const circumference = 2 * Math.PI * 88 // ~553
|
|
|
|
|
+ const totalVal = this.levels.reduce((s, l) => s + l.value, 0)
|
|
|
|
|
+ const arcs = []
|
|
|
|
|
+ let accAngle = -90 // start at top
|
|
|
|
|
+ for (let i = 0; i < this.levels.length; i++) {
|
|
|
|
|
+ const ratio = this.levels[i].value / totalVal
|
|
|
|
|
+ const arcLen = Math.round(ratio * circumference)
|
|
|
|
|
+ const gapLen = Math.round(circumference - arcLen)
|
|
|
|
|
+ arcs.push({
|
|
|
|
|
+ dash: arcLen + ' ' + gapLen,
|
|
|
|
|
+ rotate: Math.round(accAngle)
|
|
|
|
|
+ })
|
|
|
|
|
+ accAngle += ratio * 360
|
|
|
|
|
+ }
|
|
|
|
|
+ return arcs
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ this.fetchData()
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ if (this.chart) {
|
|
|
|
|
+ this.chart.dispose()
|
|
|
|
|
+ this.chart = null
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ async fetchData() {
|
|
|
|
|
+ const res = await getLabStats()
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ this.total = res.data.total
|
|
|
|
|
+ this.levels = res.data.levels
|
|
|
|
|
+ this.status = res.data.status
|
|
|
|
|
+ this.$nextTick(() => this.initDonutChart())
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 根据分级主色计算稍亮的数值展示色 */
|
|
|
|
|
+ levelValColor(color) {
|
|
|
|
|
+ const map = {
|
|
|
|
|
+ '#cc0000': '#ff6666',
|
|
|
|
|
+ '#ff8000': '#ffaa44',
|
|
|
|
|
+ '#ffcc00': '#ffe066',
|
|
|
|
|
+ '#0066cc': '#4499ff'
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[color] || color
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 初始化ECharts环形图 - 展示四级分类占比,中心显示总数 */
|
|
|
|
|
+ initDonutChart() {
|
|
|
|
|
+ this.chart = echarts.init(this.$refs.donutChart, null, {
|
|
|
|
|
+ renderer: 'canvas',
|
|
|
|
|
+ devicePixelRatio: 2
|
|
|
|
|
+ })
|
|
|
|
|
+ const option = {
|
|
|
|
|
+ backgroundColor: 'transparent',
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ trigger: 'item',
|
|
|
|
|
+ formatter: '{b}: {c}间 ({d}%)',
|
|
|
|
|
+ backgroundColor: 'rgba(3,14,42,0.92)',
|
|
|
|
|
+ borderColor: 'rgba(30,144,255,0.3)',
|
|
|
|
|
+ textStyle: { color: '#a8cce8', fontSize: 28 }
|
|
|
|
|
+ },
|
|
|
|
|
+ graphic: [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ top: '40%',
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: String(this.total),
|
|
|
|
|
+ fill: '#ffd740',
|
|
|
|
|
+ font: 'bold 80px Arial',
|
|
|
|
|
+ textAlign: 'center'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'text',
|
|
|
|
|
+ left: 'center',
|
|
|
|
|
+ top: '58%',
|
|
|
|
|
+ style: {
|
|
|
|
|
+ text: '实验室总数(间)',
|
|
|
|
|
+ fill: 'rgba(168,204,232,0.65)',
|
|
|
|
|
+ font: '22px Arial',
|
|
|
|
|
+ textAlign: 'center'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ series: [{
|
|
|
|
|
+ type: 'pie',
|
|
|
|
|
+ radius: ['42%', '68%'],
|
|
|
|
|
+ center: ['50%', '50%'],
|
|
|
|
|
+ startAngle: 100,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ borderRadius: 6,
|
|
|
|
|
+ borderColor: 'rgba(3,14,31,0.6)',
|
|
|
|
|
+ borderWidth: 3
|
|
|
|
|
+ },
|
|
|
|
|
+ label: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ formatter: function (params) {
|
|
|
|
|
+ return '{lvl|' + params.name + '}\n{cnt|' + params.value + '间}\n{pct|' + params.percent + '%}'
|
|
|
|
|
+ },
|
|
|
|
|
+ rich: {
|
|
|
|
|
+ lvl: { fontSize: 24, fontWeight: 700, color: '#ddf0ff', lineHeight: 36 },
|
|
|
|
|
+ cnt: { fontSize: 30, fontWeight: 900, color: '#ffd740', lineHeight: 42 },
|
|
|
|
|
+ pct: { fontSize: 22, color: 'rgba(168,204,232,0.6)', lineHeight: 32 }
|
|
|
|
|
+ },
|
|
|
|
|
+ distanceToLabelLine: 8
|
|
|
|
|
+ },
|
|
|
|
|
+ labelLine: {
|
|
|
|
|
+ show: true,
|
|
|
|
|
+ length: 20,
|
|
|
|
|
+ length2: 25,
|
|
|
|
|
+ lineStyle: { color: 'rgba(30,144,255,0.45)', width: 2 }
|
|
|
|
|
+ },
|
|
|
|
|
+ data: this.levels.map(function (l) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ value: l.value,
|
|
|
|
|
+ name: l.name,
|
|
|
|
|
+ itemStyle: {
|
|
|
|
|
+ color: l.color,
|
|
|
|
|
+ shadowBlur: 12,
|
|
|
|
|
+ shadowColor: l.color + '80'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }),
|
|
|
|
|
+ emphasis: {
|
|
|
|
|
+ scale: true,
|
|
|
|
|
+ scaleSize: 8,
|
|
|
|
|
+ itemStyle: { shadowBlur: 30, shadowColor: 'rgba(30,144,255,0.6)' },
|
|
|
|
|
+ label: { fontSize: 28 }
|
|
|
|
|
+ }
|
|
|
|
|
+ }]
|
|
|
|
|
+ }
|
|
|
|
|
+ this.chart.setOption(option)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.lab-stats {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ border-radius: 15px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: $bg-panel;
|
|
|
|
|
+ border: 1px solid $border;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.lab-stats::before {
|
|
|
|
|
+ content: '';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ border-radius: inherit;
|
|
|
|
|
+ background: linear-gradient(135deg, rgba(30, 144, 255, 0.05) 0%, transparent 50%, rgba(0, 216, 255, 0.03) 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- 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;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- Status dot ---- */
|
|
|
|
|
+.status-dot {
|
|
|
|
|
+ width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ animation: dotPulse 2s ease-in-out infinite;
|
|
|
|
|
+
|
|
|
|
|
+ &.green {
|
|
|
|
|
+ background: $green;
|
|
|
|
|
+ box-shadow: 0 0 15px $green;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes dotPulse {
|
|
|
|
|
+ 0%, 100% { transform: scale(1); opacity: 1; }
|
|
|
|
|
+ 50% { transform: scale(1.3); opacity: 0.7; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- Upper section ---- */
|
|
|
|
|
+.upper-section {
|
|
|
|
|
+ flex: 4;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Left gauge side */
|
|
|
|
|
+.gauge-side {
|
|
|
|
|
+ flex: 4;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 18px;
|
|
|
|
|
+ padding: 25px 20px;
|
|
|
|
|
+ border-right: 1px solid $border;
|
|
|
|
|
+ background: linear-gradient(180deg, rgba(30, 144, 255, 0.04), transparent);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.gauge-label {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+
|
|
|
|
|
+ &-main {
|
|
|
|
|
+ font-size: 30px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: $cyan;
|
|
|
|
|
+ letter-spacing: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &-sub {
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ color: $text-dim;
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+ letter-spacing: 2px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.color-bar-wrap {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.color-bar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+ height: 12px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.color-bar-labels {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ color: $text-dim;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Right donut side */
|
|
|
|
|
+.donut-side {
|
|
|
|
|
+ flex: 6;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 25px 25px 20px;
|
|
|
|
|
+ gap: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.donut-chart {
|
|
|
|
|
+ flex: 0 0 340px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- Level detail list ---- */
|
|
|
|
|
+.level-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.level-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ font-size: 26px;
|
|
|
|
|
+ padding: 10px 16px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ border-left: 4px solid transparent;
|
|
|
|
|
+
|
|
|
|
|
+ &-left {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &-val {
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.level-dot {
|
|
|
|
|
+ width: 16px;
|
|
|
|
|
+ height: 16px;
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ---- Lower section ---- */
|
|
|
|
|
+.lower-section {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 0 25px 20px;
|
|
|
|
|
+ border-top: 1px solid $border;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.status-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.status-badge {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 15px 10px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border: 1px solid $border;
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ border-color: $green;
|
|
|
|
|
+ background: rgba(0, 230, 118, 0.07);
|
|
|
|
|
+
|
|
|
|
|
+ .val { color: $green; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.warning {
|
|
|
|
|
+ border-color: #f59e0b;
|
|
|
|
|
+ background: rgba(245, 158, 11, 0.07);
|
|
|
|
|
+
|
|
|
|
|
+ .val { color: #f59e0b; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.idle {
|
|
|
|
|
+ border-color: $indigo;
|
|
|
|
|
+ background: rgba(67, 97, 238, 0.07);
|
|
|
|
|
+
|
|
|
|
|
+ .val { color: $indigo; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .val {
|
|
|
|
|
+ font-size: 48px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .lbl {
|
|
|
|
|
+ font-size: 25px;
|
|
|
|
|
+ color: $text-dim;
|
|
|
|
|
+ margin-top: 5px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|