我前阵子刚把一个《基于微信小程序的丽江市旅游分享平台》从零搭到能发布,前后折腾了三周左右。真正让我熬夜的不是丽江景点数据怎么填,而是 wx.login 登录态、地图组件、上线前合法域名配置这些不起眼却又绕不开的问题。这篇文章按我真实开发顺序来写:功能模块怎么拆、数据表怎么设计、地图和富文本怎么做,再到登录头像天气三个授权场景,最后是自建服务器部署和提交发布阶段遇到的那些坑。无论你是做毕设、课程设计,还是想低成本搭一个本地旅游类小程序,都可以把它当一份带坑位标注的参考。
1. 从毕设题目到可落地功能:丽江市旅游平台的边界与数据设计
拿到题目后第一件事不是打开微信开发者工具,而是把“分享平台”四个字拆成具体页面。我吃过这个亏:一开始照着旅游App的感觉堆功能,结果做了半个月发现评论、关注、私信、消息通知全都要做,工作量完全失控。后面冷静下来,把平台定位成“游客用的景点信息工具 + 轻量分享社区”,功能边界就清楚多了。
1.1 拆清楚游客和分享者分别需要什么
我把用户分成两类:绝大多数人是游客,打开小程序就是想看丽江有什么好玩的、丽江古城怎么去、玉龙雪山现在天气如何;另一部分人才是愿意发布攻略的分享者。前者要的是“浏览体验”,后者要的是“发布闭环”。
最终我确定的功能清单如下,推荐你在动手前也画一张类似的表,别上来就开写。
| 角色 | 核心需求 | 对应页面与功能 |
|---|---|---|
| 游客 | 快速了解丽江景点分布 | 首页地图,景点以 marker 形式展示 |
| 游客 | 看景点详情和交通建议 | 景点详情页,图文介绍、视频宣传、天气 |
| 游客 | 了解当前天气是否适合出行 | 首页和详情页展示实时天气 |
| 游客 | 浏览别人分享的攻略 | 内容流 feed,按时间倒序分页 |
| 分享者 | 发布图文攻略 | 发布页,支持选地点、传图片 |
| 分享者 | 看自己的历史记录 | 个人中心,我发布的、我收藏的、我的足迹 |
| 游客/分享者 | 点赞、评论、收藏 | 帖子详情页的互动区 |
注意我刻意没做“关注用户”和“私信”。旅游分享平台的魅力在内容本身,不在社交关系链。而且从毕设答辩角度看,把景点、攻略、评论、收藏这条路走通,已经能证明你具备完整的前后端设计能力。功能做太多反而会让每个模块都显得单薄。
1.2 六张表撑起整个项目
数据模型我前后改了三版,最后留下来的核心表就是六张:用户表、景点表、攻略帖子表、帖子图片表、评论表、收藏表。维护景点时还需要一个简单的管理员账号,这个我没有单独建角色表,而是给用户表加了一个 is_admin 字段,省去一套权限体系,因为小程序端根本不需要做管理后台,管理员直接在电脑端维护数据就够了。
sql复制CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
openid VARCHAR(64) NOT NULL UNIQUE,
nickname VARCHAR(64) DEFAULT '',
avatar VARCHAR(500) DEFAULT '',
city VARCHAR(64) DEFAULT '',
is_admin TINYINT DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE scenic_spot (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
lng DECIMAL(10, 6) NOT NULL,
lat DECIMAL(10, 6) NOT NULL,
summary TEXT,
cover_url VARCHAR(500),
detail_html MEDIUMTEXT,
video_url VARCHAR(500),
city_id VARCHAR(32) DEFAULT '',
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
景点表里有两个字段容易忽略:一个是 video_url,我用它保存景点宣传视频的 mp4 地址;另一个是 city_id,这是给和风天气接口用的城市编码,为的是在景点详情页能显示对应区域的天气。很多旅游类项目只做了数据库里存天气快照,但快照会过期,我后面直接改成实时请求天气接口,交互体验好很多。
景点详细的图文介绍我用了 detail_html 字段,里面可以存富文本HTML,也可以存纯文本加图片URL的JSON。这个选择我后面会单独讲,这里先记住:景点内容修改频率低、由管理员维护,和用户发布的攻略要区分开。
发布表的核心字段是 content、images 和 location 三件套,帖子允许不关联景点,这样可以覆盖“随便在丽江街头分享一家小店”的场景。
sql复制CREATE TABLE post (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
spot_id INT DEFAULT NULL,
content TEXT,
images JSON,
location_name VARCHAR(128) DEFAULT '',
location_lng DECIMAL(10, 6) DEFAULT NULL,
location_lat DECIMAL(10, 6) DEFAULT NULL,
like_count INT DEFAULT 0,
comment_count INT DEFAULT 0,
status TINYINT DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
images 直接用 JSON 类型存数组,省去单独查图片表的麻烦。点赞数、评论数我是直接冗余在 post 表里,每次点赞或评论成功后做加一或减一操作。对毕设体量来说,这种“用空间换性能”的冗余设计完全够用,还方便列表页排序。
1.3 接口列表和调用顺序该怎么定
数据表设计好之后,接口就顺理成章了。我常用的后端路径是:
text复制POST /api/user/login
GET /api/spots/list
GET /api/spots/detail?id=1
GET /api/weather/now?location=100.229,26.875
GET /api/posts/feed?page=1&page_size=10
POST /api/posts/create
POST /api/comments/create
GET /api/comments?post_id=1
POST /api/favorite/toggle
这里要特别注意调用顺序。用户首次打开小程序,前端要先去 wx.login 拿一个临时 code,再带着 code 请求后端的 /api/user/login,由后端换回 openid 并生成自己的登录态 token。后续所有需要身份的请求都带这个 token,不是每次都要 wx.login。
读接口的调用顺序也有讲究。首页如果同时请求景点列表、天气、内容流三个接口,会出现页面加载时一段白屏。我的做法是先请求景点列表和内容流,天气接口放在景点列表返回之后再发,因为首页要展示的天气直接取丽江古城默认坐标就行,没必要因为天气接口慢而卡住整个首屏。后端接口拆分得越细,前端越灵活,这是我在这个项目里比较深的体会。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 地图找景点、攻略看图文:内容展示层的三个关键选型
旅游分享平台和普通内容社区最大的差异,在于它天然依赖地理位置。用户想知道“玉龙雪山在哪个方向”“丽江古城附近有什么可逛的”,这些需求如果只靠文字列表,交互体验会差很多。所以地图组件、富文本、视频这三个展示层选型,值得单独拿出来说。
2.1 地图接入高德,还是只用原生 map 组件
微信小程序内置的 map 组件本身已经很好用,可以直接展示 marker、实现缩放拖动。但有一点得先搞清楚:内置 map 组件的地图数据底层来自腾讯位置服务,如果你需要做POI搜索(比如搜“丽江古城附近美食”),直接用 map 组件是完不成的。
我最终接的是高德地图微信小程序 SDK。原因有两个:第一,我对高德的POI数据和景点数据更熟,丽江的景点POI覆盖比较全;第二,高德提供了专门的小程序 SDK,调用成本不高。接入选型做完后,踩坑才真正开始。
如果你也要接高德,流程大概是这样的:
- 在高德开放平台创建“微信小程序”类型的应用,拿到 Key。
- 在微信公众平台后台的“开发管理 - 开发设置 - 服务器域名”里,把
https://restapi.amap.com加到 request 合法域名。 - 下载高德的小程序 SDK 文件,放到项目 utils 目录下。
- 页面里通过 require 引入 SDK,初始化 AmapWX 实例。
js复制const amap = new AmapWX({
key: '你的高德Key'
})
amap.getPoiAround({
query: '景点',
location: '100.229,26.875',
success: (res) => {
console.log(res.markers)
}
})
代码看起来不多,但有一个非常关键的优化点:不要让小程序前端直接调用高德的 REST API,而是把高德的请求统一放在后端去做。你可能会问,前端调一次不也能拿到数据吗?问题是 Key 暴露在小程序包里,别人反编译就能拿到,而且高德对普通 Key 的并发有配额限制。先在小程序端调一次拿到数据,再把结果缓存在自己服务器上,既能保护 Key,又能减少配额消耗,这个方案我实测下来顺畅很多。
2.2 攻略正文不迷信富文本,文本加图片数组最省心
很多第一次做社区类小程序的同学,一上来就想给用户接一个富文本编辑器,让攻略像公众号文章一样图文混排。这个想法听起来很美,但真实开发成本特别高,而且微信小程序的 rich-text 组件对图片点击事件的支持非常有限。
rich-text 可以解析 HTML 字符串,但它是纯展示型组件,用户点击里面的图片时,你没有简单办法拿到点击的是哪一张图,自然就没法做 wx.previewImage 图片预览。那怎么做图文内容呢?我最后的方案是“文本 + 图片数组”分离:用户在发布页的 textarea 里写文字,图片通过上传组件单独选择,提交后 content 字段存文本,images 字段存图片 URL 数组。详情页渲染时,先显示文字段落,下面再接一个九宫格图片墙,点击任意一张图都能全屏预览。
js复制// 发布表单提交的数据结构
const formData = {
content: '今天从丽江古城出发去玉龙雪山,蓝月谷的水真的很好看…',
images: [
'https://cdn.yourdomain.com/posts/1.jpg',
'https://cdn.yourdomain.com/posts/2.jpg'
],
location_name: '蓝月谷',
location_lng: 100.2,
location_lat: 27.0
}
这个方案还有一个隐藏优点:发布页不用处理富文本编辑器的兼容性问题,小程序端的 textarea 在 iOS 和 Android 上表现稳定得多。视觉设计上,文本在上、九宫格图片在下的结构非常清晰,用户浏览起来不累。
景点详情页的 detail_html 字段我也没有直接塞富文本。管理员在维护景点介绍时,我用的是“多段文本 + 多张图片”的结构化录入,展示时顺序渲染段落和图片。后期如果真想升级成图文混排,再把 detail_html 换成 Markdown 文本,前端引入一个 Markdown 渲染库即可,这条路从今天看依然是顺畅的。
2.3 video 素材的来源与 swiper 嵌套的全屏错位
景点宣传视频在小程序里播放,最稳妥的方式是拿到 mp4 文件的 HTTPS 直链,然后用 video 组件播放。视频素材的来源一定要提前规划好:是用云存储、自己的服务器存储,还是图省事放腾讯视频链接?
腾讯视频链接在小程序里处理起来会让你怀疑人生。微信小程序不支持直接解析腾讯视频播放页 URL,用 web-view 去加载腾讯视频页面又会受到业务域名白名单限制。所以我的结论是:除非你申请接入腾讯视频官方插件,否则别在旅游项目里花时间研究腾讯视频链接。不如直接让管理员上传一份 mp4 到自己的服务器,video 组件填 src 地址,简单可靠。
还有个高频问题来自搜索词里的那句“swiper 组件嵌套 video 组件导致全屏错位”。这个坑我在做景点轮播图时也遇到过。轮播图里如果有一个 swiper-item 放的是视频,用户点击全屏播放后再退出,经常会出现视频画面
