资讯动态

AJAX实战:从XMLHttpRequest到axios,蓝桥杯“组课神器”全解析

发布时间:2026/9/7 4:36:56 来源:尧图企业网站定制
本系列第28篇我们继续 Web 应用开发实战。很多同学在准备蓝桥杯 Web 应用开发方向时最担心的往往不是 HTML 布局也不是 CSS 样式而是 AJAX 请求按钮点击后页面没反应、后端明明有数据却渲染不出来、请求发出去了却报 404……这些问题看似零散其实都指向同一个能力点——你能不能在前端页面中正确发起异步请求并且拿到数据后完成页面动态更新。本文以一道蓝桥杯真题风格的模拟题“组课神器”为例完整拆解 AJAX 请求从理论到实战的完整链路。题目场景是学生在前端页面选择课程方向和学习难度页面通过 AJAX 请求向后端获取课程列表再勾选若干课程发送给后端进行课程时间冲突检测并展示推荐组合结果。整个项目包含原生 XMLHttpRequest、jQuery 的$.ajax、axios 三种写法以及 Flask 后端接口设计覆盖了蓝桥杯 Web 应用开发中 AJAX 模块的高频考点。无论你是刚接触 AJAX 的新手还是准备蓝桥杯冲刺的备赛选手这篇文章都能提供一套可以直接复制的代码模板和排查思路。1. 题目背景与考点定位1.1 “组课神器”是一个什么题“组课神器”本质是一个课程组合推荐工具。学生用户打开页面后会看到一个筛选表单上面有“课程方向”和“难度等级”两个条件选择并点击“查询课程”后页面下方会动态显示符合条件的课程卡片。用户可以勾选 2 到 4 门课程点击“智能组课”系统会判断所选课程是否存在时间冲突并返回组合后的总学分与推荐提示。这个场景在真实业务里非常常见换成“选课系统”“预约系统”“商品筛选系统”都说得通。蓝桥杯 Web 应用开发真题也经常以这类“表单查询 动态渲染 数据回传”的题目出现考察点非常集中前端表单数据的获取与拼接AJAX 请求的 URL、请求方法、请求参数设置后端接口返回 JSON 数据的结构设计前端拿到 JSON 后如何遍历渲染POST 请求中 JSON 格式数据的序列化与解析。1.2 蓝桥杯 Web 应用开发为什么爱考 AJAXAJAX 是现代 Web 应用前后端交互的基石。在蓝桥杯 Web 应用开发方向的题目中静态页面只能拿基础分真正区分水平的就是动态数据交互。真题中经常出现的“数据列表加载”“条件筛选”“异步提交”都依赖 AJAX。同时蓝桥杯的 Web 应用开发赛项也支持 Python Flask/Django、Java Spring 等后端技术栈前后端分离的开发方式越来越普遍。在考试中选手需要在规定时间内完成后端接口编写再在前端页面中通过 AJAX 请求调用接口并完成数据渲染。只要 AJAX 请求这一步没掌握哪怕后端写得再完整页面也无法展示出结果最终得分会受到很大影响。1.3 本文使用的技术栈与运行环境为了让代码最小可运行本文后端以 Python Flask 为例Flask 2.x / 3.x 均可运行。如果你参加蓝桥杯时使用的是 Django原理完全一致只需要把路由和请求解析方式替换为 Django 对应写法即可。环境说明操作系统Windows / macOS / Linux 均可Python3.8 及以上Flask2.x 或 3.x浏览器Chrome / Edge 最新版编辑器VS Code、PyCharm 均可版本不一定需要完全一致重点是把 AJAX 请求的开发思路掌握清楚。2. 先搞懂 AJAX 请求的底层逻辑2.1 什么是 AJAX它解决了什么问题AJAX 全称 Asynchronous JavaScript And XML中文叫“异步 JavaScript 和 XML”。注意这里虽然名字里有 XML但在实际开发中现代接口几乎都返回 JSON 而不是 XML。在没有 AJAX 的时代浏览器每次向服务器请求数据都会刷新整个页面。用户体验差服务器压力也比较大。AJAX 出现后页面可以通过 JavaScript 在后台悄悄发出请求拿到服务器返回的数据后只更新页面中的某一块区域不用刷新整个页面。以“组课神器”为例用户点击“查询课程”按钮后页面不会跳到新地址也不会整体刷新而是课程列表区域出现新的卡片数据。这就是 AJAX 的典型应用。2.2 一次 AJAX 请求包含哪些关键要素无论使用哪种 AJAX 库一次完整的请求都包含以下关键要素要素作用示例url请求的接口地址/api/coursesmethod / type请求方法GET、POSTdata请求参数{direction: 后端开发, level: 中级}dataType期望的返回类型jsonsuccess请求成功后的回调渲染课程列表error请求失败后的回调弹出错误提示其中最容易写错的就是请求参数的类型。GET 请求的参数通常是拼在 URL 后面的查询字符串比如/api/courses?direction后端开发level中级而 POST 请求的参数可以放在请求体中常见有两种格式application/x-www-form-urlencoded表单格式application/jsonJSON 字符串格式。这个区别直接决定了后端解析参数的方式后面实战部分会重点演示。2.3 同步与异步AJAX 的“异步”到底指什么AJAX 中的 A 是 Asynchronous异步。异步的意思是浏览器发出请求后不需要傻等服务器返回结果可以继续执行后面的 JavaScript 代码。等服务器响应返回后浏览器再触发回调函数。理解这一点很重要。很多同学写代码时想在发出请求的下一行直接使用返回数据结果发现数据是undefined。原因就是请求还没完成代码已经继续往下执行了。$.ajax({ url: /api/courses, success: function (res) { console.log(请求成功拿到数据, res); } }); console.log(这行代码会在请求完成前先执行);这种异步特性需要一段时间适应但只要记住“数据只能在 success 回调里使用”就不会踩坑。后续我们也会提到可以使用async/await把异步代码写成更接近同步阅读习惯的样式。3. 环境准备与项目结构3.1 搭建项目目录先在本地创建一个项目文件夹命名为group_course目录结构如下group_course/ ├── app.py └── templates/ └── index.htmlFlask 默认会从templates目录下查找模板文件所以把前端页面放在这里。如果后续代码量大你还可以继续拆分出static/js和static/css目录但本文示例为了专注于 AJAX 逻辑将 JavaScript 内联在 HTML 页面中。3.2 安装 Flask在命令行进入项目目录创建并激活虚拟环境推荐然后安装 Flaskpython -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install flask安装完成后可以先测试一下 Flask 是否能正常导入from flask import Flask app Flask(__name__) print(Flask 导入成功)只要能正常输出就说明环境准备好了。3.3 设计后端接口“组课神器”需要两个接口接口请求方式功能/api/coursesGET根据方向、难度筛选课程列表/api/combinePOST接收选中的课程 id 列表检测冲突并返回组合信息接口返回格式统一使用如下结构{ code: 0, msg: success, data: {} }其中code为业务状态码0表示成功msg是提示信息data是具体业务数据。这个结构在后面讲解最佳实践时会进一步说明。4. 原生 XMLHttpRequest 实现课程查询4.1 为什么先学 XMLHttpRequest虽然现在很多人直接使用 jQuery 或 axios但蓝桥杯真题中偶尔会要求“不使用第三方库”或者直接考察原生 JS 实现。因此先掌握原生写法非常重要。XMLHttpRequest 是浏览器内置的对象简称 XHR。它不需要安装任何依赖所有现代浏览器都支持。一个最简单的 GET 请求如下var xhr new XMLHttpRequest(); xhr.open(GET, /api/courses, true); xhr.onreadystatechange function () { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); console.log(res); } }; xhr.send();这段代码的逻辑分成三步创建 XHR 对象调用open方法指定请求方式和地址第三个参数true表示异步请求调用send发送请求并在onreadystatechange中监听状态变化。readyState有 0 到 4 五个值通常只需要关心4代表请求已完成。status是 HTTP 状态码200代表成功。4.2 带参数发送 GET 请求在“组课神器”中用户选择方向和难度后查询参数是拼接到 URL 上的。直接使用字符串拼接很容易出错尤其当参数里包含中文时需要调用encodeURIComponent进行编码。我们可以封装一个工具函数来处理参数拼接function buildQuery(params) { var arr []; for (var key in params) { if (params.hasOwnProperty(key)) { arr.push(encodeURIComponent(key) encodeURIComponent(params[key])); } } return arr.join(); }然后查询课程列表function loadCourses() { var params { direction: document.getElementById(direction).value, level: document.querySelector(input[namelevel]:checked).value }; var query buildQuery(params); var xhr new XMLHttpRequest(); xhr.open(GET, /api/courses? query, true); xhr.onreadystatechange function () { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); renderCourses(res.data); } }; xhr.send(); }这里有一个常见误区页面代码里使用了document.querySelector(input[namelevel]:checked)如果用户没有选择任何难度会返回null再访问.value就会报错。所以前端在发送请求之前应该先做空值校验。4.3 用 XHR 发送 POST 请求参数格式是关键POST 请求与 GET 最大的区别在于参数放入请求体且必须设置Content-Type。如果后端使用request.get_json()解析 JSON 数据那么前端的Content-Type必须为application/json并且send的参数必须是 JSON 字符串而不是对象。function combineCourses() { var checkedBoxes document.querySelectorAll(input[namecourseIds]:checked); var ids []; checkedBoxes.forEach(function (box) { ids.push(Number(box.value)); }); if (ids.length 2) { alert(请至少选择两门课程); return; } var xhr new XMLHttpRequest(); xhr.open(POST, /api/combine, true); xhr.setRequestHeader(Content-Type, application/json); xhr.onreadystatechange function () { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); renderCombineResult(res.data); } }; xhr.send(JSON.stringify({ ids: ids })); }需要注意JSON.stringify({ ids: ids })把 JavaScript 对象转成了 JSON 字符串Content-Type也显式设置为application/json。如果这两点缺少一个后端很可能解析不到数据。4.4 数据渲染拿到 JSON 之后怎么展示当loadCourses拿到课程列表后需要把这些数据渲染成页面上的卡片。一般思路是先用innerHTML拼接 HTML 字符串然后一次性赋值给容器节点。function renderCourses(courses) { var container document.getElementById(courseList); if (courses.length 0) { container.innerHTML p classempty没有符合条件的课程/p; return; } var html ; courses.forEach(function (course) { html div classcourse-card; html label; html input typecheckbox namecourseIds value course.id ; html span classcourse-name course.name /span; html span classcourse-meta course.direction / course.level /span; html span classcourse-credit course.credit 学分/span; html /label; html /div; }); container.innerHTML html; }这种拼接方式简单直接适合比赛场景。需要注意的是课程名称如果是用户输入的数据直接拼接到innerHTML中可能存在 XSS 注入风险。在蓝桥杯题目中课程数据一般是写死的模拟数据所以问题不大但如果是在真实业务中就需要做 HTML 转义处理。5. 更常用的 jQuery 与 axios 写法5.1 引入方式jQuery 和 axios 都是第三方库。在蓝桥杯考试环境中通常已经允许使用本地引入或 CDN 引入。你也可以直接在页面顶部用 CDNscript srchttps://cdn.jsdelivr.net/npm/jquery3.7.1/dist/jquery.min.js/script script srchttps://cdn.jsdelivr.net/npm/axios1.7.2/dist/axios.min.js/script如果比赛机器不能联网建议提前下载到本地static/js目录再通过相对路径引入。5.2 jQuery 的 $.ajax 写法jQuery 的$.ajax是封装程度较高的一种写法蓝桥杯历年真题中出现频率很高。它的参数是一个对象优势是可以通过type、data、dataType等字段快速配置请求。GET 请求查询课程$.ajax({ url: /api/courses, type: GET, data: { direction: $(#direction).val(), level: $(input[namelevel]:checked).val() }, dataType: json, success: function (res) { renderCourses(res.data); }, error: function (xhr, status, error) { console.error(请求失败, error); } });POST 请求提交课程组合$.ajax({ url: /api/combine, type: POST, contentType: application/json, data: JSON.stringify({ ids: ids }), dataType: json, success: function (res) { renderCombineResult(res.data); }, error: function () { alert(提交失败请稍后重试); } });如果不设置contentType: application/jsonjQuery 默认会按表单格式application/x-www-form-urlencoded提交。此时 Flask 后端应该使用request.form.get来获取参数而不能使用request.get_json()。这是非常经典的参数解析不一致问题。5.3 表单快速序列化在实际题目中如果表单字段很多可以使用 jQuery 的serialize()方法快速把表单内容拼成查询字符串var query $(#filterForm).serialize(); // 输出类似direction后端开发level中级 $.ajax({ url: /api/courses? query, type: GET, dataType: json, success: function (res) { renderCourses(res.data); } });serialize()会自动对特殊字符进行编码对于中文和空格都能正确处理推荐在比赛时优先使用能减少手写参数拼接的错误。5.4 axios 与 async/await 写法axios 更符合现代前端项目的写法配合async/await后代码阅读起来接近同步逻辑async function loadCourses() { try { const response await axios.get(/api/courses, { params: { direction: document.getElementById(direction).value, level: document.querySelector(input[namelevel]:checked).value } }); renderCourses(response.data.data); } catch (err) { console.error(请求失败, err); } }POST 请求async function combineCourses() { const ids getCheckedCourseIds(); if (ids.length 2) { alert(请至少选择两门课程); return; } try { const response await axios.post(/api/combine, { ids: ids }); renderCombineResult(response.data.data); } catch (err) { alert(提交失败请稍后重试); } }axios 与 jQuery 的$.ajax有一个明显区别axios 请求成功后返回对象response中的data才是后端返回的 JSON 内容所以代码里会连续访问两次data。这一点初学者特别容易搞混可以把后端返回结构打印到控制台确认一下。6. 完整案例组课神器的前后端实现前面把知识点拆开了这一节给出一个可以直接运行的完整项目。后端使用 Flask前端使用原生 JavaScript XMLHttpRequest并同时提供 jQuery 版本的替代代码注释说明。6.1 后端接口完整代码文件路径group_course/app.pyfrom flask import Flask, jsonify, render_template, request app Flask(__name__) # 模拟数据库课程表 COURSES [ {id: 1, name: HTML网页基础, direction: 前端开发, level: 初级, day: 1, period: 3, credit: 2}, {id: 2, name: CSS布局实战, direction: 前端开发, level: 中级, day: 1, period: 5, credit: 3}, {id: 3, name: JavaScript核心语法, direction: 前端开发, level: 中级, day: 2, period: 3, credit: 4}, {id: 4, name: Vue组件开发, direction: 前端开发, level: 高级, day: 3, period: 3, credit: 4}, {id: 5, name: Python程序设计, direction: 后端开发, level: 初级, day: 1, period: 3, credit: 2}, {id: 6, name: Flask接口开发, direction: 后端开发, level: 中级, day: 2, period: 5, credit: 3}, {id: 7, name: Django Web开发, direction: 后端开发, level: 中级, day: 4, period: 3, credit: 4}, {id: 8, name: 数据库设计, direction: 后端开发, level: 高级, day: 5, period: 3, credit: 4}, {id: 9, name: Python数据分析, direction: 数据开发, level: 初级, day: 3, period: 5, credit: 3}, {id: 10, name: SQL查询优化, direction: 数据开发, level: 中级, day: 4, period: 5, credit: 3}, {id: 11, name: 数据可视化, direction: 数据开发, level: 高级, day: 5, period: 5, credit: 2}, {id: 12, name: 爬虫与数据采集, direction: 数据开发, level: 高级, day: 2, period: 3, credit: 4}, ] app.route(/) def index(): return render_template(index.html) app.route(/api/courses, methods[GET]) def get_courses(): direction request.args.get(direction, 全部) level request.args.get(level, 全部) result [] for course in COURSES: if direction ! 全部 and course[direction] ! direction: continue if level ! 全部 and course[level] ! level: continue result.append(course) return jsonify({code: 0, msg: success, data: result}) app.route(/api/combine, methods[POST]) def combine_courses(): payload request.get_json(silentTrue) if not payload or ids not in payload: return jsonify({code: 1, msg: 参数缺失, data: None}) ids payload.get(ids, []) # 过滤出不存在的课程 id避免用户传入非法参数 selected [course for course in COURSES if course[id] in ids] # 检测时间冲突day 和 period 都相同则冲突 conflict False for i in range(len(selected)): for j in range(i 1, len(selected)): if selected[i][day] selected[j][day] and selected[i][period] selected[j][period]: conflict True total_credit sum(course[credit] for course in selected) data { courses: selected, conflict: conflict, total_credit: total_credit, } return jsonify({code: 0, msg: success, data: data}) if __name__ __main__: app.run(debugTrue)后端说明request.args.get用于获取 GET 查询参数request.get_json(silentTrue)用于解析 JSON 请求体加silentTrue后即使解析失败也不会抛异常而是返回Nonejsonify会把字典转换为 JSON 响应时间冲突检测使用了双重循环题目数据量小性能完全够用。6.2 前端页面完整代码文件路径group_course/templates/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title组课神器/title style * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: Microsoft YaHei, PingFang SC, sans-serif; background: #f4f6f9; padding: 30px; } .container { max-width: 900px; margin: 0 auto; background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } h1 { font-size: 24px; margin-bottom: 6px; } .subtitle { color: #888; font-size: 14px; margin-bottom: 20px; } .filter-form { display: flex; flex-wrap: wrap; gap: 20px; align-items: center; border-bottom: 1px dashed #ddd; padding-bottom: 20px; margin-bottom: 20px; } .filter-form label { font-size: 14px; margin-right: 6px; } .filter-form select, .filter-form input[typeradio] { cursor: pointer; } .btn { padding: 8px 18px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; background: #4a7cf7; color: #fff; transition: background 0.2s; } .btn:hover { background: #3b66db; } .btn:disabled { background: #a0b4e0; cursor: not-allowed; } .course-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px; } .course-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; transition: border-color 0.2s; } .course-card:hover { border-color: #4a7cf7; } .course-card label { display: flex; align-items: center; cursor: pointer; } .course-card input { margin-right: 8px; } .course-name { font-weight: 600; margin-right: auto; } .course-meta { font-size: 12px; color: #999; margin-right: 8px; } .course-credit { font-size: 12px; color: #4a7cf7; background: #eef3ff; border-radius: 4px; padding: 2px 6px; } .empty { grid-column: 1 / -1; color: #999; text-align: center; padding: 30px 0; } .result-box { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 16px; } .result-box h3 { margin-bottom: 10px; font-size: 16px; } .result-item { font-size: 14px; color: #333; margin-bottom: 4px; } .tip-success { color: #16a34a; margin-top: 8px; } .tip-warning { color: #dc2626; margin-top: 8px; } /style /head body div classcontainer h1组课神器/h1 p classsubtitle选择课程方向与难度查看推荐课程组合/p div classfilter-form div label fordirection课程方向/label select iddirection option value全部全部/option option value前端开发前端开发/option option value后端开发后端开发/option option value数据开发数据开发/option /select /div div label难度等级/label labelinput typeradio namelevel value全部 checked 全部/label labelinput typeradio namelevel value初级 初级/label labelinput typeradio namelevel value中级 中级/label labelinput typeradio namelevel value高级 高级/label /div button classbtn idsearchBtn查询课程/button /div div idcourseList classcourse-list/div button classbtn idcombineBtn智能组课/button div idresultBox classresult-box stylemargin-top:20px; display: none; h3推荐结果/h3 div idresultContent/div /div /div script // 1. 构建查询字符串 function buildQuery(params) { var arr []; for (var key in params) { if (params.hasOwnProperty(key)) { arr.push(encodeURIComponent(key) encodeURIComponent(params[key])); } } return arr.join(); } // 2. 查询课程 function loadCourses() { var direction document.getElementById(direction).value; var levelRadio document.querySelector(input[namelevel]:checked); var level levelRadio ? levelRadio.value : 全部; var query buildQuery({ direction: direction, level: level }); var xhr new XMLHttpRequest(); xhr.open(GET, /api/courses? query, true); xhr.onreadystatechange function () { if (xhr.readyState 4) { if (xhr.status 200) { var res JSON.parse(xhr.responseText); renderCourses(res.data); } else { alert(课程查询失败请检查后端服务是否启动); } } }; xhr.send(); } // 3. 渲染课程卡片 function renderCourses(courses) { var container document.getElementById(courseList); if (!courses || courses.length 0) { container.innerHTML p classempty没有符合条件的课程/p; return; } var html ; courses.forEach(function (course) { html div classcourse-card; html label; html input typecheckbox namecourseIds value course.id ; html span classcourse-name course.name /span; html span classcourse-meta course.direction / course.level /span; html span classcourse-credit course.credit 学分/span; html /label; html /div; }); container.innerHTML html; } // 4. 获取选中的课程 id 列表 function getCheckedIds() { var boxes document.querySelectorAll(input[namecourseIds]:checked); var ids []; boxes.forEach(function (box) { ids.push(Number(box.value)); }); return ids; } // 5. 提交组合 function combineCourses() { var ids getCheckedIds(); if (ids.length 2) { alert(请至少选择两门课程); return; } if (ids.length 4) { alert(最多选择四门课程); return; } var xhr new XMLHttpRequest(); xhr.open(POST, /api/combine, true); xhr.setRequestHeader(Content-Type, application/json); xhr.onreadystatechange function () { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); renderCombineResult(res.data); } }; xhr.send(JSON.stringify({ ids: ids })); } // 6. 渲染组合结果 function renderCombineResult(data) { var box document.getElementById(resultBox); var content document.getElementById(resultContent); box.style.display block; var html ; if (!data.courses || data.courses.length 0) { html p classresult-item未找到有效课程/p; } else { data.courses.forEach(function (course) { html p classresult-item course.name course.direction 第 course.day 天 第 course.period 节/p; }); html p classresult-item总学分 data.total_credit /p; if (data.conflict) { html p classtip-warning课程时间存在冲突请调整选择/p; } else { html p classtip-success课程时间无冲突推荐组合成功/p; } } content.innerHTML html; } // 7. 绑定事件 document.getElementById(searchBtn).addEventListener(click, loadCourses); document.getElementById(combineBtn).addEventListener(click, combineCourses); // 页面加载时默认展示全部课程 loadCourses(); /script /body /html这段代码里需要注意两个细节使用input[namecourseIds]:checked选择被勾选的复选框这与 HTML 中复选框的name属性一一对应Number(box.value)把字符串类型的复选框值转换为数字避免后续后端比对 id 时出现类型不一致的问题。6.3 运行与验证在项目目录下执行python app.py浏览器访问http://127.0.0.1:5000页面上会默认显示全部课程。选择“后端开发”和“中级”点击“查询课程”页面会立即刷新课程列表只显示两门课程Flask 接口开发 和 Django Web 开发。再勾选这两门课程点击“智能组课”页面下方会显示推荐结果Flask 接口开发后端开发第 2 天 第 5 节 Django Web 开发后端开发第 4 天 第 3 节 总学分7 课程时间无冲突推荐组合成功如果选择“HTML网页基础”和“Python程序设计”两门课都是第 1 天第 3 节就会提示“课程时间存在冲突请调整选择”。这就是后端冲突检测逻辑生效了。6.4 打开浏览器开发者工具观察请求按 F12 打开开发者工具切换到 Network 面板再点击“查询课程”。可以看到浏览器发出了一条GET请求地址为/api/courses?direction后端开发level中级。点击该请求在 Payload 或 Query String Parameters 中能看到完整参数。再点击“智能组课”可以看到一条POST /api/combine请求请求头中带有Content-Type: application/json请求体是{ids: [6, 7]}这个面板是排查 AJAX 问题的第一现场。如果页面没有按预期更新优先看 Network 面板里请求的状态码、请求参数和响应内容。7. 常见问题与排查思路蓝桥杯比赛时间紧张遇到问题如果反复试错会浪费大量时间。下面整理了几类 AJAX 请求的高频问题你可以按表排查。问题现象常见原因解决思路点击按钮后页面无任何反应JavaScript 报错事件未绑定F12 打开 Console 面板查看报错行请求返回 404URL 路径写错或后端路由未定义对比 Network 中的请求地址与 Flask 路由返回 JSON 字符串前端无法渲染忘记JSON.parse或 dataType 设置不一致确认xhr.responseText后先JSON.parsePOST 请求返回 400 / 后端收到 NoneContent-Type 不是application/json检查请求头使用setRequestHeader设置后端拿到参数但返回结果不对参数名不一致打印request.args或payload逐一比对页面中文显示乱码HTML 没有meta charsetUTF-8在 HTML head 中补充编码声明请求出现 CORS 跨域报错前端页面与接口不在同源地址开发时统一通过 Flask 服务访问或配置 CORS按钮重复点击产生大量请求没有做防重复提交点击后禁用按钮请求完成后恢复异步请求拿到数据时页面已经执行后面代码不理解异步回调把数据渲染逻辑放入 success 回调中其中“POST 请求后端收到 None”是最常见的问题根本原因通常是前端没有设置Content-Type: application/json。你可以把请求头打印出来或者在后端接口中临时加一行print(request.headers.get(Content-Type))如果输出不是application/json那就可以确定是请求头的问题。8. 蓝桥杯备考与工程实践建议8.1 比赛时优先保证“请求能通”蓝桥杯 Web 应用开发是限时比赛代码规范性和性能优化不是第一优先级。拿到题目后先保证前后端接口能连通再考虑代码优雅性。我建议按这个顺序检查后端路由能否直接通过浏览器访问前端 AJAX 请求地址与路由是否完全一致注意大小写和斜杠请求参数名是否与后端解析字段一致返回 JSON 是否与前端渲染字段一致页面渲染是否完成预期效果。只要这个链路跑通题目就已经拿下了大部分分数。8.2 封装统一的 AJAX 工具函数在真实项目和复赛中建议把 AJAX 请求封装成一个函数避免每个页面重复写 XMLHttpRequest 模板代码。下面是一个简单的封装示例function ajax(options) { var xhr new XMLHttpRequest(); xhr.open(options.method || GET, options.url, true); if (options.headers) { for (var key in options.headers) { xhr.setRequestHeader(key, options.headers[key]); } } xhr.onreadystatechange function () { if (xhr.readyState 4) { if (xhr.status 200 xhr.status 300) { var data JSON.parse(xhr.responseText); options.success options.success(data); } else { options.error options.error(xhr); } } }; var body null; if (options.data) { if (options.contentType application/json) { xhr.setRequestHeader(Content-Type, application/json); body JSON.stringify(options.data); } else { xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded); body buildQuery(options.data); } } xhr.send(body); }使用方式ajax({ url: /api/courses, method: GET, data: { direction: 后端开发, level: 中级 }, success: function (res) { renderCourses(res.data); }, error: function () { alert(请求失败); } });这样页面中所有请求都能保持一致的书写风格排查问题也更方便。8.3 统一后端返回格式前后端分离开发时接口返回格式一定要统一。推荐使用{ code, msg, data }结构。前端只判断code是否为 0再读取data渲染页面。这样即使后端内部逻辑变化前端代码也不需要大改。如果code非 0比如参数缺失、未登录、数据不存在前端可以直接根据msg提示用户错误处理逻辑也会简单很多。8.4 安全边界不能忘虽然蓝桥杯题目相对简单但写代码时仍然要养成基本的安全意识后端接口不要信任前端传入的 id要做存在性校验对传入参数做类型判断避免None类型的后续访问报错动态拼接 HTML 时如果是用户可控数据要做 HTML 转义课程组合、订单提交这类接口涉及写操作时建议使用 POST 而不是 GET如果接口涉及用户数据生产环境中必须增加身份认证和权限校验。这些习惯不一定能在比赛评分中直接体现但能帮助你在实际项目中避免大量线上故障。8.5 优先掌握一种写法再扩展其他写法原生 XMLHttpRequest、jQuery 的$.ajax、axios、fetch 这几种方式背后原理相同区别只是封装程度。建议先把原生 XHR 写熟再用 jQuery 和 axios 提高开发效率。考试时优先使用自己最熟悉、最有把握的写法不要为了展示技术栈去用不熟悉的库。如果时间允许可以继续学习FormData文件上传、async/await异步流程控制、JSONP 跨域原理等扩展内容。但就蓝桥杯 Web 应用开发这个方向而言把 GET 请求和 POST 请求掌握扎实已经能够覆盖大多数真题场景。备赛阶段建议手边放几个往年真题把题目中所有的数据请求都改成 AJAX 实现并在浏览器 Network 面板中观察请求参数和响应内容。练得多了AJAX 的常见坑自然就心里有数了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价