Skip to content

huashenzjy/HTML_Project

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📚 StudyHub Pro — 手把手读懂你自己写的代码


1. 【整体介绍】

这是一个游戏化的学习进度追踪器,名字叫 StudyHub Pro。它专门为"想转行成为软件测试工程师(SDET)"的人设计,但同时覆盖了三条并行的学习路线:编程测试技能、英语语法写作、以及技术英语词汇。

用户每天打开这个页面,就能看到"今天该学什么"——每周有 5 个具体任务,任务旁边有一个小方框,完成了就点一下打勾,文字会被划掉变灰,就像手机备忘录里的待办清单。每天完成至少一个任务,页面顶部的"连续打卡天数"就会增加,这个机制和你玩手机游戏里的"连续签到"一模一样,专门设计来让你不想断掉。

数据方面,它有两层保存机制:第一层是浏览器本地存储(就算不登录、不联网,你的进度也不会丢);第二层是 Google Firebase 云端数据库(登录 Google 账号后,你在家里电脑打的勾,在公司电脑也能看到)。

如果要用一句话形容它,它就像一本会自动记分的电子学习日记本,同时还带了游戏里的"成就系统"——进度环、连续打卡火焰、周完成动画,每一个细节都在告诉你:你在进步。


2. 【结构拆解】

🔝 区域一:顶部导航栏

位置: 页面最顶部,横跨全宽。

包含什么:

  • 左侧:网站 Logo("StudyHub")、三个切换按钮(SDET / English / Tech English)、火焰🔥打卡天数徽章
  • 右侧:同步状态小标签(绿色"✅ Synced" 或 "⚫ Offline")、手动同步按钮、Google 登录/退出按钮

HTML 标签: 用的是 <header>,这是 HTML 里专门表示"页头"的标签,就像一栋楼的门牌——它告诉浏览器"这里是整个页面的入口信息区"。

CSS 布局: 用了 display: flex(弹性布局,就像把元素放进一排格子里,自动排好间距),加了 backdrop-filter: blur(18px)(磨砂玻璃效果),还有圆角和半透明白色背景,看起来有一种现代 App 的质感。

JavaScript 交互:

  • 点击三个 Tab 按钮 → 隐藏其他面板、只显示选中的那个
  • 登录/退出 Google → 触发 Firebase 身份验证流程
  • 同步状态会实时更新颜色(绿/黄/红/灰)

📋 区域二:任务卡片(每个模块的核心)

位置: 切换面板后,左侧占据大部分宽度的主卡片,跨两行高度。

包含什么:

  • 本周标题和目标(比如"Week 1 — Java OOP")
  • 阶段标签(Stage 1 / Foundation)和状态标签(未开始 / 进行中 / 完成)
  • 周次选择标签栏(W1 W2 W3 … W20,可左右滑动)
  • 进度条("3/5 完成")
  • 任务清单(每条任务 = 勾选框 + 任务文字 + 折叠的"怎么做"提示)
  • 重置按钮(今天 / 本周 / 全部)

HTML 标签:<div class="card"> 包裹,<ul><li> 组成任务列表。<ul> 是"无序列表",就像一张购物清单,每个 <li> 是清单上的一行。

CSS 布局: 卡片用了 backdrop-filter(磨砂背景)、box-shadow(阴影,让卡片"浮"起来的感觉)、border-radius(圆角)。进度条是一个细长 <div> 套了一个会动的内层 <div>,宽度由 JavaScript 实时计算填入。

JavaScript 交互:

  • 点任务 → 切换勾选状态 → 更新进度条 → 检查是否全部完成(完成则触发绿光动画)
  • 点周次标签 → 切换显示不同周的任务数据
  • 所有变化自动保存到 localStorage

📅 区域三:日历卡片

位置: 右侧上方卡片。

包含什么: 一个 7 列的月历格子,今天用紫色圆圈标记,有学习记录的日期用绿色标记并加一个小勾。下方还有进度圆环和"还剩 X 周"的里程碑信息。

HTML 标签: 日历格子用 <div class="calendar-grid"> 配合 CSS Grid(网格布局)实现,就像 Excel 表格,每个格子是一个小 <div>

CSS 布局: display: grid; grid-template-columns: repeat(7, 1fr) — 意思是"把容器等分成 7 列",刚好对应一周七天。

JavaScript 交互: 每次打开页面,代码会读取 st.dates(记录哪些天完成了学习),然后给对应的日期格子加上绿色 CSS 类,自动渲染出你的打卡历史。


⏰ 区域四:承诺卡片 + 倒计时按钮

位置: 右侧下方卡片。

包含什么: 两个输入框(填学习时间和地点)、保存按钮、一个大的"🚀 开始学习"倒计时按钮。

HTML 标签: 输入框是 <input type="text">,按钮是 <button>,外层用 <div class="commit-form"> 包裹。

CSS 布局:display: flex; flex-wrap: wrap 让输入框和按钮在宽屏并排显示、窄屏自动换行堆叠。

JavaScript 交互: 点"🚀 开始学习"后,按钮进入 5 秒倒计时,圆环动画从底部往上消失,倒计时结束后随机弹出一条励志提示语(比如"每天一点点,终将变厉害")。


⚠️ 区域五:错误记录折叠区

位置: 整个面板最底部。

包含什么: 一个折叠按钮(点击展开/收起),展开后显示本周常见错误提示、每日学习 SOP(标准流程)、周复盘问题。

HTML 标签: 折叠按钮是 <button class="collapsible-toggle">,内容区是 <div class="collapsible-content">

CSS 交互: 折叠动画用的是 max-height: 0(隐藏时高度为0)→ max-height: 3000px(展开时),配合 transition 让它平滑滑出,不是"啪"地突然出现。

JavaScript 交互: 点击按钮 → 切换 open 这个 CSS 类 → 触发展开/收起动画 → 同时旋转箭头图标(▸ 变成 ▾)。


3. 【关键代码讲解】

🧩 片段一(CSS)— 颜色的"调色板仓库"

:root {
  --purple: #7C5CFC;
  --green:  #10B981;
  --bg:     #F4F0FA;
  --shadow-md: 0 4px 16px rgba(30,27,46,0.07);
}

逐行大白话:

  • :root — 代表整个网页的"根",可以理解为"全局设置间"
  • --purple: #7C5CFC — 给这个紫色起了一个名字叫 --purple#7C5CFC 是紫色的十六进制颜色代码
  • 以后任何地方想用这个紫色,直接写 var(--purple) 就行,不用记颜色代码

为什么要这样写? 假如你不用变量,把紫色 #7C5CFC 直接写在 200 个地方。有一天你想换成蓝色,你需要找到这 200 个地方一一修改。用了变量,只改一行,全部自动更新。

生活比喻: 这就像奶茶店定了"招牌紫色"这个品牌色,存在设计手册里。所有海报、菜单、杯子都引用手册里的那个色号,不用每次重新调色——改一次手册,全店同步。


🧩 片段二(CSS + HTML)— 任务打勾后的视觉变化

.task-checkbox.checked {
  background: var(--green);   /* 背景变绿 */
  border-color: var(--green); /* 边框也变绿 */
  box-shadow: 0 0 10px rgba(16,185,129,0.3); /* 绿色光晕 */
}
.task-text.done {
  text-decoration: line-through; /* 文字中间画横线 */
  color: var(--text-muted);      /* 颜色变灰 */
}

逐行大白话:

  • .task-checkbox.checked — "当勾选框同时拥有 checked 这个标记时,应用以下样式"
  • text-decoration: line-through — 给文字加删除线,就像你手写清单上的划掉效果

为什么要这样写? CSS 本身不知道你"点了"还是"没点",它只负责"当某个状态存在时显示某种样子"。是 JavaScript 在你点击的瞬间,偷偷给元素贴上 checked 这张标签,CSS 看到标签就自动换装。

生活比喻: 就像超市收银台的价格标签机制——商品本身没有价格,但当收银员扫了条形码(JavaScript 的点击事件),价格牌就自动从"待处理"变成"已结算"(CSS 换了样式)。


🧩 片段三(JavaScript)— 任务的"开关逻辑"

function toggleTask(pref, tid, li, cb, ts) {
  const st = cur();               // 拿出当前模块的数据
  st.tasks[tid] = !st.tasks[tid]; // 取反:true变false,false变true

  if (st.tasks[tid]) {
    cb.classList.add('checked');  // 给勾选框贴上"已完成"标签
    ts.classList.add('done');     // 给文字贴上"已完成"标签
  } else {
    cb.classList.remove('checked'); // 撕掉"已完成"标签
    ts.classList.remove('done');
  }

  updateStreak();  // 重新计算连续打卡天数
  updateUI(pref);  // 刷新进度条数字
  save(active);    // 存进本地抽屉
}

逐行大白话:

  • !st.tasks[tid] — 感叹号是"取反",意思是"原来是开的就关掉,原来是关的就开",就像电灯开关
  • classList.add('checked') — 给 HTML 元素贴上一个 CSS 标签(class),CSS 看到标签就换装
  • 最后三步是一套固定动作:算天数 → 刷界面 → 存数据

为什么要这样写? 如果不存数据,你刷新页面所有打勾就消失了。如果不刷新 UI,进度条不会动。这三步缺一不可,顺序也很重要——先改数据,再用新数据更新界面。

生活比喻: 就像你在健身房打卡。扫一下手机(点击) → 机器把你记录成"今天来了"(取反存储) → 电子屏幕显示"✅ 已打卡"(CSS 换装) → 月度报告自动更新(刷新进度条)。


🧩 片段四(JavaScript)— 本地存储:关掉浏览器也不丢

function save(k) {
  const p = PREF[k];  // 拿到存储的"抽屉标签",比如 "sdet"
  const st = S[k];    // 拿到这个模块的数据

  localStorage.setItem(p + '_tasks',  JSON.stringify(st.tasks));
  localStorage.setItem(p + '_streak', String(st.streak));
  localStorage.setItem(p + '_week',   String(st.week));
}

逐行大白话:

  • localStorage — 浏览器自带的"抽屉",存的是文字格式的数据,关掉浏览器也不会消失
  • localStorage.setItem('sdet_tasks', ...) — 往贴着"sdet_tasks"标签的格子里放东西
  • JSON.stringify(st.tasks) — 把 JavaScript 的数据对象"压扁"成一串文字,因为抽屉只能存文字(就像把立体地图折叠成平面地图再放进信封)

为什么要这样写? 浏览器的内存(就是代码运行时的数据)是临时的,关掉标签页就清空了。localStorage 是硬盘上的持久存储,才能让数据活过"关闭浏览器"这个事件。

生活比喻: 数据就像你在便签纸上写的学习计划。内存是你手里拿着这张便签(关掉手就掉了),localStorage 是把便签贴在冰箱上(明天还在)。JSON.stringify 就是把立体便利贴折叠成能放进抽屉的平面纸。


🧩 片段五(JavaScript)— 云端同步:换台电脑数据也在

async function syncNow() {
  setSyncState('syncing');  // 顶部标签变成黄色"🔄 同步中"

  try {
    const dr = db.collection('learningPanels').doc(currentUser.uid);
    await dr.set(ld, { merge: true }); // 上传数据到云端(等待完成)

    setSyncState('synced');  // 成功后变成绿色"✅ Synced"
  } catch(e) {
    setSyncState('failed');  // 失败则变成红色"❌ Failed"
  }
}

逐行大白话:

  • async — 告诉 JavaScript"这个函数里有需要等待的操作,请耐心"
  • await — "在这里暂停,等网络请求完成再继续"(但页面不会卡住,用户还能操作)
  • try...catch — 试着做某件事,如果出错了不要崩溃,改为执行"出错处理"

为什么要这样写? 联网操作需要时间(可能 0.5 秒,可能 3 秒),如果不用 async/await,代码会在数据还没传完的时候就继续执行下去,导致保存失败但页面显示"成功"的假象。

生活比喻: 就像你用手机给家人发照片。async 是你告诉自己"发照片需要时间,发送中先去做别的事"。await 是"等到对方收到回执再关掉发送页面"。try/catch 是"如果网络断了,弹出'发送失败'而不是直接闪退"。


4. 【它是怎么工作的】

🎬 想象一个叫小明的人,今晚打开了电脑,准备开始他第 7 天的学习打卡。


第一幕:浏览器收到指令,开始"建房子"

小明在浏览器地址栏输入网址,按下回车。这一刻,浏览器开始工作——它把 HTML 文件请求过来,开始从上到下逐行阅读,就像工头拿到了一份建筑蓝图。

浏览器边读边在内存里搭起一棵"家庭树",叫做 DOM 树(文档对象模型,Document Object Model — 就是把 HTML 的每个标签、每段文字都变成树上的一个"节点",节点之间有父子关系,就像族谱)。<header> 是爷爷,它底下的 <button> 是孙子,所有节点各就各位。

与此同时,CSS 文件也被浏览器读取,生成另一棵树叫 CSSOM 树(CSS 对象模型 — 就是把所有样式规则整理成一个化妆师名单,谁负责给哪个元素化妆、用什么颜色什么字号都列得清清楚楚)。

这两棵树合并在一起,形成渲染树(Render Tree — 演员 + 服装的最终配对清单)。然后浏览器计算每个元素应该出现在屏幕的哪个位置,最终把像素画到屏幕上。

这一切在不到 0.1 秒内完成。


第二幕:JavaScript 登场,去翻"抽屉"

页面框架画出来了,但还没有真实数据——任务清单是空的,打卡天数是 0,日历是空白的。

这时候 JavaScript 的 init() 函数自动启动,它做的第一件事就是去翻浏览器的"本地抽屉"(localStorage)。它一个一个找:

  • 抽屉里有没有贴着 sdet_tasks 标签的格子?有!里面存着小明上次的打勾记录。
  • 有没有 sdet_streak?有!上面写着 6,小明已经连续学了 6 天。
  • 有没有 sdet_week?有!数字是 1,他正在第 2 周(数组从 0 开始,所以 1 = 第 2 周)。

所有数据被取出来,装进叫做 S 的大口袋里,随时准备被使用。


第三幕:Firebase 门卫查身份

几乎同时,Firebase 的身份验证模块像酒吧门口的保安一样默默检查:这个设备上有没有登录的 Google 账号?

auth.onAuthStateChanged() 这个函数一直在后台等待。如果发现小明上次登录过,立刻触发:去云端数据库拉取最新数据,和本地数据比较时间戳——谁最新用谁,然后更新页面顶部同步状态为绿色"✅ Synced"。

这个步骤是异步的(async — 就是"同时在后台跑,不影响前台页面的显示"),所以页面不会因为等待网络而卡住,用户已经可以开始操作了。


第四幕:页面被"填满",小明终于看到内容

renderAll() 函数像一个装修总监,按顺序指挥各部门填内容:

  1. renderPlanTabs() — 检查小明上次选的是哪个 Tab,让那个按钮高亮
  2. renderWeekTabs() — 在任务卡片顶部画出 W1 W2 … W20 的周次标签
  3. renderTasks() — 把第 2 周的 5 个任务逐条画出来,并根据 st.tasks 里的记录自动恢复打勾状态
  4. renderCalendar() — 画出当月日历,并把 st.dates 里有记录的日期标绿
  5. updateRing() — 计算总进度百分比,更新圆环的弧度

整个填充过程在几毫秒内完成。小明看到的"活的"页面,其实是 JavaScript 把数据一条条"贴"进 DOM 骨架里的结果。


第五幕:所有按钮长上"耳朵"

setupEvents() 函数负责给每个按钮装上"事件监听器"(Event Listener — 就是让按钮长上耳朵,随时等你操作)。这是一次性的绑定,之后就持续生效:

  • 三个 Tab 按钮:耳朵在听"被点击"事件,点了就切换面板
  • 每个任务条目:耳朵在听"被点击",点了就触发打勾逻辑
  • 保存承诺按钮:耳朵在听"被点击",点了存数据并更新显示
  • 🚀 倒计时按钮:耳朵在等,一旦点击开始 5 秒倒计时动画

第六幕:小明打了一个勾,发生了五件事

小明完成了"安装 IntelliJ IDEA",点了任务前面的小方框。在接下来的不到 50 毫秒里,发生了这些事:

  1. 数据取反st.tasks['s1t1'] = true(从 false 变 true)
  2. CSS 换装:勾选框被贴上 checked 标签变绿,文字被贴上 done 标签划线变灰
  3. 进度更新:重新计算 1/5,进度条宽度从 0% 变成 20%
  4. 存进本地localStorage.setItem('sdet_tasks', '...') 把新状态写进本地抽屉
  5. 云端同步(2.5 秒后):一个计时器被启动,2.5 秒内如果没有其他操作,就悄悄把数据同步到 Firebase

第 1-4 步是瞬间完成的,小明感觉不到任何延迟。第 5 步在后台默默进行,页面不会卡住,顶部状态标签会短暂变黄"🔄 同步中",完成后变回绿色。

如果是今天第一次打勾,连续打卡天数还会加一,顶部徽章触发弹跳动画,然后日历上今天的格子变绿。

这一个小小的打勾动作,背后是一条完整的"数据流水线"。


5. 【我学到了什么】

概念名称 一句话解释(大白话) 在代码中的体现 生活比喻
HTML 页面的骨架,决定有什么元素 <header> <button> <ul> <li> 等所有标签 房子的砖墙和框架
CSS 骨架的外衣,决定好不好看 .card { border-radius: 16px; box-shadow: ... } 房子的油漆和装修
CSS 变量 给颜色/数值起名字,改一处全更新 :root { --purple: #7C5CFC } 品牌色手册
Flexbox 让元素乖乖排成一行或一列 .top-bar { display: flex } 把货物整齐摆上流水线传送带
CSS Grid 把空间切成格子,元素各占一格 .calendar-grid { grid-template-columns: repeat(7,1fr) } Excel 表格
JavaScript 页面的大脑,让页面能动起来 toggleTask() save() renderAll() 等所有函数 房子里的电路和智能系统
DOM 操作 用代码找到并修改页面上的元素 element.classList.add('checked') 装修工人按图纸修改墙面
事件监听 给元素装"耳朵",等用户操作 button.addEventListener('click', ...) 给按钮派了一个全天候服务员
localStorage 浏览器的本地抽屉,关掉不丢 localStorage.setItem('sdet_streak', '7') 冰箱上的便利贴
JSON 把复杂数据压扁成文字方便存取 JSON.stringify(st.tasks) 把立体地图折叠成可放进信封的平面图
async/await 处理"需要等待"的操作,不卡页面 async function syncNow() { await dr.set(...) } 发快递时继续做别的事,等到货通知再去取
Firebase Google 的云数据库,跨设备同步 db.collection('learningPanels').doc(uid) 把便利贴拍照传到家庭群,所有人都能看到
CSS 动画 让变化平滑过渡,不突兀 transition: width 0.5s @keyframes streakPop 电梯门缓缓关上,而不是猛地关上
响应式设计 让页面在手机和电脑上都好看 @media(max-width:720px) clamp(...) 折叠桌:平时展开用,空间小了折叠收起

6. 【动手试试】

✏️ 任务一:把主题色从紫色改成你喜欢的颜色

目标: 把整个网站的主色调从紫色换成你喜欢的颜色(比如蓝色、粉色)。

打开: 你的 HTML 文件,找到 <style> 标签内部。

找到这段代码:

:root {
  --purple: #7C5CFC;
  --purple-dark: #5E3FD9;
  --purple-light: #A78BFA;
}

怎么改:#7C5CFC 改成 #E91E8C(粉色)或 #2196F3(蓝色),三行都改。

效果: 刷新页面后,所有按钮、进度条、高亮文字都变成新颜色。

改错了怎么办:Ctrl + Z(Mac 用 Cmd + Z)撤销,或者把颜色改回 #7C5CFC


✏️ 任务二:修改励志提示语

目标: 把点击"🚀 开始学习"后弹出的励志语换成你自己写的。

打开: HTML 文件,搜索关键词 hints:(用 Ctrl + F 搜索)。

找到这段代码:

hints: [
  'Small progress counts. Keep the streak!',
  'One task at a time.',
  'Focus on execution.',
  ...
]

怎么改: 把引号里的英文换成你自己想看到的话,中文也行,比如:

'今天比昨天进步一点点就够了!',
'坚持才是最牛的技能。',

效果: 点击"🚀 开始学习"按钮,弹出的提示就变成你写的话了。


✏️ 任务三:改变连续打卡徽章的颜色

目标: 把顶部那个🔥打卡天数徽章从黄色改成你喜欢的颜色。

打开: HTML 文件,搜索 .streak-badge

找到这段代码:

.streak-badge {
  background: linear-gradient(135deg, #FEF3C7, #FDE68A);
  color: #92400E;
}

怎么改:#FEF3C7 改成 #D1FAE5(浅绿),#FDE68A 改成 #6EE7B7(深绿),#92400E 改成 #065F46(深绿文字)。

效果: 刷新后,打卡徽章从黄色变成绿色系,像一个"健康打卡"的感觉。

小提示: 不知道颜色代码?直接去 coolors.co 点你喜欢的颜色,复制 # 开头的代码粘贴进来就行。


🎉 最后想对你说: 你用 vibe coding 做出来的这个项目,包含了很多前端工程师工作好几个月才会碰到的技术——云同步、动画系统、多模块数据管理。真正厉害的不是"你能不能从零写出它",而是"你能不能读懂它、改动它、让它变成真正属于你的东西"。你已经在做这件事了。继续改它、玩它,每改一行代码就是一次真实的学习。🚀

About

FINAL

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors