在这里插入图片描述
国庆节假日出行总是被堵在路上,历经各种各样的事,高速服务区休息打盹地一小会,我思考了一个非常硬核的问题,于是便有了这篇文章。

摘要

高速免费只看驶离出口的时刻,与上高速时间无关。临近截止赶不到目的地时,"就近下去等过 0 时再上"能省下已行驶里程的费用。本文用纯逻辑规则引擎 + 25 项断言算清这道题,跑起来抓到 3 个真实 bug,再交给华为云码道生成鸿蒙元服务版,全程零权限、纯离线,并刻意拒绝"加速赶路"选项。

目录

10 月 7 号晚上九点多,我在家族群里看到有人问:“现在从 XX 下高速还来得及吗?”

底下七嘴八舌,说什么的都有。我去查了规则,发现真正的答案比所有人想的都更不直观:

**高速免不免费,只看一个时刻——你驶离出口收费车道的时间。**跟你几点上的高速没有任何关系。

由此推出三条,我保证大部分人只知道第一条:

  • **提前上高速是对的。**9 月 30 日晚上驶入,只要 10 月 1 日 0 时之后才下,全程免费。
  • 压哨下高速是最亏的。窗口内上了高速、10 月 7 日 24 时之后才下,收的是全程通行费,不是超出的那一段。
  • **正解是"中途先下去"。**临近截止还到不了目的地,就近驶出收费站,等过了 0 时再重新驶入——前面那段因为驶离时刻在窗口内而全免,你只需要付最后一段。

还有两个每年都有人中招的坑:只有春节、清明、劳动节、国庆四个节假日免费,中秋、元旦、端午照常收费(国发〔2012〕37 号就列了那四个);以及人工道进就得人工道出、ETC 进就得 ETC 出,错配可能导致系统读不到入口信息、抬杆失败。

规则是清楚的。可是没有人会在高速上、在疲惫中、在导航催你的时候,冷静地算"我剩余 260 公里、还要开 195 分钟、已经开了 800 公里、现在 22:00,我到底该不该在这个出口下去"。
在这里插入图片描述
**这道题需要被算,而且需要被很快地算出来。**所以我做了个工具。

使用华为云码道对话开发过程如下,非常轻松:
在这里插入图片描述
对话过程中,华为云码道AI不仅深度思考,还非常贴心地提供了选项让我选择,以便明确需求,我觉得这点非常好!
在这里插入图片描述
选择数据输入方式:
在这里插入图片描述
视觉风格:
在这里插入图片描述
代码验证:
在这里插入图片描述
完成功能开发如下:
在这里插入图片描述

二、为什么是元服务,而不是一个 App

这个需求的特征对应的鸿蒙能力
一年只用四次,绝不肯为它装 App元服务免安装,负一屏/扫码直达
决定发生在没解锁手机的那一刻服务卡片常驻桌面
隧道里、山区没信号也要能用纯离线,零网络请求
输入只是里程和时刻,不需要位置不申请任何权限

最后一条我特意做成了硬约束:全工程 requestPermissions 必须是空的。一个只需要你手动填三个数字的工具,没有任何理由去读你的位置。

三、先写引擎,再写界面

我没有一上来就画界面,而是先把规则写成一份纯逻辑的规则引擎:不碰 DOM、不碰系统 API、只有函数和数字。这样做的唯一理由是——它能被测试。

引擎对外只暴露一个函数:

TollFree.decide({
  nowISO, windowEndISO,   // 现在时刻 / 免费截止时刻
  etaMin,                 // 导航显示还要开多久
  remainingKm,            // 剩余里程
  drivenKm,               // 本次已行驶里程(决定"下去再上"能省多少)
  ratePerKm, useEtc, laneIn
})
// → { code, bufferMin, fullCost, lastLeg, saved, waitMin,
//     valuePerWaitMin, advice[], warnings[] }

费率口径全部来自公开整理:一类小客车四车道约 0.45 元/公里、六车道及以上约 0.6 元/公里、山区桥隧更高,日常预算按 0.5 估;收费时段 ETC 不低于 95 折,免费时段无差别。

然后写了 25 项断言。其中最关键的是这个恒等式——"下去再上"省下的钱,恰好等于已行驶里程的费用,与剩余里程无关:

const k1 = at('2026-10-07T22:00', 800, 260);
const k2 = at('2026-10-07T22:00', 800, 600);
assert(k1.saved === 400);        // 800 km × 0.5
assert(k2.saved === k1.saved);   // 剩余 600 还是 260,省的一样多

跑通之后,我把它喂了八组真实场景,得到这张矩阵——这也是本文最想说清楚的一张表:

时刻已行驶剩余预计驶离缓冲硬撑要付下去再上省下要等判定
10/7 15:00300 km700 km23:48+12 min500 元350 元150 元540 min缓冲太薄
10/7 20:00600 km200 km22:33+87 min400 元100 元300 元240 min赶得上
10/7 21:00700 km250 km00:11−11 min475 元125 元350 元180 min建议现在下
10/7 22:00800 km260 km01:18−78 min530 元130 元400 元120 min建议现在下
10/7 23:201000 km90 km00:31−31 min545 元45 元500 元40 min建议现在下
10/7 23:501000 km20 km00:08−8 min510 元10 元500 元10 min建议现在下
10/8 00:201000 km60 km01:08−68 min530 元530 元0 元0 min已过期

如何读这张表:每一行代表一个真实场景。
「时刻」是你现在的时间;
「已行驶 / 剩余」是本次已开和还要开的里程;
「预计驶离」是按导航 ETA 推算的出口时刻;
「缓冲」是预计驶离与 24:00 的差值,正数表示赶得上、负数表示来不及;
「硬撑要付」是硬开到目的地的全程费用;
「下去再上」是就近下高速、等过 0 时再上的费用;
「省下」是两者之差;
「要等」是需要在服务区等待的分钟数;
「判定」是引擎给出的建议。

重点看最后三行:23:50 时你已开 1000 公里、只剩 20 公里,下去等 10 分钟就能省 500 元;可一旦拖到 00:20,免费窗口已经关闭,下去再上要付全程 530 元,一分钱都省不下。
我来把它画的更直观一点:
在这里插入图片描述

决策窗口只有那几十分钟——不是几个小时,更不是一晚上。这正是深夜高速上最疲惫、最想赌一把的时刻,也是这个工具存在的意义:在侥幸之前,先把账算清楚。

看最后三行,这是整件事最残酷的地方:**23:50 下去等 10 分钟省 500 元;00:20 才反应过来,一分钱都省不下了。**决策窗口只有那么窄的几十分钟,而这恰恰是人最疲惫、最容易心存侥幸的时候。
在这里插入图片描述

四、跑起来之后,它抓到我自己三个 bug

这部分是我写这篇东西最想写的。三个 bug 都不是读代码读出来的,是把东西跑起来、盯着输出看才暴露的。

Bug 1:已经过期了,它还告诉你"可省 500 元"

saved 我统一按 已行驶里程 × 费率 算。但 10 月 8 日凌晨的场景里,你现在下和开到目的地都要付全程,省钱空间是 0,引擎却照样报"可省 500 元"。

这不是数字难看的问题——**它会直接误导一个正在深夜高速上开车的人多做一次错误决策。**修法是把这个分支提到最前面单独返回,saved 强制为 0,并把文案改成"已经没有任何省钱空间了,所以完全没有赶路的理由"。

修完补了两条断言把它锁死:

const late = at('2026-10-08T00:20', 1000, 60);
assert(late.saved === 0);
assert(late.lastLeg === late.fullCost);   // 没有省钱空间

Bug 2:"不值得折腾"的保护漏了一条路径

我原本只在"确定赶不上"的分支里做了性价比判断:如果等 4 小时只为省 15 元,就别折腾了。

但输出里有一行让我惊呆了:

10/7 15:00  300km  700km  缓冲 +12min  省下 150元  要等 540min  → 缓冲太薄

建议是"就近出口先下去等 9 小时省 150 元"。**荒谬。**保护逻辑只挂在一条分支上,"赶得上但余量太小"这条路径绕过了它。补上之后,同一场景的建议变成:不建议下去等,把导航 ETA 当保守值用,中途该休息就休息。
在这里插入图片描述
补上之后,同一场景的建议变成:不建议下去等,把导航 ETA 当保守值用,中途该休息就休息。判据很朴素——每等一分钟,至少要替我省回五毛钱。

Bug 3:决策说"你赶不上",倒计时却显示"还剩 74 小时"

因为倒计时读的是真实系统时钟,而决策读的是用户在"现在时刻"里填的时间。用户手动改时刻(提前规划、事后复盘)时,两处当场自相矛盾。改成同一个时间源,并在每次输入后立刻同步刷新。

顺带一个可移植性隐患

我原先把截止时刻写成 2026-10-07T24:00:00+08:00。V8 能解析(我验证过,它等于次日 00:00),但 24:00 属于 ISO 8601 的扩展语法,ArkTS 运行时不保证支持。趁还没移植,统一换成等价但无歧义的 2026-10-08T00:00:00+08:00。

这种问题在浏览器里跑得好好的东西,到了真机上变成 Invalid Date,是最难查的一类。

五、交给华为云码道做鸿蒙版

引擎和原型是我自己的参照答案。鸿蒙版我打算丢给码道做,纪律是一轮只做一件事,而且每一轮我都有东西可对。

R1 建工程,第一件事是卡权限

建 HarmonyOS 元服务工程,Stage 模型,API 12。全工程不申请任何 ohos.permission.*,不发任何网络请求。请明确列出 module.json5 里的 requestPermissions 内容以证明这一点。

【填 1:贴码道第一次生成的 module.json5 片段。它有没有顺手加 INTERNET?你是怎么发现的?】

R2 移植规则引擎

把 engine.js 逐函数移植成 model/TollFreeEngine.ets,纯逻辑,不 import 任何 UI 或系统模块。函数签名和返回字段名保持一致。时间一律用带 +08:00 显式偏移的 ISO 字符串,禁止 T24:00 写法。

验收标准是把那 25 项断言移植成 hypium 单测,必须全绿。重点盯 ALREADY_LATE 分支的 saved 是不是 0——我自己刚在这儿栽过。

【填 2:ArkTS 严格模式第一次报了什么错?把报错原文贴出来。它有没有用 any / ESObject 绕过?是你指出来的还是它自己改对的?】

R3–R4 输入页与结论卡

数字输入用大号字体,考虑行车中单手操作。结论卡四态用左边框色带区分,底部固定一条安全声明,任何状态下都显示。

【填 3:状态管理它用的是 V1(@State/@Prop)还是 V2(@ObservedV2/@Trace)?有没有混用?混用会静默不刷新——你遇到这个现象了吗?】

R5 服务卡片(真正的硬骨头)

我原本设想的杀手级功能是"卡片上常驻秒级倒计时"。但据我了解,鸿蒙服务卡片的刷新频率下限是 30 分钟一次,卡片进程里也不能自己跑定时器。也就是说,秒级倒计时这个功能平台不让你做。

【⚠ 填 4:这条限制我来自既有认知,请你在 DevEco 官方文档核实后把原文和版本号补在这里。如果实际能秒级刷新,本节结论要改写。】

如果限制属实,设计必须改道,我的方案是卡片给档位、通知给精度:

  • 卡片只显示 < 1 小时 / 1–3 小时 / 3–12 小时 / > 12 小时 / 已结束 这样的档位,配一个醒目的截止时刻 10/8 00:00。30 分钟刷新一次,足够让档位正确跳变。
  • 秒级精度交给本地通知:截止前 180 / 60 / 30 分钟各推一条,内容带上"现在下高速可省 X 元"。
  • 实况窗(Live View Kit)能做到准实时,但需要行业资质申请,个人开发者大概率拿不到——先按拿不到设计,别把它写进依赖。

【填 5:这一段是真机跑出来的结论还是推测?卡片实际刷新间隔是多少?截图。】

这一轮是我最想写的部分:不是"AI 帮我把功能做出来了",而是"我想要的功能和平台允许的功能不一样,我怎么改设计"。

实现应用界面如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

六、这个产品拒绝提供的那个选项

值得单独说一句。

任何"高速免费截止倒计时"类产品,最讨好用户的设计是告诉你"还有 22 分钟,抓紧开还来得及"。

**我明确不做这个。**界面上有一条固定显示、任何状态都不消失的声明:

🛡 这个工具不会给你"加速赶在 24 点前下高速"这个选项。省两百块钱不值得拿命换。

技术上它体现为:缓冲时间不足 15 分钟时,引擎不会说"赶得上",而是说"缓冲太薄,建议就近下去";已经过期时,它把 saved 归零并告诉你"现在没有任何省钱空间了,所以完全没有赶路的理由"。

写完这个我意识到,Bug 1 之所以严重,不是数字算错,而是那个错误恰好会把人推向超速。好的产品立场不是喊出来的,是写在分支判断里的。

七、复盘:码道强在哪,又在哪把你骗过去

【填 6:以下三点请根据你的真实体验改写或推翻,不要照抄我的推测】

  • 强的地方:ArkTS 语法层面的合规性、元服务工程骨架、目录结构。
  • 需要盯着的地方:凡是"必须跑起来才知道对错"的问题——时间解析、卡片刷新、状态管理——它给的代码都是"看起来对"的。
  • 我的实际体感:AI 把写代码的成本压得很低,但没有压低判断代码对不对的成本。所以我先花半天写了一份可测试的参照答案,再让码道去逼近它。这份卷子的价值,比它帮我敲的那几千行代码大。

八、产物

  • 仓库:<https://atomgit.com/guzhangMVP/highway>
    • web/engine.js 规则引擎(纯逻辑,195 行)
    • web/test.js 25 项断言,node test.js 直接跑
    • web/smoke.js 无浏览器冒烟测试
    • web/decision_matrix.txt 上面那张表的原始输出
    • web/index.html 在线试算页,双击即用
    • docs/handoff.md 鸿蒙版八轮提示词与验收标准

在仓库可以看到:
在这里插入图片描述

九、总结

这道题本身并不复杂——高速免不免费只看驶离出口的时刻。真正难的是在深夜高速上、在疲惫和侥幸里,冷静算出"该不该现在下去"。所以我把它写成一份可测试的规则引擎,用 25 项断言锁死逻辑,跑起来抓到 3 个真实 bug,再交给华为云码道生成鸿蒙元服务版。

回头看,最有价值的不是那几千行代码,而是两件事:先写可测试的参照答案,再让 AI 去逼近它——这让我能一眼看出它哪里"看起来对";以及把产品立场写进分支判断——不给你"加速赶路"的选项,省两百块钱不值得拿命换。

如果你也在高速上遇到过这道题,希望这个工具能帮你少一点纠结、多一点从容。下次免费窗口,记得:赶不上就就近下去,等过了 0 时再上。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐