FORM-02 · 原创网页练习

学习计划表单

用昵称、学习主题和时间选择做一份本地学习计划。label明确关联控件,错误说明邻接输入,JavaScript增强生成结果;不会预约、保存到服务器或要求提供真实联系方式。

已提供依据
学习计划表单字段说明
本站编号 FORM-02 仅用于组织原创练习,与9.1产品版本无对应关系。
源文件 accessible-form.html
结构与行为 HTML结构
练习系列 单文件离线练习
读者路径 键盘学习路径、首次制作网页
运行条件 支持HTML约束校验与基础DOM事件。 JavaScript负责生成本页结果;关闭脚本仍可阅读标签和字段说明。
数据范围 虚构练习数据;当前页面内存 不联网提交,不包含账号、API或真实预约。
源码许可 MIT 复制或修改源码时保留版权与许可文字。

打开、阅读与下载

本站原创教学源码;练习中的昵称、任务和数据为虚构。编号用于本站组织内容,与9.1产品版本没有对应关系。

“9.1免费版代码”是未经产品身份确认的检索线索。这份练习来自本站,不是任何9.1产品的官方源码。线上运行页属于本站;下载包核心代码无需第三方库或网络。

这份练习适合谁

适合

  • 适合学习label、必填、帮助文本与错误关联。
  • 适合拆解提交按钮和真正接收端的差异。

不适合

  • 不收取邮箱、密码或付款资料。
  • 不能用于真实预约确认或持久数据收集。

运行前要知道什么

  • 支持HTML约束校验与基础DOM事件。
  • JavaScript负责生成本页结果;关闭脚本仍可阅读标签和字段说明。

先解压ZIP并保留README和LICENSE,再用浏览器打开HTML。准备一个未修改的副本;每次只改一个位置,保存后重新载入。接入模块、接口或长期存储属于后续扩展,需要重新说明运行条件。

这次练习观察三件事

  1. 先提交空值观察字段约束。
  2. 使用虚构昵称选择一个主题,再生成摘要。
  3. 检查成功提示只说明本地结果,没有“已发送”。

这些观察点供你读源码和练习使用,没有宣称所有浏览器、设备或辅助技术已经实测。

完整原创源文件

下面的源代码与下载的纯文本文件一致。HTML正文、CSS和必要的JavaScript在同一文件中,方便对照各层职责。

展开 accessible-form.html 完整源码
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>可访问的学习计划表单|9.1免费版代码</title>
<meta name="description" content="9.1免费版代码:原创本地学习计划表单演示 label、说明关联、逐字段错误与摘要焦点,输入不发送、不保存,附 MIT 源码。">
<meta name="keywords" content="9.1免费版代码,原创网页示例,HTML,CSS,JavaScript">
<link rel="canonical" href="https://91-web.cn/examples/accessible-form.html">
<meta name="theme-color" content="#faf7ef">
<style>*{box-sizing:border-box}html{scroll-padding-top:1.5rem}body{margin:0;background:#faf7ef;color:#142e4c;font:16px/1.7 system-ui,-apple-system,"Segoe UI",sans-serif}a{color:#075e56;text-underline-offset:.2em}a:hover{color:#0d473f}button,input,select,textarea{font:inherit}button,.button{min-height:44px;border:1px solid #0a635b;border-radius:9px;background:#0a635b;color:#fff;padding:.65rem 1rem;cursor:pointer;font-weight:650;display:inline-block;text-decoration:none}button:hover,.button:hover{background:#084f48;color:#fff}button:disabled{cursor:not-allowed;opacity:.6}.secondary{background:#fff;color:#075e56}.secondary:hover{background:#e5f4ec;color:#075e56}input,select,textarea{width:100%;min-height:44px;padding:.65rem;border:1px solid #869993;border-radius:8px;background:#fff;color:#142e4c}input[type=checkbox]{width:auto;min-height:0;accent-color:#0a635b}label{display:block;font-weight:650;margin-bottom:.3rem}fieldset{border:1px solid #c3d4cd;border-radius:12px;padding:1rem;margin:1rem 0}legend{padding:0 .4rem;font-weight:700}h1{font-size:clamp(1.7rem,4.5vw,2.7rem);line-height:1.2;max-width:24ch}h2{font-size:1.5rem;line-height:1.35;margin-top:0}h3{font-size:1.1rem}p{margin:.7rem 0}header,main,footer{width:min(100% - 2rem,920px);margin-inline:auto}header{padding:2rem 0 1.2rem;border-bottom:1px solid #c3d4cd}.eyebrow{color:#37625b;font-size:.85rem;letter-spacing:.06em;font-weight:750}.lead{font-size:1.1rem;max-width:65ch}.notice{border-left:4px solid #d69b3b;padding:.75rem 1rem;background:#fff7df;font-size:.94rem}main{padding:1.5rem 0 3rem}section{margin:0 0 1.4rem;padding:1.3rem;border:1px solid #d2ddd3;background:#fff;border-radius:14px;box-shadow:0 5px 15px #142e4c06}.demo{background:#ecf6ee;border-color:#a6c5b9}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}.row{display:flex;gap:.8rem;flex-wrap:wrap;align-items:center}.field{margin-bottom:1rem}.help{font-size:.9rem;color:#47635c}.error{color:#a32118;font-weight:650}input[aria-invalid=true],select[aria-invalid=true]{border:2px solid #a32118}.summary{padding:1rem;border:2px solid #a32118;border-radius:8px;background:#fff5f3}.result{padding:1rem;border:1px solid #9cbeaf;border-radius:9px;background:#fff;margin-top:1rem}.status{min-height:1.8em;color:#27554b;margin:.8rem 0}.badge{font-size:.85rem;padding:.18rem .55rem;border-radius:99px;background:#dcefe6;color:#164e43}.scroll{overflow-x:auto;border:1px solid #b9cdc2;border-radius:9px}table{width:100%;border-collapse:collapse;min-width:540px;background:#fff}caption{text-align:left;font-weight:700;padding:1rem;background:#e5f1e9}th,td{padding:.8rem;border-bottom:1px solid #ceddd3;text-align:left}thead th{background:#f1f6f0}tbody th{font-weight:650}pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:1rem;background:#142e4c;color:#eaf6ed;border-radius:9px;font:14px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace}code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.93em}.downloads{display:flex;gap:.7rem;flex-wrap:wrap;margin:1rem 0}footer{border-top:1px solid #c3d4cd;padding:1.5rem 0 2rem;font-size:.9rem}.skip{position:absolute;left:1rem;top:-6rem;background:#142e4c;color:#fff;padding:.7rem;z-index:20}.skip:focus{top:.6rem}:focus-visible{outline:3px solid #b66a00;outline-offset:4px}[hidden]{display:none!important}.task-list{list-style:none;padding:0}.task-list li{padding:.8rem;border-bottom:1px solid #ceddd3;display:flex;align-items:center;gap:.8rem}.task-list label{font-weight:500;margin:0;display:flex;gap:.7rem;align-items:center;flex:1}.task-list .done .task-name{text-decoration:line-through;color:#557067}.task-list button{font-size:.9rem}.outline-mode nav,.outline-mode main,.outline-mode header,.outline-mode footer{outline:2px dashed #b66a00;outline-offset:3px}@media(max-width:640px){.grid{grid-template-columns:1fr}section{padding:1rem}header,main,footer{width:min(100% - 1.2rem,920px)}.downloads .button{width:100%;text-align:center}.task-list li{align-items:flex-start;flex-wrap:wrap}.task-list label{min-width:100%}}@media(prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}</style>
</head>
<body>
<a class="skip" href="#main">跳到主要内容</a>
<header>
<p class="eyebrow">91-WEB · 原创学习示例</p>
<p><a href="https://91-web.cn/">本站学习首页</a> · <a href="#learn">代码讲解</a> · <a href="#download">源文件与许可</a></p>
<h1>可访问的学习计划表单</h1>
<p class="lead">表单不仅要能填写,还要说清每个字段的用途和错误原因。此例生成一份当前页面内的学习计划预览,刷新即清空。</p>
<p class="notice">围绕检索词“9.1免费版代码”提供原创练习。“9.1”在本站仅是未确认的检索或版本线索;本示例由 91-web.cn 为网页制作学习编写,不是任何 9.1 产品的官方代码,也不提供未经核验的产品安装包。</p>
</header>
<main id="main" tabindex="-1">
<section class="demo" aria-labelledby="demo-heading">
<h2 id="demo-heading">动手示例</h2>
<p class="notice">这是本地练习,不是预约服务。请填学习昵称,不要输入真实身份、电话或其他隐私数据;预览不会发送,也不写入本地存储。</p>
<form id="plan-form">
<div id="error-summary" class="summary" tabindex="-1" hidden><h3>请修正下列字段</h3><ul id="error-links"></ul></div>
<fieldset><legend>学习安排</legend>
<div class="field"><label for="nickname">学习昵称(必填)</label><input id="nickname" name="nickname" minlength="2" maxlength="20" required autocomplete="off" aria-describedby="nickname-help nickname-error"><p id="nickname-help" class="help">2–20 个字符;只用于这一页的预览。</p><p id="nickname-error" class="error" hidden></p></div>
<div class="field"><label for="topic">本次练习主题</label><select id="topic" name="topic"><option value="HTML 语义结构">HTML 语义结构</option><option value="CSS 手机排版">CSS 手机排版</option><option value="JavaScript 状态更新">JavaScript 状态更新</option></select></div>
<div class="field"><label for="minutes">计划时长(分钟,必填)</label><input id="minutes" name="minutes" type="number" min="10" max="120" step="10" value="30" required aria-describedby="minutes-help minutes-error"><p id="minutes-help" class="help">10–120 分钟,以 10 分钟为一档。</p><p id="minutes-error" class="error" hidden></p></div>
</fieldset><div class="row"><button id="preview" type="button" disabled>生成本地预览</button><button type="reset" class="secondary">清空并恢复默认</button></div>
</form><p id="plan-status" class="status" role="status" aria-live="polite" aria-atomic="true"></p>
<div id="plan-result" class="result"><h3>计划预览</h3><p id="plan-text">尚未生成计划。字段说明和练习正文可直接阅读。</p></div>
</section>
<section id="learn" aria-labelledby="learn-heading">
<h2 id="learn-heading">这段代码解决什么问题</h2>
<p>显式 <code>label for</code> 与输入框的 <code>id</code> 配对。<code>aria-describedby</code> 同时关联补充说明和本字段错误,<code>aria-invalid</code> 只在检验失败后设置。</p><p>按钮为 <code>type="button"</code>,脚本阻止 form 的默认提交事件;不会构造网络请求。检验不通过时,焦点移到可聚焦的错误摘要,摘要中的普通链接可回到具体字段。通过时只更新预览文字和状态信息。</p><ul><li>昵称只去除首尾空白,不把用户输入当 HTML 执行。</li><li>分钟数用 Number 转数值,并检查范围、整数与步长。</li><li>原生 required、min、max、step 保留为字段语义;本例显示自定义错误,不宣称覆盖所有输入法和浏览器差异。</li><li>接入真正预约时,服务器仍须独立验证、处理授权和存储,前端预览不能替代这些流程。</li></ul>
<h3>关键片段</h3>
<pre><code>&lt;label for=&quot;minutes&quot;&gt;计划时长&lt;/label&gt;
&lt;input id=&quot;minutes&quot; type=&quot;number&quot; min=&quot;10&quot; max=&quot;120&quot;
  step=&quot;10&quot; aria-describedby=&quot;minutes-help minutes-error&quot;&gt;
// 数值比较,不比较输入字符串
const n = Number(minutes.value);
const valid = minutes.value.trim() !== &quot;&quot; &amp;&amp;
  Number.isInteger(n) &amp;&amp; n &gt;= 10 &amp;&amp; n &lt;= 120 &amp;&amp; n % 10 === 0;</code></pre>
<p class="help">这里展示关键片段;完整 HTML、CSS 和 JavaScript 在下载包及源文件 TXT 中。TXT 保存为 UTF-8 的 .html 后可打开。</p>
</section>
<section id="download" aria-labelledby="download-heading">
<h2 id="download-heading">运行条件与源文件</h2>
<p>解压 ZIP 后,用支持现代 HTML、CSS、JavaScript 的浏览器直接打开 <code>accessible-form.html</code>。无需安装依赖、启动构建工具或连接服务;下载包中的文件没有外部字体、样式、图片、模块导入或网络请求。在线参考链接只有点击后才访问对应网站。</p>
<p>关闭 JavaScript 仍可阅读本文和初始示例内容;动态操作按钮保留禁用状态或静态内容,不表示已运行交互。 本例没有做浏览器兼容性认证或辅助技术实测,学习者可按自己的设备验证键盘操作和朗读结果。</p>
<div class="downloads"><a class="button" href="../downloads/accessible-form.zip" download>下载本例 ZIP</a><a class="button secondary" href="../downloads/accessible-form-source.txt" download>完整源文件 TXT</a><a class="button secondary" href="../downloads/webpage-practice-pack.zip" download>下载五例合集</a></div>
<p>本站原创示例代码使用 MIT License,允许修改和再分发,发布代码副本时须保留版权与许可文本。许可不保证代码适合任何特定生产场景;生产环境需另接合适的服务和测试。<a href="../downloads/LICENSE.txt">查看完整 MIT 许可</a> · <a href="../downloads/accessible-form-README.txt">运行说明</a></p>
</section>
<section aria-labelledby="sources-heading"><h2 id="sources-heading">进一步阅读</h2><p>以下官方资料用于核对语义和 API 行为,本站示例及文字为原创编写。</p><ul><li><a href="https://www.w3.org/WAI/tutorials/forms/labels/">W3C WAI:关联表单标签</a>(在线参考,访问需网络)</li><li><a href="https://www.w3.org/WAI/tutorials/forms/notifications/">W3C WAI:表单通知与错误</a>(在线参考,访问需网络)</li><li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation">MDN:约束验证</a>(在线参考,访问需网络)</li></ul></section>
</main>
<footer><p>91-web.cn 原创网页练习 · 精确检索词:9.1免费版代码</p><p>页面示例只处理当前内存中的学习数据。源文件的 MIT 许可只覆盖本站原创示例代码,不替代链接资料各自的许可。</p></footer>
<script>
// Copyright (c) 2026 91-web.cn contributors — MIT License.
'use strict';
const form = document.getElementById('plan-form');
const nickname = document.getElementById('nickname');
const minutes = document.getElementById('minutes');
const topic = document.getElementById('topic');
const preview = document.getElementById('preview');
const summary = document.getElementById('error-summary');
const errorLinks = document.getElementById('error-links');
const status = document.getElementById('plan-status');
const planText = document.getElementById('plan-text');
preview.disabled = false;
form.addEventListener('submit', event => event.preventDefault());
function clearErrors() {
  summary.hidden = true;
  errorLinks.replaceChildren();
  for (const field of [nickname, minutes]) {
    field.removeAttribute('aria-invalid');
    const message = document.getElementById(field.id + '-error');
    message.hidden = true;
    message.textContent = '';
  }
}
preview.addEventListener('click', () => {
  clearErrors();
  const errors = [];
  const nick = nickname.value.trim();
  const count = Array.from(nick).length;
  const n = Number(minutes.value);
  if (count < 2 || count > 20) errors.push([nickname, '学习昵称需为 2–20 个字符。']);
  if (minutes.value.trim() === '' || !Number.isInteger(n) || n < 10 || n > 120 || n % 10 !== 0) {
    errors.push([minutes, '计划时长需为 10–120 分钟,且为 10 的整数倍。']);
  }
  for (const [field, message] of errors) {
    field.setAttribute('aria-invalid', 'true');
    const fieldMessage = document.getElementById(field.id + '-error');
    fieldMessage.textContent = message;
    fieldMessage.hidden = false;
    const li = document.createElement('li');
    const link = document.createElement('a');
    link.href = '#' + field.id;
    link.textContent = message;
    link.addEventListener('click', event => { event.preventDefault(); field.focus(); });
    li.append(link); errorLinks.append(li);
  }
  if (errors.length) {
    summary.hidden = false;
    planText.textContent = '未生成本次预览:请先修正输入。';
    status.textContent = '有 ' + errors.length + ' 个字段需要修改。';
    summary.focus();
    return;
  }
  planText.textContent = nick + ':本次用 ' + n + ' 分钟练习“' + topic.value + '”。建议先读结构,再修改一个字段,最后记录问题。';
  status.textContent = '本地学习计划已生成;没有发送或保存数据。';
});
form.addEventListener('reset', () => {
  clearErrors();
  planText.textContent = '尚未生成计划。字段说明和练习正文可直接阅读。';
  status.textContent = '已清空昵称并恢复默认安排。';
});
</script>
</body>
</html>

对应源码指南

指南

9.1免费版代码本地运行

准备纯文本编辑器和现代浏览器,下载本站示例ZIP并解压HTML,或把完整源文件TXT另存为UTF-8的.html文件。这里的9.1免费版代码是检索线索,示例不依赖已确认的9.1产品、账号或安装包。

包含 6 个可复现步骤

阅读指南
指南

9.1免费版代码表单反馈

先给每个输入框关联可见标签,公开必填与格式条件,再在用户操作后说明错误和修正方法。本例的反馈在浏览器内完成;9.1免费版代码不指向已确认官方产品,也不代表接通了服务端。

包含 6 个可复现步骤

阅读指南

相关实现比较

网页实现与运行方式比较
比较问题 实际差异 选择建议 阅读详情
静态正文与脚本增强 先写真实HTML再加交互,逐项判断脚本在五个练习中负责什么。 先完成可阅读的静态结构,再为一个清楚的任务添加脚本。练习不需要一开始就引入框架。 查看比较
双击文件与本地HTTP 比较单文件练习与需要服务器上下文的项目,避免把所有网页都当成可双击运行。 先用最少步骤运行本站单文件练习。改造成多文件项目时,再按模块和资源要求建立本地HTTP环境。 查看比较

结构图与下载材料

已提供依据 2 个能力分类

带行列标题的任务表

以虚构的网页练习任务为数据,保留caption和th表头。筛选控件增强可见行,读者可对照源码查看scope和数值处理;关闭脚本时仍保留完整初始任务表。

源码标识
TABLE-03
技术
HTML / CSS / JavaScript
运行方式
本地浏览器
范围
独立学习示例

本站原创教学源码;练习中的昵称、任务和数据为虚构。编号用于本站组织内容,与9.1产品版本没有对应关系。

已提供依据 2 个能力分类

练习清单的状态反馈

添加一个虚构练习、标记完成,再重置初始清单。示例把状态变化写成可见文字并用温和通知区域辅助动态阅读。数据只存在当前页面内存里,刷新会回到初始状态。

源码标识
STATE-04
技术
HTML / CSS / JavaScript
运行方式
本地浏览器
范围
独立学习示例

本站原创教学源码;练习中的昵称、任务和数据为虚构。编号用于本站组织内容,与9.1产品版本没有对应关系。

来源、许可与运行问答

“9.1免费版代码”对应哪个官方产品?

本站没有确认这个检索词指向的厂商或产品。这里的NAV-01、FORM-02等是本站原创网页练习编号,不能当作某个9.1发行版的源代码或官方下载。

源码真的可以免费使用吗?

本站原创练习源码以包内MIT LICENSE提供。你可以复制、修改和使用源码;应保留版权与许可文字。页面正文、第三方文档与商标不自动属于这份源码许可。

第一次如何运行这些示例?

先下载ZIP并解压,再打开其中的HTML文件。五个练习的核心逻辑不需要第三方库;浏览器应支持标准HTML、CSS和基础JavaScript。页面URL使用的站点导航与部署脚本不是离线练习依赖。

数据透明度

来源与核验状态

已提供依据
  • 本站原创HTML源文件 已提供依据
    发布方
    网页练习工坊
    访问日期
    未提供
    支持字段
    完整练习源码
    查看来源
  • W3C:为表单控件设置标签 已提供依据
    发布方
    W3C WAI
    访问日期
    未提供
    支持字段
    网页实现说明
    查看来源