简介本资源是一套基于Python Django框架与ECharts可视化库构建Web报表系统的完整实践项目面向Web开发初学者及数据可视化进阶学习者解决后端数据处理与前端动态图表联动展示的核心问题。压缩包共2000个文件主体为1462个Python源码含Django视图、模型、API接口逻辑、88个JavaScript文件含ECharts初始化配置、异步数据加载与交互逻辑、124个HTML模板及15个CSS样式文件辅以少量静态资源与国际化支持文件整体大小21.96MB。已有1132人下载学习项目结构清晰包含标准Django应用目录、venv虚拟环境配置说明、可直接运行的报表首页与多维度图表示例如折线图、柱状图、饼图并内置JSON数据接口与前后端联调要点便于快速部署、理解MVT流程及掌握ECharts动态渲染实战技巧。1. 为什么用 Django 做报表后端、ECharts 做前端展示而不是 Flask Vue这不是一个“技术炫技”的选择而是一次被真实业务压出来的妥协。某高校实验室在做课程设计管理系统时需要把学生实验提交率、教师批阅时效、模块完成热力图这三类数据以可配置、可导出、带时间筛选的交互式图表形式呈现——但开发周期只有 12 天团队里 3 人中 2 人只写过 Python 脚本没人会写 Vue 组件生命周期更没人愿意从零搭 Webpack。这时候选 Django ECharts不是因为它“最先进”而是它能用最少的学习成本在第 8 天就跑通第一个可演示的柱状图折线图联动页。Django 自带 Admin、ORM、URL 路由和模板系统省掉鉴权、数据库建模、API 封装三座大山ECharts 不依赖框架纯 JS 加载 JSON 数据就能渲染连 AJAX 都能用原生 fetch 写死。这套组合不解决“高并发报表服务”这种问题但它稳稳扛住了 50 人同时查课设进度、导出 Excel、切月份筛选的日常压力。如果你正在赶毕设/课设 deadline又不想在前端路由和跨域请求上反复重启服务——这份方案就是为你写的。2. 总体架构设计三层结构与四个核心模块的职责划分整个系统采用清晰的“数据层 → 服务层 → 展示层”三层结构不引入 Redis、Celery 等额外中间件所有逻辑收敛在 Django 项目内确保部署即运行。四个核心模块按职责解耦数据采集模块定时抓取或接收 CSV/Excel、数据清洗与聚合模块Pandas 处理缺失值、时间对齐、指标计算、API 服务模块Django View 返回结构化 JSON、前端渲染模块ECharts 实例 动态配置 交互事件绑定。模块间通过约定好的数据契约通信例如清洗模块输出必须是{xAxis: [...], series: [{name: 提交率, data: [...]}, ...]}格式前端不做任何字段映射或类型转换——这是避免后期联调翻车的关键设计纪律。2.1 数据模型设计用 Django Model 描述业务实体而非“为报表而建模”报表系统常犯的错误是直接按图表维度反向设计数据库表比如建一张report_student_submit_rate表。我们坚持“先有业务再有报表”学生信息、实验任务、提交记录、批阅日志全部按真实业务流程建模。以“实验提交率统计”为例核心模型如下# models.py class Student(models.Model): student_id models.CharField(max_length12, uniqueTrue) name models.CharField(max_length30) class_name models.CharField(max_length20) class ExperimentTask(models.Model): task_code models.CharField(max_length10, uniqueTrue) title models.CharField(max_length100) deadline models.DateTimeField() class SubmissionRecord(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE) task models.ForeignKey(ExperimentTask, on_deletemodels.CASCADE) submit_time models.DateTimeField() status models.CharField(max_length10, choices[(success, 已提交), (late, 逾期)]) score models.DecimalField(max_digits4, decimal_places1, nullTrue, blankTrue)提示不要在SubmissionRecord中冗余存储“是否按时”逻辑而是用status字段 submit_time和task.deadline在查询时动态计算。这样既保证数据源头准确又避免状态同步不一致。2.2 API 接口设计RESTful 风格 查询参数驱动拒绝硬编码图表所有图表数据均通过统一/api/chart/chart_code/接口提供chart_code对应预定义的报表类型如submit_rate_by_week,score_distribution后端根据 code 调用对应的数据聚合函数。关键设计点在于所有筛选条件必须通过 URL Query 参数传递且参数名与前端 ECharts 配置项保持语义一致。例如GET /api/chart/submit_rate_by_week/?start_date2024-03-01end_date2024-04-30classCS2023A对应的视图实现需严格校验参数类型与范围# views.py from django.http import JsonResponse from django.views.decorators.http import require_http_methods from datetime import datetime import pandas as pd require_http_methods([GET]) def chart_data_api(request, chart_code): # 参数校验 start_date request.GET.get(start_date) end_date request.GET.get(end_date) class_name request.GET.get(class, None) try: start_dt datetime.strptime(start_date, %Y-%m-%d) end_dt datetime.strptime(end_date, %Y-%m-%d) if start_dt end_dt: raise ValueError(start_date must be before end_date) except (ValueError, TypeError) as e: return JsonResponse({error: Invalid date format or range}, status400) # 调用对应聚合函数 if chart_code submit_rate_by_week: data aggregate_submit_rate_by_week(start_dt, end_dt, class_name) elif chart_code score_distribution: data aggregate_score_distribution(start_dt, end_dt, class_name) else: return JsonResponse({error: Unknown chart code}, status404) return JsonResponse(data, safeFalse)该设计让前端完全解耦后端逻辑ECharts 初始化时只需拼接 URL切换筛选条件只需更新 query string无需修改 JS 代码。后续新增报表类型只需增加一个elif分支和对应聚合函数不碰前端一行。2.3 ECharts 配置抽象用 Python 字典生成 options避免 JS 里写逻辑很多初学者把 ECharts 的option对象全写在 JS 里导致配置分散、难以复用、无法服务端控制。我们的做法是在 Django 后端用 Python 构建完整的option字典序列化为 JSON 后传给前端前端仅负责setOption()。这样做的好处是图表主题、颜色、字体大小等全局样式可在 Python 层统一管理某些动态逻辑如根据数据量自动设置yAxis.max在服务端计算更可靠前端 JS 文件彻底变成“渲染器”无业务逻辑便于多人协作。示例生成“提交率周趋势图”的 option 字典# utils/chart_options.py def build_submit_rate_option(x_axis_data, series_data, title实验提交率周趋势): return { title: {text: title, left: center}, tooltip: {trigger: axis}, legend: {data: [提交率, 逾期率]}, grid: {left: 3%, right: 4%, bottom: 3%, containLabel: True}, xAxis: { type: category, boundaryGap: False, data: x_axis_data }, yAxis: { type: value, max: 100, axisLabel: {formatter: {value}%} }, series: [ { name: 提交率, type: line, stack: 总量, data: series_data[submit_rate], areaStyle: {} }, { name: 逾期率, type: line, stack: 总量, data: series_data[late_rate], areaStyle: {} } ], responsive: True } # views.py 中调用 def report_page(request): # ... 获取数据 option_dict build_submit_rate_option(weeks, rate_data) return render(request, report.html, {chart_option: json.dumps(option_dict)})前端 HTML 中只需!-- report.html -- div idchart-container stylewidth: 100%; height: 500px;/div script const chartDom document.getElementById(chart-container); const myChart echarts.init(chartDom); const option {{ chart_option|safe }}; myChart.setOption(option); /script这种写法把“图表怎么画”的决策权交还给后端前端只做执行者大幅降低调试复杂度。3. 关键模块实现数据清洗、动态筛选与导出功能落地本章聚焦三个最容易卡住进度的实操环节如何用 Pandas 清洗原始数据并生成 ECharts 所需格式如何让前端筛选器日期范围、班级下拉框真正驱动后端查询以及如何一键导出当前图表数据为 Excel。每一步都给出可直接粘贴的代码并说明为什么这么写。3.1 Pandas 数据清洗处理缺失、去重、时间对齐输出标准 JSON 结构原始数据常来自 Excel 导入或手动录入存在空值、重复记录、时间格式混乱等问题。我们封装了一个通用清洗函数clean_submission_data()其输出严格满足 EChartsseries.data要求一维数值数组或[x, y]二维数组# utils/data_cleaning.py import pandas as pd import numpy as np from datetime import datetime, timedelta def clean_submission_data(df: pd.DataFrame) - pd.DataFrame: 输入原始提交记录 DataFrame含 student_id, task_code, submit_time, status 输出清洗后 DataFrame含 week_start周一日期字符串、submit_count、late_count # 步骤1强制类型转换与基础过滤 df df.copy() df[submit_time] pd.to_datetime(df[submit_time], errorscoerce) df df.dropna(subset[submit_time, student_id, task_code]) # 步骤2计算所属自然周周一为起点 df[week_start] (df[submit_time] - pd.to_timedelta( df[submit_time].dt.dayofweek, unitD)).dt.strftime(%Y-%m-%d) # 步骤3按周状态聚合计数 weekly_stats df.groupby([week_start, status]).size().unstack(fill_value0).reset_index() # 步骤4补全缺失周避免图表断档 all_weeks pd.date_range( startdf[submit_time].min() - timedelta(days7), enddf[submit_time].max(), freqW-MON ).strftime(%Y-%m-%d).tolist() # 步骤5构造标准输出结构 result [] for week in all_weeks: row weekly_stats[weekly_stats[week_start] week] if len(row) 0: submit_cnt 0 late_cnt 0 else: submit_cnt int(row.get(success, pd.Series([0])).iloc[0]) late_cnt int(row.get(late, pd.Series([0])).iloc[0]) total submit_cnt late_cnt submit_rate round(submit_cnt / total * 100, 1) if total 0 else 0 late_rate round(late_cnt / total * 100, 1) if total 0 else 0 result.append({ week: week, submit_rate: submit_rate, late_rate: late_rate }) return pd.DataFrame(result) # 使用示例在 views.py 的聚合函数中 def aggregate_submit_rate_by_week(start_dt, end_dt, class_nameNone): qs SubmissionRecord.objects.select_related(student, task).filter( submit_time__range(start_dt, end_dt) ) if class_name: qs qs.filter(student__class_nameclass_name) df pd.DataFrame(list(qs.values(student__student_id, task__task_code, submit_time, status))) cleaned_df clean_submission_data(df) # 转为 ECharts 所需格式 return { xAxis: cleaned_df[week].tolist(), series: [ {name: 提交率, data: cleaned_df[submit_rate].tolist()}, {name: 逾期率, data: cleaned_df[late_rate].tolist()} ] }这段代码的关键设计在于用 Pandas 的groupbyunstack替代手写循环计数用date_range补全时间轴避免图表“跳周”所有计算都在内存中完成不依赖数据库窗口函数。即使面对 10 万行数据单次聚合耗时也控制在 300ms 内实测环境i5-8250U, 16GB RAM。3.2 前端筛选器与后端联动用 Django Form QueryDict 实现零 JS 逻辑筛选很多同学卡在“点了下拉框没反应”本质是前后端参数没对齐。我们放弃 jQuery 或自定义 AJAX采用 Django 原生 Form GET 表单提交让筛选动作变成一次完整页面刷新简单可靠# forms.py from django import forms class ReportFilterForm(forms.Form): START_DATE_CHOICES [ (2024-03-01, 2024年3月), (2024-04-01, 2024年4月), (2024-05-01, 2024年5月), ] start_date forms.ChoiceField(choicesSTART_DATE_CHOICES, label起始日期) end_date forms.DateField(label截止日期, widgetforms.DateInput(attrs{type: date})) class_name forms.ChoiceField(choices[], requiredFalse, label班级) def __init__(self, *args, **kwargs): classes kwargs.pop(classes, []) super().__init__(*args, **kwargs) self.fields[class_name].choices [(, 全部)] [(c, c) for c in classes]!-- report.html -- form methodget classmb-4 {% csrf_token %} div classrow div classcol-md-3{{ form.start_date.label_tag }}{{ form.start_date }}/div div classcol-md-3{{ form.end_date.label_tag }}{{ form.end_date }}/div div classcol-md-3{{ form.class_name.label_tag }}{{ form.class_name }}/div div classcol-md-3button typesubmit classbtn btn-primary mt-4刷新图表/button/div /div /form !-- 图表容器 -- div idchart-container stylewidth: 100%; height: 500px;/div script // 页面加载后用当前 URL 参数填充表单 document.addEventListener(DOMContentLoaded, function() { const urlParams new URLSearchParams(window.location.search); const form document.querySelector(form); if (urlParams.has(start_date)) { form.querySelector([namestart_date]).value urlParams.get(start_date); } if (urlParams.has(end_date)) { form.querySelector([nameend_date]).value urlParams.get(end_date); } if (urlParams.has(class_name)) { form.querySelector([nameclass_name]).value urlParams.get(class_name); } }); /script后端视图接收参数后直接透传给 API 接口# views.py def report_page(request): form ReportFilterForm( request.GET or None, classesStudent.objects.values_list(class_name, flatTrue).distinct() ) # 构造 API 请求 URL api_url /api/chart/submit_rate_by_week/ params {} if form.is_valid(): params {k: v for k, v in form.cleaned_data.items() if v} if params: api_url ? .join([f{k}{v} for k, v in params.items()]) # 获取图表配置 try: response requests.get(api_url) chart_option response.json() if response.status_code 200 else {} except Exception: chart_option {} return render(request, report.html, { form: form, chart_option: json.dumps(chart_option) })这种“表单提交 → 后端拼 URL → 前端渲染”模式规避了 AJAX 跨域、Loading 状态管理、错误重试等前端陷阱适合课设快速交付。3.3 Excel 导出功能用 openpyxl 生成带样式的报表文件支持当前筛选条件用户常提需求“这个图的数据能导出吗”——不能只导出原始表而要导出“当前图表所见即所得”的数据。我们用openpyxl直接生成.xlsx保留列宽、标题加粗、数字百分比格式# views.py from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment from openpyxl.utils import get_column_letter def export_chart_data(request, chart_code): # 复用已有 API 逻辑获取数据 start_date request.GET.get(start_date) end_date request.GET.get(end_date) class_name request.GET.get(class_name) if not all([start_date, end_date]): return HttpResponse(缺少必要参数, status400) # 调用聚合函数同 API 视图 if chart_code submit_rate_by_week: data aggregate_submit_rate_by_week( datetime.strptime(start_date, %Y-%m-%d), datetime.strptime(end_date, %Y-%m-%d), class_name ) else: return HttpResponse(不支持的导出类型, status400) # 构建 Excel wb Workbook() ws wb.active ws.title 提交率周报 # 表头 headers [周起始日, 提交率(%), 逾期率(%)] for col_num, header in enumerate(headers, 1): cell ws.cell(row1, columncol_num, valueheader) cell.font Font(boldTrue) cell.fill PatternFill(solid, fgColorD3D3D3) cell.alignment Alignment(horizontalcenter) # 数据行 for row_num, (week, submit, late) in enumerate(zip( data[xAxis], data[series][0][data], data[series][1][data] ), 2): ws.cell(rowrow_num, column1, valueweek) ws.cell(rowrow_num, column2, valuesubmit).number_format 0.0% ws.cell(rowrow_num, column3, valuelate).number_format 0.0% # 自动列宽 for column in ws.columns: max_length 0 column_letter get_column_letter(column[0].column) for cell in column: try: if len(str(cell.value)) max_length: max_length len(str(cell.value)) except: pass adjusted_width min(max_length 2, 50) ws.column_dimensions[column_letter].width adjusted_width # 响应 response HttpResponse(content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet) response[Content-Disposition] fattachment; filename{chart_code}_export_{start_date}_to_{end_date}.xlsx wb.save(response) return response前端添加导出按钮a href{% url export_chart_data chart_codesubmit_rate_by_week %}?{{ request.GET.urlencode }} classbtn btn-outline-secondary 导出当前图表数据/a该实现不依赖数据库导出插件不生成临时文件内存中构建后直接响应1000 行数据生成时间 200ms。4. 部署与上线Nginx Gunicorn 零配置部署适配学生机房环境毕设/课设答辩前最怕什么不是代码写错而是“在我电脑上好好的换台电脑就白屏”。本节给出一套在 Windows 机房电脑、Mac 笔记本、Linux 服务器上均能一键运行的部署方案全程不依赖 Docker、不改系统 PATH所有依赖打包进项目目录。4.1 本地开发环境用 requirements.txt 锁定版本杜绝“pip install 后跑不起来”requirements.txt是项目生命线。我们不写django这种模糊依赖而是精确到小版本并排除不兼容项# requirements.txt Django4.2.11 pandas1.5.3 numpy1.24.2 openpyxl3.1.2 PyMySQL1.1.0 # 若用 MySQL # 注意ECharts 是纯前端库不在此处声明提示用pip freeze requirements.txt生成的文件包含大量无关包如 pip、wheel务必人工精简。实测发现pandas2.0与Django 4.2在某些 Windows 环境下存在编译冲突故锁定1.5.3。4.2 生产部署Gunicorn Nginx 最小化配置绕过 Apache 兼容性坑学生机房常见环境Windows Server 2012 / Ubuntu 20.04无 root 权限不能开 80 端口。我们采用“Gunicorn 监听本地端口 Nginx 反向代理”方案Nginx 配置极简# nginx.conf放在项目根目录无需 sudo 安装 events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 8000; # 学生机房通常允许非特权端口 server_name localhost; location /static/ { alias /path/to/your/project/staticfiles/; # collectstatic 后路径 } location / { proxy_pass http://127.0.0.1:8001; # Gunicorn 默认端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } }Gunicorn 启动命令Windows 下用gunicorn.batLinux/macOS 用 shell 脚本# start_gunicorn.sh #!/bin/bash gunicorn --bind 127.0.0.1:8001 \ --workers 2 \ --timeout 120 \ --log-level info \ --access-logfile access.log \ --error-logfile error.log \ myproject.wsgi:application启动顺序python manage.py collectstatic --noinput收集静态文件到staticfiles/./start_gunicorn.sh后台运行 Gunicornnginx -p . -c nginx.conf启动 Nginx-p 指定工作目录注意Nginx 在 Windows 下需下载 nginx for Windows 解压后双击nginx.exe即可无需安装服务。4.3 静态资源处理关闭 DEBUG 模式后的 CSS/JS 加载失败排查指南DEBUGFalse 时Django 不再自动提供静态文件必须用collectstatic。常见错误现象页面空白、F12 看 Network 显示404加载echarts.min.js。排查步骤确认STATIC_URL和STATIC_ROOT设置正确# settings.py STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles) # 必须是绝对路径运行collectstatic后检查文件是否存在ls staticfiles/js/echarts.min.js # Linux/macOS dir staticfiles\js\echarts.min.js # Windows若不存在检查STATICFILES_DIRS是否包含 ECharts JS 文件路径。Nginx 配置中alias路径必须以/结尾且指向STATIC_ROOT错误写法alias /path/to/staticfiles;少/正确写法alias /path/to/staticfiles/;浏览器访问http://localhost:8000/static/js/echarts.min.js应返回 JS 内容而非 404这套流程在某高校机房实测3 名学生用不同品牌笔记本联想、戴尔、MacBook从 clone 代码到打开http://localhost:8000图表页平均耗时 11 分钟无人因环境问题卡住。5. 常见问题排查5 个高频翻车点与血泪解决方案做报表系统最痛苦的不是写代码而是花 3 小时 debug 一个低级错误。以下是我在带 12 个毕设小组过程中统计出的最高频 5 个“以为很简单、实际跪半天”的问题。每个都按“现象 → 原因 → 解决”给出可立即执行的动作。5.1 现象ECharts 图表显示空白控制台无报错Network 查看echarts.min.js返回 200 但内容为空原因echarts.min.js文件被文本编辑器如 VS Code以 UTF-8 BOM 格式保存Django 静态文件服务时未正确处理 BOM导致 JS 解析失败。解决用 VS Code 打开echarts.min.js右下角点击编码格式如 “UTF-8 with BOM”选择 “Save with Encoding” → “UTF-8”重新运行collectstatic并刷新页面。验证用浏览器直接访问http://localhost:8000/static/js/echarts.min.js查看源码开头是否为var echarts ...无乱码字符。5.2 现象筛选班级后图表数据不变但 URL 参数已更新原因Django View 中未将class_name参数传入 ORM 查询或filter()条件写错如student_class_name...而非student__class_name...。解决在 View 中打印最终 QuerySet SQLqs SubmissionRecord.objects.filter(...) print(qs.query) # 查看生成的 SQL 是否含 WHERE 条件检查外键关系Student模型中class_name字段关联时必须用双下划线student__class_name在 Shell 中手动测试 from myapp.models import SubmissionRecord SubmissionRecord.objects.filter(student__class_nameCS2023A).count() 127 # 确认有数据5.3 现象导出 Excel 时中文乱码单元格显示“???”原因openpyxl默认使用utf-8编码写入但 Excel 应用默认按系统编码如 Windows-1252读取。解决无需改编码只需在写入字符串时显式指定str()并确保内容为 Unicode# 错误ws.cell(..., valueb\xe4\xb8\xad\xe6\x96\x87) # bytes # 正确ws.cell(..., value中文) # str若数据来自数据库确认DATABASES配置中OPTIONS含charset: utf8mb4MySQL或encodingutf-8SQLite。5.4 现象时间筛选start_date2024-03-01传到后端变成2024-03-01T00:00:00导致查询无结果原因HTMLinput typedate提交的是YYYY-MM-DD字符串但 Django Form 的DateField会自动转为datetime.date对象而 ORM__range查询要求datetime类型。解决在 View 中统一转为datetimestart_dt datetime.combine(form.cleaned_data[start_date], datetime.min.time()) end_dt datetime.combine(form.cleaned_data[end_date], datetime.max.time()) qs qs.filter(submit_time__range(start_dt, end_dt))5.5 现象部署到机房电脑后图表加载缓慢Network 显示echarts.min.js耗时 2s原因机房电脑禁用了 DNS 预取且echarts.min.js体积较大~700KB未启用 Gzip 压缩。解决在 Nginx 配置中开启 Gzipgzip on; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;将echarts.min.js替换为 CDN 版本答辩演示时可用正式提交需离线!-- report.html head 中 -- script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script注意CDN 方式需确保机房网络可访问 jsdelivr否则回退到本地文件。6. 答辩与验收用“三页 PPT 一个可操作 Demo”通过答辩答辩不是讲技术原理而是证明“这个系统真能用”。我带过的毕设小组90% 以上卡在“老师问‘能现场改个需求吗’就懵了”。以下是我打磨出的答辩通关策略亲测有效。6.1 答辩材料准备三页 PPT 覆盖全部得分点不要做 20 页技术细节 PPT。答辩委员会平均每人看 3 个学生注意力窗口只有 5 分钟。三页 PPT 必须直击要害页码标题内容要点设计意图Page 1需求与价值解决了什么真问题• 用 1 句话描述场景“为课程设计管理系统提供实时、可筛选、可导出的实验进度可视化”• 列 3 个具体痛点- 教师手工统计提交率耗时 2 小时/周- 学生无法自助查看个人进度- 管理员导出数据需联系开发者• 配图对比图左Excel 手工表右本系统图表让评委 30 秒内理解项目意义建立“这东西有用”的第一印象Page 2技术实现为什么选这个方案• 技术栈表格组件Page 3成果与验证它真的能跑吗• 截图 3 张- 主页带日期筛选器、班级下拉框的图表页- 导出效果Excel 文件打开截图显示“提交率(%)”列带百分号- 响应式手机浏览器访问截图证明适配移动端• 性能数据- 单次图表加载 800msChrome DevTools Network Tab 截图- 支持 50 并发用户Locust 压测报告摘要用证据说话消除“纸上谈兵”质疑提示PPT 中所有截图必须来自你自己的运行环境不能用网上下载图。答辩前用录屏软件录 30 秒操作视频筛选→刷新→导出备用。6.2 现场 Demo 操作清单5 分钟内完成 3 个必演动作答辩老师最爱现场改需求提前准备好“后悔药”脚本能极大提升可信度。以下 3 个动作必须脱稿完成总时长控制在 5 分钟内动作一更换图表类型2 分钟打开views.py找到chart_data_api函数新增一个elif分支elif chart_code top_students: # 新增逻辑查询提交次数 Top 10 学生 top_students SubmissionRecord.objects.values(student__name).annotate( countCount(id) ).order_by(-count)[:10] return { xAxis: [item[student__name] for item in top_students], series: [{name: 提交次数, data: [item[count] for item in top_students]}] }修改前端 URL将/api/chart/submit_rate_by_week/改为/api/chart/top_students/刷新页面展示新图表。本文还有配套的精品资源点击获取