起因:每天中午最痛苦的不是工作,是选吃什么
说实话这个 App 的起因特别朴素。我们办公室五个人,每天中午十一点半开始那场"吃什么"的讨论,平均要持续十五分钟。有人不想吃辣,有人昨天刚吃过黄焖鸡,有人觉得太远不想走……
我之前试过几个随机选餐厅的小工具,但都只有一个"随机"按钮。说白了,单纯随机解决不了多人场景——你随机出一个结果,总有人说"这个不行换一个",然后就无限循环了。
所以我干脆自己写了一个,叫「决定今天吃什么」,今年上了鸿蒙应用市场。核心想法是:不同场景用不同决策模式,一个人吃就转盘,多人吃就投票先排除再随机。
为什么选鸿蒙
两个原因。一是我手上有台 Mate 60,想体验下 ArkTS 开发到底什么手感;二是这类小工具在鸿蒙市场竞争没 iOS/Android 那么卷,同类产品少,有机会被看到。
实际开发下来,ArkTS 写起来比我预想的顺畅。声明式 UI 那套逻辑跟 SwiftUI 很像,上手成本不高。
三种模式的设计
🎡 转盘模式(一个人用)
最简单的场景。把你收藏的餐厅放进转盘,转一下就走。我做了个 selectWheelIndexes 来处理超出转盘分段数的情况——比如你收藏了 30 家餐厅,转盘最多显示 12 格,那就取权重最高的前 12 家:
export function selectWheelIndexes(weights: number[]): number[] {
if (weights.length <= MAX_WHEEL_SEGMENTS) return weights.map((_, i) => i)
const indexed = weights.map((w, i) => ({ w, i }))
indexed.sort((a, b) => b.w - a.w)
return indexed.slice(0, MAX_WHEEL_SEGMENTS).map(x => x.i)
}
```
权重这个东西挺有意思,你可以给经常想去的餐厅设高权重,它被转到的概率就大一些。但不是绝对的,还是有随机性。
### 🗳️ 投票模式(多人用)
这是我觉得最有价值的模式。逻辑是这样:先列出候选餐厅,每个人划掉自己不想吃的(投"反对票"),最后票数最少的胜出——也就是大家都不反对的那家。
平票的情况我处理了一下,用票数做权重再随机一次:
```arkts
export function finishVote(session: VoteSession): string {
const entries = Object.entries(session.votes)
let max = -1
let winners: string[] = []
for (const [id, count] of entries) {
if (count > max) { max = count; winners = [id] }
else if (count === max) { winners.push(id) }
}
if (winners.length === 1) return winners[0]
const weights = winners.map(id => session.votes[id])
const total = weights.reduce((a, b) => a + b, 0)
const r = Math.random() * total
let acc = 0
for (let i = 0; i < winners.length; i++) {
acc += weights[i]
if (r <= acc) return winners[i]
}
return winners[winners.length - 1]
}
```
实际用下来,这个模式比纯随机的接受度高很多。因为结果是"大家都没反对的",心理上更容易接受。
### 🎲 随机模式(带排重)
纯随机但有一个细节:会避开最近 N 次吃过的。这个 N 在设置里可以调。我一般设成 3,意思是最近三顿吃过的不会再出现。
实现上就是把最近历史记录里的餐厅 ID 收集起来,给它们权重设为 0:
const avoidIds = new Set()
for (let i = 0; i < Math.min(n, recentHistory.length); i++) {
avoidIds.add(recentHistory[i].resultRestaurantId)
}
这个功能看起来简单,但没有它的话,连续三天随机到同一家的概率真不低(餐厅库小的时候)。
## 筛选过滤
候选列表支持按标签、预算上限、距离过滤。数据模型里每个餐厅都有 `price`(min/max)、`distanceKm`、`tags` 这些字段。筛选逻辑写在 `getCandidates` 里,就是标准的 filter chain,没什么花哨的。
我自己日常用的时候,午饭会筛"步行 1km 以内 + 人均 30 以下",周末聚餐就放开距离限制。
## 数据存储
整个 App 是纯本地存储,用的 HarmonyOS 的轻量级偏好数据持久化。没有服务端,没有账号系统。餐厅库、历史记录、设置项全部存在本地。
这么做有个好处:打开就能用,不需要联网,不需要注册。坏处是换手机得重新添加餐厅。后面版本可能会考虑用华为云端备份,但目前这个体量还没必要。
## 目前的状态
App 已经在鸿蒙应用市场上架,当前版本 1.2.0。说实话下载量还很小,刚起步阶段。我自己和同事每天都在用,投票模式基本成了我们午饭的固定流程——十一点半有人在群里喊一声"开投",两分钟搞定。
有一点遗憾是多人投票目前还是"传手机"的方式,没有做局域网同步。这个功能我想过用 HarmonyOS 的分布式软总线来做,多台设备协同投票,但说实话工程量比我想象的大,暂时搁置了。
## 一些开发体验
ArkUI 的声明式布局写转盘动画还挺方便,`animateTo` 配合旋转角度就能实现基本的转盘效果。组件状态管理用 `@State` 和 `@Link`,跟 React 的 useState 思路差不多,没什么学习门槛。
比较折腾的是列表性能。餐厅库大了之后(我测了 200 条数据),LazyForEach 的复用机制需要仔细处理 key,不然滑动会有明显卡顿。这块踩了两天坑。
## 写在最后
这个 App 的定位很小,就是解决一个具体的生活问题。没想做成什么大而全的美食平台,就是一个决策工具。
如果你也在做 HarmonyOS/ArkTS 开发,或者对这类轻量工具型 App 的实现感兴趣,欢迎交流。鸿蒙应用市场搜「决定今天吃什么」就能找到。
386



被折叠的 条评论
为什么被折叠?



