想做一个“大众点评美食版小程序”,先别急着打开微信开发者工具。做点评类小程序,我见过太多人把精力花在UI还原上,首页做个九宫格、详情页堆满图片,最后却死在评价冷启动和平台审核上。这篇文章我不写小说,就根据我做过本地生活类项目的经验,把这款小程序的产品定位、页面结构、核心筛选逻辑、数据表设计、上线避坑一次讲清楚。适合独立开发者、学生毕设,以及准备接本地商家订单的小团队参考。
1. 项目定位与技术选型:别把点评做成“美团”
1.1 大众点评真正值钱的不是App外壳
很多人在复刻大众点评美食版时,第一反应是照着App界面抄:首页九个宫格、红色橙色按钮、商户详情页的图集轮播。但大众点评能活到今天,核心资产不是UI,而是沉淀多年的评价数据和“让用户做决策”的能力。
所以你在设计这款小程序之前,要先回答一个问题:用户打开你的小程序,是想“随便看看”,还是想“今天中午吃什么”。如果答案是后者,你的产品定位就不是内容展示,而是决策工具。围绕“决策”两个字去做,首页要解决的是信息过载问题,而不是把功能堆得越全越好。
我的建议是,第一版不要想着完整还原大众点评,而是做“发现美食→对比商户→到店消费/核销”这条最小闭环。等评价数据跑起来,再慢慢加榜单、签到、会员体系这些锦上添花的功能。很多项目就是死在第一版功能太多,商家没几家,评价没几条,用户进来逛一圈就走了。
1.2 技术选型:uni-app 还是原生微信小程序
玩法确认之后,再谈技术选型。这块我不太爱站队,主要看你的目标平台和团队情况。
| 对比项 | 原生微信小程序 | uni-app |
|---|---|---|
| 上手曲线 | 熟悉 JS + WXML + WXSS,文档直接 | 熟悉 Vue 语法即可,入门快 |
| 跨端能力 | 只能跑微信 | 可同时编译到微信、支付宝、H5、App |
| 原生能力支持 | 最新功能适配最快 | 偶尔要等插件更新 |
| 适合场景 | 只做微信端、深度依赖微信API | 后续要做App/H5,避免重复开发 |
| 生态资料 | 官方文档、社区极多 | 插件市场丰富,组件现成 |
如果是学生毕设或者只做微信端的项目,直接上原生也没问题,避免踩 uni-app 里某些基础库的适配坑。但如果是小团队想以后同时覆盖App或H5,我建议直接上 uni-app,Vue 语法写起来比 WXML 舒服,一套代码按需编译,后期的维护成本低很多。我之前做过一个本地生活类项目,本来只想做微信小程序,后来客户要 H5 版发到朋友圈,因为用的是 uni-app,半天就编译出来了,省了一整个二期开发。
后端选型上,初期强烈建议用微信云开发(云函数 + 云数据库),省掉服务器、域名备案和 HTTPS 证书配置,一个人就能把那套完整后端跑起来。等用户量上来了,再迁移到自建 Node.js 或 Java 服务都来得及。别一开始就买服务器搭环境,小程序审核对合法域名要求很严格,云开发直接绕过了这个麻烦。
1.3 LBS 是美食小程序的地基:经纬度与“附近”距离
大众点评美食版和普通资讯类小程序最大的差异点,就是“同城 + 位置”。没有 LBS 的美食点评,用户还得手动输入城市、翻找列表,体验直接掉一个档次。
LBS 这块的底层逻辑其实不复杂:用户进入小程序时调用定位接口拿到经纬度,商家数据表里存有自己的经纬度,然后按“距离排序”或“范围内筛选”把结果展示出来。距离计算不能直接用经纬度做平面两点距离,因为地球是球面,我一般直接用 Haversine 公式:
function haversineDistance(lat1, lng1, lat2, lng2) { const rad = Math.PI / 180 const dLat = (lat2 - lat1) * rad const dLng = (lng2 - lng1) * rad const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * rad) * Math.cos(lat2 * rad) * Math.sin(dLng / 2) * Math.sin(dLng / 2) const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) return 6371 * c // 返回公里数 }生产环境中,小程序端负责拿坐标,推荐距离计算放在服务端或云函数里做,避免把整个商家列表拉下来在前端算。如果用的是腾讯云开发数据库,可以直接用db.command.geoNear做地理位置查询,按距离返回商家,配合maxDistance限制范围,性能会好很多。距离计算是“附近美食”这个核心功能的命根子,一定要把这部分单独封装成工具函数,后续排序、筛选、缓存都要复用。
2. 页面拆解:把大众点评的信息架构装进小程序
2.1 首页信息流:不是功能越多越好
首页是一个点评产品的门面,但很多人会把首页做得像“功能集合页”。我的经验是,首页只保留三块:顶部搜索框、金刚区(分类入口)、推荐Feed流。
搜索框放在最上面,用户进来第一件事往往是直接搜“火锅”“烤鱼”“XX路附近有什么吃的”。金刚区放8到10个高频分类入口就够,比如美食、小吃快餐、火锅、日料、面包甜点、奶茶咖啡、烧烤、轻食,点进去对应的是该分类下的商家列表页。不需要搞“全部58个分类”的十几个层级的二级页面,移动端用户没耐心翻那么深。
推荐Feed流则是根据用户定位返回“附近热门商家”和“朋友/同城最近评价过的好店”。这里有个细节:小程序的头部标题不要写死,可以根据当前定位城市或商圈动态设置,比如“广州·体育西附近美食”,用户一看就知道自己被服务到了。这里可以用wx.setNavigationBarTitle实现。
2.2 商家详情页:一个点评产品的核心资产
商家详情页决定了用户会不会“买单”,是整个小程序里最需要花心思的页面。它至少要包含以下信息模块:
- 基础信息:店铺名称、头像/封面图、地址、电话、营业时间、人均价格。
- 评分信息:综合评分、口味/环境/服务三项细分评分。
- 商家图集:菜品图、环境图,最好是用户上传的真实图片。
- 营业状态:判断“营业中”/“已打烊”,要基于营业时间做实时计算,不只是展示字符串。
- 评价列表:用户评价带图、带评分、带标签。
- 交易入口:团购券、代金券、预约排队按钮。
这里我踩过一个坑:营业时间存成字符串“10:00-22:00”,前端直接展示没问题,但后来要做“营业中/打烊”状态就麻烦了,得重新解析字符串。正确的做法是把营业时间拆成结构化字段,比如openTime: "10:00"、closeTime: "22:00",再配合isCrossDay(是否跨天)标记存储。这样前端可以轻松计算实时状态,商家后台也能直接编辑。
2.3 搜索与筛选排序:让用户最快找到“吃什么”
分类页和搜索结果页的筛选逻辑,是美食点评类小程序和普通内容展示最大的区别。筛选条件至少要有:距离排序、综合排序、评分排序、人均价格区间选择。
这里给出一套我在实际项目中使用的综合排序权重思路,你可以直接抄:
综合分 = 商家基础分 * 0.4 + 近期热度分 * 0.3 + 评价质量分 * 0.3 近期热度分 = 近7天浏览/收藏/下单次数,做归一化处理 评价质量分 = 加权平均评分 * 评价数系数(评价越多,可靠性越高)评分高但只有两条评价的商家,不应该排到评分4.7但几百条评价的商家前面。所以我引入了“评价数系数”:评价数加权后才能参与排序,否则一个店铺刷了5条五星好评就能霸榜,产品基本就废了。
搜索词这块,要做简单分词。用户搜“珠江新城 火锅”,要能识别出两个关键词:商圈“珠江新城”和品类“火锅”,分别做地理位置过滤和分类过滤,而不是直接把整串字符拿去模糊匹配,否则搜索出来的结果让你怀疑人生。
3. 核心功能落地:评价、交易与合规
3.1 评价体系:怎么保证评价能“信”
评价是点评类产品的灵魂,也是最难做的模块。用户评判一个点评产品靠不靠谱,只看一点:这里的评价是真实的,还是商家刷出来的。
第一版评价系统建议包含以下字段:用户ID、商户ID、综合评分(1-5分)、口味/环境/服务三个细分评分、文字内容、图片列表、消费凭证(是否到店消费过)。为了鼓励真实消费用户评价,可以在下单核销后24小时内推送提醒“评价晒图可得积分”,初期积分可以兑换周边或优惠券,这是最朴素的冷启动玩法。
为了保证内容安全,所有发出来的评价必须在后端调一次微信内容安全接口做文本检测,包含垃圾广告、违法违规内容的话直接拦截。图片也必须做检测,别完全依赖前端拦截,后端子接口校验不能省。具体到技术实现,不要把敏感词过滤做成本地关键词表,直接调用微信官方security.msgSecCheck和security.imgSecCheck接口更省心,新账号还要注意emoji和错别字的变体绕过问题,所以建议用官方能力而不只是自己的词库。
3.2 动态标题与缓存时间:两个提升留存的小细节
很多初学者忽略的两个点是:动态设置标题、设置缓存时间。前者影响用户“我在哪”的感知,后者直接影响小程序启动速度和服务器压力。
比如用户在北京,首页标题自动变成“北京 · 附近美食”,点进某个商圈列表页,标题变成“北京 · 三里屯/工体”。wx.setNavigationBarTitle官方文档就有,几分钟能搞定,但很多产品压根没做,结果用户定位到了另一个城市,首页还呆呆写着“全国热门美食”,那个违和感,用户一秒钟就会关掉小程序。
缓存方面,我的经验是:
- 首页Feed流数据:缓存5分钟。
- 商家详情基础信息:缓存10分钟,因为商家营业时间、地址基本不变。
- 用户当前定位:缓存5分钟,位置变化快。
- 分类列表数据:缓存30分钟。
缓存的实现也简单,用wx.setStorageSync存数据,同时存一个时间戳,读取时对比当前时间,超过有效期就重新请求:
function getWithCache(key, ttl, fetchFn) { const cached = wx.getStorageSync(key) const timestamp = wx.getStorageSync(key + '_time') if (cached && timestamp && Date.now() - timestamp < ttl) { return Promise.resolve(cached) } return fetchFn().then(data => { wx.setStorageSync(key, data) wx.setStorageSync(key + '_time', Date.now()) return data }) }这里有一个坑:wx.setStorageSync单个 key 上限是 1MB,总容量是 10MB。不要把整张商家列表一次性塞进去,分页缓存更合理。另外缓存清理策略要跟上,用户换城市之后必须马上清掉旧城市的缓存,否则会出现“人在广州,列表全是北京餐厅”的灵异事件。
3.3 交易闭环:团购、代金券与到店核销
定位到“美食版”,迟早要做交易。小程序的交易闭环一般是这样:商家在后台创建团购券或代金券(商品)→ 用户在小程序商城内下单支付 → 生成券码 → 到店出示小程序核销码 → 商家端扫码核销。
这个闭环里,技术实现重点是订单状态表和核销状态。订单状态至少要有:待支付、已支付待消费、已核销、已过期、已退款。核销方式推荐用动态二维码并配合一个“核销密文”,而不是直接把固定订单号贴在页面上,防止截图盗用。核销成功后,再触发评价提醒,形成“种草—购买—到店—核销—评价—再种草”的完整循环。
支付侧,如果你的主体是个人,微信支付基本申请不下来,小程序个人主体也没法开通微信支付。想做交易,建议注册个体工商户或企业主体,通过微信支付商户号接入。这里提醒一句:凡涉及虚拟商品的支付场景,在 iOS 端会遇到 IAP 限制,但美食团购和到店核销属于线下实物/服务消费,不属于虚拟支付,整体合规压力会小很多。
3.4 审核与隐私合规:被驳回的高频原因
小程序上线审核是很多项目的拦路虎。做美食点评类小程序,最容易被驳回的原因我梳理一下:
- 类目选错:很多团队选了“工具”或“生活服务”,但实际涉及餐饮、商家信息,审核员会要求你补充“美食/餐饮”相关类目,个人主体往往没有对应资质,所以建议直接注册个体工商户或企业主体。
- 隐私协议缺失:只要用到了用户头像、昵称、位置信息,就必须有单独的隐私协议弹窗,并且在 App.json 里声明隐私相关接口。
- 用户生成内容(UGC)问题:包含用户评价、发帖功能,需要提供内容安全审核能力和用户举报入口。后台得有一个“举报评价”按钮,并且要接内容安全接口。
- AI类功能的高压线:现在平台对“文本深度合成(如AI问答)”功能审查很严,如果小程序里带AI点评助手、智能问答这类功能,需要补充相应服务说明和合规材料。我的建议是,第一版老老实实不加AI生成功能,把“人工真实评价”作为卖点,省掉一堆麻烦。
4. 数据库设计与接口约定
4.1 核心表结构:照着这个设计少走弯路
不管用云开发还是自建数据库,表结构基本是相通的。这里给一份我实际用过的精简版设计草案。
商户表shops:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 主键 |
| name | string | 商家名称 |
| address | string | 详细地址 |
| location | geoPoint/经纬度 | 用于附近查询 |
| category | string | 分类ID |
| openTime | string | 营业开始(如 10:00) |
| closeTime | string | 营业结束(如 22:00) |
| avgPrice | number | 人均价格(元) |
| rating | number | 综合评分 1-5 |
| ratingCount | number | 评价总数 |
| images | array | 封面和商家图 |
| status | number | 0未上架 1营业中 2休息 |
评价表reviews:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 主键 |
| shopId | string | 关联商户ID |
| userId | string | 评价人ID |
| score | number | 综合评分 |
| tasteScore | number | 口味评分 |
| envScore | number | 环境评分 |
| serviceScore | number | 服务评分 |
| content | string | 文字内容 |
| images | array | 图片列表 |
| hasConsumed | boolean | 是否有消费记录 |
| status | number | 待审核/展示/隐藏 |
订单表orders:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 主键 |
| orderNo | string | 订单号 |
| userId | string | 用户ID |
| shopId | string | 商户ID |
| goodsId | string | 团购/代金券ID |
| amount | number | 实付金额 |
| status | number | 待支付/已支付/已核销/已过期/已退款 |
| verifyCode | string | 核销码 |
分类表categories、商品表goods、用户表users、收藏表favorites就不逐一罗列了,核心是每个表中都要加createTime和updateTime,后面做增量同步和缓存失效都靠它们。
4.2 附近商家分页查询的具体实现
附近商家的查询,如果用云开发数据库,可以直接用地理位置索引。我在项目里是这么写的:
// 云函数:getNearbyShops const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command exports.main = async (event) => { const { latitude, longitude, page = 0, pageSize = 10, category } = event const where = { location: _.geoNear({ geometry: db.Geo.Point(longitude, latitude), maxDistance: 5000, // 5公里内 minDistance: 0 }) } if (category) where.category = category const res = await db.collection('shops') .where(where) .skip(page * pageSize) .limit(pageSize) .get() return { list: res.data, page } }注意事项:geoNear查询需要给集合创建地理位置索引,云开发控制台里把location字段设为地理位置索引即可。排序规则默认按距离由近到远返回,如果需要综合排序,就先查出目标范围内的商家,再用综合分字段排序,但注意分页和排序要在同一批查出的数据上做,避免出现“翻页后数据错乱”。
4.3 榜单与热门推荐:别在客户端实时算
很多新手写榜单接口时,习惯在请求时动态去聚合计算各家店铺的评分和浏览数,结果用户一多,数据库直接被打爆。
正确做法是“离线预热 + 缓存”。每天晚上用定时触发器或云函数定时任务,跑一遍所有商家的日访问量、日收藏量、评价增量,计算出第二天的排行榜数据,写入一张独立的rank_daily表。前端请求“附近热门”接口时直接读这张表,查不到再用TOP N兜底查询。这种做法在美食类目里尤其重要,因为用户对榜单实时性要求没那么高,今天的数据基本能满足明天的展示,但性能差距是数量级的。
5. 上线流程与高频问题排查
5.1 从代码到真机的完整发布路径
小程序从写完到真正上线,流程是:在微信开发者工具里编译调试 → 上传代码 → 在 mp.weixin.qq.com 后台把上传的版本设为体验版 → 真机扫码体验 → 提交审核 → 审核通过后发布上线。
常见的一个现象是“开发版小程序已过期,请在开发者工具重新扫码”。这种情况通常不是代码错了,而是开发版的登录态过期了。处理也很简单:在开发者工具右上角重新扫码,然后清缓存、重新编译即可。如果是用了 uni-app,出现这种情况通常是基础库版本不匹配或 appid 没有对应权限,记得在 manifest.json 里重新检查 appid。
还有一个经常被问的问题:家里电脑能当服务器部署小程序吗?答案是可以,但非常不推荐。小程序要求所有请求接口必须是 HTTPS 且域名已经备案,家用宽带基本没有固定公网IP,也没有正规备案条件,就算用内网穿透,稳定性也扛不住生产流量。我见过一个团队用家里电脑跑接口,结果一天崩溃三次,用户差评一堆。老老实实用云开发或正规云服务器,一个月几十块钱,省心得多。
5.2 高频问题速查表
| 常见问题 | 原因分析 | 解决办法 |
|---|---|---|
| 开发版小程序已过期,需要重新扫码 | 登录态失效 | 开发者工具右上角重新扫码,清缓存编译 |
| 审核被驳回“涉及餐饮美食类目” | 类目选择不对或主体是个人 | 升级为个体工商户/企业主体,补充类目资质 |
| 地图/定位不显示 | 未在小程序后台配置位置接口权限 | 在 mp 后台“开发-接口设置”里开通位置权限 |
| 发布后接口请求失败 | 合法域名没有配置或未备案 | 云开发可免域名配置;自建服务器必须配置合法HTTPS域名 |
| 自定义导航栏高度不对 | 各机型状态栏高度不同 | 使用wx.getSystemInfoSync()获取 statusBarHeight,再换算 |
| 单选框和原生组件样式错乱 | 原生组件层级问题 | 使用 cover-view 覆盖或改用 picker、自绘单选框 |
| 评价内容被拦截 | 触发了内容安全接口 | 提示用户修改并保留记录,不要直接报错崩溃 |
| 缓存显示旧数据 | TTL过期策略没设对 | 用时间戳校验并主动清理更换城市后的缓存 |
| 小程序年审忘记办 | 每年需要重新年审 | 在mp后台设置到期提醒,年审期间不影响已上线版本 |
开发过程中,顶部导航栏的自定义适配也是一个出现频率很高的问题。使用自定义导航栏时,需要注意胶囊按钮的高度和位置,不能写死数值。正确做法是通过wx.getMenuButtonBoundingClientRect()获取胶囊按钮位置,然后计算导航栏整体高度。这个在小程序真机上不同机型差异很大,写死数字必然翻车,记得一定用API动态计算。
最后分享一点个人经验
做美食点评小程序,技术上真正难的不是某个页面,而是冷启动阶段的“真实评价”。我见过不少项目靠刷量造数据,短期好看,一旦被平台发现或被用户识破,信用直接崩塌。我的建议是,上线初期先不要覆盖全城,选一个商圈、找5到10家愿意配合的商家,做“真实顾客评价送小菜/饮品”的活动,让评价先跑起来。产品形态上,也别一上来把所有功能堆满,把“附近到底有什么好吃的、值不值得去”这两件事做到极致,比花哨的会员体系和翻倍的功能更抗打。小程序开发里最值钱的是节奏感,先把最小闭环跑通,之后再慢慢加榜单、签到、会员体系,每一步都有数据支撑,才不会白忙。