点此查看 所有教程、项目、源码导航
文章目录
- [1. 任务说明](#1. 任务说明)
- [2. 后端返回统一格式讲解](#2. 后端返回统一格式讲解)
- [3. 修改HTML文件,引入样式和JS文件](#3. 修改HTML文件,引入样式和JS文件)
- [4. 编写 common.js:通用POST请求封装](#4. 编写 common.js:通用POST请求封装)
- [5. 功能1:查询活动列表,渲染表格](#5. 功能1:查询活动列表,渲染表格)
- [6. 功能2:新增活动,弹窗表单提交接口](#6. 功能2:新增活动,弹窗表单提交接口)
- [7. 功能3:删除活动接口](#7. 功能3:删除活动接口)
- [8. 小结](#8. 小结)
1. 任务说明
副队:毛哥,咱们项目中JS、CSS文件不多,就不单独建文件夹了,全部和html放在同一级目录。另外有一个关键点,通用的post请求函数,属于整个项目公用代码,可以抽取到通用的JS文件中。
毛哥:哦,我懂了!如果以后还有学生页面,也需要发请求,那还得再复制一遍这个函数,就重复了对吧?
副队:对的,咱们单独新建一个
common.js放通用请求方法,所有页面都可以引入它。咱们开发顺序调整为:先实现查询活动列表、再实现新增活动、最后实现删除活动,页面打开优先展示已有活动列表,更符合用户正常使用习惯。
调整后的目录结构:
├── common.css
├── activity-manage.css
├── common.js
├── activity-manage.js
└── activity-manage.html
common.css:公共样式,导航、按钮、表格、弹窗通用样式activity-manage.css:活动管理页面专属样式common.js:项目通用JS,这里放封装好的post请求函数activity-manage.js:活动管理页面专属交互、业务逻辑activity-manage.html:活动管理页面主体
2. 后端返回统一格式讲解
副队:在写接口之前,咱们再巩固一个重点:咱们所有接口,后端返回数据统一是
{code, msg, data}这个JSON结构,这个设计非常关键,你想想为什么要统一格式?毛哥:我猜一下,如果每个接口返回的样子不一样,我写前端的时候,每个接口都要单独判断,会很乱?
副队:没错!就是这个道理,我给你拆开讲三个字段:
code:状态码,数字。code:200代表本次请求业务成功;其他数字代表失败,比如400参数不对、500服务器出错。前端判断code就能知道这次操作成功还是失败。msg:提示消息,字符串。成功的时候可以写"新增成功",失败的时候写"活动名称不能为空"这类文字,直接alert给用户看。data:真正的数据。成功的时候,需要返回的数据放在这里;像删除这种不需要返回数据的接口,data可以是空。
毛哥:我明白了!不管是查列表、新增、删除,返回的外壳都是这三个字段,我写前端代码的时候,处理逻辑是一套,不用来回改判断方式,太方便了!
3. 修改HTML文件,引入样式和JS文件
在上一篇的 activity-manage.html 的 <head> 里面,先引入公共样式,再引入页面专属样式:
html
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="activity-manage.css">
JS引入顺序很重要:先引入common.js(通用方法),再引入页面自己的activity-manage.js,保证页面JS里可以调用postRequest。
在 </body> 标签上方引入JS:
js
<script src="common.js"></script>
<script src="activity-manage.js"></script>
副队:顺序不能反,要是先引入页面JS,那时候通用请求方法 还没有定义,直接报错。
毛哥:记下了,公共的在前,页面业务在后。
4. 编写 common.js:通用POST请求封装
新建 common.js,写入通用请求函数:
js
/* 定义基准请求地址,此处是副队提前部署好的服务器域名+端口+后端项目名 */
let baseUrl = "http://www.pandabrother.cn:8080/java-api-demo";
/**
* 通用POST请求函数
* @param {string} url 请求地址
* @param {object} params 传给后端的参数对象
* @param {function} callback 请求完成后的回调函数
*/
function postRequest(url, params, callback) {
// 创建XHR对象
let xhr = new XMLHttpRequest();
// 使用POST方式打开连接
xhr.open('POST', baseUrl + url);
// 设置请求头,告诉后端传递的是JSON格式数据
xhr.setRequestHeader('Content-Type', 'application/json');
// 监听请求状态变化
xhr.onreadystatechange = function () {
// readyState=4 代表请求已经完成
if (xhr.readyState === 4) {
// 把后端返回的JSON字符串转为JS对象
let res = JSON.parse(xhr.responseText);
// 把返回结果交给回调函数处理
callback(res);
}
}
// 参数对象转为JSON字符串发送给后端
xhr.send(JSON.stringify(params));
}
毛哥:副队,这里的 callback 我有点懵,这个回调到底是什么意思?
副队:这个是很多初学者卡在这里的地方,我给你打个比方。咱们发网络请求,是一件慢动作 ,不是立刻就能拿到结果。
毛哥:慢动作?
副队:对,代码执行到
xhr.send()的时候,只是把消息发给后端了,然后JS会继续往下跑别的代码,不会卡在原地等后端返回数据。等后端处理完、数据回来了,onreadystatechange才会触发。毛哥:那我怎么知道数据什么时候回来?
副队:所以我们提前把"拿到数据之后要干什么"这一段逻辑,打包成一个函数,当做参数传给 postRequest。这个打包好的函数,就是 callback,回调函数。
毛哥:哦!相当于我提前写好一张纸条:等数据回来了,就执行纸条上的操作?
副队:这个比喻很到位!postRequest 本身只负责一件事:发POST请求、等响应。它不关心拿到数据之后是弹窗、刷新表格还是删除提示 。这些业务逻辑,交给外面传进来的 callback。
毛哥:我好像懂了!那每次调用 postRequest 的时候,我写的那个 function(res) { ... } 就是 callback?
副队:没错!你看后面查询列表那里:
postRequest('/api/activity/list', {}, function(res) { // 这里就是回调,拿到res之后渲染表格 })这一段 function,就是 callback。请求成功拿到 res,postRequest 内部执行 callback(res),把后端返回的数据送进去,交给你写的业务代码处理。
毛哥:那如果我不用回调,直接写一个变量接收返回值行不行?
副队:不行,这就是异步的特点。如果写
let result = postRequest(...),result 拿到的一定是 undefined,因为请求还没回来,代码早就跑完了。必须等网络响应回来,才能处理结果。毛哥:明白了!postRequest 是工具,只管发请求;回调是我交给这个工具的任务清单,等数据回来自动执行清单上的逻辑。
5. 功能1:查询活动列表,渲染表格
副队:咱们优先写查询。页面一打开,第一件事就是调用列表接口,从后端拿到已经存在的活动数据,渲染到表格。用户进来第一眼就能看到全部活动。
毛哥:懂了,不管数据库里有没有数据,页面加载都会自动查询一遍。
首先给表格的tbody一个idtable-body,便于操作表格。
然后编辑 activity-manage.js,先写查询列表代码:
js
// 获取活动列表,渲染表格
function getActivityList() {
postRequest('/activity/list', {}, function(res) {
// 打印一下,看下后端返回的结果是什么
console.log("getActivityList返回:",res);
// 统一判断code,200代表业务成功
if (res.code === 200) {
// res.data 就是后端返回的活动数组
let list = res.data;
let tableBody = document.getElementById('table-body');
// 先清空表格原有内容
tableBody.innerHTML = '';
// 循环遍历数据,拼接表格行
for (let i = 0; i < list.length; i++) {
let item = list[i];
let tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.time}</td>
<td>${item.address}</td>
<td>${item.maxNum}</td>
<td>
<button class="btn btn-del" onclick="delActivity(${item.id})">删除</button>
</td>
`;
tableBody.appendChild(tr);
}
} else {
// 请求业务失败,取出msg提示用户
alert(res.msg);
}
})
}
// 页面加载自动执行,加载活动列表
getActivityList();
注意,循环遍历数据,拼接表格行使用了模板字符串,
${item.id}这个语法,可以将item对象的id属性值插入到字符串中。
代码写完之后运行结果:

看到结果后毛哥狂喜,这是他人生第一次从后端接口拿到数据,并渲染到了界面上。
接着副队提醒毛哥看控制台数据:

哇哦,控制台将后端返回的对象打印的一清二楚,太棒了。
6. 功能2:新增活动,弹窗表单提交接口
副队:查询功能写完了,接下来实现新增。点击新增按钮弹出弹窗,填写信息提交给后端保存。新增成功之后,再次调用查询方法刷新表格,就能立刻看到刚新增的数据。
毛哥:提交之前加上非空校验,避免空数据传给后端。
首先,为了方便获取网页元素,给按钮和弹窗元素设置id值:
html
<!-- 新增活动按钮 -->
<button class="btn btn-add" id="btnAdd">新增活动</button>
<!-- 弹窗 -->
<div class="mask" id="editMask">
<!-- 弹窗内容盒子 -->
<div class="dialog">
<h3>新增活动</h3>
<!-- 表单每一组 -->
<div class="dialog-item">
<label>活动名称</label>
<input type="text" id="name">
</div>
<div class="dialog-item">
<label>分类</label>
<input type="text" id="type">
</div>
<div class="dialog-item">
<label>活动时间</label>
<input type="text" id="time">
</div>
<div class="dialog-item">
<label>活动地点</label>
<input type="text" id="address">
</div>
<div class="dialog-item">
<label>报名截止时间</label>
<input type="text" id="endTime">
</div>
<div class="dialog-item">
<label>人数上限</label>
<input type="text" id="maxNum">
</div>
<div class="dialog-item">
<label>简介</label>
<textarea id="about"></textarea>
</div>
<!-- 弹窗底部按钮区域 -->
<div class="dialog-footer">
<button class="cancel" id="btnCancel">取消</button>
<button class="submit" id="btnSubmit">提交</button>
</div>
</div>
</div>
继续补充到 activity-manage.js:
js
// 获取弹窗和表单元素
const editMask= document.getElementById('editMask');
// 提交新增活动
function submitAddActivity() {
// 获取表单输入值
let name= document.getElementById('name').value;
let type= document.getElementById('type').value;
let time= document.getElementById('time').value;
let address= document.getElementById('address').value;
let endTime= document.getElementById('endTime').value;
let maxNum= document.getElementById('maxNum').value;
let about= document.getElementById('about').value;
// 简单前端非空校验
if (!name|| !type|| !time|| !address|| !endTime|| !maxNum|| !about) {
alert('请填写全部活动信息!');
return;
}
// 组装传给后端的参数
let params = {
name: name,
type: type,
time: time,
address: address,
endTime: endTime,
maxNum: maxNum,
about: about
}
// 向后端发送请求
postRequest('/activity/add', params, function(res) {
if (res.code === 200) {
alert('新增活动成功!');
// 关闭弹窗
editMask.style.display = 'none';
// 新增成功,重新查询列表刷新页面数据
getActivityList();
} else {
alert(res.msg);
}
})
}
// 新增按钮,打开弹窗(flex用于居中)
document.getElementById('btnAdd').onclick = function () {
editMask.style.display = 'flex';
}
// 取消弹窗按钮
document.getElementById('btnCancel').onclick = function () {
editMask.style.display = 'none';
}
// 提交按钮绑定新增方法
document.getElementById('btnSubmit').onclick = submitAddActivity;
写完代码后,单击界面新增按钮:

然后打击提交后,因为在此调用了查询接口,所以界面自动刷新了数据:

哇塞,这感觉太棒了,毛哥爽了。
7. 功能3:删除活动接口
副队:查询、新增都完成,最后做删除。点击删除按钮,先弹出确认框,防止误操作,确认之后发送删除请求,删除完成后刷新列表。
毛哥:这个流程很贴合真实使用:先看数据 → 有需要就新增 → 不需要的就删掉。
继续补充到 activity-manage.js:
js
// 删除活动
function delActivity(id) {
if (!confirm('确定要删除这个活动吗?')) {
return;
}
let params = {
id: id
}
postRequest('/activity/delete', params, function(res) {
if (res.code === 200) {
alert('删除成功');
getActivityList();
} else {
alert(res.msg);
}
})
}
当看到删除成功后,界面自动刷新(因为调用了getActivityList()),毛哥又乐了。
8. 小结
副队:咱们现在的顺序是:查询 → 新增 → 删除 ,符合用户真实访问逻辑:打开页面先加载并查看所有活动,再按需添加新活动,过时的活动执行删除。公共请求函数抽离到 common.js,后续其他页面可以直接复用。
毛哥:这个顺序很贴合日常使用!新增、删除之后都会重新查询列表,页面数据和后端数据库保持同步。
副队:没错。当前管理员页面还差修改活动功能,下一节我们就实现编辑弹窗,原有数据带入弹窗回显,然后对接后端修改接口。