展开阅读
# 项目四:平陆运河旅游——综合教学文稿
## 项目概述
以旅游主题练习 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¤t=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组;先独立答,再对照教师答案解释原因。