ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Web-Dev-For-Beginners 银行应用实战:基于状态管理实现 “Add Transaction“ 交易录入对话框

Web-Dev-For-Beginners 银行应用实战:基于状态管理实现 “Add Transaction“ 交易录入对话框 Web-Dev-For-Beginners 银行应用实战基于状态管理实现 Add Transaction 交易录入对话框【免费下载链接】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」第 4 节《状态管理State Management》的课后作业为核心完整讲解如何在已有集中式状态管理与 localStorage 持久化系统之上为仪表盘Dashboard实现一个专业、可无障碍访问的添加交易Add Transaction对话框。读完本文你将掌握模态对话框的两种实现路径独立页面 vs 弹窗、完整的数据校验与错误处理、与 Express API 的对接方式以及如何让新交易在不刷新页面的情况下立即出现在仪表盘上——所有内容均有本仓库源码7-bank-project/solution/与 API 服务7-bank-project/api/作为事实依据。1. 任务背景本作业在前四节课程中的定位本作业将前四节银行应用课程所学的内容串联起来它们分别是课程小节核心知识点与本作业的关系1. 模板与路由HTMLtemplate模板、SPA 路由对话框 UI 以模板形式挂在 Dashboard 模板内部2. 表单处理表单校验、FormData、提交拦截交易表单的收集、校验与提交逻辑3. 数据获取fetch/异步请求、账户数据加载提交后刷新账户数据、错误处理4. 状态管理集中式 state、Object.freeze、localStorage 持久化新交易通过updateState()写入状态并持久化作业的完成目标非常明确在不破坏已有状态管理系统的前提下让用户能够自助录入交易。也就是说你要新增的功能必须「长在」现有的updateState()/state/ 路由体系之上而不是另起炉灶。前置条件请先确保已完成 7-bank-project/3-data 的数据获取课程并按 7-bank-project/api/README.md 启动 API 服务。验证方式在终端执行curl http://localhost:5000/api返回Bank API v1.0.0即表示服务正常API 默认监听 5000 端口与主应用所在端口相互独立请勿关闭。2. Step 1添加交易按钮Add Transaction Button作业要求先在仪表盘页面上放置一个用户易于发现和访问的「Add Transaction」按钮并给出四项硬性要求放置在仪表盘的逻辑合理位置通常位于交易列表标题区右侧使用清晰、面向操作的按钮文案按钮样式与现有 UI 设计保持一致按钮支持键盘访问原生button天然满足。本仓库的解决方案在 7-bank-project/solution/index.html 的 Dashboard 模板中给出了标准做法——把按钮放在交易列表标题h2旁边div classtransactions-title h2 idtransactions-description i classfa-solid fa-receipt aria-hiddentrue/i Transactions /h2 button classbtn btn-primary typebutton onclickaddTransaction() i classfa-solid fa-plus aria-hiddentrue/i Add transaction /button /div设计要点拆解按钮语义使用button typebutton而非a或div保证 Tab 键可聚焦、回车/空格可触发点击行为onclickaddTransaction()直接调用对话框的打开函数这是作业 Option B「模态对话框」的入口样式一致性复用全局.btn .btn-primary类与登录页、注册页按钮同源图标辅助i图标均带aria-hiddentrue避免屏幕阅读器朗读装饰性图标。从 7-bank-project/solution/app.js 可以看到addTransaction()做的事很简单——给对话框加show类、重置表单并把日期默认设为今天、把焦点移到第一个字段、挂载关闭事件function addTransaction() { const dialog qs(transactionDialog); if (!dialog) return; dialog.classList.add(show); // Reset form and set today 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); }3. Step 2对话框的两种实现路径推荐模态弹窗作业给出了两条技术路线并要求二选一Option A独立页面Separate Page——为交易表单新建一个 HTML 模板在路由系统中新增一条路由并实现表单页与仪表盘之间的往返导航Option B模态对话框Modal Dialog推荐——使用 JavaScript 在不离开仪表盘的前提下显示/隐藏对话框可用 HTML 的hidden属性 或 CSS 类实现并做好焦点管理。方案 B 之所以被推荐是因为它不触发页面切换天然适合与 SPA 路由 集中式状态配合交互也更连贯。本仓库解决方案完整实现了 Option B其结构分为 HTML 标记、CSS 显隐与 JS 控制三部分。3.1 HTML对话框标记挂在 Dashboard 模板内部7-bank-project/solution/index.html 中对话框section使用hidden属性作为默认隐藏手段同时用 ARIA 属性描述对话框身份section idtransactionDialog classdialog hidden div classdialog-backdrop>.dialog { display: none; /* 默认隐藏 */ position: fixed; inset: 0; /* 覆盖整个视口 */ overflow: auto; background-color: rgba(0,0,0,.45); /* 半透明遮罩 */ justify-content: center; align-items: flex-start; padding: var(--space-sm); z-index: 1000; } .dialog.show { display: flex; } /* 打开时切换为 flex 居中 */.dialog-content还配有弹出动画animation: dialogIn .25s ease-out both从透明 上移 14px 过渡到正常状态让开合手感更顺滑。注意.dialog.show { display: flex }写在.dialog之后由于两者特异性相同、后者胜出从而正确覆盖默认的display: none。3.3 JS打开与关闭的控制流关闭对话框的完整逻辑7-bank-project/solution/app.js包含三种触发源点遮罩data-dismiss元素、按 Escape 键、点 Cancel 按钮且关闭后要把焦点还给触发按钮function onDialogDismissClick(e) { if (e.target?.hasAttribute?.(data-dismiss)) { cancelTransaction(); } } 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[onclickaddTransaction()]); if (opener) opener.focus(); // 焦点返回触发按钮 }关于焦点陷阱focus trap作业要求「打开对话框时把焦点困在对话框内」。仓库解决方案采用「打开时聚焦第一个字段 关闭时返还焦点」的简化实现。如果你希望严格实现焦点陷阱需要在keydown中监听 Tab 键将焦点限制在.dialog-content内可聚焦元素input、button之间循环——这是本作业可以自行强化的进阶点。4. Step 3无障碍实现Accessibility作业明确要求对话框满足模态对话框的无障碍标准分键盘导航与屏幕阅读器两部分键盘导航支持 Escape 键关闭对话框解决方案已实现见上方onDialogKeydown打开时焦点被困在对话框内解决方案采用聚焦首字段的简化做法关闭后焦点返回触发按钮已实现cancelTransaction()中opener.focus()。屏幕阅读器支持添加恰当的 ARIA 角色与标签——解决方案使用roledialogaria-modaltruearia-labelledbytxDialogTitle标题aria-describedbytxDialogDesc说明文字播报打开/关闭状态——表单错误区#transactionError使用了rolealertaria-livepolite错误出现时屏幕阅读器会主动朗读提供清晰的字段标签与错误信息——每个input上方都有label for...错误信息通过setFormError()写入并focus()到错误节点。在 7-bank-project/solution/index.html 中登录/注册表单同样沿用这一套 ARIA 模式如#loginError、#registerError均为rolealert说明这是整个应用的统一无障碍约定新增对话框应保持一致。5. Step 4交易表单的创建与校验5.1 表单字段设计作业要求表单收集三个必填字段字段类型说明Date日期input typedate交易发生日期默认今天Description描述/对象input typetext maxlength50交易用途说明Amount金额input typenumber stepany正值表示收入credit负值表示支出debit解决方案对应的 HTML7-bank-project/solution/index.htmllabel fordateDate/label div classfield input iddate namedate typedate required /div label forobjectObject/label div classfield input idobject nameobject typetext maxlength50 required /div label foramountAmount (negative for debit)/label div classfield input idamount nameamount typenumber value0 stepany inputmodedecimal required /div这里有几个值得注意的细节typenumberstepany允许任意精度包括小数inputmodedecimal在移动端弹出带小数点的数字键盘金额标签直接写明「negative for debit」在 UI 层就引导用户用负数表示支出表单位于template iddashboard内部由 SPA 路由克隆注入表单的name属性与FormData直接对应便于序列化。5.2 提交前的输入校验作业要求「提交前校验用户输入并对非法数据给出清晰错误信息」。解决方案在 7-bank-project/solution/app.js 的confirmTransaction()中做了双重校验async function confirmTransaction() { const form qs(transactionForm); if (!form) return; // 客户端内联校验 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; } clearFormError(transactionError); const jsonData JSON.stringify(Object.fromEntries(new FormData(form))); const data await createTransaction(state.account.user, jsonData); // ...后续状态更新 }配套的错误显示工具函数function setFormError(id, message) { updateElement(id, message); const el qs(id); if (el) el.focus(); // 把焦点移到错误提示方便读屏用户 } function clearFormError(id) { const el qs(id); if (el) el.textContent ; }校验逻辑值得学习的两点数量有限但精准金额用Number.isFinite()判断是否为有效数字描述用trim()后判断非空——与后端 7-bank-project/api/server.js 中Amount must be a valid number、Object is required的校验口径一致错误即焦点setFormError()不仅写文案还把焦点移到错误节点配合rolealert实现「错误即时播报」。6. Step 5API 集成——对接交易录入端点6.1 端点规格来自 7-bank-project/api/README.md方法路由说明POST/api/accounts/:user/transactions为指定账户添加交易GET/api/accounts/:user获取指定账户全部数据DELETE/api/accounts/:user/transactions/:id删除指定交易请求体 JSON 格式示例{ date: 2020-07-23T18:25:43.511Z, object: Bought a book, amount: -20 }6.2 服务端行为细节7-bank-project/api/server.js阅读服务端源码可以确认交易接口的完整行为这对客户端错误处理至关重要必填参数date、object、amount三者缺一即返回400 { error: Missing parameters }金额校验amount无法转换为数字时返回400 { error: Amount must be a number }账户校验/api/accounts/:user不存在时返回404 { error: User does not exist }幂等去重服务端用 MD5date object amount生成交易 ID重复交易返回409 { error: Transaction already exists }成功响应返回201与包含id、date、object、amount的完整交易对象同时服务端会自动更新账户余额account.balance transaction.amount。⚠️ 注意API 的 server.js 明确注明数据仅存于内存in-memory服务停止即丢失同时该 API 是为教学预先构建好的不属于本次作业的修改范围。6.3 客户端集成从表单 JSON 到错误处理解决方案中createTransaction(user, jsonData)是一个对 API 的封装在 7-bank-project/solution/app.js 中为本地 mock 实现模拟了同样的错误语义Malformed transaction data、Amount must be a valid number、Object is required、Account not found而confirmTransaction()的集成流程是用Object.fromEntries(new FormData(form))收集表单数据JSON.stringify序列化后交给createTransaction(state.account.user, jsonData)若返回data.error用setFormError()展示错误并终止流程成功则进入 Step 6 的状态更新环节。对真实 API 的对接作业要求「适当处理网络错误」——即try/catch包裹fetch调用对网络中断、超时给出用户可读的提示例如「无法连接服务器请检查 API 是否启动」这也是本作业中服务端源码没有覆盖、需要你自行完善的客户端逻辑。7. Step 6状态管理集成——交易立即可见这是整个作业的收尾关键新增交易后仪表盘必须不刷新页面就立即显示最新交易并保持状态一致性。7.1 更新本地状态7-bank-project/solution/app.js// Update local state const newAccount { ...state.account, balance: (Number(state.account.balance) || 0) data.amount, transactions: [...(state.account.transactions || []), data] }; updateState(account, newAccount); // Close dialog and update view cancelTransaction(); updateDashboard();这段代码充分体现了前四节课程沉淀的不可变状态更新模式用展开运算符...state.account复制旧状态再覆盖balance与transactions绝不直接修改state.accountbalance的累加方式与服务端一致原余额 交易金额保证前后端口径统一通过唯一的updateState()写入新状态自动触发 localStorage 持久化。7.2 updateState 与持久化来自第 4 节课程核心回顾 7-bank-project/4-state-management/README.md 建立的updateState()模式它正是本作业状态集成的基石function updateState(property, newData) { state Object.freeze({ ...state, [property]: newData }); localStorage.setItem(storageKey, JSON.stringify(state.account)); }冻结每次更新都生成新对象并Object.freeze()防止意外篡改自动持久化更新即写localStoragekey 为savedAccount这就是「新交易刷新后依然存在」的底层保证集中入口所有状态变更登录、注册、登出、刷新数据、添加交易都走这一个函数方便调试与审计。7.3 界面刷新与数据新鲜度confirmTransaction()在updateState()之后调用updateDashboard()后者根据state.account重新渲染余额与交易表格。由于状态是响应式的数据源、UI 是状态的投影交易会立即出现无需整页刷新。同时第 4 节课程引入的「刷新即取新数据」模式routes[/dashboard]的init: refresh保证了每次进入仪表盘都会从服务端拉取最新账户数据async function updateAccountData() { const account state.account; if (!account) return logout(); const data await getAccount(account.user); if (data.error) return logout(); updateState(account, data); } async function refresh() { await updateAccountData(); updateDashboard(); }因此整个作业完成后应用的交易数据流是表单提交 → API 创建 → 本地状态更新含余额与交易数组→ localStorage 持久化 → updateDashboard 即时重绘。此后刷新页面或重新进入仪表盘还会从服务端同步最新数据形成闭环。8. 测试你的实现8.1 功能测试验证「Add Transaction」按钮清晰可见且可访问测试对话框能正常打开与关闭三种关闭途径Escape、遮罩/Cancel、提交成功确认表单对所有必填字段生效清空描述、输入非数字金额应看到错误提示检查成功添加的交易立即出现在仪表盘交易列表中且余额正确变化确保非法数据如重复提交同一笔交易与网络问题未启动 API都有明确的错误反馈。8.2 无障碍测试仅使用键盘完成「打开对话框 → 填写表单 → 提交 → 关闭」的完整流程使用屏幕阅读器验证对话框标题、描述、字段标签与错误信息的播报验证焦点管理打开时进入表单、关闭时返回触发按钮检查所有表单元素都有恰当的label。可用的手动验证命令启动 API 后模拟一笔来自其他来源的交易以验证数据新鲜度curl --request POST \ --header Content-Type: application/json \ --data { \date\: \2020-07-24\, \object\: \Bought book\, \amount\: -20 } \ http://localhost:5000/api/accounts/test/transactions9. 评估标准Rubric作业给出了三档评估维度可用于自评维度优秀Exemplary合格Adequate待改进Needs Improvement功能性功能完美遵循课程全部最佳实践用户体验优秀功能正确但个别最佳实践缺失或有轻微可用性问题功能部分可用或存在显著可用性问题代码质量组织良好、遵循既有模式、错误处理完善、与状态管理无缝集成可运行但组织欠佳或与现有代码模式不一致结构性缺陷或未与现有模式集成无障碍完整键盘导航、读屏兼容、遵循 WCAG、焦点管理出色基本无障碍已实现但缺少部分键盘/读屏细节几乎没有无障碍考虑用户体验直观、精致的界面反馈清晰交互流畅体验良好反馈或视觉有可改进之处界面混乱、缺少用户反馈对照仓库解决方案它在「功能」「代码质量」「用户体验」上均达到优秀档无障碍部分实现了 Escape 关闭、焦点返还、ARIA 与错误播报但焦点陷阱采用了简化实现可作为自行加强的方向。10. 进阶挑战可选作业在基础需求之外提供了两条进阶路线全部可选增强功能为交易添加分类餐饮、交通、娱乐等实现带实时反馈的输入校验如输入即校验、防抖提示为高级用户提供键盘快捷键增加交易编辑与删除能力。高级集成为新添加的交易实现撤销undo功能支持从 CSV 文件批量导入交易实现交易搜索与过滤实现数据导出。这些进阶功能可以结合 7-bank-project/4-state-management/README.md 末尾的「Copilot Agent 挑战」带 undo/redo 的完整状态历史系统一起练习——状态历史数组配合不可变更新模式正是实现撤销/重做的基础。结语通过本作业你在一个真实的银行应用中完成了一条完整的功能开发链路入口按钮 → 模态对话框 → 无障碍表单 → API 对接 → 集中式状态更新 → 即时 UI 刷新。其核心方法论——不可变状态、单一更新入口、自动持久化、刷新保持新鲜度——正是 Redux、Vuex、Zustand 等主流状态管理库的底层思想。仓库中的完整实现7-bank-project/solution/app.js、7-bank-project/solution/index.html、7-bank-project/solution/styles.css与 API 服务7-bank-project/api/server.js可作为你动手实现时的对照参考但强烈建议先独立完成再对照源码查漏补缺。【免费下载链接】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),仅供参考
返回列表