宠物咖啡馆这几年在一二线城市真的很火但真正把“咖啡点单”和“宠物互动预约”打通的管理系统其实并不多。我手上这套基于SpringBootVueMyBatisMySQL的宠物咖啡馆平台管理系统就是专门来解决这种线下融合业态的管理痛点用户端能在线看菜单、下单、预约撸猫时段管理端能管宠物档案、库存、会员和订单报表。整条链路从前端页面到后端接口全部打通适合正在做全栈练习的开发者也适合想给实体咖啡馆做信息化改造的团队参考。这篇文章我会从技术选型、数据库设计、核心代码实现到部署排查一条龙拆解不绕弯子全部是能直接落地的东西。1. 项目整体设计与技术选型拆解1.1 为什么选择“SpringBootVueMyBatisMySQL”这套组合先说后端。SpringBoot现在是Java后端的绝对主流它对第三方组件的整合能力极强内嵌Tomcat之后连部署都省心。做这种中小型管理系统要求的是快速迭代、稳定可靠SpringBoot的自动装配机制和生态成熟度天然匹配。再加上RESTful API的开发模式前端后端分离以后各改各的互不干扰。MyBatis的选择有一层现实考量宠物咖啡馆这种业务涉及的查询逻辑不复杂但变化频繁比如不同时段的预约人数统计、咖啡销量排行、会员储值明细每种需求对应的SQL都不太一样。MyBatis把SQL写在XML里改起来不用重新编译Java代码这个灵活性在业务调整频繁的场景里特别有用。而且它不屏蔽SQL本身开发者对每一条查询都心里有数排查性能问题也更直接。数据库用MySQL就不用多解释了开源免费、文档丰富、国内使用面最广。宠物咖啡馆的数据量级别是中小型的单库单表完全扛得住根本不需要过早引入分布式中间件来增加复杂度。整套技术栈的逻辑是用最稳妥的组合覆盖80%的业务场景剩下20%的个性化需求通过MyBatis的SQL可控性和Vue的组件化来兜底。1.2 核心业务模块与功能边界划分整个系统拆成用户端和管理端两条线端口分开、权限分开、数据通过接口互通。用户端围绕的是一个普通顾客进店前后的完整动线菜单浏览与咖啡点单展示咖啡、甜点、宠物零食等商品支持加入购物车、统一下单。宠物互动预约选择时间段预约与店内的猫狗互动核心是时段名额控制防止某个时段人太多影响宠物状态。会员中心注册登录、储值、消费记录查询、会员等级展示。订单管理查看历史订单、订单状态跟踪待付款、已付款、进行中、已完成。管理端对应的是门店运营人员的日常操作界面宠物档案管理每只宠物的名字、品种、年龄、疫苗状态、注意事项支持图片上传。商品管理上下架咖啡饮品、宠物零食、周边商品维护库存和价格。预约管理查看所有时段的预约情况支持手动取消或改期。会员管理客户列表、储值记录、消费频次标签。订单管理订单列表、发货/核销操作、退款处理。数据看板当日营业额、热门商品、宠物接客频次统计。模块划分的时候有一个关键原则用户端页面要轻、管理端要全、两端共用同一套后端接口但通过角色权限做隔离。这样将来如果想给用户端做小程序版本后端接口基本不用动只需要重新写一套视图层。1.3 业务流程梳理与状态机设计业务流程是最容易写出问题的地方尤其是订单和预约这两个核心链路。订单流程我设计成五个状态待付款、待制作、已完成、已取消、已退款。用户下单后系统自动生成订单号前端倒计时15分钟未付款自动关闭。支付成功后状态变为待制作后台操作员点击开始制作可以加一个“制作中”的中间状态但对接到咖啡机等IoT设备时再扩展也不迟。咖啡做好后进行核销操作状态置为已完成这时才真正扣减库存。预约流程会稍微复杂一点因为涉及到人与动物的互动。我设计了“预约提交-待确认-已确认-已完成-已取消”五个状态。用户提交预约后不直接占用名额需要门店手工确认确认时系统检查当前时段剩余名额名额足够才置为已确认。这里有个细节MySQL的事务隔离级别默认是可重复读如果并发情况下两个用户同时预约同一个时段可能会导致超卖所以在确认预约时用了SELECT FOR UPDATE来锁定时段记录确保同一时刻只有一个请求能通过名额校验。这样做的好处是状态流转路径清晰前后端联调时对接口的预期一致后续接消息通知比如微信模板消息也能在状态变更点挂上钩子。2. 功能落地用户端与管理端的关键实现2.1 用户点单与购物车的接口设计先说购物车。购物车本质上是一个临时数据容器可以用前端状态管理Vuex/Pinia实现也可以用后端表存起来。我的建议是登录用户购物车存后端游客购物车存前端。原因很简单如果用户换设备登录后端购物车能直接同步过来如果是游客后端存了也无法识别身份反而留下脏数据。后端购物车表核心字段就这些cart_id、user_id、product_id、quantity、checked。接口设计成四个就够了RestController RequestMapping(/api/cart) public class CartController { GetMapping(/list) public ResultListCartVO getCartList(RequestParam Long userId) { return Result.success(cartService.getCartList(userId)); } PostMapping(/add) public ResultVoid addToCart(RequestBody CartAddDTO dto) { cartService.addToCart(dto); return Result.success(); } PutMapping(/update) public ResultVoid updateCart(RequestBody CartUpdateDTO dto) { cartService.updateQuantity(dto.getCartId(), dto.getQuantity()); return Result.success(); } DeleteMapping(/remove/{cartId}) public ResultVoid removeFromCart(PathVariable Long cartId) { cartService.removeFromCart(cartId); return Result.success(); } }这里面的一个细节是商品价格不能直接从前端传否则用户可以改价格下单。正确做法是前端只传product_id和quantity后端实时从数据库读当前价格再计算小计和总价。我见过不少项目在这里偷懒结果被薅羊毛属于典型的低级事故。点单提交时还要做一个库存预扣防止用户下单了但咖啡豆不够用。预扣的意思是下单时不真正扣库存只锁定一定数量订单超时未支付再释放。我加了一个定时任务每分钟扫描一次超过15分钟未支付的订单把预扣库存加回来保证库存数据的准确性。2.2 宠物互动预约的时段与名额控制宠物互动预约是整个系统里最需要细心设计的模块它直接关系到店铺的接待能力和宠物福利。我设计的预约规则每天分成若干个时段10:00-11:30、13:00-14:30、15:00-16:30等每个时段每只宠物最多接待3位顾客。这个数字纯粹是从运营角度定的猫狗接客太多会烦躁少了大不赚钱。核心表结构是pet_appointment_slots记录每个时段的基础信息每天提前生成未来7天的时段记录CREATE TABLE pet_appointment_slots ( slot_id BIGINT PRIMARY KEY AUTO_INCREMENT, pet_id BIGINT NOT NULL COMMENT 宠物ID, slot_date DATE NOT NULL COMMENT 可预约日期, start_time TIME NOT NULL COMMENT 开始时间, end_time TIME NOT NULL COMMENT 结束时间, total_quota INT NOT NULL DEFAULT 3 COMMENT 总名额, booked_count INT NOT NULL DEFAULT 0 COMMENT 已预约人数, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-可预约 0-已关闭, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_pet_slot (pet_id, slot_date, start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;预约接口我用了乐观锁来防并发超卖SQL是这样写的update idbookSlot UPDATE pet_appointment_slots SET booked_count booked_count 1, version version 1 WHERE slot_id #{slotId} AND booked_count lt; total_quota AND version #{version} /update执行后判断影响行数如果为0说明名额已满或者version不匹配直接返回“该时段已被约满”。这里使用version字段做乐观锁配合更新条件里的booked_count total_quota双保险。实测下来高并发场景下偶尔会有几个请求被拒绝但不会有超卖符合实际业务预期。2.3 管理端订单处理与库存联动管理端订单列表我用了分页加多条件筛选的组合支持按订单号、用户手机号、订单状态、日期区间查询。前端用一个搜索表单后端对应一个动态SQL查询。核心的订单确认接口要做三件事更新订单状态、扣减实际库存、生成操作日志。这三件事必须在同一个事务里完成否则就会出现库存扣了但订单状态没更新或者订单显示已完成但库存对不上的情况。Transactional(rollbackFor Exception.class) public void confirmOrder(Long orderId) { Order order orderMapper.selectById(orderId); if (order null || !待制作.equals(order.getStatus())) { throw new BusinessException(订单状态异常无法确认); } // 扣减库存 ListOrderItem items orderItemMapper.selectByOrderId(orderId); for (OrderItem item : items) { int affected productMapper.deductStock(item.getProductId(), item.getQuantity()); if (affected 0) { throw new BusinessException(商品库存不足 item.getProductName()); } } // 更新订单状态 orderMapper.updateStatus(orderId, 制作中); // 记录日志 operationLogMapper.insert(new OperationLog(确认订单, orderId)); }事务回滚的时候要特别小心业务异常必须抛出RuntimeException或者自定义异常rollbackFor要设置为Exception.class否则Spring默认只在RuntimeException时回滚。我现在写代码基本都习惯在Transactional注解上显式指定rollbackFor减少踩坑的概率。3. 数据库设计与MyBatis实战细节3.1 核心表结构设计思路数据库设计在这个项目里是生命线。我建了这些核心表用户表、会员卡表、商品表、购物车表、订单表、订单明细表、宠物表、预约时段表、预约记录表、库存流水表、操作日志表。订单表和订单明细表的关系是一对多设计上必须分开因为一张订单可能包含多杯咖啡、几份甜点。订单表存的是订单级别的汇总数据订单号、总金额、状态、支付时间订单明细表存的是每个商品项的数量、单价、小计。会员与用户表我选择分开设计。user表解决的是登录认证member表解决的是会员业务储值余额、积分、等级。拆开的理由并不是所有注册用户都会充值成为会员混在一张表里会有一堆冗余空字段。member表通过user_id外键关联没有会员身份的用户查询时left join出来直接就是null。宠物表的设计要考虑运营维度除了基本信息名字、品种、年龄外我加了health_status健康状况、temperament性格特点、daily_max_visitors每日最大接待人数这些字段。认真做的话还能加上feeding_record喂食记录、vaccine_expiry_date疫苗到期日这种养护类字段能提升系统的专业感。3.2 MyBatis分页插件与缓存配置分页是老生常谈的问题但这个项目里一定要用对。我集成的是PageHelper理由很简单使用成本极低侵入性小。dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency调用方式是在Mapper查询方法前一行设置分页参数public PageResultOrderVO getOrderPage(OrderQueryDTO query, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListOrderVO list orderMapper.selectOrderList(query); PageInfoOrderVO pageInfo new PageInfo(list); return PageResult.of(pageInfo); }分页插件有几个容易犯的错我把自己的经验写出来PageHelper.startPage只对紧接着的下一条查询生效查询完要立即获取结果中间不能穿插其他SQL操作否则分页会失效或者分页到错误的查询上。如果Mapper方法里有foreach批量插入或者多个select语句分页可能被意外吞掉建议这种复杂查询自己手写LIMIT。查询结果要用PageInfo来包装它能拿到total、pageNum、pageSize、pages这些分页元数据前端的分页组件直接对接即可。MyBatis的缓存我建议把二级缓存默认关掉理由跟很多资深开发者的观点一致缓存失效条件不好控制尤其是有联表查询的场景一张表更新后关联表缓存无法自动清除很容易查出脏数据。一级缓存是SQLSession级别的在Spring管理下每次请求都会新建SQLSession所以一级缓存的意义也不大。这个项目里的缓存主要靠Redis解决页面上频繁查询且变化不频繁的数据比如热门商品列表直接Cache Aside模式写入Redis。3.3 高频SQL的性能优化与慢查询排查开发阶段我开了MySQL的慢查询日志标准设置为超过1秒的记录同时配合MyBatis的日志打印把所有SQL输出到控制台开发时用眼睛扫就能发现很多写得很难看的SQL。# application.yml 中的 MyBatis 日志配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl慢查询的典型案例是订单列表联表查询。订单表、订单明细表、用户表、商品表四表联查如果没有索引数据量一旦过万就会明显变慢。我给所有外键字段都建了索引order_id、product_id、user_id、pet_id、slot_id。还有一个容易被忽略的点状态和时间范围查询一定要建联合索引比如(status, create_time)这样按状态过滤后再按时间排序才能用上索引。之前我遇到过一个UPDATE执行慢的问题排查发现是更新条件里的字段没有索引导致全表扫描。UPDATE虽然不产生结果集但WHERE子句的匹配一样要扫描加索引后执行时间从300ms降到了5ms以内。还有一点批量更新的时候不要用循环一条条UPDATE而是用CASE WHEN的方式拼一条SQL批量更新虽然SQL看起来长一些但数据库只需要解析一次性能和日志量都会好很多。4. 前端Vue实现与环境部署细节4.1 Vue3环境搭建与依赖管理前端我选的是Vue3 Vite Element Plus的组合。Vite启动速度快开发体验比Webpack时代舒服太多了。环境搭建的几个核心步骤# 使用 Vite 初始化项目 npm create vitelatest pet-cafe-web -- --template vue # 进入项目目录并安装依赖 cd pet-cafe-web npm install # 安装路由、状态管理、UI组件库、HTTP库 npm install vue-router4 pinia element-plus axios这里提醒一句Node版本不能太低我用的是Node 18Vite 5要求Node版本18。如果你还在用Node 14建议直接升级到18或20长期支持版本否则装依赖的时候会报一堆engine提示甚至直接安装失败。Vite的代理配置要特别留意开发环境下前端和后端端口不同跨域问题靠Vite的server.proxy解决而不是后端开启CORS。开发时配置代理生产环境用Nginx做反向代理前后端都部署在同一个域名下从根上规避跨域// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })4.2 页面组件拆分与路由配置Vue项目最忌讳一个页面塞几百行代码。我把页面粒度拆到“一个业务功能一个页面”比如订单模块拆成订单列表页、订单详情页、订单创建页每个页面里的弹窗表单再拆成子组件。路由配置用了懒加载按需加载页面资源避免首屏一次性加载所有体积过大的JS文件const routes [ { path: /, component: () import(/layouts/MainLayout.vue), children: [ { path: /dashboard, component: () import(/views/dashboard/index.vue) }, { path: /orders, component: () import(/views/order/OrderList.vue) }, { path: /pets, component: () import(/views/pet/PetList.vue) }, { path: /login, component: () import(/views/login/index.vue) } ] } ]路由守卫里要做两件事未登录的用户统一跳到登录页已经登录但访问了无权限页面时跳转404页。判断逻辑写在router.beforeEach里从Pinia或localStorage中读取token没有token直接next(/login)。状态管理用Pinia代替了Vuex接口请求我统一封装成request工具类基于axios实例创建设置baseURL、超时时间加请求拦截器带token响应拦截器统一处理错误码。用户登录后token保存在localStorage每次请求自动携带在Authorization的请求头里。4.3 数据可视化与视频流播放的落地管理端的数据看板我用了ECharts实现画了当日营业额趋势折线图、商品销量排行柱状图、各时段预约人数热力图。ECharts和Vue的集成方式很简单npm安装echarts组件里用ref绑定DOM节点在onMounted里初始化图表。需要注意的一个坑是ECharts实例在组件销毁时一定要调用dispose()方法释放资源否则页面频繁切换会出现内存泄漏页面越来越卡。另外图表数据最好在接口返回后统一format成ECharts需要的格式不要在option里做复杂转换可维护性差。宠物展示页的视频播放是个加分项。咖啡馆可以在店里装摄像头把宠物的实时画面传到系统里给用户看。在Web端播放m3u8格式的视频流我用的方案是hls.js库如果你不想额外引入库新版Safari浏览器原生支持m3u8播放但Chrome和Firefox都必须借助hls.js。template video refvideoRef controls autoplay muted/video /template script setup import { ref, onMounted } from vue; import Hls from hls.js; const videoRef ref(null); onMounted(() { if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(https://your-stream-url/live/pet-room.m3u8); hls.attachMedia(videoRef.value); hls.on(Hls.Events.MANIFEST_PARSED, () { videoRef.value.play(); }); } }); /script实测下来hls.js的播放延迟大约在3-8秒用于店内的宠物实时展示完全够用。如果需要低延迟可以换WebRTC方案但服务器配置和技术复杂度会成倍增加不是这种管理系统的重点。5. 部署上线与问题排查实录5.1 前后端分离的生产环境部署流程部署这块我自己走了一遍完整的流程从裸机到能访问大概半小时。服务器配置是2核4G的云主机装的CentOS 7.9MySQL和Nginx用包管理器安装Java环境用OpenJDK 17。后端打包和启动# 跳过测试打包 mvn clean package -DskipTests # 启动 jar 包指定生产环境配置 nohup java -jar pet-cafe-server.jar --spring.profiles.activeprod logs/app.log 21 生产环境的配置文件里我做了几项调整数据库连接加了时区参数serverTimezoneAsia/Shanghai连接池最大连接数设置为20Redis如果有的话也配置上日志级别调整为INFO避免生产环境打印大量DEBUG日志拖慢性能。前端构建npm run build构建产物在dist目录下我直接把它上传到服务器的/usr/share/nginx/html目录然后在Nginx配置里加反向代理和前端路由的重写规则server { listen 80; server_name pet-cafe.example.com; root /usr/share/nginx/html; index index.html; # 前端路由 history 模式配置 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源缓存策略 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { expires 7d; add_header Cache-Control public, max-age604800; } }这里最关键的配置是try_files $uri $uri/ /index.html;。因为Vue Router使用history模式用户在订单详情页刷新时Nginx直接按文件路径找肯定找不到对应的物理文件必须把所有路径都重写到index.html再由前端路由接管。5.2 上线后遇到的典型问题与排查方法我把上线后这段时间踩过的比较有代表性的坑整理成了一张速查表方便你遇到同类问题时直接对照问题表现排查思路解决方案页面能打开但接口全部报404检查Nginx的location /api代理规则是否生效确认proxy_pass的target地址和后端端口是否一致用curl直接访问后端接口验证数据库连接超时报Too many connections连接池配置过大或连接未释放减小连接池大小加Druid/ HikariCP的泄漏检测参数预约时段名额超卖并发校验逻辑有竞态条件加乐观锁或SELECT FOR UPDATE重测并发场景前端页面刷新后白屏路由模式与Nginx配置不匹配检查try_files配置history模式必须重写到index.html上传宠物图片后访问403图片文件权限不对检查静态资源目录的读写权限或用Nginx的alias指定图片目录用户支付成功但订单状态未更新支付回调接口被防火墙拒绝在Nginx中放行支付平台回调的URL不要用全局鉴权拦截还有一个非常容易坑人的点生产环境用HTTPS后如果页面里加载的接口还是http协议浏览器会直接拦截混合内容。统一把API地址改成https或者采用相对路径发起请求让浏览器自动继承页面协议。另外我在生产环境开启了Sentry前端错误监控用户端报错的时候能第一时间看到堆栈信息而不是等用户打电话来反馈“页面打不开”。后端日志用logback滚动策略每天一个文件保留30天磁盘占用也不会太大。5.3 初始化脚本与快速复现指南最后分享一个让整套系统快速跑起来的方法。我在发布时把初始化的SQL脚本整理成init.sql包含了建库、建表、基础数据插入、测试账号创建。拿到的同学只需要三件事就能跑起来# 1. 初始化数据库执行 init.sql含建库语句和测试数据 mysql -uroot -p init.sql # 2. 启动后端确认 application-prod.yml 中数据库账号密码改为自己的 mvn spring-boot:run -Dspring-boot.run.profilesprod # 3. 启动前端确认 .env.production 中的接口地址正确 npm run dev测试账号我预置了两个一个管理员admin/admin123一个普通用户test/test123。前端登录后根据角色自动渲染对应菜单管理端能看到完整的管理功能普通用户只能看自己的点单和预约记录。数据库的初始化脚本里我还加了索引优化和少量视图。比如订单金额统计的报表可以用视图实现但考虑到MySQL视图的物化性能和调试难度最终报表还是用临时表聚合查询直接做的逻辑更透明出了问题也好排查。写在最后的几句实在话开发这套系统前前后后花了大概三周踩过的坑比我原以为的多不少。从最初的需求设计到数据库建表再到前后端联调和部署上线每一个环节都有值得复盘的地方。如果你是想学习全栈开发、做毕业设计或者给线下咖啡馆做信息化改造这套系统的技术栈和业务复杂程度刚好合适——既有普通增删改查又有预约并发控制、订单状态机、报表统计这类稍有挑战性的点。个人最大的感受是很多项目不是难在技术本身而是难在业务细节的考量和数据边界的约束。比如预约机制里人和宠物的双向保护订单和库存的一致性维护这些东西如果开发前没有想清楚写代码的时候就会反复推翻重来。把这个项目的业务逻辑吃透比单纯背十个框架面试题有用得多。
企业数字化 ERP 产品动态
相关推荐
STM32开发踩坑实录:从环境搭建到硬件调试的完整指南 1. 入坑前的第一课:开发环境和工具链的坑,比芯片本身还多很多人拿到STM32开发板,第一反应是赶紧写代码点亮LED。但真正让我在项目初期消耗大量时间的,反而不是代码本身,而是开发环境的搭建和工具链的配置。这篇文章的记… · 2026/9/24 23:33:39
palera1n 越狱教程:A8–A11 设备越狱到底要几步? palera1n 越狱教程:A8–A11 设备越狱到底要几步? 【免费下载链接】palera1n Jailbreak for A8 through A11, T2 devices, on iOS/iPadOS/tvOS 15.0, bridgeOS 5.0 and higher. 项目地址: https://gitcode.com/GitHub_Trending/pa/palera1n
你手里… · 2026/9/24 23:33:39
8位SAR ADC设计全程:从D触发器到Cadence仿真实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:48
广东省地级市SVG地图:从解压解析到交互可视化的完整实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:47
STM32 HAL库SBUS协议解析:DMA循环接收+IDLE中断+状态机实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:47
AD常用元器件库中英文对照表:74LS与CD4000系列速查指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:47
S905L3刷机深度指南:芯片级原理与实操避坑 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:47
IMU静态外参标定:6D刚性变换求解与工程实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:01:41
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37