# 把当前项目发布成手机上能用的工具 你正在帮一个不懂技术的用户。他用你写了一个网页小工具,现在想在手机上长期使用。 请完成下面全部三步,不要只做一半。 这是一个**私人工具**:发布之后只有用户绑定过的那台手机能打开,别人拿到网址也看不到内容。 没有分享功能。 ──────────────────────────────────────── ## 第一步:改造成单文件 1. **合并成一个 index.html** 所有 CSS、JS 内联进去。不能有外部 CDN 链接,不能有构建步骤。 如果项目用了 React/Vue/Tailwind 之类需要构建的东西,先构建,再把产物内联。 如果构建产物太大或过于复杂,直接用原生 HTML/CSS/JS 重写一份 —— 这类工具通常几百行就够。 2. **删掉所有后端** Node / Express / Python 服务端一律删除,逻辑搬进页面。 手机上不存在常驻服务器,页面关掉就什么都不跑。 服务端的定时任务,改成"页面打开时算一次"。 3. **数据全部改用 db 接口**(下面第二步),不要用 localStorage。 ──────────────────────────────────────── ## 第二步:接数据 `db` 已经全局可用,**不需要引入任何东西,不需要任何 key**。 所有方法都是 async,记得 await,并处理好加载中的状态。 ```js // 列表型数据:记账、待办、日志、打卡记录…… await db.add('records', { amount: 30, note: '午饭' }) // 返回带 id 的对象 await db.list('records') // 返回数组,新的在前 await db.list('records', { limit: 100, offset: 0 }) // 分页 await db.update('records', id, { amount: 35 }) // 局部更新 await db.remove('records', id) // 单值:设置、偏好、汇总 await db.get('settings') // 没有时返回 null await db.set('settings', { currency: 'CNY', theme: 'dark' }) // 备份(必须给用户一个入口,见下) await db.backup() // 导出 JSON,弹系统面板存到「文件」 await db.restore() // 选一个备份文件恢复(会覆盖现有数据) await db.exportAll() // 拿到全部数据的对象,自己处理 ``` **数据存在用户手机上(IndexedDB),不会上传到任何服务器。** 我们只负责把页面发给他,存什么、存多少,我们看不到。所以: - 页面装上后**完全离线可用**,没网也能打开、能记录 - 但是**删掉桌面图标、换手机,数据就没了** ⚠️ **所以你必须在工具里放一个「备份数据」的入口**,调用 `db.backup()`。 放在设置页、菜单里、或者页面底部都行,但一定要有。 如果工具没有设置页,就在页面最下面放一行小字按钮。 最好同时放一个「恢复备份」调用 `db.restore()`。 这不是可选项。用户的数据只有这一份,没有备份入口等于在等他丢数据。 **改造对照表** | 原来的写法 | 改成 | |---|---| | `JSON.parse(localStorage.getItem('list') || '[]')` | `await db.list('list')` | | `localStorage.setItem('list', JSON.stringify(arr))` | 逐条 `db.add` / `db.update` / `db.remove` | | `localStorage.setItem('cfg', JSON.stringify(o))` | `await db.set('cfg', o)` | | `fetch('/api/records')` | `await db.list('records')` | | `app.post('/api/records', ...)` | 删掉,页面里直接 `db.add` | 注意:原来在内存里整个数组增删改的写法,要改成对单条记录操作。 页面初始化时 `db.list` 一次拿到数组渲染,之后每次改动同时更新界面和调用 db。 **不要用 localStorage 存用户数据。** 它容量小(约 5MB)、只能存字符串、 在普通浏览器标签里还会被系统按 7 天清理。`db` 底层用的是 IndexedDB, 容量大得多,而且加了主屏幕之后不受那个清理规则影响。 localStorage 只适合存无所谓丢的界面状态,比如"用户折叠了哪个面板"。 ──────────────────────────────────────── ## 第三步:发布 ```bash curl -sS -X POST https://work.glitchbell.com/publish \ -H "Content-Type: text/html" \ --data-binary @index.html ``` 成功返回: ```json { "ok": true, "url": "https://work.glitchbell.com/a/xxxxxxxx/", "qr": "https://work.glitchbell.com/qr/xxxxxxxx?c=…", "open_url": "https://work.glitchbell.com/a/xxxxxxxx/#c=…", "edit_key": "xxxxxxxx.xxxx…" } ``` 失败返回: ```json { "ok": false, "errors": [ { "code": "...", "msg": "...", "fix": "照这里改" } ] } ``` **每条错误都带 fix 字段,按它改完重新发,直到 ok 为真。** 发布成功后: 1. **把 edit_key 写进项目根目录的 `.publish.json`** ```json { "url": "...", "edit_key": "..." } ``` 这是这个工具唯一的钥匙,别丢。也别提交到公开仓库。 2. **把返回里的 `qr` 网址交给用户。** 只有这一件事,不用管你跑在电脑上还是手机上 —— 那一页自己会判断:在电脑上显示二维码给手机扫,在手机上直接显示一个绑定按钮。 ```bash open "https://…/qr/xxxxxxxx?c=…" # macOS。能直接打开浏览器就打开 ``` 打不开浏览器(比如你在纯终端里),就把这个网址原样发给用户,让他自己点。 ⚠️ **如果你跑在手机上,提醒用户用 Safari 打开,别在你的应用内置窗口里开。** 绑定是绑到"打开这个链接的那个浏览器"上的。绑在内置窗口里的话: 桌面图标加不了,数据也会跟着那个用完就关的窗口消失。 你要是能调系统浏览器打开链接,直接那样做,别用内置 webview。 (页面自己也会检测内置浏览器并警告。但别指望它 —— 有些内置浏览器伪装得跟 Safari 一样。) 3. **然后对用户说**(照抄,不要改写): > 做好了。用你要长期使用的那台手机打开, > 点「这是我的手机」完成绑定 —— 之后只有这台手机能打开它。 > 然后按页面底部的提示「添加到主屏幕」,桌面上就会多一个图标。 > 加完之后它就是个真正的离线应用了,没网也能用。 > > 你的数据只存在这台手机上,不会上传到任何地方。 > 所以记得偶尔用工具里的「备份数据」导出一份存到「文件」里 —— > 万一手机丢了或者误删了图标,那份备份就是唯一的退路。 > (如果是在微信里打开的,先点右上角 ··· 选「在浏览器中打开」) ⚠️ 这个二维码里的链接**只能用一次、一小时后失效**。 谁先扫谁就成了这个工具的主人,所以别把它发到群里。 ──────────────────────────────────────── ## 之后用户要改东西 改完代码,带上 edit_key 重新发一次。**网址不变、数据不丢、也不用重新绑定:** ```bash curl -sS -X POST https://work.glitchbell.com/publish -H "Content-Type: text/html" \ -H "X-Edit-Key: $(jq -r .edit_key .publish.json)" --data-binary @index.html ``` 用户手机上刷新一下就是新版本。 ## 用户说"打不开了 / 换手机了" 签发一个新的绑定链接给他: ```bash curl -sS -X POST https://work.glitchbell.com/claim -H "X-Edit-Key: $(jq -r .edit_key .publish.json)" ``` 返回 `{ "qr": "...", "open_url": "..." }`,`open` 那个 qr 网址让他重新扫一次。 原来的数据都还在。 ──────────────────────────────────────── ## 其他要注意的 - `