news 2026/9/20 11:03:41

ant-design-vue Tree 树形控件完全指南:从 API 详解到源码级实现原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ant-design-vue Tree 树形控件完全指南:从 API 详解到源码级实现原理

ant-design-vue Tree 树形控件完全指南:从 API 详解到源码级实现原理

【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue

多层次的结构列表在业务系统中无处不在——文件夹目录、组织架构、生物分类、国家地区,世间万物的大多数结构都是树形结构。ant-design-vue 的Tree树形控件正是为展现这种层级关系而设计,它完整支持展开/收起、节点选择、复选框勾选、异步加载、拖拽排序、虚拟滚动等交互能力,并内置了适合文件目录场景的DirectoryTree

本文以 ant-design-vue 仓库中 components/tree/index.zh-CN.md 官方文档为骨架,结合Tree.tsxDirectoryTree.tsxvc-tree底层实现与仓库内 13 个 demo 示例,系统讲解 Tree 的全部配置项、事件、方法与常见实战场景,帮助你在项目中熟练驾驭树形控件,并理解其底层工作原理。

何时使用 Tree

树形控件的核心价值在于用最直观的层级缩进和展开收起,完整展现数据的父子关系。当你的业务数据满足以下特征时,优先考虑使用Tree

  • 数据存在明确的父子层级(如文件夹、组织架构、类目体系);
  • 需要交互能力(展开/收起、选择、勾选、拖拽、右键菜单)来浏览和操作层级数据;
  • 需要在大数据量下保持良好的浏览性能(配合虚拟滚动)。

快速上手:基本用法

Tree 既支持通过treeData数据驱动渲染,也支持(旧式)直接以a-tree-node子组件手动构造节点。官方推荐使用treeData,仓库源码 Tree.tsx 中也会对仅使用slots.default而不传treeData的写法发出弃用警告。

以官方基础示例 demo/basic.vue 为例,一个集成了勾选、选中、展开、禁用、自定义标题渲染的最简用法如下:

<template> <a-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" checkable :tree-data="treeData" > <template #title="{ title, key }"> <span v-if="key === '0-0-1-0'" style="color: #1890ff">{{ title }}</span> <template v-else>{{ title }}</template> </template> </a-tree> </template> <script lang="ts" setup> import { ref, watch } from 'vue'; import type { TreeProps } from 'ant-design-vue'; const treeData: TreeProps['treeData'] = [ { title: 'parent 1', key: '0-0', children: [ { title: 'parent 1-0', key: '0-0-0', disabled: true, // 禁用整节点 children: [ { title: 'leaf', key: '0-0-0-0', disableCheckbox: true }, // 仅禁用复选框 { title: 'leaf', key: '0-0-0-1' }, ], }, { title: 'parent 1-1', key: '0-0-1', children: [{ key: '0-0-1-0', title: 'sss' }] }, ], }, ]; const expandedKeys = ref<string[]>(['0-0-0', '0-0-1']); const selectedKeys = ref<string[]>(['0-0-0', '0-0-1']); const checkedKeys = ref<string[]>(['0-0-0', '0-0-1']); </script>

这里演示了三个关键能力:

  • v-model 双向绑定expandedKeys(展开)、selectedKeys(选中)、checkedKeys(勾选)均可通过v-model:前缀实现受控绑定;
  • 节点级控制disabled禁用整节点、disableCheckbox仅禁用该节点复选框;
  • 插槽定制:通过#title插槽拿到{ title, key, dataRef }等上下文,自定义标题渲染。

Tree props:完整配置详解

官方文档共定义了 25 个Tree配置项,下表在保留文档原意的基础上,结合 Tree.tsx 中的 props 定义补充了类型与默认值细节。

参数说明类型默认值版本
allowDrop是否允许拖拽时放置在该节点({ dropNode, dropPosition }) => boolean-
autoExpandParent是否自动展开父节点(搜索联动场景常用)booleanfalse
blockNode是否节点占据一整行(拖拽/目录树场景常用)booleanfalse
checkable节点前添加 Checkbox 复选框booleanfalse
checkedKeys (v-model)(受控)选中复选框的树节点。注意父子关联:传入父节点 key 则子节点自动选中;全部子节点 key 都传入时父节点也自动选中。当同时设置checkablecheckStrictly时,其值为含checkedhalfChecked属性的对象,父子选中不再关联string[] | number[] | { checked: string[] | number[]; halfChecked: string[] | number[] }[]
checkStrictlycheckable 状态下节点选择完全受控,父子节点选中状态不再关联booleanfalse
defaultExpandAll默认展开所有节点。异步数据需在数据返回后再实例化,建议用v-if="data.length";当存在expandedKeysdefaultExpandAll失效booleanfalse
disabled将整棵树禁用boolfalse
draggable设置节点可拖拽booleanfalse
expandedKeys (v-model)(受控)展开指定的树节点string[] | number[][]
fieldNames替换 treeNode 中 title、key、children 字段为 treeData 中对应的字段object{ children: 'children', title: 'title', key: 'key' }3.0.0
filterTreeNode按需筛选树节点(高亮),返回 true 表示命中function(node)-
height设置虚拟滚动容器高度,设置后内部节点不再支持横向滚动number-
loadData异步加载数据,展开节点时触发function(node)-
loadedKeys(受控)已经加载的节点,需配合loadData使用string[] | number[][]
multiple支持点选多个节点(节点本身,非复选框)booleanfalse
selectable是否可选中booleantrue
selectedKeys (v-model)(受控)设置选中的树节点string[] | number[]-
showIcon是否展示 TreeNode title 前的图标,无默认样式,置 true 后需自行定义图标与相关样式booleanfalse
showLine是否展示连接线,常用于文件目录结构boolean | { showLeafIcon: boolean }(3.0+)false
switcherIcon自定义展开/折叠图标,插槽参数含 active、checked、expanded、loading、selected、halfChecked、title、key、children、dataRef、data、defaultIcon、switcherCls 等v-slot-
title自定义标题(旧 API,推荐使用#title插槽)slot-2.0.0
treeDatatreeNodes 数据,设置后无需手动构造 TreeNode 节点;key 在整个树范围内必须唯一TreeNode[]--
virtual设置 false 时关闭虚拟滚动booleantrue3.0

源码中的默认值佐证:Tree.tsx 通过initDefaultProps显式声明了checkable: falseselectable: trueshowIcon: falseblockNode: false四个默认值,其余未声明默认值的项由底层vc-tree(components/vc-tree/props.ts)接管。

值得注意的源码细节

  • 虚拟滚动默认开启Tree.tsxvirtual直接取useConfigInject('tree', props)返回的全局配置(Tree.tsx),默认值true,渲染时通过virtual={virtual.value}传给底层VcTree
  • 虚拟列表行高固定 28pxitemHeight = 28作为默认行高传入(Tree.tsx),该值决定虚拟滚动的视口计算精度;
  • replaceFields已废弃:源码通过watchEffect给出 dev 警告(Tree.tsx),并在渲染时用fieldNames = props.replaceFields做兼容,新项目请一律使用fieldNames
  • RTL 支持:当 ConfigProvider 的directionrtl时,Tree 自动追加-rtl样式类(Tree.tsx)。

数据驱动:treeData 与字段映射 fieldNames

treeData的数据结构为DataNode(定义见 components/vc-tree/interface.tsx):

interface DataNode extends BasicDataNode { children?: DataNode[]; key: string | number; // 全树唯一 title?: any; // 节点标题,可为字符串或 VNode }

自定义字段映射 fieldNames

当后端返回的字段名不是title/key/children时,无需改造接口,直接映射即可。参考示例 demo/replaceFields.vue:

<a-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" checkable :tree-data="treeData" :field-names="fieldNames" > <template #title="{ name, key }"> <span v-if="key === '0-0-1'" style="color: #1890ff">{{ name }}</span> <template v-else>{{ name }}</template> </template> </a-tree> <script lang="ts" setup> const fieldNames: TreeProps['fieldNames'] = { children: 'child', title: 'name', }; const treeData = ref([ { name: 'parent 1', key: '0-0', child: [ /* ... */ ] }, ]); </script>

注意fieldNames的类型定义(components/vc-tree/interface.tsx)支持titlekeychildren三个字段的任意组合替换。字段映射在底层通过fillFieldNames归一化后贯穿数据转换、展开传导、勾选传导等全部逻辑(如 DirectoryTree.tsx 中的fillFieldNames(props.fieldNames))。

事件系统:交互回调解读

Tree 共暴露 11 个事件,官方文档定义如下:

事件名称说明回调参数
check点击复选框触发function(checkedKeys, e: { checked: bool, checkedNodes, node, event })
dragenddragend 触发时调用function({ event, node })
dragenterdragenter 触发时调用function({ event, node, expandedKeys })
dragleavedragleave 触发时调用function({ event, node })
dragoverdragover 触发时调用function({ event, node })
dragstart开始拖拽时调用function({ event, node })
dropdrop 触发时调用function({ event, node, dragNode, dragNodesKeys })
expand展开/收起节点时触发function(expandedKeys, { expanded: bool, node })
load节点加载完毕时触发function(loadedKeys, { event, node })
rightClick响应右键点击function({ event, node })
select点击树节点触发function(selectedKeys, e: { selected: bool, selectedNodes, node, event })

从 Tree.tsx 的实现可以看到,checkexpandselect三个核心事件在发出时会同步派发对应的update:xxxKeys更新事件,这正是v-model:checkedKeys/v-model:expandedKeys/v-model:selectedKeys双向绑定得以工作的底层机制。

底层事件签名(含类型化参数)定义在 components/vc-tree/props.ts,例如drop的完整参数还包括dropPosition(-1/0/1 分别表示放置在上方/内部/下方)与dropToGap,这在拖拽重排数据时非常关键(详见下文拖拽示例)。

复选框勾选的父子关联原理

checkedKeys的"父子联动"并非简单约定,而是由底层算法fillConductCheck保证的(components/vc-tree/utils/conductUtil.ts):

  1. 自上而下传导:父节点被勾选时,所有未禁用的子节点 key 被递归加入checkedKeys
  2. 自下而上回填:从叶子层向上,若某父节点的子节点全部被勾选,则父节点置为勾选;若仅部分勾选,父节点进入halfCheckedKeys(半选状态);
  3. 禁用边界disableddisableCheckboxcheckable === false的节点(isCheckDisabled判定)不参与传导,既不会被自动勾选,也不会被自动回填。

当设置checkStrictly后该传导被关闭,此时checkedKeys需传入{ checked, halfChecked }对象形式以区分勾选与半选。

Tree 方法:scrollTo

名称说明
scrollTo({ key: string | number; align?: 'top' | 'bottom' | 'auto'; offset?: number })虚拟滚动下,滚动到指定 key 条目

方法通过组件 ref 暴露,Tree.tsx 中exposescrollTo以及selectedKeyscheckedKeyshalfCheckedKeysloadedKeysloadingKeysexpandedKeys等计算属性,方便父组件在受控场景下读取最新状态:

<script lang="ts" setup> import { ref } from 'vue'; const treeRef = ref(); // 在虚拟滚动树中定位到某个节点 const jumpTo = () => { treeRef.value?.scrollTo({ key: '0-0-0-1', align: 'top', offset: 0 }); }; </script> <template> <a-tree ref="treeRef" :height="233" :tree-data="treeData" /> </template>

TreeNode:节点级配置

当你选择以子组件方式构造节点(旧式写法)而非使用treeData时,TreeNode提供以下节点级配置(官方文档表格):

参数说明类型默认值版本
checkable当树为 checkable 时,设置独立节点是否展示 Checkboxboolean-
class节点的 classstring-
disableCheckbox禁掉 checkboxbooleanfalse
disabled禁掉响应booleanfalse
icon自定义图标,可接收组件,props 为当前节点 propsslot | slot-scope-
isLeaf设置为叶子节点(设置了loadData时有效)boolean-
key被树的 (default)ExpandedKeys / (default)CheckedKeys / (default)SelectedKeys 属性所用。注意:整个树范围内所有节点的 key 值不能重复!string | number内部计算出的节点位置
selectable设置节点是否可被选中booleantrue
style节点的 stylestring | object-
title标题string'---'

节点级属性同样可以直接写在treeData的节点对象上(BasicDataNode定义见 components/vc-tree/interface.tsx),因此在数据驱动写法下,每个节点对象都可以声明自己的disableddisableCheckboxisLeafselectablecheckable等属性。

DirectoryTree:内置目录树

DirectoryTree是 Tree 的场景化封装,专为"文件资源管理器"式的目录浏览设计,在 index.tsx 中随TreeTreeNode一并导出并注册为全局组件。

DirectoryTree props

参数说明类型默认值
expandAction目录展开逻辑,可选false'click''dblclick'stringclick

与普通 Tree 的区别(源码佐证)

  • 默认展示文件夹图标:源码默认showIcon: true(DirectoryTree.tsx),并通过getIcon按节点状态渲染图标——叶子节点为FileOutlined,文件夹展开为FolderOpenOutlined、收起为FolderOutlined(DirectoryTree.tsx);
  • 默认整行块级展示blockNode强制为true
  • 点击/双击展开expandAction: 'click'时点击文件夹即触发展开,内部通过 200ms 防抖(debounce(expandFolderNode, 200, { leading: true }))调用底层onNodeExpand(DirectoryTree.tsx),并在 shift/ctrl/meta 组合键按下时跳过展开以保留多选手势;
  • 原生多选体验multiple模式下支持ctrl(Windows) /command(Mac) 点选追加、shift范围连选。范围选中的 key 集合由 utils/dictUtil.ts 中的calcRangeKeys基于展开态节点计算(只考虑已展开节点以优化性能),选中的节点列表则经convertDirectoryKeysToNodes还原。

参考示例 demo/directory.vue:

<a-directory-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" multiple :tree-data="treeData" />

实战场景一:异步加载(loadData)

点击展开节点时才向后端请求子级数据,适用于大型组织架构、动态目录等场景。参考 demo/dynamic.vue:

<a-tree v-model:expandedKeys="expandedKeys" v-model:selectedKeys="selectedKeys" :load-data="onLoadData" :tree-data="treeData" /> <script lang="ts" setup> const treeData = ref<TreeProps['treeData']>([ { title: 'Expand to load', key: '0' }, { title: 'Expand to load', key: '1' }, { title: 'Tree Node', key: '2', isLeaf: true }, // 标记为叶子,不触发加载 ]); const onLoadData: TreeProps['loadData'] = treeNode => { return new Promise<void>(resolve => { if (treeNode.dataRef.children) { resolve(); // 已有子级,无需再加载 return; } setTimeout(() => { treeNode.dataRef.children = [ { title: 'Child Node', key: `${treeNode.eventKey}-0` }, { title: 'Child Node', key: `${treeNode.eventKey}-1` }, ]; treeData.value = [...treeData.value]; // 触发响应式更新 resolve(); }, 1000); }); }; </script>

要点:

  • loadData必须返回 Promise,加载完成后通过替换treeData引用treeData.value = [...treeData.value])触发重渲染;
  • 不希望展开时请求的节点,用isLeaf: true标记为叶子;
  • 若需精确控制"已加载"状态,可配合受控的loadedKeys;加载过程 Tree 会自动展示 loading 图标(见 iconUtil.tsx,loading 状态下渲染LoadingOutlined)。

实战场景二:搜索高亮

可搜索树是后台管理系统的标配。核心思路:监听搜索词 → 计算命中节点的所有祖先 key→ 更新expandedKeys并配合autoExpandParent自动展开父级;标题命中部分用#title插槽高亮。参考 demo/search.vue:

<a-input-search v-model:value="searchValue" style="margin-bottom: 8px" placeholder="Search" /> <a-tree :expanded-keys="expandedKeys" :auto-expand-parent="autoExpandParent" :tree-data="gData" @expand="onExpand" > <template #title="{ title }"> <span v-if="title.indexOf(searchValue) > -1"> {{ title.substring(0, title.indexOf(searchValue)) }} <span style="color: #f50">{{ searchValue }}</span> {{ title.substring(title.indexOf(searchValue) + searchValue.length) }} </span> <span v-else>{{ title }}</span> </template> </a-tree> <script lang="ts" setup> watch(searchValue, value => { // 遍历 dataList 找到所有命中节点,向上回溯其父级 key 并去重 const expanded = dataList .map(item => (item.title.indexOf(value) > -1 ? getParentKey(item.key, gData.value) : null)) .filter((item, i, self) => item && self.indexOf(item) === i); expandedKeys.value = expanded; autoExpandParent.value = true; // 重新展开父节点 }); </script>

搜索时需自行实现getParentKey递归回溯父级(demo 中给出了完整实现),并在onExpand中将autoExpandParent置回false,避免用户手动收起节点后又被强制展开。

实战场景三:拖拽排序(draggable)

设置draggable后节点即可被拖拽到其他节点内部或前后。Tree 提供完整的dragstart/dragenter/dragover/dragleave/drop/dragend事件链,但数据重排需要你自行完成——组件只负责交互与回调,不直接改写treeData

参考 demo/draggable.vue 的drop处理核心逻辑:

const onDrop = (info: AntTreeNodeDropEvent) => { const dropKey = info.node.key; // 放置目标节点 const dragKey = info.dragNode.key; // 被拖拽节点 const dropPos = info.node.pos.split('-'); const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // dropPosition: -1 放置到目标节点前,1 放置到目标节点后,0 放入内部 // 1. 从 treeData 中摘除被拖拽节点 // 2. 按 dropToGap / dropPosition / 目标节点展开状态判断插入位置 // 3. 重新赋值 gData.value 触发更新 };

关键判定逻辑:

  • info.dropToGap === false:放入目标节点内部(作为其子级);
  • dropToGap === true且目标节点有子级、已展开、dropPosition === 1:追加为目标节点的最后一个子级
  • 其余情况按dropPosition === -1(插入到目标节点前)或1(插入到目标节点后)处理。

若需要对放置位置做精细化限制,可通过allowDrop: ({ dropNode, dropPosition }) => boolean实现(底层类型AllowDropOptions见 components/vc-tree/props.ts)。

实战场景四:虚拟滚动(virtual / height)

当节点数量达到数千级时,默认开启的虚拟滚动(virtual: true)可保证流畅滚动。参考 demo/virtual-scroll.vue:

<a-tree v-model:selectedKeys="selectedKeys" v-model:checkedKeys="checkedKeys" default-expand-all checkable :height="233" <!-- 设置高度即切换为虚拟滚动 --> :tree-data="dig()" <!-- 递归生成 10×10×10 规模数据 --> />

注意文档中的两点约束:

  • 设置height后虚拟列表被激活,内部节点不再支持横向滚动(横向内容将被裁剪);
  • 若业务上不需要虚拟化,可显式:virtual="false"关闭。

配合scrollTo方法,可在虚拟滚动树中实现"定位到某个节点"的导航能力。

实战场景五:连接线、自定义图标与右键菜单

showLine 连接线

文件目录场景常用showLine开启节点间的连接线(示例 demo/line.vue)。它接受boolean{ showLeafIcon: boolean }(3.0+)。连接线模式下展开/收起图标默认切换为MinusSquareOutlined/PlusSquareOutlined,叶子节点默认渲染FileOutlined,均可在 utils/iconUtil.tsx 中看到完整分支。

switcherIcon 自定义展开/折叠图标

通过#switcherIcon插槽接管展开/收起图标,插槽参数中的switcherCls用于保持图标与默认样式对齐,defaultIcon为默认图标可作兜底(示例 demo/switcher-icon.vue):

<a-tree v-model:expandedKeys="expandedKeys" show-line :tree-data="treeData"> <template #switcherIcon="{ switcherCls }"> <down-outlined :class="switcherCls" /> </template> </a-tree>

switcherIcon传入函数/插槽时,底层会注入{ ...nodeProps, defaultIcon, switcherCls }上下文(iconUtil.tsx),因此你可以针对不同节点状态(展开、选中、加载中等)渲染不同图标。

自定义节点图标(icon / showIcon)

showIcon开启 title 前图标位,配合#icon插槽按节点 key 或选中状态定制图标(示例 demo/customized-icon.vue)。注意官方文档提示:showIcon只负责展示,没有默认样式,开启后需要自行定义图标相关样式

rightClick 右键菜单

@rightClick拿到{ event, node },结合a-dropdowncontextmenu触发方式即可实现节点级右键菜单(示例 demo/context-menu.vue):

<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData"> <template #title="{ key: treeKey, title }"> <a-dropdown :trigger="['contextmenu']"> <span>{{ title }}</span> <template #overlay> <a-menu @click="({ key: menuKey }) => onContextMenuClick(treeKey, menuKey)"> <a-menu-item key="1">1st menu item</a-menu-item> <a-menu-item key="2">2nd menu item</a-menu-item> </a-menu> </template> </a-dropdown> </template> </a-tree>

FAQ:showLine 时如何隐藏子节点图标?

官方 FAQ 明确说明:连接线模式下出现在子节点前的"图标"实际上是通过switcherIcon渲染出来的(叶子节点默认FileOutlined)。若不需要该图标,可通过覆盖对应样式类隐藏,例如:

/* 隐藏 showLine 模式下叶子节点的文件图标 */ .ant-tree-show-line .ant-tree-switcher-leaf-line { display: none; }

从 iconUtil.tsx 的实现看,叶子节点在showLineshowLeafIcon为 false(showLine传对象形式)时,渲染的是-switcher-leaf-line这个纯样式 span(连接线的占位),否则渲染FileOutlined-switcher-line-icon)。因此更精确的做法是按需配置:

<!-- 连接线保留、但叶子节点不展示文件图标 --> <a-tree :show-line="{ showLeafIcon: false }" :tree-data="treeData" />

源码结构导览

若想深入 Tree 的实现,建议按以下路径阅读:

  • components/tree/Tree.tsx:ATree组件主体,props 定义、默认值、事件转发、expose方法、样式注入(CSS-in-JS);
  • components/tree/DirectoryTree.tsx:目录树封装,图标逻辑、expandAction、shift/ctrl 多选;
  • components/tree/index.tsx:导出TreeTreeNodeDirectoryTree及类型,并提供app.use()全局注册;
  • components/vc-tree/props.ts:底层树全部 props 与事件签名(与 ant-design 的 rc-tree 对齐);
  • components/vc-tree/utils/conductUtil.ts:勾选父子传导算法(fillConductCheck);
  • components/vc-tree/utils/treeUtil.ts:treeData与实体关系(keyEntities)转换、fieldNames归一化;
  • components/tree/utils/iconUtil.tsx:展开/折叠/叶子图标渲染规则;
  • components/tree/utils/dictUtil.ts:目录树范围选择(shift 连选)计算;
  • components/tree/demo:13 个官方示例(basic、dynamic、search、draggable、directory、line、virtual-scroll、context-menu、switcher-icon、replaceFields、customized-icon 等),是最佳上手素材;
  • components/tree/tests/demo.test.js:通过demoTest('tree')对全部 demo 做渲染冒烟测试,可作为组件行为的回归验证参考。

结语

ant-design-vue 的 Tree 是一个"开箱即用、深可定制"的树形组件:数据驱动的treeData+fieldNames让它能无缝对接任意后端数据结构;v-model受控三件套(checked/selected/expanded)与完整事件体系覆盖绝大多数交互诉求;DirectoryTree、异步加载、虚拟滚动、拖拽与右键菜单等能力则支撑起文件管理、组织架构、权限树等真实业务场景。理解其底层"勾选传导算法"与"受控事件转发"机制后,即使遇到高度定制化的需求,也能基于源码从容扩展。

【免费下载链接】ant-design-vue🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜项目地址: https://gitcode.com/gh_mirrors/an/ant-design-vue

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

uBlock Origin 3 分钟装好:免费广告拦截器,装完即用

uBlock Origin 3 分钟装好&#xff1a;免费广告拦截器&#xff0c;装完即用 【免费下载链接】uBlock uBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean. 项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock uBlock Origin 是 Chro…

作者头像 李华
网站建设 2026/9/20 10:58:43

VMware Workstation 17 Pro 离线激活与深度配置指南

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

作者头像 李华
网站建设 2026/9/20 10:56:15

家用NAS怎么选?群晖、威联通、飞牛fnOS深度对比与避坑指南

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

作者头像 李华