从“impeccable”到工程实践:代码格式化、静态检查与CI流水线
“impeccable”这个词按读音是 /ɪmˈpɛkəbəl/意思是“无可挑剔、毫无瑕疵”。我见过不少人把它当成代码注释里的形容词写“keep the code impeccable”。说实话第一次看到某公司前端代码仓库的提交规范里用这个词作为质量门禁的代号就觉得挺妙的——它比“perfect”更克制比“good”更严格刚好代表了一种工程态度不追求炫技但求每个环节都挑不出毛病。这篇东西想聊聊怎么把一个“让项目变得无可挑剔”的目标拆解成具体可落地的工程实践。不聊虚的“工匠精神”只看代码格式化、静态检查、提交规范、CI 流水线这几件日常绕不开的事。内容更偏向前端工程化但思路对任何语言项目都适用。1. 为什么是“impeccable”一次 Code Review 给我的启发1.1 从一次差点翻车的重构说起之前某天下午我重构一个老模块。功能都跑通了测试也过了发 Merge Request合并请求之前信心十足。结果同事在 Review 里指出来一处细节一个异步函数里catch 块吞掉了错误日志只返回了一个布尔值 false。逻辑上没错但排查问题的时候这种写法会让人很痛苦——线上报错了你只知道“失败了”却不知道“为什么失败”。这件事让我意识到一个常被忽略的事实好的代码不是“能跑的代码”而是“不容易引入问题、出问题之后能快速被定位的代码”。而“impeccable”这个标准恰好对应了工程上的几个可执行维度风格一致、规则前置、提交有迹可循、变更自动验证。1.2 把“无可挑剔”拆解成可执行的标准很多人一听“代码质量”就头疼觉得这是个抽象概念没法落地。但我觉得这东西其实可以拆成四个具体层面第一层是风格统一。团队里十个人写代码有人用 Tab有人用空格有人单引号有人双引号。这些看似细枝末节一旦混在一起diff 会变得极其难读。第二层是静态检查。在代码运行之前通过规则引擎发现潜在 bug、反模式和坏味道。ESLint 这类工具干的就是这件事。第三层是提交规范。Git 提交信息是工程的“编年史”写得乱七八糟将来回溯问题的时候就是一场灾难。第四层是自动化流转。靠人记着跑 lint、跑测试不现实必须把检查放进 CI持续集成流程里让机器强制执行。这四层叠加起来才是完整的“impeccable”。不是说某一个环节做到极致而是每一环都有基本保障。下面我从工具选型到具体配置逐个展开讲。2. 风格统一让代码看起来像一个人写的2.1 格式化工具选型我最终选择了 Prettier关于代码格式化前端圈有个经典争论ESLint 负责风格规范Prettier 负责代码格式化两者职责到底怎么划分我用了一年以后个人观点越来越明确格式化这件事直接交给 Prettier 就行ESLint 里关于格式化风格的规则最好关闭。为什么这么选核心原因是“职责单一”。ESLint 这类工具的本质是规则引擎它能把某种写法判为错误但它不太擅长自动改写代码而 Prettier 的本质是“固执己见的格式化器”它只做一件事——把代码重新排成统一的样式。举个例子ESLint 可以配置强制单引号写得不对就报错。但报错之后你得自己手动改或者用eslint --fix去修而 fix 通常会做一些比较粗暴的替换遇到复杂场景可能还会改错东西。Prettier 不一样它从 AST抽象语法树层面重新生成代码处理字符串拼接、换行、模板字面量这些场景要稳得多。注意千万不要同时让 ESLint 和 Prettier 管同一类格式问题否则会出现两个工具互相打架的尴尬场景。解决办法是安装eslint-config-prettier它可以关掉 ESLint 里所有和格式相关、和 Prettier 冲突的规则。2.2 配置落地细节一份实际可用的方案我推荐的做法是这样的Prettier 归 PrettierESLint 归 ESLint。Prettier 负责把所有代码格式统一化ESLint 专注代码质量和潜在错误检查。Prettier 的配置很简单在项目根目录建一个.prettierrc.json{ printWidth: 100, singleQuote: true, trailingComma: all, semi: true, tabWidth: 2 }这几个参数我解释一下。printWidth是单行最大宽度设成 100 比较适合现在的大屏显示器太窄容易频繁折行太宽会导致 git diff 难以阅读。singleQuote: true是统一风格trailingComma: all是我个人强烈推荐的——多行对象和数组的最后一项也加逗号这样以后增删项时 git diff 更干净只改动实际变化的那一行而不是连带上一行也变红。semi: true保留分号这在大多数语言里都是更不容易出错的习惯。光有配置文件还不够关键的一步是让 VS Code 在保存时自动格式化。在项目根目录建.vscode/settings.json{ editor.defaultFormatter: esbenp.prettier-vscode, editor.formatOnSave: true, editor.codeActionsOnSave: { source.fixAll.eslint: explicit } }这一套配完之后团队里每个人打开项目保存文件时都会自动统一风格。谁都不用记“这里用单引号还是双引号”机器全包了。我在实操中最大的体会是格式化工具的收益不在于省事而在于消除了团队里无意义争论的源头。代码评审的时间应该花在逻辑和架构上而不是纠缠缩进。3. 静态检查在编译之前发现潜在隐患3.1 从零搭建 ESLint不仅仅是“装个包”很多项目的 ESLint 配置是脚手架自动生成的能用但未必合适。我发现不少团队的项目里ESLint 跑了一遍只有零个报错、零个警告看似一切正常实际上是规则配置得太宽松等于没设防。以目前前端的主流配置为例我建议的起点是 ESLint 官方推荐的规则集再配上 TypeScript 的检查。需要的依赖大致有npm install -D eslint typescript eslint/js typescript-eslint eslint-config-prettier推荐用扁平化配置文件eslint.config.js这是 ESLint 官方持续演进的方向import eslint from eslint/js; import tseslint from typescript-eslint; import prettierConfig from eslint-config-prettier; export default tseslint.config( { ignores: [dist, node_modules, coverage], }, eslint.configs.recommended, ...tseslint.configs.recommended, prettierConfig, );这里有两个关键点。第一eslint.configs.recommended里包含一些非常基础但实用的规则比如防止声明了变量却没用、防止条件表达式里出现常量、防止重复的 case 分支等。这些规则在 ESLint v9 之后的扁平化配置里可以直接拿来用。第二typescript-eslint提供的类型感知规则虽然更强大但对项目有额外要求初期先用recommended级别就够了。初始化配置跑通后一定要做一次全面检查。执行npx eslint .如果项目是老代码第一次跑完肯定一片红。这时候别试图一次性全修完更不要直接把报错关掉。我的习惯是先把错误分一下类真正会引发运行时问题的错误比如no-await-in-loop这类优先修风格层面的问题交给 Prettier 去兜底剩下的可以记录成 TODO分批处理。3.2 规则集需要持续演进而不是一成不变有些团队的 ESLint 配置写了三年没变过这其实有点浪费。ESLint 和 TypeScript 的插件更新很快新规则不断补充进来老规则的默认行为也可能调整。但每次升级之后你都不太可能只加上依赖就完事。拿我比较看好的两条规则举例。一是typescript-eslint/no-floating-promises它要求 Promise 必须有后续处理await 或.catch。这条规则能抓到一类非常隐蔽的 bug函数里调用了一个异步操作忘了 await结果后面的代码以为结果已经拿到了实际上根本没有。二是eqeqeq强制使用严格相等和!避免带来的隐式类型转换陷阱。新增一条规则的具体流程我是这么做的。先在配置里加进去然后全局跑一遍看看有多少违规。如果只有零星几个直接修掉如果数量很多那就先加off或warn给团队一个缓冲期等到新代码逐步规范后再提升为error。规则升级不该是一刀切的行政命令而应该是平滑过渡的工程过程。避坑提示不要把.eslintignore当成懒惰的遮羞布。我见过有人把文件中报错的模块整个扔进 ignore 列表理由是“这部分历史包袱太重先不管”。结果这个“暂时”往往会变成三年。更稳妥的做法是用eslint-disable-next-line在具体代码行禁止个别规则并配上注释说明原因至少给后来人留一条线索。4. 提交规范把 Git 历史变成工程资产4.1 Husky Commitlint拒绝“乱七八糟的提交信息”前面说的 lint 和格式化管的是代码本身。但代码仓库里还有一个很容易被忽略的部分Git 提交信息。一款软件让它“无可挑剔”提交信息绝对是薄弱环节。你搜一下项目历史大概率能看到大量“update”“fix”“aaa”“修改”这种毫无信息量的记录。如果你要追查某个功能是什么时候加的、为什么加的靠这种提交信息根本无从查起。Commitlint 解决的问题就是让每一次提交信息都符合统一的规范。主流的规范是 Conventional Commits约定式提交提交信息的基本格式是type(scope): subject其中type是提交类型常见的有feat新功能、fix修复 bug、docs文档改动、refactor重构既不修 bug 也不加功能、perf性能优化、test测试相关、chore构建或辅助工具改动。实际例子可能是这样的feat(store): 添加基于 IndexedDB 的本地缓存模块 fix(auth): 修复 token 刷新后用户信息未更新的问题 docs(readme): 补充本地开发环境的搭建说明我建议把习惯培养成一个模板git commit -m fix(auth): 修复 token 刷新后用户信息未更新的问题。信息里带着 scope影响范围看的人一眼就知道这次提交动了哪里。4.2 落地配置三步走第一步安装依赖npm install -D commitlint/cli commitlint/config-conventional husky第二步生成 Commitlint 配置文件commitlint.config.jsexport default { extends: [commitlint/config-conventional], };第三步启用 Husky 的 commit-msg 钩子让每次提交前自动检查npx husky init echo npx --no -- commitlint --edit \$1 .husky/commit-msg这里解释一下 Husky 的原理。Git 本身有hooks机制在提交、推送之前可以执行脚本。但 Git 原生 hooks 需要在.git/hooks目录下手动创建脚本不能跟随项目仓库同步给团队每个人。Husky 的作用就是把这些钩子脚本放进项目目录通过安装依赖的过程自动激活团队里任何人 clone 项目后npm install一下钩子就生效了不需要手动配置什么。装上之后如果有人提交时信息写成了git commit -m add somethingcommit-msg 钩子会直接拦截提示格式不对。实测下来团队的提交信息规范程度能立竿见影地提升。5. CI 流水线让机器替你把关5.1 一个极简但完整的 CI 工作流示例前四步解决的是“本地开发时每个环节有保障”但人类是不可靠的。总有那么一次有人改了配置文件没有跑 lint或者急着上线本地跳过了测试。CI持续集成的作用就是建立一个兜底机制代码推送到远程仓库后自动跑一遍全套检查不合格就拒绝合并。以 GitHub Actions 为例一个极简的 workflow 长这样name: ci on: push: branches: [main] pull_request: branches: [main] jobs: lint-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-nodev4 with: node-version: 20 cache: npm - run: npm ci - run: npm run lint - run: npm run build - run: npm test这个工作流的逻辑很直白每次有代码推送到主分支或者有人提交 Pull Request就启动一台干净的 Linux 机器装上依赖依次跑类型检查、lint、构建和测试。任何一步失败PR 都会显示红色无法合并。有一个小细节值得注意为什么用npm ci而不是npm installnpm ci会严格按照 package-lock.json 里锁定的版本安装依赖速度更快而且不会因为依赖漂移引入莫名其妙的构建差异。CI 环境要的是确定性npm install允许自动更新 lock 文件这在国内前面说过的“稳定”这件事上是一个容易出错的点。5.2 CI 流水线里容易翻车的三个地方我在多个项目的 CI 落地过程中踩过几个坑逐个说一下。第一个坑是只推送不检查。很多仓库只在 push 事件上触发 CI这意味着开发者直接往主分支推送虽然会触发水流线跑但只要不限制权限检查过了红线也挡不住不合规的提交。我建议把分支保护加在主分支上不允许强制推送要求 Pull Request 必须通过检查才能合并关键路径还要有至少一个人 Review 通过。第二个坑是本地缓存和 CI 依赖不一致。有时候本地一切正常CI 上却报错原因往往是package-lock.json没同步或者哪个同事手动改了node_modules里的东西没提交。所以团队规范里要明确不要手动改 lock 文件新增依赖一律用npm install或者npm install xxx自动更新 lock 并提交。第三个坑是贪多求快跑全量测试。项目规模大了以后全量测试可能要跑十几分钟。这时候不能砍掉测试而是该做增量或者分层。比如 lint 只要跑改动的文件构建只需要确认能编过完整的测试可以放到 nightly夜间定时任务。CI 的价值是快速反馈不是折腾十分钟然后告诉你有几十个 lint 报错——那种体验只会让大家想办法绕过 CI而不是依赖它。6. 常见问题与排查技巧实录我整理了一张速查表基本都是实战中反复遇到的典型问题按照“症状、原因、处理”的方式记录。症状常见原因处理办法ESLint 报错但--fix改不了规则本身不支持自动修复比如逻辑相关的检查手动修改代码或查看规则文档确认是否支持 fix保存文件后代码风格不一致VS Code 没有加载项目的 Prettier 配置确认安装了 Prettier 扩展且没有装其他格式化器抢占默认格式化类型commit 信息明明符合规范还是被拦截没有生成 commitlint 配置文件或钩子脚本路径不对检查commitlint.config.js是否存在执行echo $?验证钩子退出码CI 上 lint 失败但本地通过本地依赖版本和 lock 文件不一致删掉 node_modules 重新npm ci确认 lock 文件已提交Prettier 和 ESLint 互相矛盾两者都配置了格式相关规则引入eslint-config-prettier关闭 ESLint 里所有格式规则提交信息里带大段临时文本同事直接git commit -m写了一堆没有遵循规范使用带模板的提交工具或者 PR squash 合并时统一重写消息再分享两个排查经验。第一个是关于“预提交钩子太慢”的。项目大了以后每次提交都要跑全量 lint可能会卡上十几秒甚至一分钟非常影响体验。解法是只检查暂存区staged里改动的文件而不是全项目。这个过程可以用lint-staged这个工具自动化在 package.json 里配置{ lint-staged: { *.{ts,tsx,js,jsx}: [eslint --fix, prettier --write] } }配好后提交时只会对 git 暂存区内改动的文件跑检查和格式化速度能快一个数量级。同时eslint --fix和prettier --write会顺手修正文件然后这些修正过的文件需要重新加入暂存区Husky 的钩子里可以做这一步很多模板已经内置处理了。第二个经验是关于“新规则引入时团队抵触”的。强推规则往往会引发反感我的做法是把规则变更做成一个 Pull Request而不是直接改主分支配置。PR 说明里写清楚“为什么加这条规则”给出两三个历史 bug 作为案例让团队成员在 Review 时看到实际收益。这样规则的引入是团队共识的结果而不是一个人发号施令。最后说说我踩过几次坑之后的体会这套“impeccable 工程实践”不是一次重构就能到位的。我当时是先搭了 Prettier统一了最显眼的格式问题然后加了 ESLint 的 recommended 规则集修了第一批历史问题接着上了 Husky 和 Commitlint管住提交信息最后才在 CI 里把整套检查固化下来。每一步都很小但每一步都让仓库的“健康度”肉眼可见地提升。我个人现在的一个习惯是每次在代码里看到// eslint-disable-next-line都会花两分钟确认这条豁免是否还有必要。规则豁免是一种负债时间越久越没人说得清当初为什么豁免。不定时清理这些豁免就会像后院的杂草一样长满整个仓库。另外还想说一点这套配置本身并不复杂复杂的是让团队真的跑起来。代码质量工具不是装了就算数它需要维护需要有人对规则的演进负责也需要在“效率”和“严格”之间找到团队的平衡点。如果你能把这条链路从本地编辑器一路打通到 CI 门禁那你手里的代码仓库至少离“impeccable”又近了一步。

相关新闻

AI测试工具ROI评估方法论:从成本拆解到收益量化实战指南

AI测试工具ROI评估方法论:从成本拆解到收益量化实战指南

测了三个月AI测试工具,我总结了一套ROI评估方法先说结论:绝大多数测试团队在引入AI工具时,根本没搞清这笔账怎么算。问起来就是"感觉效率提升了""用例生成快了不少",但你要是追问一句:具体快了多少…

2026/10/11 10:23:09 阅读更多 →
Hope Agent 源码开发教程:如何构建 Rust + Tauri + React 19 项目并提交贡献

Hope Agent 源码开发教程:如何构建 Rust + Tauri + React 19 项目并提交贡献

【免费下载链接】hope-agent 🦭 A cross-device desktop AI agent with memory, autonomous goals, dynamic workflows, and headless deployment | 会记忆、能持续推进目标、会动态编排多 Agent 的跨端桌面 AI 助手,也可服务化常驻 NAS / 云端 项目地址…

2026/10/11 10:23:09 阅读更多 →
看懂代码托管平台日榜:从star增速到上手落地的开源项目筛选指南

看懂代码托管平台日榜:从star增速到上手落地的开源项目筛选指南

每天早上一杯咖啡还没喝完,我就习惯性打开代码托管平台的日榜页面,看看今天又冒出哪些新项目。2026-10-04 这天的榜单特别有意思,既有连续霸榜好几天的熟面孔,也有几个刚发布就冲上来的新项目。对很多开发者来说,日榜是…

2026/10/11 10:23:09 阅读更多 →

最新新闻

Linux C++符号混淆实战:从符号泄露到崩溃还原

Linux C++符号混淆实战:从符号泄露到崩溃还原

上个月处理一个发布版Linux软件被逆向的排查,第一次动手就发现一个很扎心的现象:那个C项目没做任何符号层面的处理,对手一条nm命令下来,整个项目的内部函数名、类名、全局变量全部原样躺在那里。软件里有一块自研的核心算法&#…

2026/10/11 13:08:48 阅读更多 →
MFC下载器开发实战:WinInet、进度条与自绘界面

MFC下载器开发实战:WinInet、进度条与自绘界面

简介:这是一份MFC单文档/对话框框架下的文件下载工程源码包,面向Win32桌面应用初学者与需要实现HTTP下载、进度反馈和自定义界面的C开发者。包内包含完整的工程文件、界面位图资源、说明文档及编译生成文件,涵盖CInternetSession/CHttpFile的…

2026/10/11 13:08:48 阅读更多 →
VS2019 MFC双人五子棋实战:编译环境配置与核心代码解析

VS2019 MFC双人五子棋实战:编译环境配置与核心代码解析

简介:基于MFC框架的双人五子棋完整工程,已配好VS2019编译环境,适合有一定C基础、正在学习Windows界面编程或博弈算法实现的开发者。程序使用纯图形界面,支持黑白双方轮流落子、自动判断胜负、悔棋,以及棋局的保存与打开…

2026/10/11 13:08:48 阅读更多 →
BWO-KELM预测:白鲸优化调参提升KELM泛化能力

BWO-KELM预测:白鲸优化调参提升KELM泛化能力

简介:本资源是一套基于MATLAB实现的智能优化算法与机器学习融合的回归预测方案,面向高校研究生、科研人员及工程技术人员,解决小样本非线性回归建模中模型参数调优难、泛化能力弱等实际问题。压缩包共6个文件(4个核心m脚本、1个加…

2026/10/11 13:08:48 阅读更多 →
商品评论情感分析毕业设计:Python数据清洗到GUI模型部署全流程

商品评论情感分析毕业设计:Python数据清洗到GUI模型部署全流程

简介:一套基于Python的机器学习商品评论情感分析毕业设计完整项目,采用SVM与LSTM算法,并提供GUI可视化界面,适合计算机相关专业学生完成大作业、毕业设计及项目实战练习。项目经导师指导并评审通过,评分98分&#xff0…

2026/10/11 13:08:48 阅读更多 →
ImageJ Windows版从闪退到批量出图:内存设置、宏批处理与插件安装全攻略

ImageJ Windows版从闪退到批量出图:内存设置、宏批处理与插件安装全攻略

简介:这款ImageJ Windows版本采用64位Java 8捆绑,开箱即用,适合生物医学、材料科学等领域研究人员进行图像分析与测量。资源共430个文件,压缩包约47.72MB,以ijm宏、dll动态库、jar插件和java源码为主,同时内…

2026/10/11 13:07:48 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

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

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

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

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/10 10:38:42 阅读更多 →