Claude Code Hooks系统:从事件驱动到自动化工作流的深度实践
1. 项目概述为什么我们需要一个Hooks系统如果你最近在折腾AI编程助手尤其是Claude Code那你大概率已经不止一次地听到“Hooks”这个词了。它就像一个隐藏在Claude Code强大功能背后的“开关总控室”虽然不常被新手直接操作但却是决定你能否高效、个性化使用这个工具的关键。简单来说Claude Code的Hooks系统是一套允许开发者在AI助手执行特定动作如生成代码、分析文件、运行命令的前后注入自定义逻辑的机制。这解决了什么痛点想象一下你每次让Claude Code生成一段API调用代码它默认的模板可能不符合你团队内部的代码规范或者你希望它在每次分析一个Python文件时都自动先运行一遍代码格式化工具又或者你想把AI生成的代码片段自动同步到你的知识库笔记里。如果没有Hooks你要么事后手动修改要么反复给AI输入同样的提示词效率低下且容易出错。而Hooks系统就是让你能把这些重复性的、定制化的“后处理”或“预处理”工作自动化让Claude Code真正成为贴合你个人或团队工作流的智能伙伴而不仅仅是一个对话式的代码生成器。2. Hooks系统的核心架构与工作原理要玩转Hooks首先得理解它的“触发-响应”模型。这套系统并非Claude Code UI上某个显眼的按钮而更像是一套基于事件订阅的插件机制。它的核心架构可以分解为以下几个部分。2.1 事件生命周期与Hook点Claude Code内部定义了一系列清晰的事件生命周期节点我们称之为“Hook点”。这些点就像是AI助手工作流程中的一个个检查站。常见的Hook点包括pre_generation: 在Claude Code根据你的指令开始生成代码或文本之前触发。你可以在这里修改用户输入的提示词或者添加上下文信息。post_generation: 在AI生成内容完成之后、返回给用户之前触发。这是最常用的Hook点用于对生成的结果进行格式化、安全检查、规范校验等。pre_analysis: 在Claude Code分析一个文件或代码块之前触发。可以用于预处理文件内容。post_analysis: 在分析完成之后触发用于对分析结果进行加工或提取关键信息。on_command: 当执行特定Claude Code命令时触发。这允许你扩展或重写内置命令的行为。每个Hook点都为你打开了一个窗口让你能在AI的“思考”和“输出”流程中插入自己的代码逻辑。2.2 Hook脚本的执行环境与数据流你的Hook脚本通常是Python或JavaScript文件会在一个受控的、与Claude Code主进程隔离的环境中运行。这是出于安全考虑防止恶意脚本影响IDE的稳定性。当事件触发时Claude Code会将一个包含了当前操作上下文Context的数据对象传递给Hook脚本。这个上下文对象是Hook的灵魂它通常包含user_input: 用户原始的输入指令。generated_text: 对于post_generation等Hook这里就是AI生成的原生文本。file_path: 当前活动文件的路径。language 当前文件的编程语言。metadata: 其他元数据如会话ID、模型名称等。你的Hook脚本接收这个上下文对其进行读取、修改然后返回一个新的、修改后的上下文对象。Claude Code会接着使用这个修改后的上下文继续后续流程。例如一个post_generation的Hook可以读取generated_text用black格式化Python代码然后将格式化后的字符串写回generated_text最后返回上下文。2.3 配置与加载机制Hooks的配置通常通过一个配置文件如claude_code_hooks.json或.claude/hooks.json来管理。这个配置文件定义了哪个Hook脚本对应哪个Hook点。Claude Code在启动或检测到配置文件变更时会加载这些定义。一个典型的配置结构如下{ hooks: { post_generation: [ { name: format_python_code, script: ~/.claude/hooks/format_python.py, trigger: { language: python } } ], pre_generation: [ { name: inject_company_prompt, script: ~/.claude/hooks/inject_prompt.js } ] } }这种配置方式使得Hook的管理非常灵活你可以轻松启用、禁用或调整Hook的执行顺序。3. 实战从零构建你的第一个Hook理解了原理我们动手写一个最实用、最能立刻提升体验的Hook自动为生成的Python代码添加类型注解Type Hints。很多AI生成的函数默认没有类型提示这在团队协作和后期维护时是个隐患。我们可以用Hook在生成后自动补上。3.1 环境准备与项目结构首先找到你的Claude Code配置目录。这通常在用户主目录下如~/.claudeLinux/macOS或C:\Users\YourName\.claudeWindows。如果没有hooks文件夹就创建一个。我们的项目结构如下~/.claude/ ├── hooks/ │ ├── add_type_hints.py # 我们的Hook脚本 │ └── utils.py # 可能的工具函数 └── claude_code_hooks.json # Hook配置文件3.2 编写核心Hook脚本接下来创建add_type_hints.py。我们将使用libcst这个库它是一个用于无损解析和修改Python代码的强大工具比正则表达式可靠得多。# ~/.claude/hooks/add_type_hints.py import libcst as cst import libcst.matchers as m from typing import Optional, Dict, Any class TypeHintTransformer(cst.CSTTransformer): 一个CST转换器用于为简单函数添加类型注解。 def __init__(self): self.changed False def leave_FunctionDef( self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef ) - cst.FunctionDef: # 只处理没有返回类型注解的函数 if updated_node.returns is None: # 这里实现一个简单的类型推断逻辑示例根据参数名猜测 # 实际应用中你可以集成mypy或使用更复杂的推断逻辑。 new_params [] for param in updated_node.params.params: # 如果参数已经有注解则保留 if param.annotation is None: # 简单推断name - str, count - int, items - List[Any] type_annotation self._infer_type_from_name(param.name.value) if type_annotation: new_param param.with_changes(annotationtype_annotation) new_params.append(new_param) self.changed True else: new_params.append(param) else: new_params.append(param) # 同样可以尝试推断返回类型这里简化处理 # 假设函数名以‘get_’、‘find_’、‘calculate_’开头可能返回非None值 returns_annotation None if updated_node.name.value.startswith((get_, find_, calculate_)): returns_annotation cst.Annotation(annotationcst.Name(Any)) self.changed True new_params_obj updated_node.params.with_changes(paramsnew_params) return updated_node.with_changes(paramsnew_params_obj, returnsreturns_annotation) return updated_node def _infer_type_from_name(self, param_name: str) - Optional[cst.Annotation]: 非常基础的根据参数名推断类型仅用于演示。 type_map { name: str, message: str, text: str, count: int, index: int, size: int, items: List[Any], data: Dict[str, Any], file_path: str, url: str, } py_type type_map.get(param_name) if py_type: # 将字符串类型表示转换为CST节点是一个复杂过程这里极度简化。 # 实际项目应使用更稳健的方法例如cst.parse_expression。 try: # 这是一个简化示例对于复杂类型如List[Any]会失败。 # 生产环境建议使用条件判断和cst.Subscript等构建。 if [ not in py_type: return cst.Annotation(annotationcst.Name(py_type)) except: pass return None def post_generation(context: Dict[str, Any]) - Dict[str, Any]: Claude Code Hook 入口函数。 接收上下文修改其中的 generated_text。 generated_text context.get(generated_text, ) language context.get(language, ).lower() # 只处理Python代码 if language ! python or not generated_text.strip(): return context try: # 1. 解析代码为CST module cst.parse_module(generated_text) # 2. 应用我们的转换器 transformer TypeHintTransformer() modified_module module.visit(transformer) # 3. 只有当代码被修改过才更新上下文 if transformer.changed: context[generated_text] modified_module.code # 可以添加一个提示信息如果Claude Code支持在UI显示 # context.setdefault(metadata, {}).setdefault(hook_messages, []).append(已自动添加类型注解。) else: # 可选添加日志说明未修改 pass except Exception as e: # 异常处理至关重要不能让Hook崩溃影响主流程。 # 可以记录日志这里简单打印到标准错误实际应使用日志库 import sys print(f[TypeHint Hook Error]: {e}, filesys.stderr) # 发生错误时选择原样返回生成的文本保证用户体验不受损 pass return context注意上面的类型推断逻辑_infer_type_from_name极其简陋仅用于演示原理。在生产环境中你需要更稳健的类型推断可以考虑集成infer、jedi等静态分析库或者只为你明确知道的模式添加注解。安全性和稳定性是Hook设计的第一原则。3.3 配置与启用Hook现在创建或编辑~/.claude/claude_code_hooks.json配置文件{ hooks: { post_generation: [ { name: add_python_type_hints, script: ~/.claude/hooks/add_type_hints.py, trigger: { language: python }, active: true } ] } }name: Hook的唯一标识便于管理。script: Hook脚本的绝对路径或相对于配置目录的路径。trigger: 条件触发器。这里我们设置只对language为python的生成内容生效。你还可以根据file_path通配符匹配、user_input包含特定关键词等来触发。active: 是否启用该Hook。保存配置文件后重启Claude Code或等待其自动重载配置。现在当你用Claude Code生成Python函数时它就会尝试自动为参数和返回值添加类型注解了。4. 高级Hook应用场景与设计模式掌握了基础Hook开发后我们可以探索更高级的应用这些才是Hooks系统真正发挥威力的地方。4.1 场景一代码规范与风格强制统一团队协作中代码风格不一致是常见问题。你可以创建一个post_generationHook集成black格式化、isort导入排序和flake8语法检查。实现思路在Hook脚本中将generated_text写入一个临时文件。使用subprocess模块依次调用black、isort格式化该文件。调用flake8进行检查如果只有风格警告而非语法错误可以自动修复或仅将警告信息附加到生成文本的注释中。读回格式化后的内容更新context[“generated_text”]。注意事项性能频繁调用外部命令行工具会有开销。可以考虑为格式化工具设置超时或者只在生成代码块较大时触发。错误处理格式化工具可能失败如语法错误。Hook必须捕获这些异常并优雅地回退到原始文本同时可能添加一条提示信息。4.2 场景二智能上下文感知与提示词增强一个pre_generationHook可以根据你当前正在编辑的文件自动为你的问题添加上下文。例如你正在编辑一个FastAPI应用当你问Claude Code“如何添加一个用户登录接口”时Hook可以自动读取当前目录的requirements.txt、主要的app.py文件结构并将这些信息作为系统提示词或上下文前缀插入到你的问题中使AI的回答更贴合你的项目现状。实现思路分析context[“file_path”]确定项目根目录。读取关键架构文件如pyproject.toml,main.py,router目录结构。将这些信息总结成一段文本拼接到context[“user_input”]的前面或放入一个专用于上下文的字段如果Claude Code API支持。4.3 场景三自动化工作流与外部工具集成这是Hooks系统最强大的地方它能将Claude Code嵌入到你现有的开发流水线中。自动生成测试post_generationHook检测到生成的是某个类或函数自动调用pytest的代码生成模板为其创建对应的测试用例骨架并询问你是否要一并插入测试文件。知识库同步当Claude Code生成了一个很好的算法解释或解决方案后Hook可以自动提取摘要通过API提交到你的Notion、Obsidian或公司Wiki页面。安全扫描对于生成的代码尤其是涉及网络、命令执行、文件操作的可以立即用bandit、semgrep等安全工具进行静态扫描并将潜在风险以注释形式标注在生成代码中。设计模式建议 对于复杂的工作流建议采用“管道Pipeline”模式。即一个Hook点配置多个脚本每个脚本只负责一个单一职责如格式化、检查、同步。通过配置文件控制执行顺序使得每个Hook小而专易于维护和测试。5. 调试、排查与性能优化开发Hook难免遇到问题掌握调试方法至关重要。5.1 调试Hook脚本由于Hook在独立环境运行不能直接用IDE的调试器附加。最实用的方法是日志记录。文件日志在你的Hook脚本中使用Python的logging模块将信息写入一个固定的日志文件。import logging logging.basicConfig( levellogging.DEBUG, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, filename/tmp/claude_code_hooks.log, # 指定一个路径 filemodea ) logger logging.getLogger(__name__) def post_generation(context): logger.info(fHook triggered for language: {context.get(language)}) # ... 你的逻辑 logger.debug(fGenerated text length: {len(context.get(generated_text, ))}) return context标准输出/错误Hook脚本打印到stdout/stderr的内容有时会出现在Claude Code的开发者控制台或系统日志中取决于具体实现。这是一个快速查看简单信息的方法。上下文注入调试信息有些Claude Code实现允许Hook在context[‘metadata’]中添加信息这些信息可能会在UI的某个调试面板显示。查阅官方文档确认。5.2 常见问题排查表问题现象可能原因排查步骤Hook完全不生效1. 配置文件路径错误。2. 配置文件格式错误JSON语法。3. Hook脚本路径错误或无权执行。4.active字段为false。1. 确认配置文件在正确的.claude目录下。2. 使用JSON验证器检查配置文件。3. 检查脚本文件是否存在、有读权限如果是Python脚本确保有执行权限chmod x。4. 检查配置中active是否为true。Hook生效但修改未应用1. Hook脚本逻辑错误提前返回或未修改正确字段。2. 脚本存在语法或运行时异常被静默处理。3. 触发条件trigger不匹配。1. 添加详细日志检查脚本是否被调用以及context内容。2. 在脚本开头加入try...except捕获所有异常并打印。3. 检查trigger配置确保当前操作满足条件如语言、文件路径。Claude Code变慢或卡顿1. Hook脚本执行耗时过长。2. Hook脚本存在阻塞操作如同步网络请求。3. 配置了过多或过于复杂的Hook。1. 在Hook脚本中记录时间戳计算执行耗时。2. 将网络请求等IO操作改为异步如果环境支持或移至后台线程。3. 精简Hook逻辑或考虑将某些重型操作移到on_command这类非实时性Hook中。生成的内容被意外破坏1. Hook脚本对文本的处理逻辑有bug如错误的字符串替换。2. 使用的第三方库如libcst处理边缘案例时出错。1. 在修改前先备份原始的generated_text到日志。2. 对输入内容做更严格的校验对于无法处理的格式直接原样返回。3. 增加单元测试覆盖各种代码样例。5.3 性能优化与最佳实践惰性加载与缓存如果你的Hook需要加载大型模型如用于代码分析的本地ML模型或初始化复杂客户端如数据库连接不要在每次调用时都初始化。使用全局变量或模块级缓存在脚本第一次被加载时初始化。超时机制为你的Hook逻辑设置一个合理的超时时间例如2秒。如果处理超时则放弃修改并返回原始上下文避免阻塞用户。条件执行充分利用trigger配置。不要对所有生成内容都运行重型Hook。例如代码格式化Hook可以设置为只当生成文本超过10行或包含特定语言关键字时才触发。保持无状态尽量将Hook设计为无状态的纯函数。输出只依赖于输入的context。这避免了潜在的并发问题也使Hook更容易测试。测试驱动开发为你的Hook脚本编写单元测试。模拟不同的context输入验证输出是否符合预期。这能极大提升Hook的可靠性。Hooks系统将Claude Code从一个优秀的AI助手转变为一个可编程的、深度集成到你工作流中的自动化核心。它要求你从“使用者”转变为“扩展者”这需要一些学习和调试成本但带来的效率提升和个性化体验是巨大的。我个人在深度使用后最大的体会是最好的Hook往往是那些解决你自己特定、微小痛点的工具而不是追求大而全的复杂系统。从一个简单的、自动添加TODO注释的Hook开始逐步构建你的自动化工具箱这个过程本身就像是在教你的AI伙伴如何更好地与你合作。

相关新闻

HSTS错误全面解析:从原理到排查,彻底解决网站访问被拒问题

HSTS错误全面解析:从原理到排查,彻底解决网站访问被拒问题

1. 问题现象与核心概念解析 如果你在浏览器里看到“您目前无法访问,因为此网站使用了 HSTS。网络错误和攻击通常是暂时的,因此,此网页稍后可能会恢复正常”这个提示,先别急着怀疑自己的网络或者电脑出了问题。这个看似“禁止访问”…

2026/10/17 1:49:23 阅读更多 →
企业AI落地首选销售场景:技术栈、ROI与实施路径全解析

企业AI落地首选销售场景:技术栈、ROI与实施路径全解析

1. 为什么销售是AI落地的“最佳突破口”? 如果你在企业里负责过技术落地,或者和业务部门打过交道,大概率听过这样的对话:技术团队兴致勃勃地介绍一个AI新功能,业务老大听完后,沉默几秒,问&#…

2026/10/17 2:35:33 阅读更多 →
MySQL实现UPSERT操作:从INSERT ON DUPLICATE KEY UPDATE到临时表方案详解

MySQL实现UPSERT操作:从INSERT ON DUPLICATE KEY UPDATE到临时表方案详解

1. 从一个常见的业务场景说起做后端开发或者数据处理的同学,肯定遇到过这样的场景:有一张用户信息表,每天会从上游系统同步过来一批最新的用户数据。这批数据里,有些用户是全新的,需要插入到我们的表里;有些…

2026/10/7 7:37:18 阅读更多 →

最新新闻

无论最终选择海康 VisionMaster、Halcon,还是直接调用 SDK,第一步永远相同:让 PC 和相机“找到彼此”

无论最终选择海康 VisionMaster、Halcon,还是直接调用 SDK,第一步永远相同:让 PC 和相机“找到彼此”

无论最终选择海康 VisionMaster、Halcon,还是直接调用 SDK,第一步永远相同:让 PC 和相机“找到彼此”。这一步没走通,后面所有的算法、标定、通信都是空谈。 相机连接与 IP 配置:网口相机的“第一道门槛” GigE 相机与…

2026/10/17 2:35:38 阅读更多 →
传统视觉与深度学习视觉,本质上是“人定规则”与“机器学规则”的分野。两者不是替代关系,而是适用于不同问题域的工具

传统视觉与深度学习视觉,本质上是“人定规则”与“机器学规则”的分野。两者不是替代关系,而是适用于不同问题域的工具

传统视觉与深度学习视觉,本质上是**“人定规则”与“机器学规则”**的分野。两者不是替代关系,而是适用于不同问题域的工具。 核心差异总览维度传统视觉深度学习视觉规则来源人根据物理原理设计机器从数据中学习可解释性每一步透明可追溯黑箱&#xff0c…

2026/10/17 2:35:38 阅读更多 →
采图的本质,是**把相机传感器上的物理信号,通过一套标准化的“搬运规则”,变成上位机内存里可被算法消费的数据

采图的本质,是**把相机传感器上的物理信号,通过一套标准化的“搬运规则”,变成上位机内存里可被算法消费的数据

采图的本质,是把相机传感器上的物理信号,通过一套标准化的“搬运规则”,变成上位机内存里可被算法消费的数据。 无论用 VisionMaster、Halcon 还是直接调 SDK,底层都在做同一件事:触发 → 曝光 → 传输 → 缓冲 → 交付…

2026/10/17 2:35:38 阅读更多 →
UE5 MovieRenderQueue 渲染异常排查:条纹、黑块、幽灵球体与全黑帧根因解析

UE5 MovieRenderQueue 渲染异常排查:条纹、黑块、幽灵球体与全黑帧根因解析

简介:本资源是一份面向Unreal Engine 5中高级开发者与影视渲染工程师的实战型技术指南,聚焦MovieRenderQueue(MRQ)在实际项目中高频出现的渲染异常问题,如图像条纹、黑块、场景多出物体、全黑输出、相机偏移及着色器编…

2026/10/17 2:35:38 阅读更多 →
XERJ 搜索 Jupyter Notebook:按 Markdown 标题分区索引 .ipynb 与同目录 .py 文件

XERJ 搜索 Jupyter Notebook:按 Markdown 标题分区索引 .ipynb 与同目录 .py 文件

【免费下载链接】xerj XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent me…

2026/10/17 2:35:38 阅读更多 →
UGUI撞上DOTS:Canvas性能黑洞与ECS渲染管线重构

UGUI撞上DOTS:Canvas性能黑洞与ECS渲染管线重构

简介:UGUIDots 是一套面向数据技术栈(DOTS)的 UI 解决方案,专为希望在 Unity 中兼顾 UGUI 工作流与 ECS 性能的开发者设计。它并非替代 Unity 原生 UI,而是在其基础上扩充,让 UI 设计流程基本保持不变&…

2026/10/17 2:34:37 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →