Skip to content

综合案例:学员注册页面 ​

这一节不引入 CSS、JavaScript 或后端框架,只使用前面学过的 HTML,完成一张结构清楚、能够原生校验的学员注册页面。

明确案例目标 ​

页面需要包含:

  1. 完整的 HTML 文档骨架。
  2. 页面导航和课程说明。
  3. 本地头像占位图与帮助链接。
  4. 报名步骤列表和课程信息表格。
  5. 分组合理的注册表单。
  6. 浏览器原生必填、长度、类型与范围校验。

规划页面结构 ​

text
body
├── header
│   ├── 标题
│   └── nav
├── main
│   ├── 课程说明 section
│   ├── 报名步骤 section
│   ├── 课程安排 section
│   └── 注册表单 section
└── footer

先确定内容层级,再填写标签。此时不讨论颜色、间距和横向布局。

搭建文档骨架与页头 ​

html
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FastAPI Web 全栈课程报名</title>
</head>
<body>
<header>
    <p>正心教育 · Web 全栈基础</p>
    <h1>FastAPI Web 全栈课程报名</h1>
    <nav aria-label="页面导航">
        <a href="#course-intro">课程说明</a>
        <a href="#schedule">课程安排</a>
        <a href="#register">填写资料</a>
    </nav>
</header>

<main>
    <!-- 页面主要内容 -->
</main>

<footer>
    <small>&copy; 2026 正心教育</small>
</footer>
</body>
</html>

导航链接通过片段标识跳到页面内对应的 id。

添加课程说明与本地图片 ​

html

<section id="course-intro">
    <h2>课程说明</h2>
    <img
            src="./images/avatar-placeholder.svg"
            alt="默认学员头像占位图"
            width="120"
            height="120"
    >
    <p>
        本课程从零学习 Web 前端,也可为后续后端项目开发做好准备。
    </p>
    <p>
        填写前可以先阅读
        <a href="#register">报名要求</a>。
    </p>
</section>

图片与页面一起放在案例目录,不依赖外部图片服务。移动整个目录后仍能正常显示。

展示报名步骤与课程安排 ​

html

<section>
    <h2>报名步骤</h2>
    <ol>
        <li>阅读课程说明。</li>
        <li>选择班级和学习方式。</li>
        <li>填写并核对个人资料。</li>
        <li>提交报名信息。</li>
    </ol>
</section>

<section id="schedule">
    <h2>课程安排</h2>
    <table id="class-schedule">
        <caption>可选班级</caption>
        <thead>
        <tr>
            <th scope="col">班级</th>
            <th scope="col">上课时间</th>
            <th scope="col">学习方式</th>
        </tr>
        </thead>
        <tbody id="class-schedule-body">
        <tr>
            <th scope="row">A01</th>
            <td>周一、周三晚</td>
            <td>线上</td>
        </tr>
        <tr>
            <th scope="row">A02</th>
            <td>周六全天</td>
            <td>线下</td>
        </tr>
        </tbody>
    </table>
</section>

列表表示有顺序的流程,表格表示具有行列关系的班级数据。

完成注册表单 ​

html

<section id="register">
    <h2>填写注册资料</h2>
    <p>标记为“必填”的项目必须填写。</p>

    <form id="student-registration-form" action="/students" method="post" enctype="multipart/form-data">
        <fieldset>
            <legend>基本信息</legend>

            <p>
                <label for="name">姓名(必填)</label>
                <input
                        id="name"
                        name="name"
                        type="text"
                        minlength="2"
                        maxlength="20"
                        autocomplete="name"
                        required
                >
            </p>

            <p>
                <label for="email">邮箱(必填)</label>
                <input
                        id="email"
                        name="email"
                        type="email"
                        autocomplete="email"
                        required
                >
            </p>

            <p>
                <label for="mobile">手机号(必填)</label>
                <input
                        id="mobile"
                        name="mobile"
                        type="tel"
                        autocomplete="tel"
                        required
                >
            </p>

            <p>
                <label for="age">年龄</label>
                <input id="age" name="age" type="number" min="16" max="80">
            </p>
        </fieldset>

        <fieldset>
            <legend>课程选择</legend>

            <p>
                <label for="class-code">班级(必填)</label>
                <select id="class-code" name="class_code" required>
                    <option value="">请选择班级</option>
                    <option value="A01">A01 · 周一、周三晚</option>
                    <option value="A02">A02 · 周六全天</option>
                </select>
            </p>

            <fieldset>
                <legend>学习方式(必填)</legend>
                <label>
                    <input name="study_mode" type="radio" value="online" required>
                    线上学习
                </label>
                <label>
                    <input name="study_mode" type="radio" value="offline">
                    线下学习
                </label>
            </fieldset>

            <fieldset>
                <legend>希望补充的基础</legend>
                <label>
                    <input name="interests" type="checkbox" value="html">
                    HTML
                </label>
                <label>
                    <input name="interests" type="checkbox" value="css">
                    CSS
                </label>
                <label>
                    <input name="interests" type="checkbox" value="javascript">
                    JavaScript
                </label>
            </fieldset>
        </fieldset>

        <fieldset>
            <legend>补充资料</legend>

            <p>
                <label for="avatar">头像</label>
                <input id="avatar" name="avatar" type="file" accept="image/*">
            </p>

            <p>
                <label for="remark">备注</label>
                <textarea
                        id="remark"
                        name="remark"
                        rows="5"
                        maxlength="200"
                        placeholder="例如:希望参加周末班"
                ></textarea>
            </p>
        </fieldset>

        <p>
            <label>
                <input name="agreement" type="checkbox" value="accepted" required>
                我已核对信息并同意报名说明(必填)
            </label>
        </p>

        <p>
            <button type="submit">提交报名</button>
            <button type="reset">恢复初始值</button>
        </p>
    </form>
</section>

认识本案例的字段契约 ​

这张表单是“课程报名”场景,class_code、study_mode 和 interests 是报名字段;它们不等同于后续学员 CRUD 接口中的 class_name、disabled 和 course。后续 JavaScript 会在具体页面中把表单数据整理成对应的数据对象,不能把显示文字、表单字段名和 API 字段名混为一谈。

可选字段也有不同的空值情况:未填写文本通常是空字符串,未勾选的复选框可能不会提交字段,未选择文件则没有文件对象。p01 只定义这些字段,空值整理和对象转换放到后续 JavaScript 章节。

当前没有 /students 后端接口,表单通过原生校验后可能进入不存在的地址。这不影响本节目标:先确认 HTML 结构和浏览器校验正确。

逐项验收页面 ​

完整页面源码见本节末尾;将文件按导入路径保存后,打开 .code/p01/c08-student-form-case/01-student-form/index.html 并依次检查:

  1. 页面标题和中文是否正常显示。
  2. 顶部三个链接是否跳到正确区域。
  3. 本地头像占位图是否显示。
  4. 不填写必填项时,提交是否被浏览器阻止。
  5. 错误邮箱、过小年龄是否触发校验。
  6. 单选按钮是否只能选择一个学习方式。
  7. “恢复初始值”是否恢复到页面加载时的状态。
  8. 浏览器开发者工具中,标签嵌套是否正确。

复盘知识来源 ​

页面内容对应知识
文档骨架、注释和路径基础标签
标题、段落、强调文字文本标签
图片、导航和页内跳转链接与图片
步骤和班级安排列表与表格
文本字段和提交方式表单基础
单选、多选、下拉和文件更多表单控件
页面区域和浏览器校验HTML5 语义与原生校验

AI 协作任务:检查表单结构 ​

把当前 HTML 文件交给任意 AI 编程助手时,不要只说“帮我优化”。可以这样说明任务:

text
目标:检查学员注册页的 HTML 结构和原生表单校验。
上下文:这是零基础课程 p01 综合案例,目前只学习 HTML。
约束:不能添加 CSS、JavaScript、框架或接口;保留现有字段名。
验收:标题层级合理,label 与控件关联,图片有 alt,必填项和输入类型正确。
请先列出问题,再一次只修改一类问题,并解释修改位置。

接受建议前先对照本章知识,修改后重新打开页面,使用鼠标和键盘填写表单,并故意漏填必填项。只有浏览器原生校验正常、页面内容没有丢失,任务才算完成。AI 建议如果加入样式或脚本,应明确拒绝,因为它超出了当前 Part 的边界。

案例验收 ​

完成本节案例后,对照下面的标准检查这张学员登记页。

验收:

  • 页面有清楚的标题层级、表单分组和字段标签。
  • 必填项、邮箱、年龄和长度约束可以触发浏览器原生校验。
  • 图片、表格和表单字段的辅助说明关系正确。

可复制的完整页面源码 ​

将两个文件按显示的相对路径放入 .code/p01/c08-student-form-case/01-student-form/,再用浏览器打开 index.html。

完整页面源码:.code/p01/c08-student-form-case/01-student-form/
html
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>FastAPI Web 全栈课程报名</title>
</head>
<body>
  <header>
    <p>正心教育 · Web 全栈基础</p>
    <h1>FastAPI Web 全栈课程报名</h1>
    <nav aria-label="页面导航">
      <a href="#course-intro">课程说明</a>
      <a href="#schedule">课程安排</a>
      <a href="#register">填写资料</a>
    </nav>
  </header>

  <main>
    <section id="course-intro">
      <h2>课程说明</h2>
      <img
        src="./images/avatar-placeholder.svg"
        alt="默认学员头像占位图"
        width="120"
        height="120"
      >
      <p>本课程用于补充 Web 前端基础,为后续 FastAPI 项目开发做准备。</p>
      <p>填写前可以先阅读<a href="#register">报名要求</a>。</p>
    </section>

    <section>
      <h2>报名步骤</h2>
      <ol>
        <li>阅读课程说明。</li>
        <li>选择班级和学习方式。</li>
        <li>填写并核对个人资料。</li>
        <li>提交报名信息。</li>
      </ol>
    </section>

    <section id="schedule">
      <h2>课程安排</h2>
      <table id="class-schedule">
        <caption>可选班级</caption>
        <thead>
          <tr>
            <th scope="col">班级</th>
            <th scope="col">上课时间</th>
            <th scope="col">学习方式</th>
          </tr>
        </thead>
        <tbody id="class-schedule-body">
          <tr>
            <th scope="row">A01</th>
            <td>周一、周三晚</td>
            <td>线上</td>
          </tr>
          <tr>
            <th scope="row">A02</th>
            <td>周六全天</td>
            <td>线下</td>
          </tr>
        </tbody>
      </table>
    </section>

    <section id="register">
      <h2>填写注册资料</h2>
      <p>标记为“必填”的项目必须填写。</p>

      <form id="student-registration-form" action="/students" method="post" enctype="multipart/form-data">
        <fieldset>
          <legend>基本信息</legend>

          <p>
            <label for="name">姓名(必填)</label>
            <input id="name" name="name" type="text" minlength="2" maxlength="20" autocomplete="name" required>
          </p>
          <p>
            <label for="email">邮箱(必填)</label>
            <input id="email" name="email" type="email" autocomplete="email" required>
          </p>
          <p>
            <label for="mobile">手机号(必填)</label>
            <input id="mobile" name="mobile" type="tel" autocomplete="tel" required>
          </p>
          <p>
            <label for="age">年龄</label>
            <input id="age" name="age" type="number" min="16" max="80">
          </p>
        </fieldset>

        <fieldset>
          <legend>课程选择</legend>
          <p>
            <label for="class-code">班级(必填)</label>
            <select id="class-code" name="class_code" required>
              <option value="">请选择班级</option>
              <option value="A01">A01 · 周一、周三晚</option>
              <option value="A02">A02 · 周六全天</option>
            </select>
          </p>

          <fieldset>
            <legend>学习方式(必填)</legend>
            <label><input name="study_mode" type="radio" value="online" required> 线上学习</label>
            <label><input name="study_mode" type="radio" value="offline"> 线下学习</label>
          </fieldset>

          <fieldset>
            <legend>希望补充的基础</legend>
            <label><input name="interests" type="checkbox" value="html"> HTML</label>
            <label><input name="interests" type="checkbox" value="css"> CSS</label>
            <label><input name="interests" type="checkbox" value="javascript"> JavaScript</label>
          </fieldset>
        </fieldset>

        <fieldset>
          <legend>补充资料</legend>
          <p>
            <label for="avatar">头像</label>
            <input id="avatar" name="avatar" type="file" accept="image/*">
          </p>
          <p>
            <label for="remark">备注</label>
            <textarea id="remark" name="remark" rows="5" maxlength="200" placeholder="例如:希望参加周末班"></textarea>
          </p>
        </fieldset>

        <p>
          <label>
            <input name="agreement" type="checkbox" value="accepted" required>
            我已核对信息并同意报名说明(必填)
          </label>
        </p>

        <p>
          <button type="submit">提交报名</button>
          <button type="reset">恢复初始值</button>
        </p>
      </form>
    </section>
  </main>

  <footer>
    <p>报名问题:<a href="mailto:support@example.com">support@example.com</a></p>
    <small>&copy; 2026 正心教育</small>
  </footer>
</body>
</html>
svg
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" role="img" aria-labelledby="title description">
  <title id="title">默认学员头像</title>
  <desc id="description">圆形头像轮廓占位图</desc>
  <rect width="120" height="120" rx="16" fill="#e8eef7"/>
  <circle cx="60" cy="43" r="22" fill="#6b7c93"/>
  <path d="M24 104c3-23 17-35 36-35s33 12 36 35" fill="#6b7c93"/>
</svg>

:::

小结 ​

  1. 写页面前先规划语义结构和内容层级。
  2. 列表、表格和表单解决不同类型的信息表达问题。
  3. 路径应尽量使用可随案例目录一起移动的相对路径。
  4. 原生校验改善填写体验,但最终数据仍由后端验证。
  5. 这一版页面只负责 HTML;下一部分再使用 CSS 改善视觉和布局。