| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |