我用AI为自己的家人制作了一个日本旅游行程规划应用

发布日期
2026年7月11日
更新日期
2026年9月16日
作者
Jacob Lloyd —— 项目完成后,在 AI 协助下撰写
阅读时长
约 10 分钟阅读

简单来说: 我专门为家人的日本之旅制作了一个小网站——上面有每日行程安排、天气信息以及地图。即便没有信号,该网站也能在手机上正常使用。网站上还有一个小型AI助手按钮,可以解答与旅行相关的疑问并调整行程计划。这篇文章则详细讲述了我在大量AI工具的协助下如何构建这个网站:哪些环节顺利完成了,哪些出现了问题,以及我是如何将一个脆弱的设计工具导出文件改造成既安全可编辑、又能安全发布到网络上的东西。您可以下载经过清理的示例文件,然后在任意浏览器中打开它。

我们全家去日本旅行,我希望每个人都能随时查看行程安排——每天的计划、随手可点的地图、天气信息,还有那些在时差困扰下容易忘掉的小细节。于是我专门为我们开发了一个小型网页应用:一个需要 PIN 码才能进入的私密行程规划器,内含逐日日程、实时天气、离线功能;当然,以我的风格,我还加入了内置的AI 助手,它能回答关于行程的各种问题,甚至能进行一些小幅度的修改。

下面是我真实的开发经历:哪些部分顺利运行了,哪些出现了意料之外的故障,以及那个原本脆弱不堪的设计工具导出文件是如何最终变成我可以放心发布到公网上的成品的。文末还提供了一个经过清理的样本文件,你可以下载后用任意浏览器打开。

简而言之

  • 这是什么:一款以移动端为主的行程规划网页应用——包含每日安排、各个站点信息、实时天气、货币与时区换算工具,支持离线使用与安装;还设有 AI 助手功能。
  • 核心思路:原本看似完美却极易损坏的设计工具导出文件,在让整个日程完全由一个小型数据文件驱动后,便变得安全可编辑了;只需运行一次构建步骤即可重新生成整个应用。
  • 访问限制:客户端设置的 PIN 码只是“摆设”——真正的防护机制是服务器端会话控制以及带频率限制的锁定策略。
  • AI 功能:采用本地自托管模型,且使用上受到严格约束——仅能回答相关问题、进行有限且受控的修改操作。
  • 反复验证的经验教训:确认改动是否生效的唯一方式就是在真实浏览器中渲染应用并亲自查看——绝不可轻信“文件理应没问题”这种假设。
行程应用的概览界面:标题为‘2026家庭日本之旅’,显示‘日本6日游’;还有实时天气板块,提示当天的穿衣建议;下方是六个可点击的每日卡片——东京塔探险、镰仓寺庙与海滩、休息日、宝可梦终极日、新宿亲子乐园以及返程日——每张卡片都配有专属天气预报与打包建议
概览界面:整个行程一览无余——标题栏、实时天气及穿衣提示,还有每日对应的卡片与各自预报。(截图中的地点名称均为虚构化处理。)

最初的起点:一个看似完美的“黑盒”

我先用一款 AI 设计工具设计了界面,随后将其导出。最终得到的是一个完全自包含的 HTML 文件:整个 React 应用,所有字体与图片均被内嵌其中——整个文件大小竟达 14 MB。它在双击后立刻运行,视觉效果也相当出色。

但它同样是个“黑盒”。整个应用经过压缩后全挤在一个巨大文本块里,日程信息也被埋藏其中。若想手动修改行程,就必须对机器生成的代码动手术。一次两次倒还行;可一旦计划有变——而家庭旅行的安排几乎每天都会调整——这种操作就变得极其麻烦。

因此,我做出的第一个关键决策就是:不再直接编辑那个导出的文件。

让后续一切成为可能的关键思路

我没有再去碰那个 14 MB 的包,而是将整个行程信息全部挪进一个小型、易读的数据文件中:即 content.json,里面记录了每日安排、各站点详情、天气备注、对应照片及费用信息。之后只需运行一次简短的构建步骤,就能依据该数据文件重新生成整个应用。

这一举措彻底改变了整个项目的性质。如今,“新增一个站点”或“调整时间”,只需在数据文件中改动两行代码即可,无论是人工还是 AI 助手都能安全完成;随后运行一次构建流程即可生效。那个原本脆弱不堪的 14 MB 应用文件从此再也不会被人手动触碰。后来我添加的各类组件——实时天气模块、双向本地时间 ↔ 东京时间时钟、双向货币换算器,以及带有照片与停车提示的可展开式站点卡片——全都是基于同一套数据驱动机制生成的。

行程应用中某天的详细页面:‘宝可梦终极日’,配有火车图标、天气预报与穿衣建议;还有交通指南板块,列出了具体列车线路;另有基地营地说明;下方则是多个可折叠的站点卡片——东京宝可梦中心、皮卡丘甜品咖啡馆、和幸炸猪排店以及山下本木乌冬面馆
某一天的详细内容——列车信息、基地营地说明,以及每个站点的折叠式卡片。所有这些信息均源自 content.json 中的条目;完全不存在手动编辑的 HTML 代码。

一个差点毁掉所有组件的陷阱:应用启动时会自行重建

下面要说的这个坑让我耗费了大量时间,特此记录下来以免他人重蹈覆辙。

那个导出的应用在启动时便会重新生成整个页面。如果你在静态 HTML 中手动插入了自定义内容——比如时钟、横幅之类的组件——它们会在应用完成挂载的瞬间被彻底抹除。原本的元素 ID 会消失,getElementById 从此永远返回 null,你的组件也就悄无声息地“消失”了。更糟糕的是:若通过搜索保存下来的 HTML 文件来验证,会发现那些代码确实还在,所以看起来一切正常;可它们根本无法显示在屏幕上。

两条规则彻底解决了这个问题:

  1. 将组件插入到应用自身的模板中,而非页面层面。 把你的组件放在应用构建 UI 的地方,这样它就会作为应用的一部分被渲染出来,从而顺利度过启动阶段。
  2. 利用 document 上的事件委托来处理交互行为。 由于切换标签页时应用会重新挂载整个区域,原本绑定在单个元素上的监听器随之失效;而通过 document 进行委托监听的方式(即检查点击对象),就能在各种重建操作中始终发挥作用。

由此引申出一条我现在奉为圭臬的测试原则:绝不可仅通过搜索文件内容或查看 HTTP 状态码来验证网页 UI。 务必在真实(无头)浏览器中完成渲染,待应用完全启动后再仔细检查输出结果。唯有如此才能发现此类隐患——我正是借此方式发现了多处问题,比如某个货币换算器只有在被挪进模板之后才正常显示。

“自包含”其实是个假象

那个导出文件号称是“单一自包含文件”,但实际上并非如此。它在运行时悄悄从公共 CDN 上加载 UI 框架,且相关 URL 片段均硬编码在代码之中——因此单纯搜索 CDN 地址根本找不到任何痕迹;文件看似具备离线使用能力,实则不然。在实施了严格安全策略的服务器上,这种隐式请求被直接拦截,整个应用便无法启动;而在没有此类限制的本地电脑上它却能运行,问题便一直被掩盖,直到真正部署到服务器上才暴露出来。

最终解决方案是:自行托管该框架(将其与应用打包在一起,从而不再需要外部请求);对于可供下载的样本文件,则直接将其代码内嵌进去,确保整个应用真真正正只需一个文件即可脱离网络环境运行。从本段内容中能得到的实用启示就是:“在我的电脑上能跑”和“能在真实服务器安全策略下正常运行”是完全不同的两回事——务必测试后者。

访问限制:表面功夫与真正的防护

最初的版本仅通过页面 JavaScript 中的 PIN 码来限制访问。这其实只是安全伪装:整个行程信息就明文存在于页面源码中,只要能打开开发者工具就能轻松读取,完全无需输入密码。这种做法或许能瞒过普通人的随意浏览,但对于真正需要保护的内容而言毫无作用。

真正的版本将验证环节移至服务器端:用户输入 PIN 码后,服务器校验无误便启动会话,此时才会向客户端发送行程数据。此外还设有基于 IP 地址的渐进式锁定机制——几次错误输入后,该 IP 地址就会被暂时封锁,且每次封锁时长还会逐步延长,如此便无法利用机器暴力尝试所有六位数组合。

我也要坦诚说明其局限性:仅具备频率限制的六位数 PIN 码对于家庭旅行这种低风险场景而言尚可接受,但远达不到金融级安全标准。若你需要保护的是敏感信息,务必采用较长的口令配合正规的登录机制;安全措施必须与所保护内容的重要程度相匹配。

AI 助手:受到严格限制的存在

有趣的是,有个小助手——我给它取名叫 Yoshi——它运行在独立的标签页中。在私人家庭版应用中,它可以回答关于行程的问题(比如“水族馆那天的安排是什么?”、“我们需要带些什么?”),还能对日程表进行少量且受控的编辑。它的作用就是修改同一个安全的数据文件,绝不会触碰易出错的 App 代码;同时它还被严格限制:可以添加或调整内容,但无法删除任何日期,航班与预订信息也被锁定,因此任何聊天指令都无法改动它们。

关键在于,Yoshi 运行的是本地自托管的小型 AI 模型——即部署在我自己硬件上的小型 AI,而非付费的云端 API。该模型通过一个受控的中继器来访问。这本身是个大话题(如何安全地将本地运行的聊天机器人对外公开,以及它在普通家用硬件上运行有多慢),因此我专门写了篇文章说明:
如何在自己的硬件上部署面向公众的聊天机器人。

关于下方可下载示例的说明: 示例中确实有助手标签页,方便大家了解其位置,但该功能在示例中被刻意关闭——并未连接任何 AI 模型,因此不会响应指令。若要在自己的版本中使用它,需将其指向你本地的 AI 模型。这样做是有意为之:我不会发布一个能悄悄调用运行中的 AI 的公共文件。

离线可用、可安装且不受网络环境限制

最后一些优化则是针对旅行时的实际使用场景:酒店 Wi-Fi 不稳定、飞机上无网络、漫游信号时有时无。于是该应用被设计成了可安装的 PWA——将其添加到主屏幕后,它就像真正的 App 一样运行;同时它还支持离线使用,只要事先加载过行程信息,即便无网络也能完整查看。我特别注意的一条规则是:只有在成功解锁后才会缓存行程数据,这样就能防止离线缓存绕过验证机制。在线状态下它会始终重新验证;离线时则信任手机上已有的副本。对于家庭旅行计划而言,这种权衡恰到好处。

(Service Worker 也有其复杂性——旧版缓存内容可能在你更新了页面后仍持续显示过时信息。因此我学会了务必发布更新后的 Worker,绝不能直接删除旧文件,这样才能确保浏览器真正采纳变更。)

像真实场景一样测试——因为这就是真实场景

由于全家人旅行时都在使用这个应用,我把每一次修改都视作一次正式部署:构建应用、在无头浏览器中渲染页面、操作实际的标签页与按钮,确认无误后才发布。旅途中我也通过这种方式推送了一些小型实时修复:比如突出显示当前行程日的标记(以东京时间为准而非用户手机时间)、天气信息的更新以及一些界面小瑕疵——每次都先在真实浏览器中验证。这再次印证了之前提到的教训:文件存在并不等于功能正常。

试试吧——下载示例文件

下方的下载文件是该应用的经过清理的公开示例。所有名称、日期、地址及细节均为虚构内容,整个行程也是虚构的东京七日游计划。只需在任何浏览器中打开 index.html 即可——无需安装、无需服务器、也无需注册账号。该应用支持离线使用;只有实时天气模块与地图链接需要联网。

有两个需坦诚说明的限制:其一,整个应用仅为一个约14 MB的文件,因为所有字体与图片均被内嵌其中,这样才能实现零依赖运行——但这会导致首次打开时速度较慢;正式发布版则会对这些资源进行拆分与压缩。其二,正如前文所述,示例中的 AI 助手功能处于关闭状态——点击该标签页后会看到相关提示信息。

自己动手构建吧

开头提到的“自行实现”并非空口号——本文内容正是为了直接提供给 AI 助手参考使用;就如同我在 如何让 LLM 将任意项目适配到你的系统 一文中所描述的那样。以下是简要的实现步骤:

  1. 将行程计划存入数据文件。 日期、站点、备注与天气信息都应集中存储于一个易读文件中,作为唯一权威数据源。
  2. 基于该数据生成应用。 通过确定的构建流程将数据转化为用户界面,这样所有编辑都只会作用于原始数据,而不会影响生成的代码。
  3. 若从设计工具导出内容,需遵循其启动逻辑。 将相关内容注入应用的模板中,使用委托事件机制,同时自行托管所有需要加载的外部资源。
  4. 验证机制应由服务器端控制而非前端页面。 会话机制与速率限制的锁定措施远比仅靠 JavaScript 实现的 PIN 码验证可靠。
  5. 若应用用于旅行场景,请采用离线优先策略——仅在真正解锁后才缓存数据。
  6. 启动后务必在浏览器中验证效果。 不能只检查“文件是否存在”,而是要查看屏幕上的实际呈现效果。

常见陷阱汇总

  • 应用启动时需重新构建 DOM——任何手动注入页面的内容都会被清除;应将代码注入应用的模板中,并在 document 上设置委托事件。
  • “自包含”未必真的如此——某些导出内容会在运行时动态加载框架;此时需自行托管相关资源,并模拟真实服务器的安全环境进行测试,而不可仅在本机测试。
  • 前端 PIN 码验证形同虚设——实际内容仍留在页面上;真正的验证机制应由服务器端控制。
  • 6位数的 PIN 码仅具备速率限制功能,并非坚不可破——对低风险场景尚可接受,但对涉及敏感信息的场合则不够安全。
  • Service Worker 的缓存行为过于激进——发布修复补丁时必须更新 Worker,单纯删除旧文件无法使其失效。
  • “文件存在”≠“功能正常”——每次修改后都必须实际渲染并查看屏幕效果。

相关文章:如何在自己的硬件上部署面向公众的聊天机器人、如何让 LLM 将任意项目适配到你的系统、在迷你 PC 上搭建小型 AI 代理团队、借助 Claude Code 将 WordPress 转为静态网站。

下载

仅限个人使用免费。如果它替你省下了一下午的时间,欢迎点旁边的咖啡按钮支持一下。


← 更多AI 与本地 LLM