Web-Dev-For-Beginners 银行应用前端解决方案:纯 HTML5/CSS/JS 构建 SPA 的完整实践
Web-Dev-For-Beginners 银行应用前端解决方案纯 HTML5/CSS/JS 构建 SPA 的完整实践【免费下载链接】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 个实战项目Build a Bank的官方参考实现——7-bank-project/solution这套方案完全使用纯 HTML5、CSS 与原生 JavaScript零框架、零依赖实现了一个名为 Squirrel Banking 的单页银行记账应用覆盖用户注册/登录、余额展示、交易增删与状态持久化等完整业务流程。读完本文你将掌握如何用template元素搭建 SPA 路由视图、用 localStorage 管理并持久化应用状态、用Intl.NumberFormat实现本地化货币格式化以及如何将一个纯前端应用与本地 Express API 服务器无缝对接运行。一、项目背景这门课在教你什么在 7-bank-project/README.md 中这一单元的目标是构建一个虚构银行应用并通过四个子课程逐步传授HTML Templates and Routes in a Web App如何用 HTML 模板组织页面、实现路由Build a Login and Registration Form构建登录与注册表单Methods of Fetching and Using Data获取与使用数据的方法Concepts of State Management状态管理核心概念。而solution目录正是这套课程全部练习完成后的参考实现由课程作者 Yohan Lasorsa 编写它把前端的路由、表单、数据、状态四大主题统一落到一个可直接运行的应用里。作为对照配套的后端 7-bank-project/api/README.md 是预先构建好的 Express API 服务不属于练习范围。二、环境准备让应用跑起来参考实现的 README 明确了运行前提与完整步骤。在启动前端之前有一个关键依赖后端 API 服务器必须先运行起来。2.1 第一步启动 API 服务器API 位于 7-bank-project/api 目录是基于 Node.js Express 构建的。按照 7-bank-project/api/README.md 的说明# 1. 进入 api 目录 cd 7-bank-project/api # 2. 安装依赖body-parser、cors、express npm install # 3. 启动服务 npm start服务默认监听5000 端口。有两个必须提前知道的要点数据仅保存在内存中README 明确指出 all entries are stored in-memory and are not persisted即一旦服务器停止所有账户与交易数据全部丢失这与课程学习前端的定位一致——把持久化责任交给浏览器端solution 中则由 localStorage 承担见后文。启动后不要关闭该终端它需要与前端服务器3000 端口同时运行。2.2 第二步启动前端静态服务器前端是纯静态文件理论上任何 Web 服务器都能承载但课程环境默认已安装 Node.js因为要跑 API因此最省事的方案是使用lite-server# 1. 用 Git 克隆本仓库 git clone https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners # 2. 打开终端进入 solution 目录 cd 7-bank-project/solution # 3. 启动开发服务器监听 3000 端口 npx lite-server .然后打开浏览器访问http://localhost:3000即可使用应用。说明npx lite-server .以当前目录为站点根目录启动静态服务器它由 lite-server 包临时下载执行无需全局安装。任何其他静态服务器如python -m http.server、VS Code Live Server 等同样可用只需保证能正确伺服index.html及其引用的资源。三、项目结构五个文件构建完整应用solution目录的文件组成非常精简体现了零框架的克制7-bank-project/solution/ ├── README.md # 运行说明本文所依据的文档 ├── index.html # 唯一 HTML 入口内含全部页面模板 ├── app.js # 全部应用逻辑路由、状态、数据、格式化 ├── styles.css # 设计令牌 明暗主题 组件样式 └── logo.svg # 品牌 Logo其中 index.html 承担了单页应用挂载点 页面模板仓库的双重角色而 app.js 则是整个应用的大脑。下面逐层拆解源码。四、源码拆解一用template实现 SPA 路由4.1 页面模板的组织方式在 index.html 中页面主体只有一个空挂载点div idapp aria-livepoliteLoading…/div所有页面都不是独立的 HTML 文件而是以template元素内联在同一个文档中template idlogin登录 注册双栏页面用户名、货币、描述、初始余额四个字段template iddashboard账户面板余额摘要、交易表格、新增交易弹窗template idtransaction交易表格的单行模板三个单元格日期、对象、金额。template元素的关键特性是其内容不会渲染、不会触发资源加载只有被 JS 克隆后插入 DOM 才生效——这正是零框架 SPA 最常见的模板即组件做法。4.2 路由表与切换逻辑app.js 定义了一张极简路由表const routes { /dashboard: { title: My Account, templateId: dashboard, init: refresh }, /login: { title: Login, templateId: login, init: attachAuthHandlers } };updateRoute()的执行流程体现了 SPA 路由的标准范式从history.state或window.location.pathname解析当前路径查找路由表未命中时回退到/dashboard取出对应template的内容并cloneNode(true)清空#app后追加克隆节点完成页面渲染调用attachGlobalHandlers()给表单、按钮绑定事件调用路由的init钩子如 dashboard 的refresh()加载数据同步更新document.title。页面跳转通过navigate(path)完成它调用history.pushState写入历史记录并更新 URL随后触发updateRoute()function navigate(path) { history.pushState({ path }, , path); updateRoute(); }配合window.addEventListener(popstate, () updateRoute())app.js浏览器的前进/后退按钮也能正常工作。这就是课程 1-template-route 所讲内容在参考实现中的完整落地。五、源码拆解二本地状态管理与 localStorage 持久化5.1 不可变状态对象app.js 用一个被Object.freeze冻结的状态对象保存当前登录账户并保证每次更新都生成全新对象而非原地修改let state Object.freeze({ account: null }); function updateState(property, newData) { state Object.freeze({ ...state, [property]: newData }); // 只持久化当前活动账户 localStorage.setItem(storageKey, JSON.stringify(state.account)); }这种只读状态 全量替换的模式正是课程 4-state-management 中状态管理思想的前端原生实现。5.2 存储键与 Schema 迁移应用使用了三个 localStorage 键app.js键用途savedAccount当前登录账户会话恢复用accounts全部账户数据的 JSON 数组schemaVersion数据结构的 schema 版本号其中migrateSchema()app.js提供了数据迁移脚手架当本地版本号低于当前schemaVersion当前为 1时会执行预留的迁移逻辑并写回新版本号。注释中明确预留了if (v 1) { /* future migrations */ }的扩展位——这是前端本地存储演进的标准做法。5.3 跨标签页同步值得一提的细节是storage事件监听app.js当其他标签页修改了accounts或savedAccount时当前标签页会自动触发refresh()重新拉取数据实现多标签页间的数据一致性window.addEventListener(storage, (e) { if (e.key accountsKey || e.key storageKey) { if (state.account?.user) refresh().catch(() {}); } });六、源码拆解三国际化货币与日期格式化应用在展示金额时并没有简单拼字符串而是充分利用了浏览器内置的IntlAPIapp.jsuserLocale navigator.language || en-IN读取用户浏览器语言默认回退为印度英语isIsoCurrency(code)校验三位 ISO 货币代码优先用Intl.supportedValuesOf(currency)做真实校验不支持该 API 的旧浏览器则回退为接受三位字母代码toCurrency(amount, currency)使用Intl.NumberFormat的currency样式格式化金额并启用currencySign: accounting——负数会用括号包裹会计风格若货币不是合法 ISO 代码例如直接输入₹符号则回退为符号 本地化数字拼接formatDate(dateStr)通过Intl.DateTimeFormat的dateStyle: medium按用户本地习惯渲染日期。这一层的意义在于金额与日期的展示完全跟随用户的浏览器区域设置无需手工处理小数点、千分位和货币符号这也是课程强调用原生能力替代重复造轮子的典型体现。七、源码拆解四表单校验、交易流程与可访问性7.1 注册表单的字段与校验注册表单index.html包含四个字段字段name约束用户名user必填maxlength20货币currency必填maxlength5占位提示 INR or ₹描述description选填maxlength100初始余额balance数字stepany默认 0提交时register()app.js先用form.checkValidity()触发原生校验再通过new FormData(form)收集数据、JSON.stringify序列化后交给createAccount()。后者app.js在返回Promise前做了完整的数据规整用户名去空格、余额Number()化、货币大写化并处理了 Username required、User already exists、Malformed account data 等错误分支——这些分支错误会回填到页面的div idregisterError中配合rolealert与aria-livepolite实现无障碍错误播报。7.2 记账交易借方为负、贷方为正新增交易通过弹窗modal dialog完成index.html。弹窗语义完整roledialog、aria-modaltrue、aria-labelledby/aria-describedby齐全提示文案写明负数金额表示支出debit正数表示收入credit按 Esc 关闭。app.js 的confirmTransaction()在提交前做内联校验const amountVal Number(form.amount.value); const objectVal String(form.object.value || ).trim(); if (!Number.isFinite(amountVal)) { /* Amount must be a valid number */ } if (!objectVal) { /* Object is required */ }通过后调用createTransaction(user, jsonData)app.js该函数在伪异步setTimeout模拟网络延迟内完成校验金额为有限数字、校验备注非空、查找账户然后生成带uuid()的交易对象更新账户余额balance balance amount并追加到交易列表。7.3 仪表盘的渲染逻辑updateDashboard()app.js演示了模板克隆 文档片段的高效渲染方式交易按日期降序排序localeCompare同日期保持插入顺序每条交易克隆template idtransaction行模板金额用toCurrency格式化依据tx.amount正负为行添加debit/credit样式类全部追加到DocumentFragment后一次性写入tbody避免多次重排。此外还有一处兼容性设计setTextByAnyId()app.js允许同一语义字段在旧版/新版标记中使用不同 id如#description与#transactions-description体现参考实现对课程历史版本的兼容考虑。八、与后端 API 的对接方式有趣的是solution 中的前端并未真正调用7-bank-project/api/server.js 提供的 REST 接口而是把数据层整体替换成了 localStorage。但源码中保留了对接未来真实服务器的设计痕迹const serverUrl http://localhost:5000/api; // reserved for future server swapAPI 服务器本身的能力范围记录在 7-bank-project/api/README.md 的路由表中方法路径说明GET/api/服务器信息POST/api/accounts/创建账户如{ user, description, currency, balance }GET/api/accounts/:user获取指定账户全部数据DELETE/api/accounts/:user删除账户POST/api/accounts/:user/transactions添加交易如{ date, object, amount }DELETE/api/accounts/:user/transactions/:id删除指定交易从 server.js 的实现可以看到几个值得留意的行为db是内存对象且预置了test测试账户余额 75、三条示例交易cors中间件只放行来自http://127.x.x.x与localhost的跨域请求交易 id 由crypto.createHash(md5)对日期对象金额计算哈希生成重复提交相同内容会返回 409 冲突。也就是说solution的本地存储数据模型user/currency/description/balance/transactions与 API 的 JSON 结构保持了高度一致为将来把存储层无缝替换为真实 API 调用做好了准备。九、运行验证与注意事项9.1 验证流程先启动 APIcd 7-bank-project/api npm install npm start监听 5000再启动前端cd 7-bank-project/solution npx lite-server .监听 3000访问http://localhost:3000用任意用户名注册一个账户即可看到余额摘要与空交易表点击 Add transaction 添加一条负数金额如-20与一条正数金额如100确认余额自动更新、行样式区分debit/credit刷新页面后登录同一用户名账户与交易仍然存在——这就是 localStorage 持久化与savedAccount会话恢复init()中 app.js 的safeParse(localStorage.getItem(storageKey), null)在起作用。9.2 注意事项数据生命周期前端数据持久于浏览器 localStorageAPI 数据仅存于内存。两者都会随清除浏览器存储/重启服务器而丢失这符合课程教学定位。端口占用3000前端与 5000API需同时可用API 进程关闭会导致依赖它的数据源不可用。HTTPS 限制uuid()生成依赖crypto.randomUUID注释明确该 API Works on HTTPS and localhost在非 HTTPS 非 localhost 环境会回退到tx- Date.now()...的时间戳方案app.js。主题与无障碍styles.css 以 CSS 自定义属性定义设计令牌并通过media (prefers-color-scheme: dark)自动适配深色模式、通过prefers-reduced-motion尊重用户减少动效的偏好这些都为前端工程化实践提供了可参考的范本。十、小结这份参考实现值得借鉴的工程点回顾整套 solution它在零框架约束下依然体现了不错的工程素养这也是本文推荐读者精读源码的原因模板驱动路由用templatehistory.pushState搭建轻量 SPA理解它有助于后续理解 Vue/React 等框架的模板与路由抽象不可变状态 本地持久化Object.freeze防止意外变更localStorage 三键账户/会话/版本分离自带 schema 迁移原生 Intl 国际化货币、日期全部交给Intl.NumberFormat/Intl.DateTimeFormat天然适配用户区域可访问性内置aria-live错误提示、roledialog弹窗、键盘 Esc 关闭、noscript降级提示前后端契约一致本地数据模型与 Express API 的 JSON 结构对齐serverUrl常量预留了未来切换到真实后端的入口。对于 Web-Dev-For-Beginners 的学习者而言把 7-bank-project/solution/index.html、app.js、styles.css 三个文件对照前四节课程通读一遍等于完整复习了模板与路由、表单、数据、状态管理四个核心主题的工程化串联方式。【免费下载链接】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),仅供参考