feat: refactor OverviewView to extract inline charts into components

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-30 17:10:12 +08:00
parent e9f6a726db
commit d860b0cc34

View File

@@ -69,16 +69,7 @@
</select> </select>
</div> </div>
<div class="rank-list"> <BarChart :items="rankedDepartments" />
<div v-for="item in rankedDepartments" :key="item.name" class="rank-row">
<span class="rank-index">{{ item.rank }}</span>
<span class="rank-name">{{ item.shortName }}</span>
<div class="rank-track">
<div class="rank-fill" :style="{ width: item.width, background: item.color }"></div>
</div>
<strong class="rank-value">{{ item.amountLabel }}</strong>
</div>
</div>
</article> </article>
<article class="panel dashboard-card bottleneck-panel"> <article class="panel dashboard-card bottleneck-panel">
@@ -110,28 +101,12 @@
<h3>预算执行率本月 <i class="pi pi-info-circle"></i></h3> <h3>预算执行率本月 <i class="pi pi-info-circle"></i></h3>
</div> </div>
<div class="budget-gauge" :style="{ '--budget-ratio': `${budgetSummary.ratio}` }"> <GaugeChart
<div class="budget-gauge-ring"></div> :ratio="budgetSummary.ratio"
<div class="budget-gauge-center"> :total="budgetSummary.total"
<strong>{{ budgetSummary.ratio }}%</strong> :used="budgetSummary.used"
<span>已执行</span> :left="budgetSummary.left"
</div> />
</div>
<div class="budget-summary">
<div>
<span>预算总额</span>
<strong>{{ budgetSummary.total }}</strong>
</div>
<div>
<span>已执行</span>
<strong>{{ budgetSummary.used }}</strong>
</div>
<div>
<span>剩余可用</span>
<strong>{{ budgetSummary.left }}</strong>
</div>
</div>
<button type="button" class="text-link">查看详情 <i class="pi pi-angle-right"></i></button> <button type="button" class="text-link">查看详情 <i class="pi pi-angle-right"></i></button>
</article> </article>
@@ -153,6 +128,8 @@ import {
} from '../data/metrics.js' } from '../data/metrics.js'
import TrendChart from '../components/charts/TrendChart.vue' import TrendChart from '../components/charts/TrendChart.vue'
import DonutChart from '../components/charts/DonutChart.vue' import DonutChart from '../components/charts/DonutChart.vue'
import BarChart from '../components/charts/BarChart.vue'
import GaugeChart from '../components/charts/GaugeChart.vue'
defineProps({ defineProps({
filteredRequests: { type: Array, required: true } filteredRequests: { type: Array, required: true }
@@ -335,6 +312,12 @@ const rankedDepartments = computed(() => {
.dashboard-card { .dashboard-card {
padding: 20px; padding: 20px;
transition: box-shadow 200ms ease, transform 200ms ease;
}
.dashboard-card:hover {
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
transform: translateY(-1px);
} }
.trend-panel, .trend-panel,
@@ -387,56 +370,22 @@ const rankedDepartments = computed(() => {
text-align: center; text-align: center;
} }
.rank-list, .bottleneck-panel,
.budget-panel {
display: flex;
flex-direction: column;
}
.bottleneck-panel .text-link,
.budget-panel .text-link {
margin-top: auto;
}
.bottleneck-list { .bottleneck-list {
flex: 1;
display: grid; display: grid;
gap: 16px; gap: 16px;
} align-content: center;
.rank-row {
display: grid;
grid-template-columns: 24px 72px minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
}
.rank-index {
width: 24px;
height: 24px;
display: grid;
place-items: center;
border-radius: 999px;
background: #94a3b8;
color: #fff;
font-size: 12px;
font-weight: 500;
}
.rank-row:first-child .rank-index {
background: #f59e0b;
}
.rank-name {
color: #475569;
font-size: 14px;
}
.rank-value {
color: #334155;
font-size: 14px;
font-weight: 500;
}
.rank-track {
height: 24px;
overflow: hidden;
border-radius: 999px;
background: #f1f5f9;
}
.rank-fill {
height: 100%;
border-radius: inherit;
} }
.bottleneck-row { .bottleneck-row {
@@ -523,63 +472,6 @@ const rankedDepartments = computed(() => {
font-size: 14px; font-size: 14px;
} }
.budget-gauge {
position: relative;
height: 128px;
display: grid;
place-items: center;
}
.budget-gauge-ring {
width: 172px;
height: 92px;
border-radius: 172px 172px 0 0;
background: conic-gradient(from 180deg, #10b981 0 calc(var(--budget-ratio) * 1.8deg), #e2e8f0 calc(var(--budget-ratio) * 1.8deg) 180deg);
-webkit-mask: radial-gradient(circle at 50% 100%, transparent 0 52px, #000 53px);
mask: radial-gradient(circle at 50% 100%, transparent 0 52px, #000 53px);
}
.budget-gauge-center {
position: absolute;
inset: auto 0 10px;
display: grid;
place-items: center;
}
.budget-gauge-center strong {
color: #10b981;
font-size: 24px;
line-height: 1;
}
.budget-gauge-center span {
margin-top: 4px;
color: #64748b;
font-size: 12px;
}
.budget-summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
margin-top: 16px;
text-align: center;
}
.budget-summary span {
display: block;
color: #64748b;
font-size: 12px;
}
.budget-summary strong {
display: block;
margin-top: 6px;
color: #1e293b;
font-size: 13px;
font-weight: 500;
}
@media (max-width: 1320px) { @media (max-width: 1320px) {
.kpi-grid { .kpi-grid {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));