点此查看 所有教程、项目、源码导航
文章目录
- [1. 本篇任务说明](#1. 本篇任务说明)
- [2. 接口约定](#2. 接口约定)
- [3. HTML 新增片段](#3. HTML 新增片段)
- [4. activity-student.css 新增样式](#4. activity-student.css 新增样式)
- [5. activity-student.js 新增与改动代码](#5. activity-student.js 新增与改动代码)
- [6. 小结](#6. 小结)
- [7. 效果展示](#7. 效果展示)
1. 本篇任务说明
毛哥:副队,上一篇我们把学生端的活动列表和详情弹窗做完了,现在该做报名功能了吧?
副队:没错。这一节我们新增报名弹窗,编写报名逻辑,调用后端报名接口,同时做好表单校验,防止重复报名。
2. 接口约定
副队:先把报名接口的信息整理好,咱们这个项目所有接口都用 POST,后端统一返回
{code, msg, data}。
| 项目 | 内容 |
|---|---|
| 请求方式 | POST |
| 请求地址 | /activity/sign |
| 请求参数 | { activityId: 活动id, studentName: 学生姓名, studentNo: 学号, phone: 手机号 } |
| 返回格式 | {code, msg, data} |
示例成功返回 JSON:
json
{
"code": 200,
"msg": "报名成功",
"data": null
}
示例失败返回 JSON(重复报名):
json
{
"code": 400,
"msg": "您已经报名过该活动,请勿重复报名",
"data": null
}
毛哥:那如果报名人数已经满了,后端也会返回对应的 code 和 msg 对吧?
副队:是的,前端不用做复杂的人数判断,只负责提交表单,业务判断交给后端,拿到返回信息直接提示用户。
3. HTML 新增片段
副队:先修改原有详情弹窗底部,增加"立即报名"按钮,作为打开报名弹窗的入口。
html
<!-- 在详情弹窗的 dialog-footer 里面追加报名按钮 -->
<div class="dialog-footer">
<button class="primary-btn" onclick="openSignMask()">立即报名</button>
<button class="close" onclick="closeDetailMask()">关闭</button>
</div>
关键点解析
onclick="openSignMask()":绑定点击事件,用来唤起报名弹窗;- 保留原来的关闭按钮,不改动原有详情弹窗关闭逻辑;
- 使用
primary-btn样式类,和整套项目按钮风格统一。
副队:下面是本篇全新增加的报名弹窗 HTML,放到页面末尾,和详情弹窗平级。
html
<!-- 新增:报名弹窗 -->
<div class="mask sign-mask">
<div class="dialog">
<h3>活动报名</h3>
<div class="dialog-item">
<label>姓名</label>
<input type="text" id="sign-name" placeholder="请输入姓名">
</div>
<div class="dialog-item">
<label>学号</label>
<input type="text" id="sign-no" placeholder="请输入学号">
</div>
<div class="dialog-item">
<label>手机号</label>
<input type="text" id="sign-phone" placeholder="请输入手机号">
</div>
<div class="dialog-footer">
<button class="primary-btn" onclick="submitSign()">提交报名</button>
<button class="close" onclick="closeSignMask()">取消</button>
</div>
</div>
</div>
关键点解析
sign-mask:独立标识类,专门用来给这个弹窗写专属 CSS,不和详情弹窗view-mask冲突;- input 的 id 命名统一带上
sign-,方便在 JS 中区分是报名表单的输入框;- 两个按钮分别绑定提交、关闭弹窗函数,职责分开。
4. activity-student.css 新增样式
副队:下面这一段是报名弹窗和新增按钮的专属样式,追加进
activity-student.css,不会影响之前写好的详情弹窗。
css
/* ===== 报名弹窗 sign-mask ===== */
.sign-mask .dialog {
width: 460px;
max-width: 92%;
background: #ffffff;
border-radius: 12px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16);
box-sizing: border-box;
}
.sign-mask .dialog > h3 {
margin: 0 0 22px;
font-size: 22px;
color: #222;
text-align: center;
padding-bottom: 14px;
border-bottom: 1px solid #eee;
}
.sign-mask .dialog-item {
display: flex;
align-items: center;
margin-bottom: 18px;
}
.sign-mask .dialog-item label {
width: 96px;
flex-shrink: 0;
font-weight: 600;
color: #303030;
}
.sign-mask .dialog-item input {
flex: 1;
padding: 8px 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 15px;
box-sizing: border-box;
}
.sign-mask .dialog-item input:focus {
outline: none;
border-color: #4096ff;
}
.sign-mask .dialog-footer {
margin-top: 28px;
text-align: center;
}
/* 按钮通用 */
.primary-btn {
padding: 9px 24px;
border: none;
background-color: #4096ff;
color: #fff;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
transition: background-color 0.2s ease;
margin: 0 6px;
}
.primary-btn:hover {
background-color: #2b7edb;
}
.close {
padding: 9px 24px;
border: 1px solid #ccc;
background-color: #fff;
color: #333;
border-radius: 6px;
cursor: pointer;
font-size: 15px;
transition: all 0.2s ease;
margin: 0 6px;
}
.close:hover {
background-color: #f5f5f5;
}
关键点解析
- 使用
.sign-mask作为前缀,样式隔离,保证多个弹窗互不干扰;flex-shrink:0固定 label 宽度,表单左右对齐,观感整齐;:focus给输入框增加聚焦高亮,提升交互体验;- 按钮抽成通用类,后续页面其他按钮可以直接复用。
5. activity-student.js 新增与改动代码
副队:先增加一个全局变量,用来保存用户当前查看、准备报名的活动编号。
js
// 新增全局变量:保存当前选中活动 id
let currentActId = null;
关键点解析
- 在打开详情的时候赋值,打开报名弹窗时直接读取;
- 因为弹窗是分开的,需要一个全局变量传递活动 id;
- 默认值为 null,如果忘记赋值,提交的时候后端拿到空值会报错,方便调试。
副队:原来的openDetailMask需要改动一行,把活动 id 存到上面这个全局变量。
js
/**
* 打开详情弹窗,回填数据
* @param {Object} act 活动对象
*/
function openDetailMask(act) {
currentActId = act.id;
document.getElementById("detailMask").classList.add("show");
document.getElementById("act-name").innerText = act.name;
document.getElementById("act-type").innerText = act.type;
document.getElementById("act-time").innerText = act.time;
document.getElementById("act-address").innerText = act.address;
document.getElementById("act-endTime").innerText = act.endTime;
document.getElementById("act-maxNum").innerText = act.maxNum;
document.getElementById("act-about").innerText = act.about;
}
关键点解析
currentActId = act.id:核心改动,把点击这条活动的 id 存起来;- 其余原有赋值、弹窗展示代码保持原样,不破坏已经跑通的详情功能。
副队:下面是打开报名弹窗函数。
js
/**
* 打开报名弹窗
*/
function openSignMask() {
// 先关闭详情弹窗
closeDetailMask();
// 清空上一次输入
document.getElementById("sign-name").value = "";
document.getElementById("sign-no").value = "";
document.getElementById("sign-phone").value = "";
document.querySelector(".sign-mask").classList.add("show");
}
关键点解析
- 先关闭详情弹窗,避免两层弹窗叠加;
- 每次打开都清空输入框,防止上一次填写的残留数据干扰;
- 通过给
sign-mask添加show类实现弹窗显示,和整套弹窗控制逻辑保持一致。
副队:关闭报名弹窗函数。
js
/**
* 关闭报名弹窗
*/
function closeSignMask() {
document.querySelector(".sign-mask").classList.remove("show");
}
关键点解析
- 和关闭详情弹窗思路统一:移除
show类隐藏弹窗;- 逻辑简单,单独封装函数,方便多处调用。
副队:最后是核心的提交报名函数,包含表单校验、组装参数、调用后端接口。
js
/**
* 提交报名
*/
function submitSign() {
const name = document.getElementById("sign-name").value.trim();
const no = document.getElementById("sign-no").value.trim();
const phone = document.getElementById("sign-phone").value.trim();
// 简单前端校验
if (!name) {
alert("请输入姓名");
return;
}
if (!no) {
alert("请输入学号");
return;
}
if (!phone) {
alert("请输入手机号");
return;
}
const params = {
activityId: currentActId,
studentName: name,
studentNo: no,
phone: phone
};
postRequest("/activity/sign", params, function (res) {
if (res.code === 200) {
alert("报名成功!");
closeSignMask();
} else {
alert(res.msg);
}
});
}
关键点解析
- 使用
.trim()去掉首尾空格,防止用户只输入空格就提交;- 前端只做空校验,真正的重复报名、人数已满、学号合法性交给后端;
- 使用我们之前封装好的
postRequest,统一 POST 请求;- 根据后端返回的
code判断结果,成功自动关闭弹窗,失败展示后端返回的提示文字。
毛哥:此处我又有一个疑问了副队:但说无妨啊
毛哥:提交报名为何只传了activityId参数,没有传活动名称activityName呢,我看之前获取的时候,是同时拿到了这两个啊。
副队:哈哈,这又是后端技术的范畴了。你传到后端活动id之后,后端完全可以写代码查到对应的活动name啊。
毛哥:嗨,我以后也要学后端,成为全栈工程师。
副队:那没毛病,看副队长的教程就行,那可是立志要写完前端写后端的。
6. 小结
副队:我们按下面步骤测试报名功能。
- 打开学生端页面,自动加载活动卡片;
- 点击卡片,弹出活动详情;
- 点击【立即报名】,详情弹窗关闭,打开报名表单;
- 如果留空提交,会弹出提示;
- 填写信息提交,后端返回成功或者报错提示;
- 点击取消,可以关闭报名弹窗。
7. 效果展示
点击卡片,弹出活动详情,效果如下:

点击【立即报名】,详情弹窗关闭,打开报名表单,效果如下:

填写信息提交,效果如下:

点击提交报名,效果如下:

此时再打开活动管理端,查看报名名单,刚刚的报名信息已经进来了:
