From 6cd15263ce5c80b8f9879c5f3445cee85404a576 Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期二, 31 三月 2026 15:57:38 +0800
Subject: [PATCH] 生产大屏

---
 src/views/productionManagement/largeScreen/index.vue | 1089 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/assets/BI/kpiIcons/kpi-cartCutout.png            |    0 
 src/assets/BI/kpiIcons/kpi-dataBoard.png             |    0 
 src/assets/BI/kpiIcons/kpi-dbIcon.png                |    0 
 src/assets/BI/kpiIcons/kpi-cloud.png                 |    0 
 src/assets/BI/kpiIcons/kpi-shield.png                |    0 
 6 files changed, 1,089 insertions(+), 0 deletions(-)

diff --git a/src/assets/BI/kpiIcons/kpi-cartCutout.png b/src/assets/BI/kpiIcons/kpi-cartCutout.png
new file mode 100644
index 0000000..0389888
--- /dev/null
+++ b/src/assets/BI/kpiIcons/kpi-cartCutout.png
Binary files differ
diff --git a/src/assets/BI/kpiIcons/kpi-cloud.png b/src/assets/BI/kpiIcons/kpi-cloud.png
new file mode 100644
index 0000000..59c5087
--- /dev/null
+++ b/src/assets/BI/kpiIcons/kpi-cloud.png
Binary files differ
diff --git a/src/assets/BI/kpiIcons/kpi-dataBoard.png b/src/assets/BI/kpiIcons/kpi-dataBoard.png
new file mode 100644
index 0000000..a7bd00a
--- /dev/null
+++ b/src/assets/BI/kpiIcons/kpi-dataBoard.png
Binary files differ
diff --git a/src/assets/BI/kpiIcons/kpi-dbIcon.png b/src/assets/BI/kpiIcons/kpi-dbIcon.png
new file mode 100644
index 0000000..f7cc628
--- /dev/null
+++ b/src/assets/BI/kpiIcons/kpi-dbIcon.png
Binary files differ
diff --git a/src/assets/BI/kpiIcons/kpi-shield.png b/src/assets/BI/kpiIcons/kpi-shield.png
new file mode 100644
index 0000000..a63b633
--- /dev/null
+++ b/src/assets/BI/kpiIcons/kpi-shield.png
Binary files differ
diff --git a/src/views/productionManagement/largeScreen/index.vue b/src/views/productionManagement/largeScreen/index.vue
new file mode 100644
index 0000000..eece56d
--- /dev/null
+++ b/src/views/productionManagement/largeScreen/index.vue
@@ -0,0 +1,1089 @@
+<template>
+  <div class="large-screen">
+    <div class="top-kpi-row">
+      <div class="group-card group-card--wide">
+        <div class="group-left">
+          <div class="group-icon group-icon--order"></div>
+        </div>
+        <div class="group-right">
+          <div class="group-metrics">
+            <div class="metric metric--plain">
+              <div class="metric-title">宸ュ崟鎬绘暟</div>
+              <div class="metric-value">{{ kpi.workOrderTotal }}<span class="unit">鏉�</span></div>
+            </div>
+            <div class="metric metric--plain">
+              <div class="metric-title">杩涜涓伐鍗�</div>
+              <div class="metric-value">{{ kpi.workOrderDoing }}<span class="unit">鏉�</span></div>
+            </div>
+            <div class="metric metric--plain">
+              <div class="metric-title">瀹屾垚宸ュ崟</div>
+              <div class="metric-value">{{ kpi.workOrderDone }}<span class="unit">鏉�</span></div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="group-card group-card--wide">
+        <div class="group-left">
+          <div class="group-icon group-icon--quality"></div>
+        </div>
+        <div class="group-right">
+          <div class="group-metrics">
+            <div class="metric metric--plain">
+              <div class="metric-title">鍚堟牸鐜�</div>
+              <div class="metric-value">{{ kpi.passRate }}<span class="unit">%</span></div>
+            </div>
+            <div class="metric metric--plain">
+              <div class="metric-title">涓嶈壇鐜�</div>
+              <div class="metric-value">{{ kpi.ngRate }}<span class="unit">%</span></div>
+            </div>
+            <div class="metric metric--plain">
+              <div class="metric-title">鎬绘姤搴熸暟</div>
+              <div class="metric-value">{{ kpi.scrapTotal }}<span class="unit">鏉�</span></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="top-stat-row">
+      <div class="stat-card stat-card--primary">
+        <div class="stat-main">
+          <div class="stat-value">{{ formatMoney(kpi.productionAmount) }}<span class="unit1">鍚�</span></div>
+          <div class="stat-title">鎬荤敓浜ф�婚噺</div>
+          <div class="stat-sub">
+            <span>杈冧笂鏈堣秼鍔匡細</span>
+            <span class="trend" :class="kpi.productionAmountTrend >= 0 ? 'up' : 'down'">
+              {{ kpi.productionAmountTrend >= 0 ? '+' : '' }}{{ kpi.productionAmountTrend }}%
+            </span>
+            <svg
+              class="trend-arrow-svg"
+              :class="[
+                kpi.productionAmountTrend >= 0 ? '' : 'trend-arrow-svg--up',
+                kpi.productionAmountTrend >= 0 ? 'is-up' : 'is-down'
+              ]"
+              viewBox="0 0 16 10"
+              aria-hidden="true"
+            >
+              <path d="M1 2 L6 7 L10 3 L15 3" />
+              <path d="M13 1 L15 3 L13 5" />
+            </svg>
+          </div>
+        </div>
+        <div class="stat-primary-decor"></div>
+      </div>
+
+      <div class="stat-card">
+        <div class="stat-main">
+          <div class="stat-value">{{ formatMoney(kpi.productionCost) }}<span class="unit">鍚�</span></div>
+          <div class="stat-title">鐢熶骇鎬绘秷鑰�</div>
+          <div class="stat-sub">
+            <span>杈冧笂鏈堣秼鍔匡細</span>
+            <span class="trend" :class="kpi.productionCostTrend >= 0 ? 'up' : 'down'">
+              {{ kpi.productionCostTrend >= 0 ? '+' : '' }}{{ kpi.productionCostTrend }}%
+            </span>
+            <svg
+              class="trend-arrow-svg"
+              :class="[
+                kpi.productionCostTrend >= 0 ? '' : 'trend-arrow-svg--up',
+                kpi.productionCostTrend >= 0 ? 'is-up' : 'is-down'
+              ]"
+              viewBox="0 0 16 10"
+              aria-hidden="true"
+            >
+              <path d="M1 2 L6 7 L10 3 L15 3" />
+              <path d="M13 1 L15 3 L13 5" />
+            </svg>
+          </div>
+        </div>
+        <div class="stat-icon stat-icon--cost"></div>
+      </div>
+
+      <div class="stat-card">
+        <div class="stat-main">
+          <div class="stat-value">{{ kpi.supplierCount }}<span class="unit">瀹�</span></div>
+          <div class="stat-title">浜у搧鎬讳緵搴斿叕鍙�</div>
+          <div class="stat-sub">
+            <span>杈冧笂鏈堣秼鍔匡細</span>
+            <span class="trend" :class="kpi.supplierCountTrend >= 0 ? 'up' : 'down'">
+              {{ kpi.supplierCountTrend >= 0 ? '+' : '' }}{{ kpi.supplierCountTrend }}%
+            </span>
+            <svg
+              class="trend-arrow-svg"
+              :class="[
+                kpi.supplierCountTrend >= 0 ? '' : 'trend-arrow-svg--up',
+                kpi.supplierCountTrend >= 0 ? 'is-up' : 'is-down'
+              ]"
+              viewBox="0 0 16 10"
+              aria-hidden="true"
+            >
+              <path d="M1 2 L6 7 L10 3 L15 3" />
+              <path d="M13 1 L15 3 L13 5" />
+            </svg>
+          </div>
+        </div>
+        <div class="stat-icon stat-icon--supplier"></div>
+      </div>
+    </div>
+
+    <div class="mid-row">
+      <div class="panel panel--rect">
+        <div class="panel-header">
+          <div class="panel-title">浜у搧浜у嚭鍒嗘瀽</div>
+        </div>
+        <div class="panel-body">
+          <div class="product-pie-wrapper">
+            <Echarts
+              :chartStyle="chartStyle"
+              :legend="categoryPieLegend"
+              :tooltip="categoryPieTooltip"
+              :series="categoryPieSeries"
+              :xAxis="[]"
+              :yAxis="[]"
+              :color="categoryPieColors"
+              :options="transparentOptions"
+              style="height: 100%"
+            />
+          </div>
+          <div class="category-cards">
+            <div v-for="(it, idx) in categoryPieData" :key="it.name" class="category-card">
+              <div class="category-name">浜у搧澶х被{{ idx + 1 }}</div>
+              <div class="category-val">{{ it.value }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="panel panel--rect">
+        <div class="panel-header">
+          <div class="panel-title">涓嶈壇鍘熷洜缁熻鍒嗘瀽</div>
+        </div>
+        <div class="panel-body">
+          <div class="ng-layout">
+            <div class="ng-list">
+              <div class="ng-list-head">
+                <div class="ng-col ng-col--name">涓嶈壇鍘熷洜</div>
+                <div class="ng-col ng-col--pct">鍗犳瘮</div>
+                <div class="ng-col ng-col--val">鏁伴噺</div>
+              </div>
+              <div class="ng-list-body">
+                <div v-for="(row, idx) in ngRows" :key="row.name" class="ng-row">
+                  <div class="ng-col ng-col--name">
+                    <span class="ng-dot" :style="{ backgroundColor: ngColors[idx % ngColors.length] }"></span>
+                    <span class="ng-name">{{ row.name }}</span>
+                  </div>
+                  <div class="ng-col ng-col--pct">{{ row.percent }}%</div>
+                  <div class="ng-col ng-col--val">{{ row.value }}</div>
+                </div>
+              </div>
+            </div>
+
+            <div class="ng-chart">
+              <div class="donut-wrap">
+                <Echarts
+                  :chartStyle="chartStyle"
+                  :legend="{ show: false }"
+                  :tooltip="pieTooltip"
+                  :series="pieSeries"
+                  :color="ngColors"
+                  :xAxis="[]"
+                  :yAxis="[]"
+                  :options="transparentOptions"
+                  style="height: 100%"
+                />
+                <div class="donut-center">
+                  <div class="donut-label">涓嶈壇鍘熷洜</div>
+                  <div class="donut-value">{{ formatMoney(ngTotal) }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="bottom-row">
+      <div class="panel panel-full">
+        <div class="panel-header">
+          <div class="panel-title">宸ュ簭涓嶈壇鍘熷洜鍒嗘瀽</div>
+          <div class="panel-actions">
+            <div class="panel-action">骞翠唤</div>
+            <el-date-picker
+              v-model="currentYear"
+              type="year"
+              format="YYYY骞�"
+              value-format="YYYY"
+              :clearable="false"
+              class="panel-year-picker"
+              size="small"
+            />
+          </div>
+        </div>
+        <div class="panel-body">
+          <div class="bottom-chart-wrap">
+            <div class="chart-unit">鍗曚綅锛�%</div>
+            <Echarts
+              :chartStyle="{ width: '100%', height: '260px' }"
+              :grid="lineGrid"
+              :legend="lineLegend"
+              :tooltip="axisTooltip"
+              :xAxis="lineXAxis"
+              :yAxis="lineYAxis"
+              :series="lineSeries"
+              :options="transparentOptions"
+              style="height: 100%"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import * as echarts from 'echarts'
+import Echarts from '@/components/Echarts/echarts.vue'
+import { ElSelect, ElOption, ElDatePicker } from 'element-plus'
+
+const chartStyle = { width: '100%', height: '100%' }
+const transparentOptions = {
+  backgroundColor: 'transparent',
+  textStyle: { color: '#3a3a3a' },
+}
+
+const kpi = ref({
+  workOrderTotal: 378,
+  workOrderDoing: 108,
+  workOrderDone: 238,
+  passRate: 98,
+  ngRate: 8,
+  scrapTotal: 38,
+  productionAmount: 989878,
+  productionAmountTrend: 16,
+  productionQty: 19878,
+  productionQtyTrend: 16,
+  productionCost: 69878,
+  productionCostTrend: -16,
+  supplierCount: 48,
+  supplierCountTrend: 16,
+  totalOutput: 1000,
+})
+
+const currentYear = ref('2025')
+
+function formatMoney(n) {
+  const num = Number(n || 0)
+  return num.toLocaleString('zh-CN', { maximumFractionDigits: 0 })
+}
+
+const axisTooltip = {
+  trigger: 'axis',
+  axisPointer: { type: 'shadow' },
+  formatter: (params) => {
+    const first = params?.[0]
+    const x = first?.axisValueLabel || ''
+    const lines = [`${x}`]
+    params.forEach((p) => {
+      if (!p) return
+      const name = p.seriesName
+      const v = p.data
+      lines.push(`${name}: ${v}`)
+    })
+    return lines.join('<br/>')
+  },
+}
+
+// 涓儴宸︿晶锛氶ゼ鐘跺浘锛堟寜浜у搧澶х被缁熻浜у嚭鏁伴噺锛�
+const categoryPieColors = ['#2D5BFF', '#4E8AFF', '#00A4ED', '#26C6DA', '#7C3AED', '#F59E0B', '#EF4444', '#10B981']
+const categoryPieData = ref([
+  { name: '浜у搧澶х被1', value: 600 },
+  { name: '浜у搧澶х被2', value: 200 },
+  { name: '浜у搧澶х被3', value: 200 },
+  { name: '浜у搧澶х被4', value: 200 },
+  { name: '浜у搧澶х被5', value: 200 },
+])
+
+const categoryPieLegend = computed(() => {
+  // 璁捐鍥句腑楗煎浘鏈韩鏄剧ず鍗犳瘮鏍囩锛屼笉棰濆灞曠ず鍥句緥
+  return {
+    show: false,
+    data: categoryPieData.value.map((d) => d.name),
+  }
+})
+
+const categoryPieTooltip = {
+  trigger: 'item',
+  formatter: (p) => `${p.marker} ${p.name}<br/>浜у嚭鏁伴噺锛�${p.value}<br/>鍗犳瘮锛�${p.percent}%`,
+}
+
+const categoryPieSeries = computed(() => [
+  {
+    name: '浜у嚭鏁伴噺',
+    type: 'pie',
+    radius: ['40%', '68%'],
+    center: ['50%', '45%'],
+    avoidLabelOverlap: false,
+    label: {
+      show: true,
+      formatter: (p) => `${p.name} ${p.percent}%`,
+      color: '#6b7280',
+      fontSize: 12,
+    },
+    labelLine: {
+      show: true,
+      lineStyle: { color: '#e5e7eb' },
+      length: 16,
+      length2: 20,
+    },
+    data: categoryPieData.value,
+    emphasis: {
+      scale: true,
+      scaleSize: 8,
+    },
+  },
+])
+
+// 涓儴鍙充晶锛氱幆褰㈤ゼ鍥�
+const ngColors = ['#2D5BFF', '#26C6DA', '#F59E0B', '#7C3AED', '#60A5FA', '#10B981']
+
+const ngReasonData = ref([
+  { name: '涓嶈壇鍘熷洜1', value: 300 },
+  { name: '涓嶈壇鍘熷洜3', value: 200 },
+  { name: '涓嶈壇鍘熷洜4', value: 100 },
+  { name: '涓嶈壇鍘熷洜5', value: 100 },
+  { name: '涓嶈壇鍘熷洜6', value: 100 },
+])
+
+const ngTotal = computed(() => ngReasonData.value.reduce((s, it) => s + Number(it.value || 0), 0))
+const ngRows = computed(() => {
+  const total = ngTotal.value || 0
+  return ngReasonData.value.map((it) => {
+    const v = Number(it.value || 0)
+    const p = total ? Math.round((v / total) * 100) : 0
+    return { name: it.name, value: v, percent: p }
+  })
+})
+
+const pieTooltip = {
+  trigger: 'item',
+  formatter: (p) => `${p.marker} ${p.name}锛�${p.value}锛�${p.percent}%锛塦,
+}
+const pieSeries = computed(() => [
+  {
+    name: '涓嶈壇鍘熷洜',
+    type: 'pie',
+    radius: ['52%', '76%'],
+    center: ['50%', '50%'],
+    avoidLabelOverlap: true,
+    label: { show: false },
+    labelLine: { show: false },
+    data: ngReasonData.value,
+  },
+])
+
+// 搴曢儴锛氭姌绾垮浘锛堝勾浠借仈鍔ㄧ殑姣忎釜宸ュ簭涓嶈壇鐜囷級
+const lineGrid = { left: '4%', right: '4%', top: 60, bottom: 30, containLabel: true }
+const lineLegend = computed(() => {
+  return {
+    show: true,
+    top: 8,
+    textStyle: { color: '#666' },
+    data: ['骞冲潎涓嶈壇鐜�', '宸ュ簭A', '宸ュ簭B', '宸ュ簭C'],
+  }
+})
+
+const chartDataByYear = {
+  '2023': {
+    x: ['2023/02/21', '2023/03/02', '2023/03/13', '2023/03/24', '2023/04/04', '2023/04/15', '2023/04/28'],
+    bar: [30, 32, 35, 40, 38, 42, 39],
+    lineA: [45, 48, 52, 60, 55, 62, 58],
+    lineB: [30, 32, 38, 45, 40, 48, 42],
+    lineC: [20, 24, 30, 35, 32, 38, 33],
+  },
+  '2024': {
+    x: ['2024/02/21', '2024/03/02', '2024/03/13', '2024/03/24', '2024/04/04', '2024/04/15', '2024/04/28'],
+    bar: [28, 30, 33, 37, 35, 39, 36],
+    lineA: [42, 46, 50, 58, 53, 60, 56],
+    lineB: [28, 31, 36, 43, 38, 46, 40],
+    lineC: [18, 21, 27, 33, 29, 35, 30],
+  },
+  '2025': {
+    x: ['2025/02/21', '2025/03/02', '2025/03/13', '2025/03/24', '2025/04/04', '2025/04/15', '2025/04/28'],
+    bar: [32, 34, 37, 42, 40, 45, 41],
+    lineA: [45, 49, 54, 61, 57, 64, 60],
+    lineB: [31, 33, 39, 47, 41, 50, 44],
+    lineC: [21, 25, 31, 37, 34, 40, 35],
+  },
+}
+
+const chartData = computed(() => {
+  return chartDataByYear[currentYear.value] || chartDataByYear['2025']
+})
+
+const lineXAxis = computed(() => {
+  return [
+    {
+      type: 'category',
+      data: chartData.value.x,
+      axisTick: { show: false },
+      axisLabel: { color: '#666' },
+    },
+  ]
+})
+
+const lineYAxis = computed(() => {
+  return [
+    {
+      type: 'value',
+      axisLabel: { color: '#666' },
+      splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } },
+      name: '涓嶈壇鐜�(%)',
+    },
+  ]
+})
+
+const lineSeries = computed(() => [
+  {
+    name: '骞冲潎涓嶈壇鐜�',
+    type: 'bar',
+    barWidth: 18,
+    data: chartData.value.bar,
+    itemStyle: {
+      color: 'rgba(59, 130, 246, 0.15)',
+      borderRadius: [4, 4, 0, 0],
+    },
+  },
+  {
+    name: '宸ュ簭A',
+    type: 'line',
+    smooth: true,
+    data: chartData.value.lineA,
+    symbol: 'circle',
+    symbolSize: 6,
+    lineStyle: { width: 2, color: '#3b82f6' },
+    itemStyle: { color: '#3b82f6' },
+  },
+  {
+    name: '宸ュ簭B',
+    type: 'line',
+    smooth: true,
+    data: chartData.value.lineB,
+    symbol: 'circle',
+    symbolSize: 6,
+    lineStyle: { width: 2, color: '#f59e0b' },
+    itemStyle: { color: '#f59e0b' },
+  },
+  {
+    name: '宸ュ簭C',
+    type: 'line',
+    smooth: true,
+    data: chartData.value.lineC,
+    symbol: 'circle',
+    symbolSize: 6,
+    lineStyle: { width: 2, color: '#10b981' },
+    itemStyle: { color: '#10b981' },
+  },
+])
+</script>
+
+<style scoped>
+.large-screen {
+  padding: 18px 20px;
+  background: #f5f7fb;
+}
+
+.top-kpi-row {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 12px;
+}
+
+.top-stat-row {
+  margin-top: 12px;
+  display: grid;
+  grid-template-columns: 1fr 1fr 1fr;
+  gap: 12px;
+}
+
+.group-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 14px 16px;
+  box-shadow: 0 6px 22px rgba(16, 24, 40, 0.08);
+  border: 1px solid rgba(2, 6, 23, 0.06);
+  display: grid;
+  grid-template-columns: 120px 1fr;
+  min-height: 112px;
+}
+
+.group-card--wide {
+  grid-column: span 1;
+}
+
+.group-left {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.group-icon {
+  width: 110px;
+  height: 110px;
+  border-radius: 18px;
+  background: transparent;
+  border: 0;
+  position: relative;
+  overflow: hidden;
+}
+
+.group-icon::before {
+  content: none;
+}
+
+.group-icon::after {
+  content: '';
+  position: absolute;
+  inset: 0;
+  border-radius: 999px;
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.group-icon--order::after {
+  background-image: url('@/assets/BI/kpiIcons/kpi-dataBoard.png');
+}
+
+.group-icon--quality::after {
+  background-image: url('@/assets/BI/kpiIcons/kpi-shield.png');
+}
+
+.group-right {
+  display: flex;
+  align-items: center;
+  padding-left: 60px;
+}
+
+.group-metrics {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 18px;
+  width: 100%;
+}
+
+.metric {
+  background: transparent;
+  border: 0;
+  border-radius: 0;
+  padding: 0;
+}
+
+.metric--plain .metric-title {
+  font-weight: 400;
+  font-size: 16px;
+  color: #111827;
+  line-height: 1.1;
+}
+
+.metric-value {
+  margin-top: 10px;
+  font-size: 30px;
+  font-weight: 400;
+  color: #111827;
+  line-height: 1;
+  letter-spacing: 0.5px;
+}
+
+.metric-sub {
+  margin-top: 10px;
+  font-size: 12px;
+  color: rgba(2, 6, 23, 0.58);
+}
+
+.unit {
+  margin-left: 4px;
+  font-size: 12px;
+  font-weight: 600;
+  color: rgba(2, 6, 23, 0.55);
+}
+.unit1 {
+  margin-left: 4px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.stat-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 14px 18px;
+  box-shadow: 0 6px 22px rgba(16, 24, 40, 0.08);
+  border: 1px solid rgba(2, 6, 23, 0.06);
+  display: grid;
+  grid-template-columns: 1fr 58px;
+  column-gap: 12px;
+  align-items: center;
+  min-height: 116px;
+}
+
+.stat-card--primary {
+  background: linear-gradient(135deg, #2f6bff 0%, #3a87ff 100%);
+  border-color: rgba(47, 107, 255, 0.18);
+  color: #fff;
+  grid-template-columns: 1fr 1fr;
+  position: relative;
+  overflow: hidden;
+}
+
+.stat-card--primary .stat-title,
+.stat-card--primary .stat-sub,
+.stat-card--primary .stat-value,
+.stat-card--primary .trend {
+  color: #fff;
+}
+
+.stat-card--primary .trend.up,
+.stat-card--primary .trend.down {
+  color: #fff;
+}
+
+.stat-card--primary .trend-arrow-svg.is-up {
+  color: #fff;
+}
+
+.stat-card--primary .trend-arrow-svg.is-down {
+  color: #fff;
+}
+
+.stat-primary-decor {
+  width: 100%;
+  height: 100%;
+  border-radius: 12px;
+  position: relative;
+}
+
+.stat-primary-decor::after {
+  content: none;
+}
+
+.stat-card--primary::after {
+  content: '';
+  position: absolute;
+  right: -12px;
+  bottom: -22px;
+  width: 120px;
+  height: 120px;
+  background-image: url('@/assets/BI/kpiIcons/kpi-cartCutout.png');
+  background-repeat: no-repeat;
+  background-position: right bottom;
+  background-size: contain;
+  opacity: 0.95;
+  pointer-events: none;
+}
+
+.stat-main {
+  display: flex;
+  flex-direction: column;
+}
+
+.stat-title {
+  margin-top: 8px;
+  font-size: 13px;
+  color: rgba(17, 24, 39, 0.86);
+}
+
+.stat-value {
+  font-size: 38px;
+  font-weight: 400;
+  color: #111827;
+  line-height: 1;
+  letter-spacing: 0.2px;
+}
+
+.currency-sign {
+  font-size: 0.58em;
+  margin-right: 2px;
+  vertical-align: 8%;
+}
+
+.stat-sub {
+  margin-top: 10px;
+  font-size: 12px;
+  color: rgba(17, 24, 39, 0.75);
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.trend {
+    font-size: 20px;
+  font-weight: 400;
+  color: #16a34a;
+}
+.trend.up {
+  color: #16a34a;
+}
+.trend.down {
+  color: #dc2626;
+}
+
+.trend-arrow-svg {
+  width: 17px;
+  height: 11px;
+  margin-left: 3px;
+  stroke: currentColor;
+  fill: none;
+  stroke-width: 1.8;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+.trend-arrow-svg--up {
+  transform: scaleY(-1);
+  transform-origin: center;
+}
+
+.trend-arrow-svg.is-up {
+  color: #16a34a;
+}
+
+.trend-arrow-svg.is-down {
+  color: #dc2626;
+}
+
+
+.stat-icon {
+  width: 54px;
+  height: 54px;
+  border-radius: 999px;
+  border: 1px solid rgba(2, 6, 23, 0.04);
+  box-shadow: 0 4px 10px rgba(2, 6, 23, 0.08), inset 0 2px 10px rgba(255, 255, 255, 0.55);
+}
+
+.stat-card--blue .stat-icon {
+  background: rgba(255, 255, 255, 0.22);
+  border-color: rgba(255, 255, 255, 0.25);
+}
+
+.stat-icon--money {
+  background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.85), rgba(255, 255, 255, 0.12) 45%),
+    linear-gradient(135deg, rgba(255, 183, 77, 0.22), rgba(255, 171, 64, 0.4));
+}
+.stat-icon--qty {
+  background: transparent;
+  border: 0;
+  box-shadow: none;
+  background-image: url('@/assets/BI/kpiIcons/kpi-dataBoard.png');
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+.stat-icon--cost {
+  background: transparent;
+  border: 0;
+  box-shadow: none;
+  background-image: url('@/assets/BI/kpiIcons/kpi-dbIcon.png');
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+.stat-icon--supplier {
+  background: transparent;
+  border: 0;
+  box-shadow: none;
+  background-image: url('@/assets/BI/kpiIcons/kpi-cloud.png');
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.mid-row {
+  margin-top: 14px;
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 14px;
+  align-items: stretch;
+}
+
+.bottom-row {
+  margin-top: 14px;
+}
+
+.panel {
+  background: #fff;
+  border-radius: 10px;
+  box-shadow: 0 4px 18px rgba(20, 28, 47, 0.06);
+  border: 1px solid rgba(20, 28, 47, 0.06);
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  min-height: 360px;
+}
+
+.panel--rect {
+  border-radius: 0;
+}
+
+.panel-full {
+  min-height: 320px;
+}
+
+.panel-header {
+  padding: 12px 14px;
+  border-bottom: 1px solid rgba(2, 6, 23, 0.06);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.panel-actions {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: rgba(2, 6, 23, 0.6);
+  white-space: nowrap;
+}
+
+.panel-select {
+  min-width: 88px;
+}
+
+:deep(.panel-select .el-select__wrapper) {
+  width: 88px;
+}
+
+:deep(.panel-select .el-select__selected-text) {
+  white-space: nowrap;
+}
+
+.panel-title {
+  font-weight: 800;
+  color: #030303;
+  font-size: 14px;
+  letter-spacing: 1px;
+  position: relative;
+  padding-left: 10px;
+}
+
+.panel-title::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 3px;
+  height: 14px;
+  border-radius: 999px;
+  background: #0047ff;
+}
+
+.panel-subtitle {
+  margin-top: 6px;
+  font-size: 13px;
+  color: rgba(2, 6, 23, 0.58);
+}
+
+.panel-body {
+  padding: 12px 14px 14px;
+  flex: 1;
+  min-height: 280px;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+}
+
+.bottom-chart-wrap {
+  width: 90%;
+  margin: 0 auto;
+}
+
+.product-pie-wrapper {
+  flex: 0 0 260px;
+}
+
+.chart-unit {
+  font-size: 12px;
+  color: rgba(107, 114, 128, 1);
+  margin-bottom: 4px;
+}
+.category-cards {
+  margin-top: 18px;
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 12px;
+}
+
+.category-card {
+  height: 64px;
+  background: #f9fafb;
+  border-radius: 6px;
+  padding: 10px 14px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.category-name {
+  font-size: 13px;
+  color: rgba(17, 24, 39, 0.7);
+}
+
+.category-val {
+  margin-top: 6px;
+  font-size: 14px;
+  color: rgba(17, 24, 39, 0.9);
+}
+
+.donut-wrap {
+  position: relative;
+  height: 100%;
+}
+
+.donut-center {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+  pointer-events: none;
+}
+
+.donut-label {
+  font-size: 12px;
+  color: rgba(2, 6, 23, 0.58);
+}
+
+.donut-value {
+  margin-top: 6px;
+  font-size: 28px;
+  font-weight: 900;
+  color: #111827;
+}
+
+.ng-layout {
+  display: grid;
+  grid-template-columns: 1.15fr 0.85fr;
+  gap: 14px;
+  height: 100%;
+}
+
+.ng-list {
+  padding: 8px 10px;
+  background: #f7f8fb;
+  border-radius: 6px;
+}
+
+.ng-list-title {
+  font-weight: 400;
+  font-size: 14px;
+  color: rgba(2, 6, 23, 0.75);
+  margin: 4px 0 10px;
+}
+
+.ng-list-head {
+  display: grid;
+  grid-template-columns: 1fr 64px 64px;
+  gap: 6px;
+  padding: 10px 8px 8px;
+  border-radius: 0;
+  background: transparent;
+  color: rgba(2, 6, 23, 0.6);
+  font-size: 13px;
+  font-weight: 400;
+  border-bottom: 1px solid rgba(2, 6, 23, 0.08);
+}
+
+.ng-list-body {
+  margin-top: 2px;
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+}
+
+.ng-row {
+  display: grid;
+  grid-template-columns: 1fr 64px 64px;
+  gap: 6px;
+  padding: 12px 8px;
+  border-radius: 0;
+  border: 0;
+  border-bottom: 1px dashed rgba(2, 6, 23, 0.14);
+  background: transparent;
+}
+
+.ng-col {
+  display: flex;
+  align-items: center;
+  font-size: 18px;
+  color: rgba(2, 6, 23, 0.72);
+}
+
+.ng-col--pct,
+.ng-col--val {
+  justify-content: flex-end;
+  font-variant-numeric: tabular-nums;
+}
+
+.ng-col--val {
+  font-weight: 400;
+  color: rgba(2, 6, 23, 0.86);
+}
+
+.ng-dot {
+  width: 7px;
+  height: 7px;
+  border-radius: 999px;
+  margin-right: 8px;
+}
+
+.ng-name {
+  font-weight: 400;
+  color: rgba(2, 6, 23, 0.78);
+}
+
+.ng-col--pct,
+.ng-col--val {
+  font-size: 20px;
+  color: rgba(2, 6, 23, 0.82);
+}
+
+.ng-chart {
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ng-chart .donut-wrap {
+  width: 100%;
+  max-width: 360px;
+  height: 320px;
+}
+
+@media (max-width: 1100px) {
+  .ng-layout {
+    grid-template-columns: 1fr;
+  }
+  .donut-wrap :deep(canvas) {
+    margin: 0 auto;
+  }
+}
+
+@media (max-width: 1600px) {
+  /* 淇鏃у竷灞�娈嬬暀锛氶伩鍏嶅己琛� span 瀵艰嚧 top-kpi/top-stat 缃戞牸閿欎綅 */
+  .top-stat-row {
+    grid-template-columns: 1fr 1fr 1fr;
+  }
+  .top-kpi-row {
+    grid-template-columns: 1fr 1fr;
+  }
+}
+
+@media (max-width: 1100px) {
+  .mid-row {
+    grid-template-columns: 1fr;
+  }
+}
+</style>

--
Gitblit v1.9.3