Vue3 + G2 实战:打造高校学生打卡数据可视化大屏(附完整源码)

Vue3 + G2 实战:打造高校学生打卡数据可视化大屏(附完整源码)

在智慧校园建设中,如何直观展示学生的运动打卡情况?如何从海量数据中挖掘出活跃时段与学院参与率?

本文将带你深入剖析一个基于 Vue3 + TypeScript + Ant Design Vue + @antv/g2 的打卡数据分析看板。我们将实现从多维统计卡片交互式热力图下钻柱状图 的全链路数据可视化方案,并处理复杂的动态时间范围筛选逻辑。


🎯 项目背景与核心功能

该页面旨在为管理者提供一站式的学生打卡数据洞察,核心包含四大模块:

  1. 关键指标概览:今日/本周的打卡、未打卡、全勤及缺勤人数统计。
  2. 运动排行榜:支持按日、周、月、学期多维度筛选的运动次数 Top 榜单。
  3. 活跃时间热力图:通过颜色深浅展示近七天不同时间段的学生活跃分布。
  4. 学院参与率分析:各学院打卡参与率柱状图,支持点击下钻查看班级详情。

🏗️ 架构设计与技术选型

技术栈

  • 框架: 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_dateend_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 实现自适应排列,确保在不同分辨率下都能美观展示。


🔥 性能优化与最佳实践

  1. 并行请求 :使用 Promise.all 并行加载统计数、排行榜、热力图和柱状图数据,减少首屏等待时间。

    typescript 复制代码
    await Promise.all([
      loadClockedCount(),
      loadRankList(),
      loadHeatmap(),
      loadCollegeChart(),
    ]);
  2. 防抖与按需渲染 :图表仅在数据加载完成且 DOM 挂载后 (nextTick) 渲染;日期切换时仅重新请求对应数据,而非全量刷新。

  3. 类型安全 :全面使用 TypeScript 接口 (interface IRankItem) 定义数据结构,利用可选链 (?.) 和空值合并 (??) 处理后端可能缺失的字段。

  4. 错误隔离 :使用 await-to-js 包裹异步请求,单个接口失败不影响其他模块展示,并在 catch 块中设置默认空数据。


📊 效果展示

  • 📈 实时概览:五色卡片清晰展示今日/本周关键指标。
  • 🔍 多维分析:一键切换日/周/月/学期,排行榜与图表即时联动。
  • 🔥 热力洞察:渐变色块直观呈现学生运动高峰时段(如傍晚 16-18 点)。
  • 📉 下钻探索:从学院宏观数据点击直达班级微观排名,辅助精准管理。

✅ 总结

本项目不仅是一个数据展示页面,更是一套完整的数据可视化解决方案。它展示了如何在 Vue3 生态中高效集成 G2 图表,如何处理复杂的业务时间逻辑,以及如何通过交互设计提升数据的可读性。

核心代码片段已开源,适用于智慧校园、企业考勤、用户行为分析等多种场景。

💡 提示:在实际生产中,建议增加图表导出图片功能,并对大数据量的热力图进行后端聚合预处理。


技术栈 :Vue3 | TypeScript | Ant Design Vue | @antv/g2 | Dayjs
适用场景:数据大屏、管理后台、BI 分析系统

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏!有任何关于 G2 配置或 Vue3 逻辑的问题,欢迎在评论区交流~