diff --git a/src/components/charts/DonutChart.vue b/src/components/charts/DonutChart.vue new file mode 100644 index 0000000..927b0ba --- /dev/null +++ b/src/components/charts/DonutChart.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/src/views/OverviewView.vue b/src/views/OverviewView.vue index 2548671..51d4f62 100644 --- a/src/views/OverviewView.vue +++ b/src/views/OverviewView.vue @@ -8,13 +8,13 @@ :style="{ '--accent': metric.accent }" >
+
+ +

{{ metric.label }}

{{ metric.displayValue }}
-
- -
@@ -47,19 +47,7 @@

费用结构

-
-
-
- {{ spendTotalLabel }} - 待处理金额 -
-
-
-
- {{ item.name }} -
-
-
+

* 百分比为占待处理金额比例

@@ -67,19 +55,7 @@

风险异常分布

-
-
-
- {{ riskTotal }} - 异常预警单 -
-
-
-
- {{ item.name }} -
-
-
+

* 近30天数据

@@ -176,6 +152,7 @@ import { budgetSummary } from '../data/metrics.js' import TrendChart from '../components/charts/TrendChart.vue' +import DonutChart from '../components/charts/DonutChart.vue' defineProps({ filteredRequests: { type: Array, required: true } @@ -227,22 +204,7 @@ const kpiMetrics = computed(() => metricBlueprints.map((metric) => { const activeTrend = computed(() => trendSeries[activeTrendRange.value]) const spendTotal = computed(() => spendByCategory.reduce((sum, item) => sum + item.value, 0)) -const spendTotalLabel = computed(() => formatCurrency(demoTotals.pendingAmount)) - -const buildDonutBackground = (items, total) => { - let current = 0 - const stops = items.map((item) => { - const start = current - current += (item.value / total) * 100 - return `${item.color} ${start}% ${current}%` - }).join(',') - - return `radial-gradient(circle, #fff 0 60%, transparent 61%), conic-gradient(${stops})` -} - -const spendDonutBackground = computed(() => buildDonutBackground(spendByCategory, spendTotal.value)) const riskTotal = computed(() => exceptionMix.reduce((sum, item) => sum + item.value, 0)) -const riskDonutBackground = computed(() => buildDonutBackground(exceptionMix, riskTotal.value)) const spendLegend = computed(() => spendByCategory.map((item) => ({ ...item, @@ -314,7 +276,7 @@ const rankedDepartments = computed(() => { .kpi-top strong { display: block; color: #1e293b; - font-size: clamp(20px, 2vw, 26px); + font-size: clamp(18px, 1.6vw, 22px); line-height: 1.2; font-weight: 700; font-variant-numeric: tabular-nums; @@ -322,22 +284,23 @@ const rankedDepartments = computed(() => { } .kpi-icon { - width: 38px; - height: 38px; + width: 44px; + height: 44px; display: grid; place-items: center; border-radius: 10px; background: color-mix(in srgb, var(--accent) 10%, white); color: var(--accent); - font-size: 16px; + font-size: 20px; flex: 0 0 auto; } .kpi-bottom { display: flex; - flex-direction: column; - gap: 1px; - margin-top: 14px; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 12px; padding-top: 12px; border-top: 1px solid #f1f5f9; } @@ -345,9 +308,9 @@ const rankedDepartments = computed(() => { .kpi-bottom span { display: inline-flex; align-items: center; - gap: 3px; + gap: 4px; font-size: 13px; - font-weight: 500; + font-weight: 600; } .kpi-bottom.up span { @@ -361,6 +324,7 @@ const rankedDepartments = computed(() => { .kpi-bottom small { color: #94a3b8; font-size: 12px; + text-align: right; } .content-grid { @@ -416,68 +380,6 @@ const rankedDepartments = computed(() => { font-size: 14px; } -.donut-wrap { - min-height: 192px; - display: grid; - grid-template-columns: minmax(116px, 1fr) minmax(96px, .8fr); - align-items: center; - gap: 10px; -} - -.donut { - width: min(148px, 100%); - aspect-ratio: 1; - display: grid; - place-items: center; - border-radius: 50%; - justify-self: center; -} - -.donut-center { - width: 84px; - aspect-ratio: 1; - display: grid; - place-items: center; - align-content: center; - border-radius: 50%; - background: #fff; - text-align: center; -} - -.donut-center strong { - color: #1e293b; - font-size: 16px; - line-height: 1; - font-weight: 700; -} - -.donut-center span { - margin-top: 6px; - color: #64748b; - font-size: 11px; -} - -.donut-legend { - display: grid; - gap: 14px; - align-content: center; -} - -.legend-row span { - display: inline-flex; - align-items: center; - gap: 8px; - color: #334155; - font-size: 12px; -} - -.legend-row i { - width: 10px; - height: 10px; - border-radius: 999px; - flex: 0 0 auto; -} - .panel-note { margin-top: 8px; color: #64748b;