简介这是一份智能仓储管理系统完整项目资料包采用JSPServletMySQL的Java Web方案构建面向软件开发学习者、毕业设计学生及物流信息化项目人员能够帮助读者快速掌握仓储业务中入库、出库、库存盘点、订单处理等核心模块的软件实现方法。压缩包内共85个文件总体积149.45MB主要包含Java工程源码jsp页面、xml配置、properties设置、iml工程文件等、SQL数据库脚本、war部署包、开发说明文档docx/txt、系统设计PPT压缩包以及操作演示视频mp4等兼顾代码学习、环境部署与成果展示需求。目前已有50人浏览学习。资源中附带的演示视频以具体企业场景为例讲解系统操作配合数据库脚本和文档可直接在IDE中导入运行适合用于课程设计、毕业设计参考也可作为中小型仓储管理系统快速开发的起点。1. 智能仓储系统.zip一个压缩包里的仓储数字化转型难点不在代码而在库存模型很多做仓储的人拿到“智能仓储系统.zip”这类源码包第一反应是赶紧解压、启动、登录后台看看页面长什么样。但真正让这套系统在仓库里跑得住的从来不是前端那几个表格和按钮而是数据库里的库存模型和单据状态流转。我见过不止一个小团队把代码跑通了一上线第二天账就对不上问题全出在“库存到底记在哪里、单据和流水怎么联动”上。这套压缩包如果要落地核心价值在于把入库、出库、移位、盘点这些动作统一成一个可追踪、可回滚的状态机而不是一张静态库存余额表。它适合正在做仓储数字化转型的传统企业、准备自研 WMS 的中小团队以及想搞清楚智能仓储到底智能在哪儿的选型人员。本文不猜这个压缩包的具体目录结构只按一套可靠、可复现的仓储系统实现路径把模型、代码、参数和踩坑讲透你拿到任何同类型源码包都能照着改。2. 先把数据模型立住智能仓储系统的五张核心表与一条主流程2.1 业务模块划分为什么先建模而不是先写页面智能仓储系统的业务范围听起来很广落到系统里无非几个闭环基础资料SKU、货主、仓库、货位管理、入库流程、出库流程、库内作业移位、补货、盘点和报表预警。常见实现是先画 ER 图再动工。我一般会先和仓库管理员聊一个动作拣货员领到一张单子后第一步扫什么、第二步点什么、出错找谁。这个流程决定了系统的灵魂也就是单据状态。任何一张入库单或出库单都要有一个从“创建”到“完成”、中间带“部分操作”的状态字段。举个例子一张入库单扫了 50 件只上架了 30 件系统必须能记录“剩余 20 件未上架”而不是直接把整单标记完成。这个状态推进关系是智能仓储和普通进销存软件的分水岭。你把这个想清楚之后再去写代码、建表节奏就顺了。2.2 五张核心表的建表 SQL 与字段说明智能仓储系统的数据模型不需要二十张表起步核心五张表就能撑住大部分业务SKU 表、货位表、库存表、库存流水表、单据表。下面这套建表 SQL 是常见可靠方案字段做了精简但保留了关键约束你拿到压缩包源码时可以先对照它理解作者的库存设计。CREATE TABLE wms_sku ( sku_id BIGINT AUTO_INCREMENT PRIMARY KEY, sku_code VARCHAR(32) NOT NULL UNIQUE, sku_name VARCHAR(128) NOT NULL, category_code VARCHAR(32) NULL, shelf_life_days INT NULL, barcode VARCHAR(64) NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT SKU基础资料; CREATE TABLE wms_location ( location_id BIGINT AUTO_INCREMENT PRIMARY KEY, location_code VARCHAR(32) NOT NULL UNIQUE, zone_code VARCHAR(32) NOT NULL COMMENT 库区如A/B/C, rack_no VARCHAR(16) NULL COMMENT 货架号, shelf_layer VARCHAR(8) NULL COMMENT 层号, location_type ENUM(STORAGE,PICKING,STAGING) DEFAULT STORAGE, max_volume DECIMAL(12,3) NULL COMMENT 最大容积单位m3, max_weight DECIMAL(12,3) NULL COMMENT 最大承重单位kg, frozen TINYINT DEFAULT 0 COMMENT 冻结标志, status ENUM(EMPTY,OCCUPIED,DISABLED) DEFAULT EMPTY ) COMMENT 货位表; CREATE TABLE wms_inventory ( id BIGINT AUTO_INCREMENT PRIMARY KEY, sku_id BIGINT NOT NULL, location_id BIGINT NOT NULL, batch_no VARCHAR(32) NOT NULL COMMENT 生产批次或入库批次号, qty DECIMAL(12,3) NOT NULL DEFAULT 0 COMMENT 实物总库存含冻结, frozen_qty DECIMAL(12,3) NOT NULL DEFAULT 0 COMMENT 冻结数量出库单锁定用, in_time DATETIME NULL COMMENT 最早入库时间FIFO按此排序, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_sku_loc_batch (sku_id, location_id, batch_no), KEY idx_location (location_id) ) COMMENT 实物库存表; CREATE TABLE wms_stock_transaction ( txn_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL COMMENT 来源单据号必须传, txn_type ENUM(IN,OUT,MOVE,FREEZE,UNFREEZE,ADJUST) NOT NULL, sku_id BIGINT NOT NULL, location_id BIGINT NOT NULL, batch_no VARCHAR(32) NULL, qty_change DECIMAL(12,3) NOT NULL COMMENT 正数入负数出, before_qty DECIMAL(12,3) NOT NULL, after_qty DECIMAL(12,3) NOT NULL, operator VARCHAR(64) NOT NULL, biz_time DATETIME NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_order_no (order_no), KEY idx_sku_biztime (sku_id, biz_time) ) COMMENT 库存流水表绝对不能只记账不动它; CREATE TABLE wms_order ( order_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL UNIQUE, order_type ENUM(INBOUND,OUTBOUND,MOVE,COUNT) NOT NULL, status ENUM(CREATE,PROCESSING,PARTIAL,DONE,CANCEL) DEFAULT CREATE, expect_time DATETIME NULL COMMENT 期望完成时间用于超时预警, finish_time DATETIME NULL, remark VARCHAR(255) NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 单据主表;库存表没有做成“一物一行的总量表”而是把单位库存拆到“SKU 货位 批次”这一粒度这一点很关键。它让先进先出、指定批次出库、货位占用统计都变得简单。如果压缩包里的库存表只有一个总数量字段没有批次和货位维度那它大概率只是个进销存系统不是仓库级系统。流水表里的before_qty和after_qty必须写不要只记qty_change。这组字段是后来做对账、排查乱账的后悔药。很多系统开发时为了省事省掉这两个字段等线上数据出问题根本不知道那笔操作之前账面是什么样子排查成本翻倍。2.3 串起主流程入库单、收货、上架与流水联动接下来用人话把主流程过一遍让不懂代码的库管也能听明白。一张入库单创建后状态是CREATE收货员扫码系统按 SKU 和批次创建收货明细此时不碰库存上架员把货放到某个货位系统做两件事往wms_inventory增加或合并一条库存记录然后往wms_stock_transaction插一条txn_type IN的流水。单据状态变为DONE或PROCESSING。这个“先收后上”的拆分是有讲究的。如果收货时就直接加库存货还在收货区没上架账面已经有了就会出现一个典型问题系统显示有货拣货员在货位上找不到。智能仓储的“智能”第一步是让账面库存反映货位上真实存在的东西。所以压缩包代码里如果看到收货即加库存建议你自己动手把它改成「收货登记 上架入库」两步。3. 把库存扣减写成代码并发、事务和单据状态机的落地实现3.1 为什么直接 UPDATE 总和库存会翻车有相当一部分仓储系统线上的负库存问题都来自一句看似无辜的 SQLUPDATE wms_inventory SET qty qty - 1 WHERE sku_id 101;这个写法在单机、低并发下没问题一旦两个拣货员同时扣同一批货就变成经典竞态条件两个请求都读到 qty 10各自减去 1最后写回的都是 9实际却发了 2 件货账面只少了 1 件。这不是数据库 bug是缺少并发控制。解决方向有两个一是用悲观锁SELECT FOR UPDATE二是用条件更新UPDATE ... WHERE qty ?配合影响行数判断。对于仓库系统这种写多读少的场景我一般选条件更新。3.2 一个最小可靠的出库扣减实现下面这段用 Python pymysql 写过出库扣减的核心事务代码。它不是玩具除了库存扣减还把锁单冻结、流水、单据状态更新放在同一个数据库事务里任一环节失败全部回滚。def outbound_pick(order_no, sku_id, location_id, batch_no, qty, operator): try: conn.begin() cur conn.cursor() # 1. 锁定库存条件更新天然防超扣 rows cur.execute( UPDATE wms_inventory SET qty qty - %s, update_time NOW() WHERE sku_id %s AND location_id %s AND batch_no %s AND qty %s , (qty, sku_id, location_id, batch_no, qty)) if rows.rowcount 0: raise Exception(f库存不足或货位批次不存在: sku{sku_id}) # 2. 读取扣减前后的数量写流水 inv cur.execute( SELECT qty, frozen_qty FROM wms_inventory WHERE sku_id%s AND location_id%s AND batch_no%s , (sku_id, location_id, batch_no)) if inv.rowcount 0: raise Exception(库存记录消失数据异常) row inv.fetchone() before_qty row[qty] qty cur.execute( INSERT INTO wms_stock_transaction (order_no, txn_type, sku_id, location_id, batch_no, qty_change, before_qty, after_qty, operator, biz_time) VALUES (%s, OUT, %s, %s, %s, %s, %s, %s, %s, NOW()) , (order_no, sku_id, location_id, batch_no, -qty, before_qty, before_qty - qty, operator)) # 3. 单据状态推进 cur.execute( UPDATE wms_order SET statusDONE, finish_timeNOW() WHERE order_no%s AND status IN (CREATE,PROCESSING) , (order_no,)) if cur.rowcount 0: raise Exception(f单据{order_no}状态不允许扣减) conn.commit() return True except Exception as e: conn.rollback() raise e代码的逻辑要点第一步用条件更新的影响行数判断库存是否充足第二步查回before_qty是为了流水完整第三步更新单据状态并校验状态机。参数说明qty是本次拣货数量可带三位小数operator会写入流水用于责任追踪batch_no必须是货位上实际存在的批次否则直接报错。这里有个细节值得注意如果在高并发下同一个货位批次被两个人同时抢条件更新AND qty %s会保证只有一个请求影响行数为 1另一个返回 0 抛“库存不足”。这样的行为是符合预期的不会出现一方覆盖一方的问题。3.3 单据状态机从 CREATE 到 DONE 的合法跳转很多新手写仓储系统时直接把状态字段做成一个可以随便改的字符串UPDATE wms_order SET statusDONE WHERE order_no...。线上跑两天就乱套因为PROCESSING的单子可以被取消DONE的单子又可以被改成CANCEL账和单对不上。常见做法是把状态推进写进代码逻辑里或者在调用层用字典约束合法跳转。我通常会把状态机规则单独列成一个映射表当前状态允许动作下一个状态CREATE开始收货 / 开始拣货PROCESSINGPROCESSING部分收货完成PARTIALPROCESSING全部处理完DONEPARTIAL继续剩余部分PROCESSINGCREATE / PROCESSING废弃CANCELDONE不允许任何修改-CANCEL 状态要谨慎入库单取消前如果已经动过库存必须先生成一张反冲流水把库存调回去否则账就挂了。这也是为什么库存流水表里必须有ADJUST或反向IN/OUT类型的原因。4. 让仓储系统“聪明”起来货位推荐与拣货路径的参数化实现4.1 从“有货可发”到“发得更快”智能策略到底智能在哪智能仓储系统的“智能”在多数开源实现里不是深度学习而是一组可解释、可调参的规则策略。最常见的两个收益点是货位推荐和拣货路径。货位推荐解决“新到货放哪、拣货去哪个货位”的问题拣货路径解决“多张订单、多个货位怎么走才能少跑路”。这两个策略如果写得好人工效率能提升 20% 到 30%。需要注意的是仓储策略和业务强相关没有一套参数放之四海而皆准。生鲜仓库看重先进先出跨境电商看重批次和管理自由。所以货位推荐和拣货路径必须要做成可配置参数而不是写死在代码里。这也是评判一个源码包实用性的重要标准把策略参数做成配置表的通常是干过现场的。4.2 可调参的货位打分器一个货位推荐策略的代码结构货位推荐的常见做法是打分制把多个策略因子按权重加权每个候选货位打一个分得分最高者被推荐。下面是一个参考实现适合入库上架和拣货两个场景共用一套打分逻辑、不同参数组合。def score_location(loc, sku, qty, batch_map, weights): score 0.0 # 因子1相同SKU/批次合并优先减少拆零 if batch_map.get(loc[location_id]) is not None: score weights[same_batch] # 因子2货位类型权重 score weights[loc_type].get(loc[location_type], 0) # 因子3空货位优先空间利用率合理度 if loc[status] EMPTY: score weights[empty] # 因子4拣互动线就近库区距离越近得分越高 score weights[dist] * max(0.0, 1.0 - loc[distance_m] / 100.0) # 因子5容积匹配货位容积与商品体积差越小越好 if loc[max_volume] and loc[max_volume] 0: volume_ratio qty * sku[unit_volume] / loc[max_volume] if volume_ratio 1.0: score - weights[overload_penalty] else: score weights[vol_fit] * (1.0 - volume_ratio) return score参数weights可以从配置表读取示例值same_batch给 50loc_type中 PICKING 区给 20、STORAGE 区给 10empty给 5dist给 0.3vol_fit给 8overload_penalty给 80。调参的时候我建议一次只动一个因子拿一周历史单据回放对比不要凭感觉同时改三个权重。还有一个容易忽略的点策略推荐只是建议货主永远有权 override。所以代码里推荐结果要带reason字段比如“同批次合并优先”“距离最近”这样操作员拒绝推荐时知道自己改的是什么。系统上线初期不要直接强制推荐结果先跑一段时间看采纳率再决定要不要做强制执行。4.3 拣货路径参数S型、U型与最近优先怎么选拣货路径是电商和批发仓最疼的问题一张波次单可能跨多个库区。常见策略有三种S型蛇形行走、U型分区行走和最近优先。三者的核心参数差异用一个表格就能说清楚策略行走规则适用场景核心参数S型蛇形按货架顺序蛇形走完全程不回头波次单覆盖整片区域货架间距、单波次最大单据数U型分区只在指定分区内绕行跨区订单拆单货物分区明确订单相对集中分区边界、越区合并阈值最近优先每次取离当前位置最近的目标货位急单、零拣订单量少距离权重、穿插次数上限选型建议很直接订单量大、货位分散的整仓拣选用 S 型SKU 分类清晰、订单往往集中在一个大区的用 U 型零散急单走最近优先。但无论选哪种都要注意参数里的“穿插次数”和“步长”控制否则算法会把拣货员往东引一下往西引一下路径总长理论最优实际动线却很难走。4.4 智能化的边界什么时候该上算法什么时候该保持人工不用迷信人工智能算法。如果你的仓库只有三百个货位一张波次单不到二十件用遗传算法求最优路径的收益撑死了省十几秒钟而调度系统本身的学习和维护成本却很高。这种规模下把货位整理整齐、用大分区的方式减少跨区行走比任何算法都有效。真正值得上智能策略的节点是货位超过一千个、日均出库行数超过三千、波次合并频繁。这时候推荐策略和路径优化才能省出看得见的人效。压缩包源码里如果自带路径规划算法可以保留并做成开关初始设置成 off跑出基准数据后再开启对比效果。5. 避坑与排查智能仓储系统上线后最容易翻车的五个场景5.1 现象账面库存和实物对不上怎么查都差十几件原因操作中途断掉比如拣货员扣了库存但单据还没更新就断网或者同一张单被人重复提交两次。解决从流水表入手排查不要直接改库存。查询条件是order_no和txn_type看每一笔before_qty和after_qty是否连续。要治本就得给重复提交做幂等控制。常见做法是在生成单据号时保证唯一并对关键库存操作表增加order_no txn_type的唯一索引第二次提交直接报错。5.2 现象出库按 FIFO 规则发货结果总是发错批次原因系统只在出库单据上写了“按先进先出”但拣货员实际操作时没有锁定具体批次拣到哪个批就发哪个批。解决在单据创建时直接锁定货位和批次把推荐结果写进出库明细表锁定后在wms_inventory里把对应数量冻结frozen_qty增加。拣货员只按锁定批次操作不能现场临时改。如果确实需要换批次必须走“解锁再重新锁定”的流程不能直接改明细。5.3 现象并发扣减出现负库存但看 SQL 好像没问题原因你在检查代码时看的是“扣减时判断了库存大于零”但那可能是先SELECT判断再UPDATE两个操作之间被其他事务插了一脚形成典型的 TOCTOU 竞态。解决改成单条条件更新像第 3 章那个函数一样把判断和扣减放进同一条UPDATE ... WHERE qty %s并用rowcount 0来判断失败。这样不管并发多大数据库层面的原子性都能兜住。5.4 现象上架时扫了一个货位系统提示成功但那个货位其实是冻结的原因货位表里有frozen和status两个字段但上架入库的代码只校验了货位是否存在没校验状态。解决在上架事务里增加货位状态校验status必须为EMPTY或OCCUPIED且frozen 0。货位冻结通常发生在盘点、维修、长期封存时货位被冻结后不允许任何入库操作但出库可以允许继续具体看业务策略建议做成参数。5.5 现象同样的源码包换台机器跑接口一直报数据库连接失败原因压缩包里常见的老问题数据库连接地址写死成了开发环境的 IP或者用了绝对路径和本机特定配置。解决把所有环境相关的配置抽到config.py或.env文件包括数据库主机、端口、账号、密码、日志路径。拿到任何源码包第一件事就是把配置项全局搜索一遍IP、端口和文件路径。换环境跑不起来这件事八成是配置问题而不是代码问题。6. 收尾验证给打包前的智能仓储系统加一道对账脚本与三天自测代码写完之后我不建议直接打包交付而是先做一轮内部一致性验证。我的习惯是写一个日终对账脚本把流水表按 SKU 汇总然后和库存表的当日期末数量比对差异一眼看出来。下面是一个可以直接放进项目目录的对账脚本核心逻辑# daily_check.py 每日库存一致性自检 def check_day_consistency(conn, biz_day): cur conn.cursor() # 流水只统计当天实际业务期初变动期末 cur.execute( SELECT sku_id, COALESCE(SUM(CASE WHEN txn_type IN (IN,ADJUST) THEN qty_change WHEN txn_typeOUT THEN qty_change END), 0) AS delta FROM wms_stock_transaction WHERE DATE(biz_time) %s GROUP BY sku_id , (biz_day,)) trans_map {row[sku_id]: row[delta] for row in cur.fetchall()} cur.execute( SELECT sku_id, SUM(qty) AS total_qty FROM wms_inventory WHERE update_time %s GROUP BY sku_id , (biz_day 23:59:59,)) inv_map {row[sku_id]: row[total_qty] for row in cur.fetchall()} for sku_id in set(trans_map) | set(inv_map): delta trans_map.get(sku_id, 0) end_qty inv_map.get(sku_id, 0) begin_qty end_qty - delta if begin_qty 0 or abs(delta) 0.0001: print(fSKU{sku_id} 期初{begin_qty} 变动{delta} 期末{end_qty} 需人工核实)这个脚本的思路是通过“期末 期初 变动”恒等式做自检。它不能发现库存和实物不符的问题但能发现代码逻辑错误比如某笔流水漏写、某次 UPDATE 没同步流水。跑脚本之前我会设计三天自测场景第一天做五笔入库、三笔出库覆盖同一批次部分出库第二天做一次跨货位移位再故意让两笔并发扣减同时打同一个 SKU第三天做一次盘点盘盈再对昨天的单据做部分取消。三天全跑完对账脚本无差异我才会认为这套系统有资格交付。仓储系统的难点从来不是某个页面有多炫而是库存模型稳不稳、流水全不全、状态机严不严。我做了几年仓储系统最深的教训是不要相信任何没有流水的库存修改也不要相信任何不校验状态的写操作。把这套习惯带进你的系统设计里比依赖某个压缩包里的功能列表重要得多希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
洱海SHP实战:从坐标校正到格式转换的GIS避坑指南 /* 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 7:36:31
原生前端三件套打造七夕音乐祝福页:歌词同步+爱心雨特效 最近被一句文案戳中了:“该怎么定义这场晚风?是涟漪,是汹涌,是遇见你之后的每一天,都觉得很走运。”如果只是把这句话发给对方,总觉得少了点仪式感。于是我用原生 HTML、CSS 和 JavaScript 做了一个七夕限定… · 2026/9/25 7:36:31
Proteus仿真DHT11总失败?从时序到代码的完整调试指南 /* 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 7:36:31
基于VUE的食堂管理系统毕业设计 摘 要
针对传统厨房管理效率低、信息协同滞后、资源浪费严重等问题,本文设计并实现了一套基于Vue.js框架的智能厨房管理系统。系统采用前后端分离架构,前端以Vue 3组合式API为核心,结合Element Plus组件库构建响应式用户界面,通过… · 2026/9/25 7:57:49
Skia C++ 编码风格规范详解:命名约定、类设计模式与 clang-format 自动化落地 图形学图像处理 【免费下载链接】skia Skia is a complete 2D graphic library for drawing Text, Geometries, and Images. 项目地址: https://gitcode.com/gh_mirrors/skia1/skia 点击查看 免费下载 本文基于 Skia 官方贡献文档 Coding Style Guidelines… · 2026/9/25 7:57:49
Atlas 300V 24G部署YOLOv5实战:从环境配置到推理调优全流程 1. 先搞清楚:Atlas 300V 24G到底是一张什么卡我在过去半年里陆续接手过几个CV项目,从最开始在GPU服务器上跑YOLO,到后来被客户要求落地到国产加速卡上,可以说踩了不少坑。Atlas这个名字,很多人第一次听说时都会有个困惑… · 2026/9/25 7:57:31
Ariakit Tab 组件完全指南:基于 WAI-ARIA Tabs Pattern 的可访问标签页实现 UI组件前端 【免费下载链接】ariakit Toolkit with accessible components, styles, and examples for your next web app 项目地址: https://gitcode.com/gh_mirrors/ar/ariakit 点击查看 免费下载 本文围绕 Ariakit 仓库中 components/tab.md 所定义的 Tab 组件展… · 2026/9/25 7:57:25
S905L3B 电视盒子安装 Armbian 到 eMMC 完整指南 S905L3B 电视盒子安装 Armbian 到 eMMC 完整指南 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk3588, rk3568, rk3399, … · 2026/9/25 7:57:25
创维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