news 2026/9/17 17:17:36

告别nvm和pyenv,用mise统一管理Node/Python与JDK多版本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
告别nvm和pyenv,用mise统一管理Node/Python与JDK多版本

1. 项目概述:多版本切换这件事,为什么值得花十分钟重构

我在本地开发时,电脑上长期同时存在多套 Node、Python 和 JDK 版本。A 仓库是前端工程,锁在 Node 16;B 仓库是数据分析,Python 必须 3.10;C 仓库是 Java 服务,又只能跑 JDK 8。以前我的日常就是 nvm use 16、pyenv global 3.11、手动改 JAVA_HOME,三个工具各管一摊。项目一多,切换成本肉眼可见地涨,更麻烦的是换电脑或带新同事,环境配置能折腾半天。后来我把 nvm、pyenv 这些“单一语言版本工具”全部换成 mise(旧名 rtx),一个工具统一管理 Node / Python / JDK 多版本,算是彻底告别了 nvm / pyenv 来回切换的日子。这篇文章把我这一年多的使用心得、安装配置和踩坑细节都写清楚,适合正在被多版本环境折磨的开发者,也适合想在团队里统一工具链的组员。

1.1 为什么 nvm、pyenv、JAVA_HOME 这套组合拳越来越难用

先说痛点,不然你可能觉得“不就是多敲两条命令吗,有什么好折腾的”。单一语言的版本管理器本身没有错,错在你要同时维护三套独立的工具链。nvm 只管 Node,pyenv 只管 Python,JDK 则靠手动下载安装包、改环境变量。每套工具有自己的安装目录、自己的配置文件、自己的升级方式,三个叠加在一起,心智负担很大。

最直接的问题是切换成本。node 端你可以写 .nvmrc,python 端可以写 .python-version,但这两套文件互不相通。一个项目如果同时依赖 Node 16 和 Python 3.10,你就要 nvm use 16 && pyenv local 3.10,两条命令缺一条,后面构建就可能出诡异问题。更别提 Java;Java 项目几乎没有统一的“版本声明文件”,很多团队还是靠 README 里写一行“请安装 JDK 8”,然后每个人装出来的位置还不一样。

第二个问题是环境变量污染。很多开发者的 ~/.bashrc 或 ~/.zshrc 里堆了一堆 nvm 初始化脚本、pyenv 初始化脚本、JAVA_HOME 的 export,还有各种 alias。每次 shell 启动要加载好几层脚本,慢还是小事,更麻烦的是顺序不对会导致版本覆盖。我见过有人 JAVA_HOME 配了 8,但 PATH 里又带着 17,最后跑起来到底是什么版本完全看运气。

第三个问题最要命:团队协作时环境不一致。你本地验证没问题,新同事拉完代码装完依赖,build 直接挂掉,最后发现是 Node 版本不同。你不可能让每个人都手动去装 N 个版本管理器。版本声明需要变成一个项目级的、可提交进 Git 的文件,而不是散落在各自终端配置文件里的记忆。这也是我转向统一工具的核心理由。

1.2 asdf、devbox、Docker、mise:多版本方案怎么选

既然要统一,可选方案其实不少。老牌的 asdf 很早就做到了一站式多语言管理,插件生态很丰富,我早期也用过;Docker 或 devbox 也能隔离环境,但使用场景和本地开发手感不太一样。我最后选择 mise,是综合了速度、配置体验和迁移成本之后的结果。

方案管理范围实现机制上手成本速度生态
nvm + pyenv + 手动 JDK单语言各自管修改 Shell 环境变量每个语言生态独立
asdf多语言Shell 插件 + shim偏慢,插件多时明显插件多但质量参差
Docker / devbox完整运行环境容器/隔离环境依赖镜像或 Nix 生态
mise多语言Rust 核心 + shim内置主流语言,兼容 asdf 插件

先解释一下为什么我排除了 asdf。asdf 解决“多语言统一”的思路是对的,但它基于 Shell 脚本实现,版本信息一多、目录层级一深,命令响应会比较慢。而且 asdf 的配置文件是 .tool-versions,虽然很简洁,但缺少更现代的开发体验,比如环境变量注入、任务定义这些能力。当然,作为老牌工具它依然可用,只是我用了一段时间后觉得它还不够干脆。

Docker 是另一个极端。它确实能完全隔离环境,但本地开发如果每个命令都要进容器跑,文件挂载、端口映射、IDE 调试都会变麻烦。Docker 适合部署环境标准化,不适合作为开发者日常切换 Node 版本的主入口。devbox 的核心是 Nix,隔离能力强但学习曲线陡,对 Java 这种依赖系统级环境变量的生态反而不够直观。

mise 的做法则是:用 Rust 写了一个核心程序,提供 shim 机制和配置文件,同时兼容 asdf 的插件生态。这意味着 asdf 上能用的语言插件,在 mise 里基本也能用,老项目里的 .tool-versions 文件也能被识别。对我来说,这属于“既有新工具的体验,又不用推翻老工具的经验积累”,所以最后选了它。

2. 核心细节解析:mise 的版本切换原理与配置体系

2.1 shim 机制与 PATH 劫持,和 nvm 有什么本质不同

很多用 nvm 的同学第一次接触 mise 时会困惑:为什么我在项目目录里 cd 一下,node 版本就自动变了?这背后的机制和 nvm 完全不同。

nvm 的做法是修改当前 Shell 的环境变量。nvm use 16 会把 PATH 指向 ~/.nvm/versions/node/v16.x.x/bin,nvm use 18 再把 PATH 指向另一个目录。它依赖你当前 Shell 的会话状态,所以新开一个终端就要重新 nvm use,或者靠 .nvmrc 里的 shell hook 自动加载。这个过程本身没问题,但它只对当前终端会话生效,换目录不会自动切,脚本里往往也得先 source nvm.sh。

mise 用的是另一种思路:shim(替身程序)。mise 初始化时会在某个统一目录下生成一堆可执行文件的“替身”,比如 node、npm、python、java、javac,然后把那个目录放到 PATH 的最前面。当你敲 node 时,Shell 找到的其实不是真正的 node,而是 mise 生成的 shim;这个 shim 会去解析你当前所在目录的版本配置,找到真正应该使用的 Node 安装路径,再把命令原样转发给它。

所以 mise 的版本切换不依赖 Shell 会话,而是依赖“当前工作目录”。你在某个项目里 cd 进去,mise 读取项目下的 .mise.toml 或 .tool-versions,自动路由到对应版本;切到另一个项目,路由规则又变了。这种机制更接近真实的“按项目切版本”,而不是“按终端切版本”。也正因为如此,在 CI、脚本、编辑器集成等场景下,mise 比 nvm 更可靠,因为你不再需要先 source 一堆环境变量。

2.2 配置作用域:全局、项目、局部如何生效

mise 的版本配置分几个层级,理解这个层级顺序,几乎所有“为什么没切成功”的问题都能解决。

优先级从低到高是:全局配置、.tool-versions、.mise.toml 项目配置。全局配置写在 ~/.config/mise/config.toml,作用在你整台机器上,适合设置默认版本。项目配置写在项目根目录的 .mise.toml,作用范围只在这个目录,适合提交进仓库让团队共享。.tool-versions 是 asdf 时代的文件,mise 为了兼容继续支持,优先级排在中间,适合老项目迁移.

全局配置文件的长这样:

[tools] python = "3.12.7" node = "22.19.0" java = "17.0.13" [env] # 这里可以写需要注入的环境变量

项目级的 .mise.toml 长这样:

[tools] node = "20.19.0" python = "3.11.9" java = "17.0.13" [env] JAVA_HOME = "{{mise_install_path}}/java/17.0.13"

mise 支持模板变量,比如 {{mise_install_path}} 会被解析成 mise 的安装根目录。这个功能在做 JAVA_HOME 这类环境变量注入时很关键,后面我会细说。

还有一个很实用的开关:legacy_version_file。mise 默认不读取 .nvmrc 和 .python-version,但你可以开启它。开启后,老项目根目录里的 .nvmrc、.node-version、.python-version、.ruby-version 都会被自动识别,等于给旧项目一个“平滑迁移”的入口。配置方式是在全局配置里加上:

legacy_version_file = true

我的建议是:新项目一律用 .mise.toml,老项目先开 legacy_version_file 兼容,等有精力再把旧配置文件迁移成 .mise.toml,没必要一上来全量改。

3. 实操过程:从安装到三语言配置完整落地

3.1 安装 mise 与初始化 Shell(Linux、macOS、Windows)

安装 mise 其实很快,但它默认不会帮你把 Shell 激活配置写好,这一步很容易漏。漏掉之后表现是 mise 命令能用,但版本不切换,所以一定要检查。

Linux 和 macOS 可以用官方脚本安装:

curl -fsSL https://mise.jdx.dev/install.sh | sh

如果你在用 Homebrew,也可以:

brew install mise

安装完成之后,mise 可执行文件通常位于 ~/.local/bin/mise。接下来要把它接入 Shell。以 zsh 为例:

echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc source ~/.zshrc

如果是 bash,就把命令里的 zsh 换成 bash。fish 则用:

~/.local/bin/mise activate fish | source

Windows 上的安装就简单多了,winget 一把梭:

winget install jdx.mise

PowerShell 用户需要手动把 activate 脚本加到 $PROFILE 里:

mise activate powershell | Out-String | Invoke-Expression

Windows 场景下我踩过一个小坑:安装完 winget 版本后,PowerShell 重启一下再执行上述命令,不然环境变量还没刷新,会报命令找不到。

验证是否安装成功,跑一下:

mise --version mise doctor

mise doctor会输出当前环境状态,包括 PATH 顺序、Shell 是否 activate、插件版本等。如果哪一步配置不对,它基本能直接指出来,这是我排查环境问题时的第一命令。

3.2 用 mise 管理 Node.js:安装、默认版本、项目级锁定

装好 mise 之后,第一件事是把 Node 管起来。我用一条命令设置全局默认版本:

mise use -g node@22.19.0

这条命令会自动判断本机有没有装过 node@22.19.0,如果没装会先下载,装完后再写入全局配置,效果等同于 nvm install 22.19.0 && nvm alias default 22.19.0。

如果你不确定要用哪个版本,可以先看远程有哪些可用版本:

mise ls-remote node

版本号会按语义化排序,很长,建议配合 grep 使用:

mise ls-remote node | grep "^22"

安装精确版本用:

mise install node@20.19.0

项目级锁定的命令是去掉 -g:

cd ~/projects/my-app mise use node@20

执行后项目根目录会生成 .mise.toml,内容类似:

[tools] node = "20"

这里我用的是“20”这种主版本号,mise 会自动解析成当前该主版本下最合适的版本,并写入实际解析结果。如果想固定死,可以直接写 20.19.0。

查看当前项目实际生效的 Node 版本:

mise current node node -v

这两条命令的输出应该一致。如果不一致,优先检查 shim 是否在 PATH 最前面,或者是否在当前目录下执行。还有一个常见操作是删除不再使用的版本:

mise prune

prune 会清理缓存的旧版本和未关联的安装包。我习惯每个月跑一次,能省出几个 G 的磁盘空间。

国内用户下载 Node 如果觉得慢,可以设置 Node 镜像源:

export MISE_NODE_MIRROR_URL="https://npmmirror.com/mirrors/node/" mise install node@22.19.0

这个环境变量最好写进 Shell 配置文件,不然下次终端重启又没了。

3.3 用 mise 管理 Python:替代 pyenv 的安装与全局切换

Python 部分是我最推荐用 mise 替代 pyenv 的地方,因为 pyenv 的源码编译在低配机器上真的很折磨。mise 支持下载预编译好的 Python 二进制版本,只要镜像源提供,安装速度能从十分钟缩到几十秒。

先设置全局默认版本:

mise use -g python@3.12.7

如果需要精确到补丁版本,也可以:

mise install python@3.12.7 mise use -g python@3.12.7

查看远程可用版本:

mise ls-remote python

如果是从源码编译,需要确保系统有编译依赖。在 Ubuntu / Debian 上通常要装:

sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

如果你的机器上没有这些库,Python 编译会在某个环节报出奇奇怪怪的错误,比如缺少 _ssl 模块,或者 pip 装包时报 ssl 错误。提前装好能少踩很多坑。

Python 也有对应的镜像环境变量:

export MISE_PYTHON_MIRROR_URL="https://registry.npmmirror.com/-/binary/python/" export MISE_PYTHON_PRECOMPILED=1

第二个变量是让 mise 优先拉取预编译二进制,而不是走源码编译。如果你经常装多个 Python 版本,强烈建议把这个也写进 Shell 配置。

Python 装完之后还有一个问题是虚拟环境。mise 只负责 Python 解释器本身的版本,不参与 venv 的创建,这一点不要误解成“装了 mise 就不用建 venv 了”。我目前的工作流是:

cd ~/projects/data-app mise use python@3.11.9 python -m venv .venv source .venv/bin/activate

mise 保证了你创建虚拟环境用的是对的 Python 解释器,后面 pip 装的包是装在 .venv 里的,两者各司其职。如果你用 uv,也可以直接 uv venv,同样没问题。

3.4 用 mise 管理 JDK:统一 JAVA_HOME 与多版本

JDK 这部分比 Node 和 Python 多一个关键问题:光有 java 命令还不够,很多 Java 构建工具(Maven、Gradle)还要读 JAVA_HOME 环境变量。mise 的 shim 能帮你把 java 命令路由到正确版本,但 JAVA_HOME 不会凭空出现,需要手动注入。

先安装并设置全局 JDK 版本:

mise install java@17.0.13 mise use -g java@17

如果你拿不准要装哪个发行版,先用mise ls-remote java看列表。mise 的 java 插件通常支持多个发行版前缀,比如 temurin、zulu、amazon-corretto 等,实际写法可能是 java@temurin-17 或 java@zulu-17。发行版之间在标准 Java 用法上没啥区别,但如果你公司内部要求特定发行版,建议用完整前缀锁定。

查看当前实际生效的 Java 路径:

mise where java@17

这会输出类似 ~/.local/share/mise/installs/java/17.0.13 的路径。拿到这个路径后,把它写进全局或项目配置的环境变量里:

[env] JAVA_HOME = "{{mise_install_path}}/java/17.0.13"

如果你用了完整发行版前缀,路径会变成 .../java/temurin-17.0.13。这时候建议先跑一遍mise where java@17,把输出路径直接粘过来用,不要凭记忆写。

项目级切换 JDK 版本同样是:

cd ~/projects/legacy-service mise use java@8

然后记得检查:

mise current java java -version echo $JAVA_HOME

如果 Java 项目在 IDEA 或 Eclipse 里始终识别不到正确版本,多半是因为 IDE 启动时没有继承 mise 注入的环境变量。我的经验是,从终端里启动 IDE(比如idea .),让 IDE 进程继承当前 Shell 的环境变量,大部分问题都能解决。实在不行再在 IDE 里手动把项目的 SDK 路径指到mise where java@17的输出路径上。

3.5 团队协作:把版本声明提交进仓库

多版本管理工具最大的收益在团队协作。我用 mise 之后,新同事入职装环境的流程缩短到三步:装 mise、跑 mise install、开始开发。不用再问“你 Node 几,JDK 装的什么版本”,因为答案都在仓库里。

一个典型的项目级 .mise.toml 长这样:

[tools] node = "22.19.0" python = "3.12.7" java = "17.0.13" [env] JAVA_HOME = "{{mise_install_path}}/java/17.0.13" legacy_version_file = true

这份文件直接提交到 Git,所有人拉下来后只需要执行:

mise install

mise 会自动读取 .mise.toml 里的 tools 列表,把缺失的版本全部装上。如果这个项目的版本之前已经装过,也不会重复安装,只是一个空操作。

为了把老项目的 .nvmrc 也兼容进来,我建议在全局配置里打开 legacy_version_file。这样团队成员即使某个仓库还没迁移成 .mise.toml,只要根目录有 .nvmrc 或 .python-version,mise 也能自动识别对应版本。理论上,这等于在不动老项目文件的前提下,先把统一切换机制铺进去。

如果你的 CI 也想用同一套版本定义,可以在 CI 脚本里先安装 mise,再执行 mise install,然后用 mise x 包裹构建命令,例如:

mise x -- npm ci mise x -- mvn package

mise x 会在指定工具的版本上下文中执行后续命令,确保 CI 和本地用的是同一套版本解析逻辑。

4. 常见问题与排查技巧实录

4.1 安装慢、超时,镜像源怎么配

这一点在国内环境下几乎是必踩的。Node 的安装包默认从官方源拉,Python 有些版本走源码编译,JDK 又依赖发行版服务器,网速稍差一点就会失败。

Node 的镜像配置我刚才提到过,用的是 MISE_NODE_MIRROR_URL。Python 则可以用 MISE_PYTHON_MIRROR_URL 配合 MISE_PYTHON_PRECOMPILED。这两个变量建议都写进 Shell 配置文件:

export MISE_NODE_MIRROR_URL="https://npmmirror.com/mirrors/node/" export MISE_PYTHON_MIRROR_URL="https://registry.npmmirror.com/-/binary/python/" export MISE_PYTHON_PRECOMPILED=1

JDK 的情况稍微特殊一点,因为 java 插件背后支持不同的发行版,下载 URL 由发行版确定。最简单的办法是先跑一次安装,观察它实际从哪个 URL 下载,然后根据需要换发行版或手动下载放到 mise 的缓存目录。实操中我发现 temurin 源通常比较稳定,如果默认源拉不动,试一下带发行版前缀的版本,比如 java@temurin-17,往往会好很多。

还要提一句:mise use -g 这种自动安装会写全局配置,如果你设置完镜像变量之后发现没生效,先确认变量有没有 export 到当前 Shell,或者干脆新开一个终端再跑。老终端里旧环境变量残留是排查时最容易忽略的点。

4.2 版本没切换成功,终端还是旧版

这是使用 mise 后最常遇到的问题:明明在项目目录里 cd 进来了,node -v 还是全局老版本。排查分三步走。

第一步看命令到底命中了什么。执行:

which node

如果输出不是类似 ~/.local/share/mise/shims/node,说明 PATH 顺序有问题。可能你之前手动改过 PATH,把别的 Node 安装目录放在了前面。保证 mise 的 shims 目录在 PATH 最前面,或者干脆把之前那些 nvm 的 path export 注释掉。

第二步看 Shell 是否真正 activate 了。执行:

type mise

输出如果是一个函数,说明 activate 生效;如果输出是 /path/to/mise,说明还没激活。这时候重新执行 activate 的初始化命令,或者退出重进终端。

第三步看配置优先级。项目目录里如果同时有 .mise.toml 和 .tool-versions,以 .mise.toml 为准。如果全局配置了 node@22,而项目目录 .mise.toml 里是 node@20,那当前目录里的版本一定是 20,这不是 bug,是作用域设计。

4.3 老项目 .nvmrc / .python-version 能不能直接用

很多老项目没有 .mise.toml,只有 .nvmrc 或者 .python-version。刚开始迁移时,我也不想把所有项目文件都改一遍,只希望停止使用 nvm 之后还能读这些文件。

答案是可以。你需要在全局配置里开启 legacy_version_file:

legacy_version_file = true

开启之后,进入一个有 .nvmrc 的目录,mise 会自动读取里面的版本号,并路由到对应 Node 版本。.python-version、.ruby-version、.node-version 同理。

需要注意优先级:如果项目根目录同时存在 .mise.toml 和 .nvmrc,mise 会优先读 .mise.toml。我的建议是既然要用 mise 统一,就慢慢把老项目的 .nvmrc 和 .python-version 重构成 .mise.toml,避免维护两套声明。legacy_version_file 只是过渡期兼容,长期保留两套文件反而会增加混乱。

4.4 权限不足,sudo 找不到 node 或 python 命令

这个问题在跑脚本、启动服务时很常见。mise 的 shim 是装在用户目录 ~/.local/share/mise/shims 下的,sudo 默认环境不一定包含这个 PATH。所以你 sudo node -v 可能出现 command not found,或者干脆调到系统自带的旧版本。

最直接的做法是保留当前用户 PATH 再执行:

sudo env "PATH=$PATH" node -v

但更好的做法是不要在 sudo 里直接跑开发工具。如果是启动服务,建议用系统服务管理器配置里指定绝对路径:

$(mise where node@22)/bin/node server.js

或者用 mise x 包裹:

mise x -- node server.js

这样既能确保版本正确,又不用污染全局 sudo 环境。

4.5 常见问题速查表

把上面这些经验整理成一张速查表,方便你遇到问题时快速定位。

症状可能原因解决办法
mise 命令找不到Shell 未加载 mise 路径检查 ~/.local/bin 是否在 PATH,重新执行安装脚本
版本没切换shims 不在 PATH 最前面调整 PATH 顺序,优先 ~/.local/share/mise/shims
node -v 和 mise current 不一致配置优先级覆盖检查 .mise.toml 和 .tool-versions 是否冲突
Python 编译报 ssl 错误缺少 libssl-dev 等依赖安装 build-essential、libssl-dev、zlib1g-dev 等
Java 命令正确但 JAVA_HOME 为空未在 [env] 中注入使用 mise where java 获取路径并写入配置
安装超时默认下载源慢设置 MISE_NODE_MIRROR_URL 或 MISE_PYTHON_MIRROR_URL
磁盘空间占用大缓存旧版本多执行 mise prune 清理

5. 最后一点经验

用 mise 管理多版本环境一年多了,我最深的感受是:它带来的最大收益不是“省了几条命令”,而是把版本信息从个人的终端配置里搬了出来,变成了项目本身的属性。新同事不用再靠口口相传安装什么版本,也没有人再因为终端里残留的 nvm 环境变量而怀疑人生。当时在 Windows 上搞 JetBrains 全家桶的 JDK 切换时,我也想过干脆回到手动改 JAVA_HOME 的老路,但坚持把所有配置整理进 .mise.toml 之后,后面换电脑、开新项目都顺了很多。

最后分享一个小技巧,也是我最近才养成的习惯:每周或者每两周跑一次mise ls看已安装版本,再用mise prune清理不用的旧版本和缓存。版本管理器用久了,磁盘缓存体积远超你想像,定期清理能让机器维持一个清爽的状态。如果你正在被 nvm、pyenv 和 JAVA_HOME 三线作战折腾,不妨找一个简单的小项目,从一份 .mise.toml 开始试起来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 17:10:57

C++文件读写与重定向:GESP四级竞赛必会的输入输出技巧

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

作者头像 李华
网站建设 2026/9/17 17:10:26

鼎捷T100凭证报表开发实战:SQL联查、字段绑定与参数配置

简介:本资源是一份面向鼎捷T100系统管理员与报表开发人员的实务型培训课件,聚焦凭证报表设计核心能力培养,解决日常财务单据模板定制、多级数据呈现及审批合规输出等关键问题。内容覆盖凭证样版(一般凭证、表格、子报表&#xff0…

作者头像 李华
网站建设 2026/9/17 17:09:40

OpenCV原生轻量级人脸识别系统设计与实战

简介:本资源是一份面向专科及本科毕业生的毕业论文文档,聚焦Python与OpenCV在人脸识别系统中的工程实践,助力读者掌握计算机视觉基础、人脸检测与识别算法实现等核心能力。文档完整覆盖研究背景、技术原理(含Haar级联、LBP、CNN等…

作者头像 李华
网站建设 2026/9/17 17:09:27

从docx到可检索结构化表:Python解析、打标签与FTS5索引实战

简介:第十三届挑战杯全国大学生课外学术科技作品竞赛部分获奖作品名单汇编,面向备赛的高校学生与指导教师,也适合关注大学生科技创新动态的读者。文档按奖项层级收录特等奖至未入围作品,覆盖新材料研发、能源环保、信息技术、生命…

作者头像 李华
网站建设 2026/9/17 17:09:12

RoboMaster电控硬件实战讲义:从电源设计到故障排查

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

作者头像 李华