← 返回教学站

教材、验收表与练习答案

项目四-平陆运河旅游-教师说明

下载 Markdown 原稿
展开阅读
# 教师说明

## 课堂组织
推荐 12 课时:HTML 2、CSS 3、交互/API 3、本地表单 4。可按班级基础拆分。每任务八环节见教学文稿,先演示结果再发对应学生起点;不得把四份成品当作学生起始文件。两人一组,轮换编写和验收。

## 文件与运行
- public/index.html 为综合成品;public/stages/task-1-static、task-2-responsive、task-3-api、task-4-form 均有独立 index.html 和相对素材路径。
- 第一阶段仅 HTML;第二阶段加入 CSS、无 JS;第三阶段加入轮播/时钟/天气,无表单;第四阶段追加本地表单。
- 学生起始素材/task-1 是待补齐语义结构的骨架;task-2/3/4 分别从前一阶段成品起步,README 写明 TODO。
- ZIP 解压后双击 public/index.html 预览。建议素材根目录运行 `python3 -m http.server 8080`,在浏览器访问 `http://localhost:8080/public/`。也可 `npm ci && npm start`,访问 `http://127.0.0.1:8838/`。Node 服务仅提供静态资源,不接收表单。
- 修改源文件 public/styles.css 或 app.js 后运行 `npm run build` 同步独立版本;第三阶段会截取任务三脚本,不夹带第四阶段教学代码。

## 天气边界
Open-Meteo 免费非商业教育接口无需 key;页面保留 Open-Meteo/CC BY 4.0 来源。访问政策以 https://open-meteo.com/en/terms 与 https://open-meteo.com/en/docs 为准。城市是教学坐标,不是景区精确气象站。API 数据是模型天气数据,不应称为现场实测。设备北京时间不是网络校时。

缓存10分钟,刷新可强制联网;请求10秒超时;失败优先显示带时间的离线历史缓存,没有则显示失败。上课前验证校园网络,不承诺接口永远可用。不提供伪造的“实时天气”备用值。

## 隐私和本地限制
只填虚构昵称与课堂计划。localStorage 按来源隔离,HTTP/HTTPS、不同端口、不同浏览器可能使用不同存储;隐私模式或 file:// 行为取决于浏览器。登记没有后台,不跨设备,无身份认证,编号不是访问令牌。删除必须确认,仅删本项目登记键。课后清除演示记录。

## 调试和验收
先检查控制台,再检查 Network HTTP 状态、响应字段及存储。测试失败用浏览器请求拦截/离线,不要改源码伪造成功值。模拟数据只用于隔离测试,必须注明,不作为联网成功证据。完整自动化测试和真实输出见 tests/ 和 evidence/。

## 内容准确性
全部 SVG 自制且标注“示意图/非实景”,可用于本项目课堂。线路只是研究顺序,不代表实际道路、通航、开放、价格或预订;未做旅游事实调查,不可把本站作为出行依据。计划中的贵港选项已删,保留钦州、南宁教学坐标。

项目四-平陆运河旅游-任务验收表

下载 Markdown 原稿
展开阅读
# 项目四任务验收表

班级:____ 姓名/课堂编号:____ 日期:____

|任务|操作证据|通过/待改|同伴意见|
|---|---|---|---|
|1 搭建 HTML 网页结构|浏览器标题与正文中文正常;导航到达目标;关闭图片后 alt 有意义;表格存在标题和表头。|____|____|
|2 用 CSS 实现响应式页面|375px 页面无整体横向滚动;1280px 卡片三列;图片无拉伸;键盘焦点可见。|____|____|
|3 添加轮播、北京时间与天气|轮播可前后切换与暂停;时钟秒数变化;联网展示响应温度和数据时间;缓存有标签;断网不伪造实时天气。|____|____|
|4 完成本地登记与编号查询|空表单/过去日期/不合法人数被拒绝;成功后有编号;刷新能查;无结果有说明;取消删除保留,确认删除清除。|____|____|

评分建议:每任务25分,功能15分、语义与可读性5分、测试与修正记录5分。需提交桌面与手机截图、天气失败说明、登记编号与删除结果(全部使用虚构内容)。

项目四-平陆运河旅游-知识巩固题

下载 Markdown 原稿
展开阅读
# 项目四知识巩固题与参考答案

## 任务1:HTML
1. nav 与普通 div 有什么差别?
2. 写出引用同级 assets/canal.svg 的 img,要求说明非实景。
3. href="#about" 为什么没有跳转?给出两项排查。
4. 线路表格为什么需要 caption 和 th?

答案:1. nav 表示导航语义,div 没有特定语义。2. `<img src="assets/canal.svg" alt="运河原创示意图,非实景">`。3. 检查目标 id="about" 是否存在及大小写/拼写是否一致,检查 id 是否重复。4. caption 说明表格主题,th 表示表头,scope 协助辅助技术关联数据。

## 任务2:CSS
1. border-box 的 width 是否包含 padding 和 border?
2. 写出 700px 及以下将 .cards 改为单列的规则。
3. object-fit:cover 会不会裁切图片?
4. 导航在手机溢出时,除了缩小字号还有什么办法?

答案:1. 包含。2. `@media(max-width:700px){.cards{grid-template-columns:1fr;}}`。3. 会按比例填满并裁切,不拉伸。4. 换行、纵向排列或在导航容器内横向滚动;不要让整页溢出。

## 任务3:JavaScript 与 API
1. setInterval(updateClock,1000) 是否从网络校时?
2. fetch 返回 HTTP 500 一定会进入 catch 吗?
3. 为什么要先检查 current,再读 temperature_2m?
4. 缓存与离线历史缓存的区别是什么?
5. 快速切换城市导致旧请求后到达时应如何处理?

答案:1. 不会,Date 读取设备时钟,Asia/Shanghai 只是时区转换。2. 不一定;必须主动检查 response.ok 并抛出异常。3. 避免缺失字段和非数字导致错误界面。4. 缓存说明复用了以前真实响应;离线历史缓存还明确本次联网失败,两者都不能冒充当前实时请求。5. 保存请求序号,仅最后一次选择的响应能更新界面,或主动中止旧请求。

## 任务4:表单与本地存储
1. preventDefault 阻止了什么?
2. localStorage 能直接保存数组吗?
3. get 与 getAll 读取兴趣复选框有何区别?
4. 编号换电脑为什么查不到?
5. 备注应使用 innerHTML 还是 textContent?为什么?
6. 清空查询、删除登记、清空所有 localStorage 是否同一操作?

答案:1. 表单默认提交/导航行为。2. 不能,需 JSON.stringify 序列化,读取后 JSON.parse。3. get 取首项,getAll 返回同名控件的全部值。4. 数据只在本浏览器当前来源存储。5. textContent,输入只作为文字,避免 HTML 注入。6. 不同:清空查询不删数据;removeItem 只删登记键;clear 会删该来源所有键,可能误删天气及其他数据,不应用于本练习。

## 综合实践(20分)
新增一个生态观察卡片(4分),手机下保持单列(4分),新增可读天气状态说明(4分),完成一次登记→刷新→查询→取消删除→确认删除录像或截图(8分)。不得写真实个人信息,不得编造景区票价或开放状态。

项目四-平陆运河旅游-教学文稿

下载 Markdown 原稿
展开阅读
# 项目四:平陆运河旅游——综合教学文稿

## 项目概述
以旅游主题练习 HTML、CSS 与原生 JavaScript,完成可独立打开的四个递进版本。所有 SVG 是原创教学示意,不冒充景区实景;学习线路不代表实际开放。天气来自真实公共 API,登记仅在当前浏览器。

## 任务编写说明
每任务建议 2—4 课时,按八个环节组织。先照步骤操作,再解释概念,最后独立改动与验收。成品位置 public/index.html;独立阶段位于 public/stages/;学生文件位于 学生起始素材/。直接打开 index.html 可看静态页面;浏览器对 file:// 存储的实现不同,推荐在素材根目录运行 python3 -m http.server 8080,再访问 http://localhost:8080/public/。无需安装框架。教学短代码用于单点练习,完整的防错处理见对应成品 app.js。

# 任务1:搭建 HTML 网页结构

## 一、任务描述
本任务完成“搭建 HTML 网页结构”。成品参照 public/stages/task-1-static/index.html;学生起点为 学生起始素材/task-1/index.html。本任务不引入 CSS 与 JavaScript。

## 二、问题引导
把内容组织清楚,为什么比先选颜色更重要?图片无法加载时读者还能了解什么?

## 三、知识准备
header、nav、main、section、footer;相对路径、alt、列表、表格。先找到对应元素或文件,再在浏览器中观察效果。

## 四、任务实施
1. 在学生起始素材/task-1 中打开 index.html。补齐页面标题与 lang="zh-CN",保留 UTF-8 和 viewport。
2. 在 header 中输入网站名称;为 nav 添加 href="#about" 的链接,为 section 添加 id="about"。逐一点击验证锚点。
3. 用 h1 表示页面主题,用 h2 表示栏目,避免为字号而跳级。写出运河印象和教学用途声明。
4. 用 img 引入 assets/canal.svg,添加“原创教学示意图,非实景”的 alt。路径相对于当前 HTML,而不是电脑磁盘根目录。
5. 把主题看点写成列表或 article;把学习线路写成带 caption、thead、tbody 的表格。th 用 scope 关联行或列。
6. 保存并刷新。与 stages/task-1-static/index.html 比较,只检查内容和结构,不要求 CSS 外观。

### 最小代码练习
```html
<nav><a href="#about">运河印象</a></nav>
<main><section id="about"><h2>运河印象</h2>
<img src="assets/canal.svg" alt="运河原创教学示意图,非实景">
<p>本页面仅用于课堂学习。</p></section></main>
```

> 小提示:保存文件后刷新;先查看控制台第一条错误,再检查路径和拼写。

**试一试:** 把图片文件改名,观察失效,再同时修正 src;说明为什么不是网络故障。

**想一想:** 把内容组织清楚,为什么比先选颜色更重要?图片无法加载时读者还能了解什么?

## 五、任务验收
浏览器标题与正文中文正常;导航到达目标;关闭图片后 alt 有意义;表格存在标题和表头。

同伴互查:逐项记录“通过/未通过/修改办法”,不能只写“页面能打开”。

## 六、任务小结
语义标签帮助机器和人理解结构。相对路径以当前文件为起点。

## 七、任务拓展
增加“资料来源”栏目,只引用可核实来源,不补造开放时间。

## 八、知识巩固
完成《知识巩固题》任务1组;先独立答,再对照教师答案解释原因。

# 任务2:用 CSS 实现响应式页面

## 一、任务描述
本任务完成“用 CSS 实现响应式页面”。成品参照 public/stages/task-2-responsive/index.html;学生起点为 学生起始素材/task-2/index.html。起点继承上一任务已完成的成果,不能跳过上一步基础。

## 二、问题引导
手机屏幕变窄时三张卡片应该如何排列?固定宽度为什么容易造成溢出?

## 三、知识准备
选择器、盒模型、CSS 变量、Flex、Grid、媒体查询、焦点状态。先找到对应元素或文件,再在浏览器中观察效果。

## 四、任务实施
1. 复制已完成的任务一,创建 styles.css,并在 head 用 link rel="stylesheet" 引入。
2. 在 :root 定义 --ink、--cream、--teal;为 body 设置字色、背景、字体和行高,为所有元素设置 box-sizing:border-box。
3. 给页头使用 Flex;给三张看点卡片设置 display:grid 和三列。给图片设置 width:100% 与 object-fit:cover。
4. 为 .section 设置 max-width 和 margin:auto,使宽屏内容不过度拉长。使用 padding 留出阅读空间。
5. 在 @media(max-width:700px) 中将卡片改为一列,让两栏正文变为单栏。表格放入 overflow-x:auto 的容器。
6. 在开发者工具检查 375、768、1280 像素;用 Tab 浏览导航与按钮,保留 :focus-visible。对照第二阶段成品。

### 最小代码练习
```css
.cards { display:grid; grid-template-columns:repeat(3,1fr); gap:20px; }
img { max-width:100%; }
@media (max-width:700px) {
  .cards { grid-template-columns:1fr; }
}
a:focus-visible { outline:3px solid #e88d55; }
```

> 小提示:保存文件后刷新;先查看控制台第一条错误,再检查路径和拼写。

**试一试:** 把卡片改成两列后测试 700px 和 701px,比较媒体查询的边界。

**想一想:** 手机屏幕变窄时三张卡片应该如何排列?固定宽度为什么容易造成溢出?

## 五、任务验收
375px 页面无整体横向滚动;1280px 卡片三列;图片无拉伸;键盘焦点可见。

同伴互查:逐项记录“通过/未通过/修改办法”,不能只写“页面能打开”。

## 六、任务小结
响应式不是缩小全部内容,而是根据可用空间改变排版。

## 七、任务拓展
为 prefers-reduced-motion 添加动画偏好适配,不依靠颜色区分所有状态。

## 八、知识巩固
完成《知识巩固题》任务2组;先独立答,再对照教师答案解释原因。

# 任务3:添加轮播、北京时间与天气

## 一、任务描述
本任务完成“添加轮播、北京时间与天气”。成品参照 public/stages/task-3-api/index.html;学生起点为 学生起始素材/task-3/index.html。起点继承上一任务已完成的成果,不能跳过上一步基础。

## 二、问题引导
每秒更新时钟是否意味着每秒请求天气?网络失败时能否显示编造的晴天温度?

## 三、知识准备
DOM、事件、setInterval、Intl、fetch、async/await、JSON、异常、缓存。先找到对应元素或文件,再在浏览器中观察效果。

## 四、任务实施
1. 复制第二阶段;添加 defer 脚本。用 querySelector 取得图片、按钮、时钟和天气结果节点。
2. 创建图片数组和当前下标。上一张、下一张通过取模切换。每 6 秒自动切换;暂停按钮和减少动态效果偏好应被尊重。
3. 创建 updateClock,使用 Intl.DateTimeFormat 指定 Asia/Shanghai,并每秒调用。注明它读取设备时间,不是网络校时。
4. 在下拉框放入钦州与南宁教学坐标。构造 Open-Meteo URL,请求 current=temperature_2m,weather_code 与 timezone=Asia%2FShanghai。
5. 先显示加载中;检查 response.ok,再 await response.json()。验证 current 的温度、代码和时间,再输出数据时间及中文天气。
6. 把真实响应和保存时间存入 localStorage,10 分钟内复用并标记缓存。用 AbortController 设置超时。失败时有缓存显示“离线历史缓存”,无缓存明确说无数据。
7. 快速切换城市时使用请求序号,只让最后一次请求更新界面。通过开发者工具阻断 api.open-meteo.com,测试失败、缓存和重试。

### 最小代码练习
```javascript
async function readWeather() {
  try {
    const response = await fetch("https://api.open-meteo.com/v1/forecast?latitude=21.98&longitude=108.65&current=temperature_2m,weather_code&timezone=Asia%2FShanghai");
    if (!response.ok) throw new Error("请求失败");
    const data = await response.json();
    document.querySelector("#weather-result").textContent = data.current.temperature_2m + " °C";
  } catch {
    document.querySelector("#weather-result").textContent = "天气获取失败,请稍后重试";
  }
}
readWeather();
```

> 小提示:保存文件后刷新;先查看控制台第一条错误,再检查路径和拼写。

**试一试:** 保存一次真实天气后断网并点刷新,观察离线标签;清除天气键再试,比较两种失败状态。

**想一想:** 每秒更新时钟是否意味着每秒请求天气?网络失败时能否显示编造的晴天温度?

## 五、任务验收
轮播可前后切换与暂停;时钟秒数变化;联网展示响应温度和数据时间;缓存有标签;断网不伪造实时天气。

同伴互查:逐项记录“通过/未通过/修改办法”,不能只写“页面能打开”。

## 六、任务小结
异步请求可能失败或乱序;时钟刷新频率与天气请求频率必须分离。

## 七、任务拓展
给天气代码增加图标,但同时保留文字;讨论缓存过期与天气变化风险。

## 八、知识巩固
完成《知识巩固题》任务3组;先独立答,再对照教师答案解释原因。

# 任务4:完成本地登记与编号查询

## 一、任务描述
本任务完成“完成本地登记与编号查询”。成品参照 public/stages/task-4-form/index.html;学生起点为 学生起始素材/task-4/index.html。起点继承上一任务已完成的成果,不能跳过上一步基础。

## 二、问题引导
为什么同一编号在另一台设备上查不到?成功提示应该在保存之前还是之后显示?

## 三、知识准备
label、required、min/max、submit、preventDefault、FormData、JSON、localStorage、find。先找到对应元素或文件,再在浏览器中观察效果。

## 四、任务实施
1. 复制第三阶段,添加 register-form 和 query-form。每个字段都有 label;昵称只用虚构值,不收集真实联系方式。
2. 加入昵称、线路、日期、人数、交通单选、兴趣复选和备注。设置昵称长度、人数 1—30、备注 200 字;日期不早于北京时间今天。
3. 监听 submit 并 preventDefault;先用 reportValidity 检查原生约束,再用 trim 检查空白昵称和日期。
4. 用 FormData 读取数据;复选框用 getAll。生成日期前缀加随机后缀的 PL 编号,不把编号当密码或全球唯一凭据。
5. 从 localStorage 读取数组,JSON.parse 还原;push 新对象,再 JSON.stringify 后 setItem。只有写入成功才显示编号。损坏或禁用存储要提示错误。
6. 在编号查询中用 find 匹配。用 textContent 输出昵称和备注,避免把输入当 HTML 执行。增加清空查询按钮,它不能删除记录。
7. 删除按钮先 confirm,再 removeItem 只删除本项目登记键,不调用 localStorage.clear。刷新页面后核对记录数。分别测试取消和确认删除。

### 最小代码练习
```javascript
const key = "classroom.practice";
const records = JSON.parse(localStorage.getItem(key) || "[]");
records.push({ id:"PL-DEMO", nickname:"课堂小舟" });
localStorage.setItem(key, JSON.stringify(records));
const result = records.find(item => item.id === "PL-DEMO");
console.log(result.nickname); // 课堂小舟
// 演示结束只删除练习键,不清空别的网站数据
localStorage.removeItem(key);
```

> 小提示:保存文件后刷新;先查看控制台第一条错误,再检查路径和拼写。

**试一试:** 备注中输入 <img src=x onerror=alert(1)>,它应作为普通文本显示,不应弹窗。

**想一想:** 为什么同一编号在另一台设备上查不到?成功提示应该在保存之前还是之后显示?

## 五、任务验收
空表单/过去日期/不合法人数被拒绝;成功后有编号;刷新能查;无结果有说明;取消删除保留,确认删除清除。

同伴互查:逐项记录“通过/未通过/修改办法”,不能只写“页面能打开”。

## 六、任务小结
localStorage 是按来源隔离的字符串存储,不是数据库,也不是跨设备预约系统。

## 七、任务拓展
为单条记录增加删除按钮和确认框;若改成真实预约需考虑后端、认证、隐私与服务条款。

## 八、知识巩固
完成《知识巩固题》任务4组;先独立答,再对照教师答案解释原因。