HTML+CSS+JS对话式实战(7)-学生端活动报名功能开发

点此查看 所有教程、项目、源码导航

文章目录

  • [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. 小结

副队:我们按下面步骤测试报名功能。

  1. 打开学生端页面,自动加载活动卡片;
  2. 点击卡片,弹出活动详情;
  3. 点击【立即报名】,详情弹窗关闭,打开报名表单;
  4. 如果留空提交,会弹出提示;
  5. 填写信息提交,后端返回成功或者报错提示;
  6. 点击取消,可以关闭报名弹窗。

7. 效果展示

点击卡片,弹出活动详情,效果如下:

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

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

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

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

相关推荐
西安小哥1 小时前
做 Agent 写到第三个小时,我开始给它算账
javascript
西安小哥1 小时前
让大模型操作浏览器跑了两周,我发现瓶颈根本不在模型
javascript
用户298698530141 小时前
用 Python 自动化把 JavaScript 代码转换为 Word 文档
javascript·python·api
学以智用1 小时前
正则表达式完整参考手册(JavaScript 版,兼容绝大多数语言)
javascript
北城笑笑2 小时前
Vue 109 ,从零搭建 Vue3 + Electron 工程:生态、安全桥接与打包选型( Vue + Electron )
前端·javascript·vue.js·安全·electron·vue
Highcharts.js2 小时前
Highcharts智能数据分析平台Orbit 多语言配置技术文档
java·前端·javascript·highcharts·orbit·智能数据分析·设置文档
ABAP-張旺3 小时前
SAP ABAP 实战:SAP GUI上做 HTML 看板报表
前端·html·sap·abap
非优秀程序员13 小时前
第07章 孙德良的阳谋|《互联网产品恩仇录》
javascript·后端
程序员大阳16 小时前
HTML+CSS+JS对话式实战(5)-活动管理页之查看报名名单的实现
javascript·css·html·查看报名名单