Web-Dev-For-Beginners 银行应用实战:实现「添加交易」对话框并接入状态管理
2026/9/20 12:50:01 网站建设 项目流程

Web-Dev-For-Beginners 银行应用实战:实现「添加交易」对话框并接入状态管理

【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners

本指南围绕 Web-Dev-For-Beginners 课程中银行应用(7-bank-project)第四课的课后作业展开:为已完成状态管理与数据持久化的应用补齐「添加交易」(Add Transaction)功能。你将学到如何设计可访问的模态对话框、构造交易表单、对接后端 API,并让新增交易通过既有状态管理系统即时刷新仪表盘。读完本文,你可以独立完成从按钮、对话框、表单到 API 与状态层整合的完整功能闭环,并掌握可复用的 Web 无障碍与前端架构实践。

作业背景与学习目标

银行应用此刻已经具备集中式状态管理(state对象 +updateState()受控更新)、localStorage 持久化以及数据刷新机制,但还缺少真实银行应用的关键能力:让用户自己录入交易。本作业将四个银行课的核心知识点串成一条完整链路——HTML 模板、表单处理、API 集成与状态管理(详见 第四课讲义 与前三课内容)。

完成本作业后,你将能够:

  • 创建一个用户友好的数据录入对话框界面;
  • 实现支持键盘与屏幕阅读器的可访问表单设计;
  • 整合新功能与既有状态管理系统;
  • 实践API 通信与错误处理;
  • 应用现代 Web 开发模式解决真实场景需求。

前置条件:环境与 API 就绪检查

作业建立在第四课之前完成的代码之上,动手前请确认环境就绪:

  • 完成 数据获取课程,应用能正常加载并展示账户数据;
  • 安装 Node.js 用于运行后端 API;
  • 参照 服务器 API 说明 在本地启动 API:进入7-bank-project/api目录执行npm install,随后npm start,服务监听5000端口(注意与主应用所在终端并存,不要关闭)。

用一条命令验证 API 是否正常运行:

curl http://localhost:5000/api # -> 应返回 "Bank API v1.0.0"

这条命令会向本地 API 发送 GET 请求、测试连接,若一切正常则返回 API 版本信息。仓库自带的 server.js 中,GET /路由正是返回pkg.description + ' v' + pkg.version,即响应内容的来源。

Step 1:在仪表盘放置「添加交易」按钮

在仪表盘页面上创建一个用户容易发现、方便使用的按钮,要求:

  • 位置合理:放在仪表盘的逻辑位置(如交易列表标题区);
  • 文案清晰:使用面向操作的按钮文本;
  • 风格一致:样式与现有 UI 设计统一;
  • 键盘可达:确保按钮可被键盘聚焦操作。

参考实现中,按钮被放在交易列表标题栏右侧,使用内联onclick调起对话框。对应的 HTML 位于 solution/index.html:

<div class="transactions-title"> <h2 id="transactions-description">Transactions</h2> <button class="btn btn-primary" type="button" onclick="addTransaction()"> <i class="fa-solid fa-plus" aria-hidden="true"></i> Add transaction </button> </div>

注意图标使用aria-hidden="true",避免屏幕阅读器朗读装饰性图标;按钮本身是原生<button>,天然支持键盘 Tab 聚焦与 Enter/Space 触发。

Step 2:对话框实现——独立页面还是模态框

作业提供两种实现路径,推荐后者:

方案 A:独立页面

  • 为交易表单创建新的 HTML 模板;
  • 在路由系统中新增一条路由;
  • 实现进入/返回表单页的导航。

方案 B:模态对话框(推荐)

  • 用 JavaScript 控制对话框的显示/隐藏,不离开仪表盘页面;
  • 使用 HTML 的hidden属性 或 CSS 类实现显隐;
  • 通过正确的焦点管理营造流畅的用户体验。

参考实现采用方案 B:对话框默认带hidden属性,配合.dialog样式(display: none),通过addTransaction()添加.show类(display: flex)来展示。核心结构见 solution/index.html:

<section id="transactionDialog" class="dialog" hidden> <div class="dialog-backdrop">function addTransaction() { const dialog = qs('transactionDialog'); if (!dialog) return; dialog.classList.add('show'); const form = qs('transactionForm'); if (form) { form.reset(); form.date.valueAsDate = new Date(); // 日期默认今天 form.date.focus(); // 焦点移到第一个字段 } const backdrop = dialog.querySelector('[data-dismiss]') || dialog; backdrop.addEventListener('click', onDialogDismissClick); dialog.addEventListener('keydown', onDialogKeydown); } function onDialogKeydown(e) { if (e.key === 'Escape') { cancelTransaction(); } } function cancelTransaction() { const dialog = qs('transactionDialog'); if (!dialog) return; dialog.classList.remove('show'); dialog.removeEventListener('keydown', onDialogKeydown); const opener = document.querySelector('button[onclick="addTransaction()"]'); if (opener) opener.focus(); // 焦点归还触发按钮 }

屏幕阅读器支持

  • 为对话框添加合适的 ARIA 角色与标签:role="dialog"aria-modal="true"aria-labelledby(指向标题)与aria-describedby(指向说明文字);
  • 向读屏软件通告对话框的开/关状态;
  • 为表单字段提供清晰的标签与错误信息。

表单错误区使用role="alert"+aria-live="polite"组合(见 solution/index.html),错误写入时读屏会自动播报;错误设置函数setFormError()还会将焦点移到错误元素上,方便键盘用户立刻定位问题:

function setFormError(id, message) { updateElement(id, message); const el = qs(id); if (el) el.focus(); }

Step 4:交易表单的创建与校验

表单需要收集三类必填数据:

字段含义输入规则
日期(Date)交易发生时间<input type="date" required>,默认今天
描述(Object)交易用途文本,maxlength="50",必填
金额(Amount)交易金额正数为收入(credit),负数为支出(debit),step="any"支持小数

参考表单结构(solution/index.html):

<form id="transactionForm" action="javascript:void(0)" novalidate> <label for="date">Date</label> <input id="date" name="date" type="date" required> <label for="object">Object</label> <input id="object" name="object" type="text" maxlength="50" required> <label for="amount">Amount (negative for debit)</label> <input id="amount" name="amount" type="number" value="0" step="any" inputmode="decimal" required> <div id="transactionError" class="error" role="alert" aria-live="polite"></div> ... </form>

表单特性要求:提交前校验用户输入、对非法数据给出明确错误信息、提供有助益的占位文本与标签、样式与现有设计一致。novalidate关闭浏览器原生气泡校验,改由 JS 统一内联校验,保证错误信息可控且能被读屏正确播报。

confirmTransaction()(solution/app.js)中的校验逻辑与后端保持同一套规则——金额必须是有限数值、描述不可为空:

const amountVal = Number(form.amount.value); const objectVal = String(form.object.value || '').trim(); if (!Number.isFinite(amountVal)) { setFormError('transactionError', 'Amount must be a valid number'); return; } if (!objectVal) { setFormError('transactionError', 'Object is required'); return; }

Step 5:API 集成——端点、数据格式与错误处理

作业要求对接后端 API,正确端点与数据格式以 服务器 API 文档 为准:

方法路由说明
GET/api/获取服务器信息
POST/api/accounts/创建账户,如{ user: 'Yohan', description: 'My budget', currency: 'EUR', balance: 100 }
GET/api/accounts/:user获取指定账户全部数据
DELETE/api/accounts/:user删除账户
POST/api/accounts/:user/transactions添加交易,如{ date: '2020-07-23T18:25:43.511Z', object: 'Bought a book', amount: -20 }
DELETE/api/accounts/:user/transactions/:id删除指定交易

本作业使用的端点即POST /api/accounts/:user/transactions,请求体 JSON 字段为dateobjectamount。后端实现(server.js)的细节可以帮助你理解预期行为:

  • 账户不存在返回404 { error: 'User does not exist' }
  • 缺少date/object/amount任一字段返回400 { error: 'Missing parameters' }
  • 金额不是有效数字返回400 { error: 'Amount must be a number' }
  • 服务端用 md5 对date + object + amount生成交易 ID,重复内容返回409 { error: 'Transaction already exists' }
  • 成功时以201返回新建的交易对象{ id, date, object, amount },并同步更新账户余额。

注意:仓库中的 server.js 将数据保存在内存中(含预置的test账户),重启即丢失,仅用于教学演示。

前端把表单数据序列化为 JSON 并提交(solution/app.js):

const jsonData = JSON.stringify(Object.fromEntries(new FormData(form))); const data = await createTransaction(state.account.user, jsonData); if (data.error) { setFormError('transactionError', data.error); return; }

错误处理要点:成功/失败消息都要呈现给用户;对网络错误要优雅降级(如提示后允许重试);服务端返回的error字段应直接映射到表单错误区。

Step 6:状态管理集成——新交易立即上屏

这是把整个功能与既有架构缝合的关键一步,要求:

  • 成功添加交易后刷新账户数据
  • 仪表盘显示无需整页刷新即可更新;
  • 新交易立即出现在交易列表;
  • 全程保持状态一致性

参考实现没有重新拉取全量数据,而是基于当前state.account不可变更新方式本地合成新账户状态(solution/app.js):

const newAccount = { ...state.account, balance: (Number(state.account.balance) || 0) + data.amount, transactions: [...(state.account.transactions || []), data] }; updateState('account', newAccount); cancelTransaction(); updateDashboard();

关键点在于复用第四课建立的受控更新入口updateState()(solution/app.js):

function updateState(property, newData) { state = Object.freeze({ ...state, [property]: newData }); localStorage.setItem(storageKey, JSON.stringify(state.account)); }

由于所有状态变更都收敛到这一个函数,持久化只需要一行代码:写入 localStorage 自动完成,updateDashboard()重读state.account后立即把余额与最新交易渲染上屏(solution/app.js),交易按日期倒序排列,负数行加debit、正数行加credit样式类。

如果希望更贴近「从服务器拿最新数据」的模式,第四课还提供了updateAccountData()+refresh()机制,并挂到路由init上(见 第四课讲义 的 Data Refresh 小节),可作为本作业的进阶选择——在提交成功后调用refresh()以服务器返回为准更新仪表盘。

预期结果

完成六个步骤后,应用应具备一个外观与行为都专业、可用的「添加交易」功能。下方是仓库提供的示例效果图:

测试你的实现

功能测试

  1. 验证「添加交易」按钮清晰可见且可访问;
  2. 测试对话框能正确打开与关闭;
  3. 确认所有必填字段的校验都生效;
  4. 检查成功提交的交易能立即出现在仪表盘;
  5. 确保非法数据与网络问题的错误处理都正常工作。

无障碍测试

  1. 仅用键盘走完整个流程;
  2. 用屏幕阅读器验证读屏通告是否正确;
  3. 验证焦点管理正常(打开时聚焦表单、关闭后回到触发按钮);
  4. 检查所有表单元素都有合适的标签。

评价标准

标准优秀合格待改进
功能性添加交易功能完美运行,用户体验出色,遵循课程全部最佳实践功能正确但可能未遵循部分最佳实践或有轻微可用性问题功能部分可用或有明显可用性问题
代码质量组织良好,遵循既定模式,错误处理正确,与既有状态管理无缝整合可运行但存在组织问题或与既有代码风格不一致结构性缺陷或与既有模式整合不佳
可访问性完整键盘导航支持、读屏兼容,遵循 WCAG 指南,焦点管理出色基础无障碍已实现但缺少部分键盘或读屏特性几乎没有或完全没有考虑无障碍
用户体验直观精致的界面,反馈清晰,交互流畅,外观专业体验良好,但反馈或视觉设计有小幅提升空间体验差,界面令人困惑或缺少用户反馈

附加挑战(可选)

完成基础要求后,可尝试以下增强:

功能增强

  • 增加交易分类(餐饮、交通、娱乐等);
  • 实现带实时反馈的输入校验;
  • 为熟练用户提供键盘快捷键;
  • 增加交易编辑与删除能力。

进阶集成

  • 实现最近添加交易的撤销(undo)功能;
  • 支持从 CSV 文件批量导入交易;
  • 提供交易搜索与筛选;
  • 实现数据导出。

这些可选功能将帮助你练习更进阶的 Web 开发概念,并让银行应用更加完整。若想进一步练习状态管理,第四课讲义的 GitHub Copilot Agent Challenge 还提供了带撤销/重做与历史上限的状态管理挑战,可作为本作业的自然延伸。

【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询