<template>
|
<div class="screen-page">
|
<el-row :gutter="10" class="kpi-row">
|
<el-col v-for="item in kpis" :key="item.label" :xl="3" :lg="6" :sm="12" :xs="24">
|
<kpi-card v-bind="item" />
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="10" class="mid-row">
|
<el-col :lg="10" :xs="24">
|
<panel-card title="垃圾分类结构" sub="学校管理层看结构,不看单次称重">
|
<template #extra>
|
<cl-select-button v-model="mixMetric" :options="mixOptions" small />
|
</template>
|
<v-chart :option="mixOption" autoresize class="chart" />
|
</panel-card>
|
</el-col>
|
<el-col :lg="14" :xs="24">
|
<panel-card title="回收趋势" :sub="trendHint">
|
<template #extra>
|
<cl-select-button v-model="trendMetric" :options="trendOptions" small />
|
</template>
|
<v-chart :option="trendOption" autoresize class="chart" />
|
</panel-card>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="10">
|
<el-col :span="24">
|
<panel-card title="各院系参与人数" sub="按用户学院名称 collegeName 汇总,人均按参与人数">
|
<v-chart :option="rankOption" autoresize class="chart chart--bar" />
|
</panel-card>
|
</el-col>
|
</el-row>
|
</div>
|
</template>
|
|
<script lang="ts" setup>
|
defineOptions({
|
name: 'report-screen-overview'
|
});
|
|
import { computed, ref } from 'vue';
|
import KpiCard from '../../components/kpi-card.vue';
|
import PanelCard from '../../components/panel-card.vue';
|
import { useChartTheme, useScreenData } from '../../hooks/use-screen';
|
|
const { data, timeRange } = useScreenData();
|
const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
|
|
const mixMetric = ref<'weight' | 'value' | 'count'>('weight');
|
const trendMetric = ref<'weight' | 'value' | 'carbon'>('weight');
|
|
const mixOptions = [
|
{ label: '重量', value: 'weight' },
|
{ label: '价值', value: 'value' },
|
{ label: '数量', value: 'count' }
|
];
|
|
const trendOptions = [
|
{ label: '重量', value: 'weight' },
|
{ label: '价值', value: 'value' },
|
{ label: '碳减排', value: 'carbon' }
|
];
|
|
const kpis = computed(() => [
|
{ label: '今日回收量', value: data.value.kpis.todayWeightKg, suffix: 'kg', fixed: 1, hint: '实时称重汇总' },
|
{ label: '本区间回收量', value: data.value.kpis.periodWeightTon, suffix: '吨', fixed: 2, hint: '当前时间范围' },
|
{ label: '资源化率', value: data.value.kpis.resourceRate, suffix: '%', fixed: 1, hint: '可回收物重量 / 总重量,不含大件和电子' },
|
{ label: '回收价值', value: data.value.kpis.periodValue, suffix: '元', fixed: 0, hint: '日表估算销售额' },
|
{ label: '碳减排', value: data.value.kpis.periodCarbonTon, suffix: '吨CO₂e', fixed: 2, hint: '分类重量 × 碳减因数' },
|
{ label: '累计回收量', value: data.value.kpis.totalWeightTon, suffix: '吨', fixed: 1, hint: '上线以来累计' },
|
{ label: '累计参与人数', value: data.value.kpis.totalParticipants, suffix: '人', fixed: 0, hint: '去重校园用户' },
|
{ label: '累计碳积分', value: data.value.kpis.totalPoints, suffix: '分', fixed: 0, hint: '账户余额合计' }
|
]);
|
|
const mixOption = computed(() => ({
|
tooltip: { trigger: 'item', formatter: '{b}<br/>{c} ({d}%)', ...tooltip() },
|
legend: { bottom: 0, textStyle: { color: mutedColor.value } },
|
series: [
|
{
|
type: 'pie',
|
radius: ['38%', '62%'],
|
center: ['50%', '46%'],
|
padAngle: 3,
|
itemStyle: { borderRadius: 6 },
|
label: { color: textColor.value, formatter: '{b}\n{d}%' },
|
data: data.value.wasteMix.map(e => ({
|
name: e.name,
|
value: e[mixMetric.value],
|
itemStyle: { color: e.color }
|
}))
|
}
|
]
|
}));
|
|
const trendHint = computed(() => {
|
const map = { today: '近 7 日', week: '按日', month: '按日', quarter: '按日', year: '按月' };
|
return map[timeRange.value];
|
});
|
|
const trendUnit = computed(() => {
|
if (trendMetric.value === 'value') return '元';
|
if (trendMetric.value === 'carbon') return 'kgCO₂e';
|
return 'kg';
|
});
|
|
const trendOption = computed(() => ({
|
grid: baseGrid(),
|
tooltip: { trigger: 'axis', ...tooltip() },
|
xAxis: { type: 'category', data: data.value.axis, ...axisStyle() },
|
yAxis: {
|
type: 'value',
|
name: trendUnit.value,
|
nameTextStyle: { color: mutedColor.value },
|
splitLine: { lineStyle: { color: splitColor.value, type: 'dashed' } },
|
axisLabel: { color: mutedColor.value },
|
axisLine: { show: false },
|
axisTick: { show: false }
|
},
|
series: [
|
{
|
type: 'line',
|
smooth: true,
|
showSymbol: false,
|
data: data.value.trend[trendMetric.value],
|
itemStyle: { color: primary.value },
|
areaStyle: { color: primary.value, opacity: 0.12 }
|
}
|
]
|
}));
|
|
const rankOption = computed(() => ({
|
grid: { containLabel: true, left: 8, right: 24, top: 16, bottom: 8 },
|
tooltip: { trigger: 'axis', ...tooltip() },
|
xAxis: {
|
type: 'value',
|
name: '人',
|
splitLine: { lineStyle: { color: splitColor.value, type: 'dashed' } },
|
axisLabel: { color: mutedColor.value },
|
axisLine: { show: false },
|
axisTick: { show: false }
|
},
|
yAxis: {
|
type: 'category',
|
data: data.value.deptRank.map(e => e.name).reverse(),
|
axisLabel: { color: textColor.value },
|
axisLine: { show: false },
|
axisTick: { show: false }
|
},
|
series: [
|
{
|
type: 'bar',
|
barWidth: 14,
|
data: data.value.deptRank.map(e => e.people).reverse(),
|
itemStyle: { color: primary.value, borderRadius: [0, 8, 8, 0] }
|
}
|
]
|
}));
|
</script>
|
|
<style lang="scss" scoped>
|
.screen-page {
|
.kpi-row,
|
.mid-row {
|
margin-bottom: 10px;
|
}
|
|
.kpi-row {
|
:deep(.el-col) {
|
margin-bottom: 10px;
|
}
|
}
|
|
.mid-row {
|
:deep(.panel-card) {
|
min-height: 360px;
|
}
|
}
|
|
.chart {
|
height: 300px;
|
width: 100%;
|
|
&--bar {
|
height: 320px;
|
}
|
}
|
}
|
</style>
|