From de83f5112fecda011bce0192dce152e66bb56f87 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期二, 31 三月 2026 17:37:24 +0800
Subject: [PATCH] Merge branch 'dev_衡阳_鹏创电子' of http://114.132.189.42:9002/r/product-inventory-management into dev_衡阳_鹏创电子
---
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