
1. 从“手账债务”到一天跑通原型宝妈记账场景到底难在哪很多宝妈都有类似的“手账债务”宝宝第一次翻身、第一次叫妈妈、第一次吃辅食照片拍了一堆却始终躺在相册里没整理。想做成手账打开排版软件选模板、调字体、加贴纸半小时过去孩子哭了手账又变成“下次一定”。这个场景的核心痛点不是不会记录而是记录链路太长长到坚持不下去。所以这次的目标很明确用 TRAE 配合 AI做一个懒人手账小程序原型聚焦宝妈记账与成长记录场景一天内跑通增删改查。技术选型上不引入框架纯 HTML CSS JavaScript数据存 localStorage双击就能在浏览器打开。这样做的原因很实际TRAE 改起来方便所有代码在一个文件里AI 能看懂上下文部署零成本扔到任意静态服务器都能跑原型迭代快想到什么功能马上改马上看。这篇文章会交付可复制的页面骨架、localStorage 读写配置、浏览器验证步骤以及我在这个过程中踩过的坑。你不需要有前端工程经验只要会复制粘贴、会打开浏览器控制台就能跟着做出来。适合谁想给孩子做成长记录的宝妈、想练手 AI 编程的新手、想快速验证产品原型的独立开发者。2. TaoToken 前置给 TRAE 接上稳定的模型能力用 TRAE 做原型本质是让 AI 帮你写代码、改 UI、排查 Bug。但很多人卡在第一步模型调用不稳定或者不知道怎么把模型能力接进自己的开发流程。这里我用的方式是先把 TaoToken 的 API Key 配好再让 TRAE 或本地脚本调用这样无论是生成页面骨架还是后续调试都有稳定的模型入口。TaoToken 官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 地址不加 UTM 参数。你需要先注册并创建一个 API Key入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。拿到 Key 之后建议先放到环境变量里不要直接写死在代码里后面配置请求时会用到。如果你只是想先验证模型能不能正常对话可以打开模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 试一句“帮我写一个手账卡片 HTML 结构”看返回是否正常。如果你打算长期用 AI 做编码和 Agent 类任务可以了解 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合高频编码场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 控制台在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。注意API Key 只放在本地环境变量或本地配置文件里不要提交到公开仓库。原型阶段可以用 localStorage 存业务数据但 Key 不要存进 localStorage。3. 可复制配置单文件 HTML 骨架 localStorage 读写3.1 页面骨架一个文件装下所有东西先建一个 index.html结构分三块顶部导航、中间内容区、底部 Tab。内容区用 render() 函数根据 AppState 全量重绘不引入虚拟 DOM页面小直接重绘足够快。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title懒人手账原型/title style * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, PingFang SC, sans-serif; background: #f7f5ff; } #app { max-width: 420px; margin: 0 auto; min-height: 100vh; background: #fff; position: relative; } .navbar { height: 48px; display: flex; align-items: center; justify-content: center; font-weight: 600; border-bottom: 1px solid #eee; } .content { padding: 16px; padding-bottom: 72px; } .tabbar { position: fixed; bottom: 0; width: 100%; max-width: 420px; height: 56px; display: flex; background: #fff; border-top: 1px solid #eee; } .tabbar button { flex: 1; border: none; background: none; font-size: 13px; color: #888; } .tabbar button.active { color: #9D5BFF; font-weight: 600; } .card { background: #faf8ff; border-radius: 12px; padding: 12px; margin-bottom: 12px; } .card h3 { font-size: 15px; margin-bottom: 6px; } .card p { font-size: 13px; color: #666; line-height: 1.6; } .btn { background: #9D5BFF; color: #fff; border: none; border-radius: 8px; padding: 10px 16px; font-size: 14px; } .btn-danger { background: #ff6b6b; } input, textarea { width: 100%; border: 1px solid #e5e0f5; border-radius: 8px; padding: 10px; font-size: 14px; margin-bottom: 10px; } textarea { min-height: 120px; resize: vertical; } /style /head body div idapp/div script const AppState { handbooks: [], activeTab: home, editingId: null }; const STORAGE_KEY lazy_handbook_v1; function loadFromStorage() { try { const raw localStorage.getItem(STORAGE_KEY); AppState.handbooks raw ? JSON.parse(raw) : []; } catch (e) { console.error(读取 localStorage 失败, e); AppState.handbooks []; } } function saveToStorage() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(AppState.handbooks)); } catch (e) { console.error(写入 localStorage 失败, e); } } function render() { const app document.getElementById(app); app.innerHTML div classnavbar懒人手账/div div classcontent${renderContent()}/div div classtabbar button class${AppState.activeTab home ? active : } onclickswitchTab(home)首页/button button class${AppState.activeTab add ? active : } onclickswitchTab(add)新建/button /div ; } function renderContent() { if (AppState.activeTab add) return renderForm(); return renderList(); } function renderList() { if (AppState.handbooks.length 0) { return p stylecolor:#999;text-align:center;margin-top:40px;还没有手账点“新建”写一篇吧/p; } return AppState.handbooks.map(item div classcard h3${escapeHtml(item.title)}/h3 p${escapeHtml(item.content)}/p p stylemargin-top:8px;font-size:12px;color:#aaa;${item.createdAt}/p div stylemargin-top:10px;display:flex;gap:8px; button classbtn onclickeditItem(${item.id})编辑/button button classbtn btn-danger onclickdeleteItem(${item.id})删除/button /div /div ).join(); } function renderForm() { const editing AppState.handbooks.find(i i.id AppState.editingId); return input idtitleInput placeholder标题比如宝宝第一次翻身 value${editing ? escapeHtml(editing.title) : } / textarea idcontentInput placeholder写点什么...${editing ? escapeHtml(editing.content) : }/textarea button classbtn onclicksaveItem()保存/button ; } function switchTab(tab) { AppState.activeTab tab; if (tab add) AppState.editingId null; render(); } function saveItem() { const title document.getElementById(titleInput).value.trim(); const content document.getElementById(contentInput).value.trim(); if (!title) { alert(标题不能为空); return; } if (AppState.editingId) { const item AppState.handbooks.find(i i.id AppState.editingId); item.title title; item.content content; } else { AppState.handbooks.unshift({ id: Date.now().toString(), title, content, createdAt: new Date().toLocaleString() }); } saveToStorage(); AppState.activeTab home; AppState.editingId null; render(); } function editItem(id) { AppState.editingId id; AppState.activeTab add; render(); } function deleteItem(id) { if (!confirm(确定删除这篇手账吗)) return; AppState.handbooks AppState.handbooks.filter(i i.id ! id); saveToStorage(); render(); } function escapeHtml(str) { return String(str).replace(/[]/g, s ({ : amp;, : lt;, : gt;, : quot;, : #39; }[s])); } loadFromStorage(); render(); /script /body /html3.2 localStorage 读写配置要点上面代码里有两个关键函数loadFromStorage() 和 saveToStorage()。读取时用 try/catch 包住防止 JSON 解析失败导致白屏写入时同样包住防止隐私模式或存储超限报错。存储键用 lazy_handbook_v1带版本号以后结构变了可以平滑迁移。数据结构是一个数组每条记录包含 id、title、content、createdAt。id 用 Date.now().toString() 生成原型阶段够用。如果你要加图片可以把图片转成 base64 存进去但要注意 localStorage 一般只有 5MB 左右图片多了会超原型阶段建议先只存文字图片用 URL 或后续接云存储。提示每次增删改后都要调用 saveToStorage()否则刷新页面数据就没了。这是新手最容易漏的一步。4. 验证请求与成功结果浏览器里跑通增删改查4.1 打开页面并检查控制台把上面的 index.html 保存到本地双击用 Chrome 或 Edge 打开。按 F12 打开控制台切到 Console 面板。如果代码没问题你应该看到页面正常渲染控制台没有红色报错。如果出现红色报错先看报错行号90% 的问题都在报错信息里。4.2 新增一条手账点击底部“新建”输入标题“宝宝第一次翻身”正文“今天宝宝自己翻过来了全家都激动坏了”点保存。页面应该自动跳回首页并显示这条卡片。此时打开控制台输入JSON.parse(localStorage.getItem(lazy_handbook_v1))你应该能看到一个数组里面有一条记录title 和 content 都是你刚输入的内容。这说明写入成功。4.3 编辑与删除验证点卡片上的“编辑”修改标题为“宝宝第一次翻身已编辑”保存后回首页标题应该更新。再点“删除”确认后卡片消失。再次在控制台执行上面的读取命令数组应该变空或少了对应记录。刷新页面数据依然保持删除后的状态说明 localStorage 持久化生效。4.4 用 TaoToken 模型对话辅助排查如果你在改代码时遇到不确定的报错可以把报错信息复制到模型对话页面 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 让模型帮你解释原因。比如“Uncaught TypeError: Cannot read properties of null”通常是 DOM 元素没找到检查 id 是否拼错。接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里有请求示例你可以照着写一个本地脚本批量生成测试数据。5. 本篇常见错排查手账原型最容易踩的坑5.1 返回按钮或编辑按钮“神秘消失”这个坑我在做的时候也遇到了。症状是点击某些操作后页面顶部的按钮没了。排查过程第一次以为是 CSS 的 z-index 问题调了半天没用第二次以为是 DOM 结构问题查了半天元素第三次打开控制台一看红了。原来 renderDetail() 里有一行$(#editBtn).onclick ...但 HTML 里根本没有 ideditBtn 这个按钮JS 报错后后面的 renderNavBar() 就没执行。修复方法保证 JS 里引用的 DOM id 在 HTML 里都存在或者用可选链和判空保护。页面出问题先看控制台这是最快的路径。5.2 textarea 高度“不听话”你把 CSS 里的 min-height 改成 320px页面上的文本框还是那么矮。原因通常是 HTML 里写了内联样式stylemin-height:100px;内联样式优先级比类选择器高CSS 被覆盖了。修复把内联样式删掉统一用 CSS 控制。CSS 优先级是个好东西但内联样式少用为妙。5.3 同一天多条记录导致图标叠罗汉如果你后面加了日历视图同一天有多篇手账时emoji 会叠在一起甚至溢出格子。修复思路用new Set()去重相同的 emoji 只显示一个最多显示 1 个多了显示N给格子加overflow: hidden。列表展示永远要考虑“多了怎么办”。5.4 localStorage 写入失败但页面没提示隐私模式下 localStorage 可能不可用或者存储超限。上面的代码里 saveToStorage() 用了 try/catch但只 console.error用户看不到。原型阶段可以加一个简单的 alert 提示或者降级到内存变量保证页面不崩。如果你要长期用 AI 做编码任务Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 更适合高频调试场景。5.5 刷新后数据丢失最常见的原因是保存时没调用 saveToStorage()或者读取时 STORAGE_KEY 写错了。检查两处写入和读取用的键名是否完全一致每次增删改后是否都调用了保存函数。可以在控制台执行localStorage.getItem(lazy_handbook_v1)看是否有值。6. 继续往下做从原型到可用工具的下一步一天跑通增删改查原型之后你可以继续加功能。比如加一个风格选择器日常、亲子、美食、旅行、探索、工作六种风格每种风格对应一组 emoji 和模板文案点一下就自动填入正文和标签。再比如加一个简单的日历视图用 getSmartEmoji() 根据记录风格智能匹配图标有自定义 emoji 就用自定义的没有就按风格随机选一个生活需要一点小惊喜。数据层可以继续用 localStorage但如果要加图片建议先只存图片 URL或者把图片压缩到很小再转 base64。云备份功能原型阶段可以模拟真实产品里再接真实 SDK。主题切换用 CSS 变量加一个 applyTheme() 函数就能实现四套配色换着用心情不一样。如果你在接入模型或调试 API 时遇到问题优先看 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 和接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 大部分配置问题都能找到答案。控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 可以查看调用记录方便排查请求是否成功。做这个原型的初衷很简单让记录这件事变得更容易一点。手机里存着成千上万张照片却很少有时间回头看。如果有一个工具能让你花 10 秒钟把那一刻的心情记下来多年后翻到还能想起当时的温暖那就够了。生活值得被记录哪怕是用最懒的方式。