Vue3 + G2 实战:打造高校学生打卡数据可视化大屏(附完整源码)
在智慧校园建设中,如何直观展示学生的运动打卡情况?如何从海量数据中挖掘出活跃时段与学院参与率?
本文将带你深入剖析一个基于 Vue3 + TypeScript + Ant Design Vue + @antv/g2 的打卡数据分析看板。我们将实现从多维统计卡片 到交互式热力图 、下钻柱状图 的全链路数据可视化方案,并处理复杂的动态时间范围筛选逻辑。
🎯 项目背景与核心功能
该页面旨在为管理者提供一站式的学生打卡数据洞察,核心包含四大模块:
- 关键指标概览:今日/本周的打卡、未打卡、全勤及缺勤人数统计。
- 运动排行榜:支持按日、周、月、学期多维度筛选的运动次数 Top 榜单。
- 活跃时间热力图:通过颜色深浅展示近七天不同时间段的学生活跃分布。
- 学院参与率分析:各学院打卡参与率柱状图,支持点击下钻查看班级详情。
🏗️ 架构设计与技术选型
技术栈
- 框架: Vue 3 (Setup Syntax) + TypeScript
- UI 库: Ant Design Vue (卡片、表格、选择器、弹窗)
- 图表库: @antv/g2 (高性能可视化引擎)
- 工具库: Dayjs (时间处理), Await-to-js (优雅的错误处理)
布局策略
页面采用 Flexbox 进行响应式布局,分为上下两个主要白色卡片区域,中间通过分割线区隔,整体风格简洁清爽。
vue
<template>
<div class="data-analysis-page">
<!-- 上块:统计卡片 + 排名/热力图 -->
<div class="page-upper">
<!-- 顶部统计卡片 -->
<div class="top-block">...</div>
<!-- 分割线 -->
<div class="divider-h" />
<!-- 中间左右分栏 -->
<div class="middle-section">
<div class="middle-left">...排行榜...</div>
<div class="divider-v" />
<div class="middle-right">...热力图...</div>
</div>
</div>
<!-- 下块:学院参与率柱状图 -->
<div class="page-lower">
<div class="bottom-section">...柱状图...</div>
</div>
<!-- 下钻弹窗 -->
<a-modal v-model:visible="collegeModalVisible">...</a-modal>
</div>
</template>
💡 核心功能实现详解
1. 动态时间范围筛选器
这是本项目的难点之一。用户可以选择"日、周、月、学期"四种模式,每种模式对应的选择器组件和参数解析逻辑完全不同。
实现思路 :
利用 v-if 动态渲染不同的 Ant Design 选择器,并通过统一的 getRankDateRange 函数将前端选择转换为后端需要的 start_date 和 end_date。
typescript
// 定义多种时间模式
const dateMode = ref<'day' | 'month' | 'semester' | 'week'>('day');
const dateRangeDay = ref<[Dayjs, Dayjs] | null>([dayjs(), dayjs()]);
const semesterValue = ref<string | undefined>(undefined);
/** 统一解析日期范围 */
function getRankDateRange(): { start_date: string; end_date: string } {
const today = dayjs().format('YYYY-MM-DD');
if (dateMode.value === 'day') {
return {
start_date: dateRangeDay.value?.[0].format('YYYY-MM-DD') || today,
end_date: dateRangeDay.value?.[1].format('YYYY-MM-DD') || today,
};
}
if (dateMode.value === 'semester' && semesterValue.value) {
// 从缓存的学期映射表中获取起止日期
const range = termRangeByValue.value[semesterValue.value];
return range || { start_date: today, end_date: today };
}
// ... 其他模式处理
return { start_date: today, end_date: today };
}
亮点:
- 级联加载 :选择"学期"后,自动触发"周次"选项的加载 (
watch(semesterForWeek)). - 数据映射 :预先拉取学期/周次列表并构建
Map,避免每次请求都计算日期。
2. 基于 G2 的热力图 (Heatmap)
使用 @antv/g2 绘制学生活跃时间热力图,X 轴为小时 (1-16 节),Y 轴为日期 (近 7 天)。
typescript
function renderHeatmap() {
heatmapChart = new Chart({ container: heatmapRef.value, autoFit: true, height: 280 });
heatmapChart.options({
type: 'view',
data: heatmapData.value, // 格式:[{ date: '05-01', hour: '1', value: 50 }, ...]
scale: {
date: { range: [1, 0] }, // Y 轴倒序,最近的日期在上面
value: { min: 0, max: 1 }, // 归一化颜色
},
children: [
{
type: 'cell',
encode: { x: 'hour', y: 'date', color: 'value' },
style: { inset: 0.5 }, // 单元格间距
scale: {
color: { range: ['#E8F0FE', '#2f54eb'] }, // 浅蓝到深蓝渐变
},
},
],
});
heatmapChart.render();
}
细节处理:
- 空数据兜底:如果接口返回空,自动生成近 7 天 x 16 小时的零值矩阵,保证图表不崩坏。
- Y 轴反转 :通过
scale.date.range: [1, 0]实现时间从上到下流逝的视觉习惯。
3. 可下钻的柱状图 (Drill-down Column Chart)
展示各学院参与率,并支持点击柱子弹出 Modal 查看该学院下的班级排名。
typescript
columnChart.on('interval:click', (e: any) => {
const row = e.data?.data;
const collegeCode = row?.college_code;
const collegeName = row?.college;
if (collegeCode) {
collegeModalTitle.value = `${collegeName} - 班级参与率`;
collegeModalVisible.value = true;
// 异步加载班级数据并渲染新图表
nextTick(() => renderCollegeModalChart(collegeCode, collegeName));
}
});
资源管理 :
在 Modal 关闭 (onCollegeModalClose) 和组件卸载 (onBeforeUnmount) 时,务必调用 chart.destroy() 防止内存泄漏。
typescript
function safeDestroyChart(chart: Chart | null): null {
if (chart) {
try { chart.destroy(); } catch (e) { console.warn(e); }
}
return null;
}
4. 统计卡片的视觉设计
顶部 5 个统计卡片采用不同的配色方案来区分业务含义(如:全勤用绿色,未打卡用橙色)。
less
.stat-card-week-all {
background: #ecfbee;
border-color: #d2eed4;
.stat-icon-green {
background: linear-gradient(138deg, #62ca6d 2.9%, #0daa1d 91%);
}
}
利用 CSS Grid 或 Flexbox 实现自适应排列,确保在不同分辨率下都能美观展示。
🔥 性能优化与最佳实践
-
并行请求 :使用
Promise.all并行加载统计数、排行榜、热力图和柱状图数据,减少首屏等待时间。typescriptawait Promise.all([ loadClockedCount(), loadRankList(), loadHeatmap(), loadCollegeChart(), ]); -
防抖与按需渲染 :图表仅在数据加载完成且 DOM 挂载后 (
nextTick) 渲染;日期切换时仅重新请求对应数据,而非全量刷新。 -
类型安全 :全面使用 TypeScript 接口 (
interface IRankItem) 定义数据结构,利用可选链 (?.) 和空值合并 (??) 处理后端可能缺失的字段。 -
错误隔离 :使用
await-to-js包裹异步请求,单个接口失败不影响其他模块展示,并在catch块中设置默认空数据。
📊 效果展示
- 📈 实时概览:五色卡片清晰展示今日/本周关键指标。
- 🔍 多维分析:一键切换日/周/月/学期,排行榜与图表即时联动。
- 🔥 热力洞察:渐变色块直观呈现学生运动高峰时段(如傍晚 16-18 点)。
- 📉 下钻探索:从学院宏观数据点击直达班级微观排名,辅助精准管理。
✅ 总结
本项目不仅是一个数据展示页面,更是一套完整的数据可视化解决方案。它展示了如何在 Vue3 生态中高效集成 G2 图表,如何处理复杂的业务时间逻辑,以及如何通过交互设计提升数据的可读性。
核心代码片段已开源,适用于智慧校园、企业考勤、用户行为分析等多种场景。
💡 提示:在实际生产中,建议增加图表导出图片功能,并对大数据量的热力图进行后端聚合预处理。
技术栈 :Vue3 | TypeScript | Ant Design Vue | @antv/g2 | Dayjs
适用场景:数据大屏、管理后台、BI 分析系统
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏!有任何关于 G2 配置或 Vue3 逻辑的问题,欢迎在评论区交流~