SpringBoot2+Vue3全栈电商系统开发实践
1. 项目概述欢迪迈手机商城的技术架构解析这个基于SpringBoot2Vue3MyBatis-PlusMySQL8.0的手机商城系统是我最近完成的一个全栈电商项目。整套系统采用前后端分离架构后端基于SpringBoot2框架提供RESTful API前端使用Vue3构建响应式界面数据持久层采用MyBatis-Plus简化数据库操作MySQL8.0作为底层数据存储。这种技术组合在当前Java Web开发领域非常主流特别适合中小型电商系统的快速开发。从实际开发体验来看这套技术栈有几个突出优势首先SpringBoot2的自动配置和起步依赖让后端服务搭建变得极其高效其次Vue3的Composition API相比Options API在复杂业务场景下代码组织更清晰再者MyBatis-Plus的Wrapper条件构造器让数据库操作代码量减少50%以上最后MySQL8.0的窗口函数、CTE等新特性在复杂报表查询时优势明显。2. 技术选型与核心组件分析2.1 SpringBoot2后端框架设计选择SpringBoot2.7.3版本作为基础框架主要考虑其成熟的生态和良好的长期支持。在项目初始化时通过start.spring.io生成的骨架项目中我特别添加了以下关键依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.2/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency注意SpringBoot2默认使用Tomcat作为内嵌容器如果考虑性能优化可以替换为Undertow实测QPS能提升15%左右在Bean管理方面针对商城业务特点我采用了混合作用域策略控制器(Controller)和服务层(Service)保持默认的单例模式购物车相关的Bean使用原型(prototype)作用域通过Scope(prototype)注解实现定时任务使用EnableScheduling开启配合Async实现异步执行2.2 Vue3前端工程化实践前端采用Vue3.2组合式API开发项目初始化时选择了Vite作为构建工具相比Webpack启动速度提升明显。工程结构组织如下src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具函数 └── views/ # 页面组件在组件开发中大量使用了script setup语法糖配合TypeScript类型系统代码可维护性显著提高。例如商品列表组件script setup langts import { ref, onMounted } from vue import { getProductList } from /api/product interface Product { id: number name: string price: number stock: number } const products refProduct[]([]) const loading ref(false) onMounted(async () { loading.value true try { products.value await getProductList() } finally { loading.value false } }) /script2.3 MyBatis-Plus高效数据访问MyBatis-Plus 3.5.2版本在本项目中发挥了巨大作用主要体现在通用Mapper自动生成基础CRUD方法Lambda表达式构建条件查询分页插件自动处理物理分页自动填充创建/修改时间等字段典型的产品查询服务实现Service public class ProductServiceImpl extends ServiceImplProductMapper, Product implements ProductService { Override public PageProduct queryByCondition(ProductQueryDTO dto) { return lambdaQuery() .like(StringUtils.isNotBlank(dto.getName()), Product::getName, dto.getName()) .ge(dto.getMinPrice() ! null, Product::getPrice, dto.getMinPrice()) .le(dto.getMaxPrice() ! null, Product::getPrice, dto.getMaxPrice()) .eq(dto.getCategoryId() ! null, Product::getCategoryId, dto.getCategoryId()) .page(new Page(dto.getPageNum(), dto.getPageSize())); } }经验在application.yml中配置mybatis-plus.mapper-locations时建议使用classpath*:mapper/**/*.xml模式这样可以扫描到所有模块的Mapper文件2.4 MySQL8.0数据库设计数据库使用MySQL8.0.28主要利用了以下新特性窗口函数实现销售排名统计CTE(Common Table Expressions)简化复杂查询JSON字段类型存储商品扩展属性原子DDL保证表结构变更的安全性核心表结构设计示例CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 商品名称, price decimal(10,2) NOT NULL COMMENT 销售价, market_price decimal(10,2) DEFAULT NULL COMMENT 市场价, cost_price decimal(10,2) DEFAULT NULL COMMENT 成本价, stock int NOT NULL DEFAULT 0 COMMENT 库存, category_id bigint DEFAULT NULL COMMENT 分类ID, specs json DEFAULT NULL COMMENT 规格参数, status tinyint NOT NULL DEFAULT 1 COMMENT 状态, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;3. 核心功能模块实现3.1 商品管理模块商品管理采用树形分类结构前端使用el-tree组件展示后端实现递归查询。关键技术点包括分类层级处理public ListCategoryTreeVO buildCategoryTree(ListCategory allCategories) { MapLong, ListCategory parentIdMap allCategories.stream() .collect(Collectors.groupingBy(Category::getParentId)); return buildTree(0L, parentIdMap); } private ListCategoryTreeVO buildTree(Long parentId, MapLong, ListCategory map) { return map.getOrDefault(parentId, Collections.emptyList()).stream() .map(category - { CategoryTreeVO vo new CategoryTreeVO(); BeanUtils.copyProperties(category, vo); vo.setChildren(buildTree(category.getId(), map)); return vo; }).collect(Collectors.toList()); }商品图片上传采用阿里云OSS存储前端实现多图上传组件template el-upload action/api/upload list-typepicture-card :file-listfileList :on-successhandleSuccess :before-uploadbeforeUpload el-iconPlus //el-icon /el-upload /template script setup const fileList ref([]) const beforeUpload (file) { const isImage file.type.startsWith(image/) const isLt5M file.size / 1024 / 1024 5 if (!isImage) { ElMessage.error(只能上传图片!) } if (!isLt5M) { ElMessage.error(图片大小不能超过5MB!) } return isImage isLt5M } /script3.2 购物车与订单系统购物车设计采用Redis缓存MySQL持久化的混合方案用户未登录时购物车数据存储在浏览器localStorage用户登录后合并本地购物车到Redis使用Hash结构存储 Key格式cart:{userId}Fieldproduct_{productId}Value商品数量订单生成流程预减库存Redis分布式锁保证原子性创建订单主表记录创建订单商品明细扣减实际库存清除购物车对应商品关键代码示例Transactional public Order createOrder(OrderCreateDTO dto, Long userId) { // 获取分布式锁 String lockKey order:lock: userId; try { boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS); if (!locked) { throw new BusinessException(操作太频繁请稍后再试); } // 验证并预减库存 checkAndReduceStock(dto.getItems()); // 创建订单 Order order new Order(); order.setUserId(userId); order.setStatus(OrderStatus.UNPAID); orderMapper.insert(order); // 创建订单项 ListOrderItem items dto.getItems().stream() .map(item - { OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setQuantity(item.getQuantity()); return orderItem; }).collect(Collectors.toList()); orderItemService.saveBatch(items); // 实际扣减库存 reduceRealStock(dto.getItems()); return order; } finally { redisTemplate.delete(lockKey); } }3.3 支付集成与回调处理支付模块对接了支付宝沙箱环境主要实现前端发起支付请求获取支付表单后端生成支付参数并签名支付宝异步通知处理支付结果查询接口支付回调处理要点验证签名确保请求来自支付宝处理幂等性同一通知可能多次发送异步更新订单状态记录支付日志用于对账PostMapping(/alipay/notify) public String alipayNotify(HttpServletRequest request) { MapString, String params convertRequestParams(request); // 验证签名 boolean signVerified AlipaySignature.rsaCheckV1( params, alipayConfig.getAlipayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType()); if (!signVerified) { return failure; } // 处理业务 String tradeStatus params.get(trade_status); if (TRADE_SUCCESS.equals(tradeStatus)) { String orderNo params.get(out_trade_no); orderService.handlePaySuccess(orderNo, params); } return success; }4. 性能优化与安全实践4.1 缓存策略设计采用多级缓存架构提升系统响应速度本地缓存(Caffeine)缓存热点数据如商品分类、基础配置Redis缓存商品详情product:{id}秒杀库存seckill:stock:{productId}用户会话session:{token}MySQL查询缓存针对配置类表启用缓存一致性解决方案写操作后删除相关缓存设置合理的过期时间重要数据采用Cache Aside PatternCacheEvict(value product, key #product.id) public void updateProduct(Product product) { productMapper.updateById(product); // 异步更新搜索引擎数据 searchService.asyncUpdateProduct(product); }4.2 接口安全防护防XSS攻击前端使用vue-dompurify对富文本内容消毒后端对字符串参数进行HTML转义CSRF防护关键操作使用POST/PUT/DELETE方法接口增加CSRF Token验证SQL注入防护全部使用MyBatis-Plus的Wrapper构建查询条件禁止拼接SQL语句接口限流RateLimiter(value 10, key login: #username) PostMapping(/login) public Result login(RequestBody LoginDTO dto) { // 登录逻辑 }4.3 前端性能优化代码分割// 动态导入组件 const ProductDetail defineAsyncComponent(() import(/views/product/Detail.vue) )图片懒加载img v-lazyproduct.image alt商品图片API请求优化合并同类请求添加请求取消功能响应数据缓存Web Worker处理复杂计算// 在主线程 const worker new Worker(/workers/calculation.js) worker.postMessage(data) worker.onmessage (e) { // 处理结果 }5. 部署与监控方案5.1 容器化部署使用Docker Compose编排服务version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:6.2 ports: - 6379:6379 volumes: - redis_data:/data backend: build: ./backend ports: - 8080:8080 depends_on: - mysql - redis frontend: build: ./frontend ports: - 80:80 volumes: mysql_data: redis_data:5.2 监控与日志SpringBoot Actuator暴露健康检查端点Prometheus Grafana监控系统指标ELK收集和分析日志SkyWalking实现分布式追踪关键监控指标JVM内存和GC情况MySQL连接池状态Redis缓存命中率接口响应时间P995.3 CI/CD流程GitLab CI配置示例stages: - build - test - deploy backend-build: stage: build script: - mvn clean package -DskipTests artifacts: paths: - backend/target/*.jar frontend-build: stage: build script: - cd frontend - npm install - npm run build artifacts: paths: - frontend/dist deploy-prod: stage: deploy script: - scp backend/target/*.jar prod-server:/app - ssh prod-server cd /app ./restart.sh only: - master6. 开发中的典型问题与解决方案6.1 MyBatis-Plus分页查询异常问题现象当使用MyBatis-Plus的分页插件时发现total字段始终为0但数据能正常返回。排查过程检查是否配置了分页插件确认SQL是否有count查询检查是否有拦截器修改了SQL解决方案Configuration public class MyBatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件必须作为第一个插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }关键点分页插件必须作为MyBatis-Plus拦截器链中的第一个插件否则可能导致分页信息计算错误6.2 Vue3组件间通信问题在商品列表和购物车组件间需要实时同步数据时遇到了组件间通信的挑战。最终采用的解决方案简单场景使用provide/inject// 父组件 provide(cartCount, ref(0)) // 子组件 const count inject(cartCount)复杂状态管理使用Pinia// stores/cart.js export const useCartStore defineStore(cart, { state: () ({ items: [], total: 0 }), actions: { addItem(product) { // 添加逻辑 } } }) // 组件中使用 const cart useCartStore() cart.addItem(product)6.3 MySQL8.0连接问题在Windows Server 2008 R2上安装MySQL8.0后应用无法连接报错caching_sha2_password。原因分析MySQL8.0默认使用新的认证插件caching_sha2_password旧版驱动可能不支持。解决方案降级认证方式不推荐ALTER USER username% IDENTIFIED WITH mysql_native_password BY password;推荐方案升级连接驱动在JDBC URL中添加参数jdbc:mysql://localhost:3306/db?useSSLfalseallowPublicKeyRetrievaltrue6.4 跨域问题处理开发阶段前后端分离带来的跨域问题通过以下方式解决后端配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .maxAge(3600); } }前端开发环境配置vite.config.jsexport default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })7. 项目扩展与优化方向7.1 微服务化改造当前单体架构可以逐步拆分为商品服务用户服务订单服务支付服务搜索服务技术选型考虑服务注册与发现Nacos服务通信OpenFeign配置中心Apollo网关Spring Cloud Gateway7.2 引入搜索引擎对于商品搜索功能可以集成Elasticsearch提升搜索体验建立商品索引实现中文分词设计相关性评分算法同步MySQL数据到ESRepository public interface ProductSearchRepository extends ElasticsearchRepositoryProductES, Long { PageProductES findByNameOrKeywords(String name, String keywords, Pageable pageable); Query({\bool\: {\must\: [{\match\: {\name\: \?0\}}]}}) PageProductES searchByName(String name, Pageable pageable); }7.3 移动端适配方案基于uniapp开发跨平台应用使用Vue3TS实现核心逻辑复用条件编译处理平台差异封装通用业务组件项目结构示例uni-app/ ├── src/ │ ├── common/ # 通用逻辑 │ ├── components/ # 跨平台组件 │ ├── pages/ # 页面 │ ├── platforms/ # 平台特定代码 │ ├── stores/ # 状态管理 │ └── utils/ # 工具函数 └── package.json7.4 大数据分析扩展用户行为分析埋点数据收集Flink实时处理用户画像构建销售预测历史数据训练时间序列分析机器学习模型可视化大屏ECharts展示核心指标实时数据更新多维度下钻分析public interface SalesAnalysisService { /** * 获取商品销售趋势 */ ListSalesTrendDTO getProductSalesTrend(Long productId, Date start, Date end); /** * 用户购买偏好分析 */ UserPreferenceDTO analyzeUserPreference(Long userId); /** * 实时销售数据看板 */ RealTimeDashboardDTO getRealTimeDashboard(); }在完成这个项目的过程中最大的收获是对全栈开发有了更系统的认识。特别是前后端协作方面建立清晰的接口文档和版本管理机制非常重要。我们使用Swagger生成API文档并通过Git Tag管理接口版本有效减少了沟通成本。另一个深刻体会是技术选型需要平衡先进性和稳定性比如Vue3虽然新特性诱人但部分周边生态还不够成熟需要谨慎评估。

相关新闻

OpenClaw单机生产环境部署:Docker Compose架构设计与实战指南

OpenClaw单机生产环境部署:Docker Compose架构设计与实战指南

1. 项目概述:为什么需要这份单机部署指南?最近在折腾OpenClaw的生产环境部署,发现网上资料要么是简单的开发环境跑通,要么就是复杂的集群方案,对于想先在一个服务器上把服务稳定跑起来、验证业务逻辑的团队来说&#x…

2026/10/19 6:19:41 阅读更多 →
Power BI数据清洗实战:从脏数据到标准报表的完整流程

Power BI数据清洗实战:从脏数据到标准报表的完整流程

1. 从“脏数据”到“干净报表”:为什么数据清洗是Power BI的命门如果你用过Power BI,大概率经历过这种场景:从销售系统导出的Excel表格,产品名称一会儿是“iPhone 15 Pro”,一会儿是“iphone15pro”,一会儿…

2026/10/19 6:53:16 阅读更多 →
AI写SEO文章全链路拆解,从关键词挖掘到排名飙升的7步闭环工作流

AI写SEO文章全链路拆解,从关键词挖掘到排名飙升的7步闭环工作流

更多请点击: https://codechina.net 第一章:AI写SEO文章全链路拆解,从关键词挖掘到排名飙升的7步闭环工作流 SEO内容生产已进入AI驱动的精细化运营阶段。传统人工写作耗时长、覆盖窄、数据反馈滞后,而一套可复用、可验证、可迭代…

2026/10/19 6:53:29 阅读更多 →

最新新闻

Deepfake检测新思路:多线索挖掘突破泛化与鲁棒性瓶颈

Deepfake检测新思路:多线索挖掘突破泛化与鲁棒性瓶颈

不得不说,现在做伪造检测,越来越像在玩一场"找茬游戏"的高难度版本。以前是找明显的马赛克、边缘模糊、脸部拼接的硬伤,现在倒好,生成模型进化得太快,肉眼几乎看不出破绽,连传统的检测模型也在快…

2026/10/20 8:11:28 阅读更多 →
大模型基础概念详解:从Transformer到Token与幻觉,一篇搞懂核心术语

大模型基础概念详解:从Transformer到Token与幻觉,一篇搞懂核心术语

最近总有朋友拿着各种大模型相关的文章来问我,说网上教程不少,但一上来就是“Transformer”“注意力机制”“预训练”这些词,看半天像天书。其实这些基础概念没那么玄乎,就是入门门槛被很多资料写高了。我自己刚接触大模型那会儿也…

2026/10/20 8:11:28 阅读更多 →
Stimulsoft Reports 2019.2.2 报表控件实战:从配置部署到避坑指南

Stimulsoft Reports 2019.2.2 报表控件实战:从配置部署到避坑指南

简介:Stimulsoft Reports报表控件是面向.NET开发者的综合性解决方案,支持在WinForms、ASP.NET和WPF环境中设计、展示与导出报表,并可输出PDF、Excel、Word及图像等格式,适合企业级管理系统、财务统计平台等场景快速集成。此份2019…

2026/10/20 8:11:28 阅读更多 →
告别王婆卖瓜式文案:AI辅助卖点翻译与痛点共鸣的写作流水线

告别王婆卖瓜式文案:AI辅助卖点翻译与痛点共鸣的写作流水线

1. "王婆卖瓜式"文案为什么越来越没人买账1.1 自嗨式文案的三宗罪先说个我这些年改稿的真实感受。看了太多运营小伙伴拿过来的初稿,三分钟之内我基本就能判断这篇能不能跑出数据。判断标准不是文笔好不好,而是看它是不是在"自嗨"——…

2026/10/20 8:11:28 阅读更多 →
AI工程如何对抗“适应落后”:技术债、基线模型与评估体系升级

AI工程如何对抗“适应落后”:技术债、基线模型与评估体系升级

说实话,在 AI 行业待得越久,我越不担心“技术落后”这件事本身,真正让我后背发凉的,是一个团队开始默默接受“落后”这个状态,并且把“落后”当成了默认前提来安排工作。这里的“落后”不是排行榜上的名次,…

2026/10/20 8:11:28 阅读更多 →
ClamAV离线安装实操:内网环境下的杀毒部署与病毒库更新

ClamAV离线安装实操:内网环境下的杀毒部署与病毒库更新

做运维这些年,遇到“服务器不能联网但必须装杀毒软件”这种需求,次数远比预想的多。前几天刚处理完一台内网主机的 ClamAV 离线部署任务,从找包、传包、解决依赖到初始化病毒库,踩了一圈坑,顺手把过程整理出来&#xf…

2026/10/20 8:10:28 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* 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 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* 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 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* 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 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →