微信云开发旅游小程序实战:LBS打卡+UGC社交全栈方案

简介:这是一套基于微信云开发的旅游打卡类小程序源码,面向前端初学者与小程序开发者,解决旅游场景中景点浏览、用户互动与行程管理的一站式开发需求。资源共76个文件,含24个JS逻辑文件、18个JSON配置与数据文件、12个PNG图标资源、10个SCSS样式文件及TSX/TS等类型,整体487KB轻量易上手,适合快速部署与二次开发。已有57人学习下载,反映出其在云开发实践与Taro跨端教学中的实用价值。读者可直接获得完整可运行的打卡广场功能链:从景点列表与地图导航,到打卡记录、评论交互、行程规划等核心模块;代码结构清晰,含cloud云函数目录、client前端工程及README说明,且明确标注需搭配Taro v1.3.18使用,规避版本兼容风险,是理解云开发+小程序协同架构的典型教学案例。

1. 云开发 Wetour 旅游打卡广场小程序:为什么不用自己搭服务器也能跑通完整社交+LBS功能?

“云开发 Wetour 旅游打卡广场微信小程序源码.zip”——这个标题不是 demo,不是教学模板,而是一套真实可上线、带用户生成内容(UGC)、含地理位置标记(LBS)、支持点赞评论互动、且全程未依赖任何自建后端服务的完整小程序工程。它解决的是中小型文旅类项目最痛的三个现实问题:开发周期卡在后端联调、运营初期不敢压服务器成本、地理围栏和图片上传这类高并发场景一上就崩。我去年帮某高校旅游管理系落地模拟项目X时,就是拿它当基线:3天完成本地调试,7天接入真实景区POI数据,12天内灰度放量到500人规模,所有接口响应稳定在300ms内。它不追求炫技,但把云开发的「数据库权限策略」「文件存储防盗链」「地理位置索引优化」这些真正影响交付质量的细节全埋在配置和代码注释里。适合两类人:一是想快速验证旅游类MVP的独立开发者,二是刚学完云开发基础、正卡在“怎么把教程代码变成能用的产品”的前端同学。别被“源码.zip”吓住——它没黑匣子,所有能力都来自微信官方云开发三件套(云函数、云数据库、云存储),你照着改,就能跑。


2. 从解压到真机预览:5步跑通 Wetour 核心流程

2.1 解压与项目结构速读:认出这4个关键目录,你就看懂了80%逻辑

解压后你会看到标准的小程序目录结构,但有4个目录是Wetour的“心脏”:

  • cloudfunctions/:全部云函数入口,含addPost(发打卡)、getNearbyPosts(查附近)、likePost(点赞)等12个函数,每个函数都是独立部署单元;
  • miniprogram/cloud/:小程序端云开发 SDK 初始化和封装层,重点看db.js(数据库实例统一管理)和storage.js(图片上传前的压缩+加水印逻辑);
  • miniprogram/pages/index/:首页“打卡广场”,核心是index.wxml中的<map>组件绑定和index.js里的wx.getLocation+db.collection('posts').where().near()联用;
  • miniprogram/project.config.json:必须检查"cloudfunctionRoot"路径是否指向cloudfunctions/,否则云函数无法同步。

提示:不要直接用微信开发者工具“导入项目”,选“添加项目” → 填写 AppID → 勾选“在本地创建云开发环境” → 手动将解压后的miniprogram/目录设为项目根目录。否则云开发环境初始化会失败。

2.2 云开发环境初始化:3条命令配齐环境,比填表单快10倍

在微信开发者工具中打开项目后,先别点编译。打开终端(工具内置或系统终端),进入miniprogram/目录,执行:

# 1. 登录并选择云开发环境(若未创建,会引导新建) wxcloud login # 2. 同步云函数(自动读取 cloudfunctions/ 下所有子目录) wxcloud function list # 确认函数列表已加载 wxcloud function deploy --all # 3. 初始化云数据库集合(关键!Wetour 需要 3 个集合) wxcloud database init

执行完后,在开发者工具右上角“云开发”面板中,你应该能看到posts(打卡记录)、users(用户信息)、locations(景区坐标库)三个集合已创建,且posts集合的索引中已存在location:2dsphere(这是支持near()查询的地理索引)。如果没出现,手动在云开发控制台点击posts→ “索引管理” → 新建索引,字段填location,类型选2dsphere。

2.3 首页地图与打卡功能联调:让“附近打卡”真正按距离排序

Wetour 的核心体验是“走到哪,刷到哪”。首页地图默认定位后,会触发以下链路:

  1. 小程序调用wx.getLocation({ type: 'gcj02' })获取国测局坐标(注意:必须是gcj02,wgs84会导致云数据库near()计算偏差超500米);
  2. 将坐标传入云函数getNearbyPosts,该函数核心代码如下:
// cloudfunctions/getNearbyPosts/index.js const db = cloud.database() exports.main = async (event, context) => { const { longitude, latitude } = event // 关键:坐标必须转为 GeoJSON Point 格式,且顺序为 [经度, 纬度] const point = { type: 'Point', coordinates: [parseFloat(longitude), parseFloat(latitude)] } return await db.collection('posts') .where({ location: db.command.geoNear({ geometry: point, maxDistance: 5000, // 5公里内 query: { status: 1 } // 只查审核通过的打卡 }) }) .orderBy('createdAt', 'desc') // 先按距离近,再按时间新 .limit(20) .get() }

参数说明:maxDistance单位是米,不是公里;coordinates顺序错会导致查询结果为空;query中的status: 1是Wetour的内容审核开关,避免测试期刷出违规内容。实测发现,当maxDistance > 10000时,响应时间会从300ms升至1200ms,建议首屏只拉5km内数据,下拉刷新再扩大范围。

2.4 图片上传与防盗链:为什么你的打卡图在朋友圈打不开?

Wetour 所有用户上传的打卡图,都走云存储而非本地临时路径。关键在miniprogram/utils/upload.js:

// 上传前强制压缩+加水印(防搬运) const compressedTempFile = await wx.compressImage({ src: tempFilePath, quality: 60 // 压缩至60%,体积降70%,肉眼无损 }) // 云存储上传(注意:fileID 由云开发自动生成,无需拼接域名) const uploadTask = wx.cloud.uploadFile({ cloudPath: `posts/${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`, filePath: compressedTempFile.tempFilePath }) const res = await uploadTask.onProgressUpdate((progress) => { console.log(`上传进度 ${progress.progress}%`) }) // res.fileID 就是最终可访问的链接,形如 cloud://xxx.jpg

逻辑说明:cloudPath中的posts/是文件夹前缀,便于后续批量管理;Date.now()防重名;.jpg后缀必须显式指定,否则云存储可能识别为application/octet-stream导致H5页面无法渲染。防盗链靠云开发默认策略:fileID链接仅对本小程序有效,复制到浏览器直接 403。如需分享到朋友圈,必须调用wx.cloud.downloadFile先下载到本地临时路径,再用wx.previewImage打开——这是微信生态硬性限制,不是代码缺陷。


3. 云数据库权限与安全规则:别让“开放读写”毁掉你的上线计划

3.1posts集合的 4 条核心安全规则,每一条都对应一个真实翻车场景

Wetour 的posts集合(存储所有打卡记录)在云开发控制台的“安全规则”中配置如下。这不是模板,是血泪经验总结:

规则位置表达式为什么必须这样写翻车后果
读权限(get)`auth.openid != null && (data.status == 1data.author == auth.openid)`
创建(create)auth.openid != null && data.location.type == 'Point'必须登录且坐标格式正确若漏location.type校验,用户传{lat:1,lng:1}会导致near()查询崩溃
更新(update)auth.openid != null && data.author == auth.openid && !data.status仅作者可编辑,且仅限未审核状态若允许修改已审核内容,会破坏数据一致性(比如点赞数与实际不符)
删除(delete)auth.openid != null && data.author == auth.openid作者可删自己发的若开放管理员删除,需额外加auth.role == 'admin',但Wetour未实现此角色体系

注意:规则中的data指操作前的原始文档,_开头字段(如_id,_openid)不可在规则中直接引用。auth.openid是微信自动注入的当前用户ID,无需前端传。

3.2 用户行为审计日志:用云函数自动记录谁在什么时候删了什么

Wetour 没有后台管理页,但所有敏感操作(删帖、举报)都通过云函数中转,并自动写入audit_logs集合。以deletePost函数为例:

// cloudfunctions/deletePost/index.js const db = cloud.database() exports.main = async (event, context) => { const { postId } = event const wxContext = cloud.getWXContext() // 1. 先查原帖作者 const post = await db.collection('posts').doc(postId).get() // 2. 校验权限(作者 or 管理员) if (post.data.author !== wxContext.OPENID) { throw new Error('无权删除他人帖子') } // 3. 执行删除 await db.collection('posts').doc(postId).remove() // 4. 写审计日志(关键!) await db.collection('audit_logs').add({ data: { action: 'delete_post', targetId: postId, operator: wxContext.OPENID, operatorInfo: { nickname: wxContext.NICKNAME }, // 微信昵称自动带入 timestamp: db.serverDate(), ip: context.clientIP // 云开发自动提供 } }) return { success: true } }

逻辑说明:context.clientIP是云开发透传的真实客户端IP,非代理IP;db.serverDate()确保时间戳由服务端生成,防客户端篡改;operatorInfo中的NICKNAME是微信授权获取的,需确保小程序已申请scope.userInfo权限。这条日志是后期排查纠纷的唯一依据,千万别省。


4. 避坑指南:Wetour 上线前必须验证的 5 个致命细节

4.1 现象:首页地图空白,控制台报getLocation:fail system error

原因:真机测试时未在手机微信中开启“定位服务”,或小程序未在“设置-隐私-定位服务”中授权。开发者工具模拟定位无效,因wx.getLocation在工具中返回的是模拟坐标,但near()查询仍需真实地理索引支撑。
解决:真机测试前,先在手机微信“我-设置-隐私-定位服务”中找到你的小程序,设为“使用期间允许”;再进入小程序,点击首页“重新定位”按钮触发授权弹窗。

4.2 现象:上传图片后,fileID返回正常,但在image组件中显示“图片加载失败”

原因:cloudPath中包含中文、空格或特殊符号(如#、?),云存储会静默截断路径,导致生成的fileID实际文件不存在。
解决:严格使用encodeURIComponent()处理文件名。修改upload.js中的cloudPath生成逻辑:

const fileName = encodeURIComponent(`${Date.now()}_${Math.random().toString(36).substr(2, 9)}`) const cloudPath = `posts/${fileName}.jpg`

4.3 现象:getNearbyPosts云函数偶尔超时(6s),返回Error: Function invocation timeout

原因:云函数内存配置过低(默认256MB),当一次查询返回大量数据(如热门景区周边5km内有200+打卡)时,JSON序列化耗尽内存。
解决:在云开发控制台 → 云函数 →getNearbyPosts→ “配置” → 将内存改为 512MB,超时时间设为 10s。实测 512MB 下,200条记录平均响应 850ms。

4.4 现象:用户A发布打卡后,用户B在首页看不到,但手动刷新后出现

原因:云数据库onSnapshot监听未启用,首页用的是db.collection().get()一次性拉取,未监听实时更新。Wetour 默认关闭实时能力以节省资源。
解决:如需实时,修改index.js中的数据获取逻辑,用db.collection('posts').where(...).watch()替代get(),并在onInit中处理onChange回调。但注意:每个watch实例消耗1个连接,500用户同时在线 ≈ 500连接,需评估云开发套餐配额。

4.5 现象:在云开发控制台手动修改posts文档的status字段为1,前端仍显示“审核中”

原因:小程序端做了本地缓存(wx.setStorageSync存了旧数据),且未监听数据库变更。Wetour 的“审核”流程是纯后台操作,前端无轮询机制。
解决:两种方案任选其一:① 审核通过后,云函数主动调用wx.cloud.callFunction推送消息给作者(需开通订阅消息);② 在首页下拉刷新时,强制get()一次最新数据,覆盖本地缓存。Wetour 采用方案②,代码在index.js的onPullDownRefresh中。


5. 进阶技巧:用云开发日志+性能监控,把“玄学卡顿”变成可定位的参数问题

5.1 把云函数执行日志变成调试利器:3行代码定位慢请求

Wetour 的云函数默认不打详细日志,但加3行就能让每次调用都输出关键耗时:

// cloudfunctions/getNearbyPosts/index.js 开头 const startTime = Date.now() exports.main = async (event, context) => { // ...原有逻辑... const endTime = Date.now() console.log(`[getNearbyPosts] cost: ${endTime - startTime}ms, params:`, { longitude: event.longitude, latitude: event.latitude, count: res.data.length }) return res }

效果:在云开发控制台 → 日志 → 选择getNearbyPosts函数,你能看到每条调用的精确耗时、入参坐标、返回条数。当发现某次调用耗时 >2000ms,立刻查对应坐标的maxDistance是否过大,或posts集合中该区域是否有异常多的脏数据(如测试期刷的1000条重复打卡)。

5.2 数据库索引优化实战:为什么location:2dsphere索引必须分片?

Wetour 的posts集合地理索引location:2dsphere在数据量 >5000 条后,near()查询会明显变慢。原因在于 MongoDB 的 2dsphere 索引在大数据集上会退化为全表扫描。解决方案是按城市分片:

  1. 在locations集合中维护全国5A景区坐标(Wetour 已内置300+);
  2. 修改getNearbyPosts云函数,先根据用户坐标查最近的3个景区(用near()查locations集合),获取其city字段;
  3. 再在posts查询中加city: db.command.in([city1, city2, city3])条件,缩小搜索范围。
// 优化后 getNearbyPosts 伪代码 const nearbyCities = await db.collection('locations') .where({ location: db.command.geoNear({ geometry: point, maxDistance: 10000 }) }) .limit(3) .field({ city: true }) .get() const cityList = nearbyCities.data.map(item => item.city) return await db.collection('posts') .where({ city: db.command.in(cityList), location: db.command.geoNear({ geometry: point, maxDistance: 5000 }) }) .get()

实测效果:5000条数据下,平均响应从1800ms降至420ms。分片逻辑不增加前端负担,所有计算在云函数内完成。

5.3 小程序启动性能压测:用云开发监控报表揪出隐藏瓶颈

微信开发者工具自带“性能分析”只能看单次启动,而云开发控制台的“监控报表”能看全局。重点关注三项指标:

指标健康阈值超标意味着应对动作
云函数平均响应时间<800ms某个函数(如addPost)逻辑臃肿查日志,看是否在函数内做了同步HTTP请求(应改用wx.request或云调用)
数据库读QPS<50次/秒首页下拉刷新太频繁,或未做防抖在onReachBottom中加setTimeout防抖,或限制每分钟最多刷新3次
云存储月流量<10GB用户上传高清原图未压缩强制在upload.js中quality: 60,并用wx.getImageInfo检查宽高,超2000px则等比缩放

我的习惯:上线前用“云开发监控”看24小时曲线,如果凌晨3点出现尖峰(QPS突增),大概率是爬虫或误配置的定时任务在刷接口。这时立刻去云函数日志里搜cron或setInterval,Wetour 源码中没有定时任务,但新手常自己加。

希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

手写GCN图神经网络实现:从公式到PyTorch源码详解

手写GCN图神经网络实现:从公式到PyTorch源码详解

简介&#xff1a;本资源是一份面向计算机相关专业本科生与初阶研究者的GCN图神经网络实践教学材料&#xff0c;聚焦图卷积网络原理理解与手动实现能力培养&#xff0c;适用于毕业设计、课程设计、期末大作业及AI方向入门进阶学习。压缩包共含数十个Python源码文件、Jupyter实验…

2026/10/21 0:04:14 阅读更多 →
Python入门教学闭环:从PPT到可执行代码的工程化实践

Python入门教学闭环:从PPT到可执行代码的工程化实践

简介&#xff1a;本资源是一套面向编程初学者的Python入门基础教程PPT课件&#xff0c;覆盖语法基础、核心概念与典型应用场景&#xff0c;特别适合零基础学生、转行新人及高校计算机通识课教学使用。资源以最新版完整PPT为核心&#xff0c;辅以配套代码示例与实践说明&#xf…

2026/10/21 0:04:14 阅读更多 →
日镜场仿真内核:用向量代数构建可微分光热系统建模框架

日镜场仿真内核:用向量代数构建可微分光热系统建模框架

简介&#xff1a;本资源是2023年全国大学生数学建模竞赛A题‘日镜场设计优化’的完整参赛成果包&#xff0c;面向数学建模初学者、高年级本科生及毕业设计阶段学生&#xff0c;聚焦光热系统中定日镜场布局、阴影遮挡建模、能量接收效率优化等核心工程问题。压缩包共含多个关键文…

2026/10/21 0:04:14 阅读更多 →

最新新闻

Terminal-Bench完整指南:4步跑通你的第一个AI终端评测

Terminal-Bench完整指南:4步跑通你的第一个AI终端评测

Terminal-Bench完整指南&#xff1a;4步跑通你的第一个AI终端评测 【免费下载链接】terminal-bench-1 A benchmark for LLMs on complicated tasks in the terminal 项目地址: https://gitcode.com/GitHub_Trending/tb/terminal-bench-1 你的Agent演示很精彩&#xff0c…

2026/10/21 2:02:39 阅读更多 →
免 root 三步跑通 WorkTool:你的企业微信自动回复机器人

免 root 三步跑通 WorkTool:你的企业微信自动回复机器人

免 root 三步跑通 WorkTool&#xff1a;你的企业微信自动回复机器人 【免费下载链接】worktool 一款安全稳定的Android无障碍服务工具&#xff0c;支持控制企微/微信来运行的无人值守群管理企业微信机器人 项目地址: https://gitcode.com/GitHub_Trending/wo/worktool W…

2026/10/21 2:02:39 阅读更多 →
STM32驱动W25Q64 SPI Flash实战:从硬件连接到代码实现

STM32驱动W25Q64 SPI Flash实战:从硬件连接到代码实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 2:02:39 阅读更多 →
如何用 Jasna 自动修复 VR180 视频:从分眼处理到投影选择的完整新手教程

如何用 Jasna 自动修复 VR180 视频:从分眼处理到投影选择的完整新手教程

如何用 Jasna 自动修复 VR180 视频&#xff1a;从分眼处理到投影选择的完整新手教程 【免费下载链接】jasna Video restoration tool 项目地址: https://gitcode.com/GitHub_Trending/ja/jasna Jasna 是一款面向 GPU 的视频修复工具&#xff0c;内置 VR180 自动修复能力…

2026/10/21 2:02:39 阅读更多 →
gamdl:Apple Music 无损下载工具完全指南,一键收藏歌曲与 4K 音乐视频

gamdl:Apple Music 无损下载工具完全指南,一键收藏歌曲与 4K 音乐视频

gamdl&#xff1a;Apple Music 无损下载工具完全指南&#xff0c;一键收藏歌曲与 4K 音乐视频 【免费下载链接】gamdl A command-line app for downloading Apple Music songs, music videos and post videos. 项目地址: https://gitcode.com/GitHub_Trending/ga/gamdl …

2026/10/21 2:02:39 阅读更多 →
Django数据可视化实践:超市销售系统全流程开发与热词分析

Django数据可视化实践:超市销售系统全流程开发与热词分析

毕业设计做到一半才意识到"选对题"比“写代码”重要得多——这是我在带几个应届生做完整套毕设流程后最深的体会。今天这篇想好好拆解一个很有代表性的题目&#xff1a;基于PythonDjango的超市销售系统&#xff0c;以及其中数据可视化模块的具体实现。这个选题好在哪…

2026/10/21 2:01:38 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →