news 2026/10/11 1:43:12

Qt Quick与QML入门到实战:跨平台界面开发完全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt Quick与QML入门到实战:跨平台界面开发完全指南

引言

在现代软件开发中,用户界面的体验与性能往往决定了产品的成败。Qt 作为一套成熟的跨平台应用开发框架,长期被用于桌面、移动和嵌入式设备的界面构建。随着设备形态和交互方式的不断丰富,开发者对界面开发的效率、流畅度以及跨平台一致性的要求也越来越高。正是在这样的背景下,Qt Quick 与 QML 应运而生,为开发者提供了一种声明式、GPU 加速、高度可定制的界面开发方案。

本文将从 Qt Quick 模块的组成与定位讲起,逐步深入到 QML 语言的核心语法、属性绑定、布局定位、信号事件、组件复用、动画过渡、模型视图以及与 C++ 的交互方式,最后介绍调试与性能优化技巧。无论你是初次接触 Qt 的新手,还是希望系统梳理 QML 知识的进阶开发者,本文都能为你提供一份清晰、完整的学习路径。

1. Qt Quick

1.1 什么是 Qt Quick

Qt Quick 是 Qt 框架中用于构建现代用户界面的模块,它建立在 QML 语言之上,提供了一整套可视化组件、布局系统、动画框架和交互模型。Qt Quick 的核心目标是让开发者能够以声明式的方式快速构建流畅、响应式的跨平台界面,同时保持与 C++ 后端的高效集成能力。

Qt Quick 的主要组成部分包括:

  • QtQuick 模块:提供基础的可视元素(如Rectangle、Text、Image)、定位器(Row、Column、Grid)和布局管理器。
  • QtQuick.Controls 模块:提供完整的桌面级控件库,包括按钮、输入框、滑块、菜单、对话框等,支持多种平台风格。
  • QtQuick.Layouts 模块:提供基于网格的响应式布局系统,支持元素在窗口尺寸变化时自动调整位置和大小。
  • QtQuick.Window 模块:提供窗口管理能力,支持多窗口、全屏、透明窗口等特性。
  • QtQuick.Particles 模块:提供粒子系统,用于实现烟雾、火焰、雪花等视觉效果。
  • QtQuick.Shapes 模块:提供矢量图形绘制能力,支持路径、渐变和复杂几何形状。

1.2 Qt Quick 与 Qt Widgets对比

在 Qt 生态中,除了 Qt Quick,还有一套传统的界面开发方案——Qt Widgets。两者各有优势,适用于不同的开发场景。下表从多个维度对它们进行对比:

对比维度Qt Quick(QML)Qt Widgets(C++)
开发范式声明式,界面结构与逻辑分离,代码简洁直观。命令式,通过 C++ 代码逐步构建界面,控制粒度细。
界面风格现代、流畅,支持丰富的动画和过渡效果,适合触屏和移动端。传统桌面风格,控件外观与操作系统原生风格接近。
性能特点渲染基于 GPU 加速的场景图,动画和特效性能出色。基于 CPU 绘制,复杂界面在高分辨率下可能性能受限。
适用场景移动应用、嵌入式设备、仪表盘、需要丰富动效的界面。传统桌面软件、复杂表单、需要深度集成系统控件的场景。
学习曲线QML 语法简单,上手快,但深入理解绑定和场景图需要时间。需要掌握 C++ 和 Qt 对象模型,学习曲线较陡。

1.3 典型应用场景

Qt Quick 凭借其跨平台能力和出色的渲染性能,在多个领域得到了广泛应用:

  • 移动应用开发:Qt Quick 支持 iOS、Android 和嵌入式 Linux,一套代码即可部署到多个移动平台。
  • 汽车仪表盘与车载系统:Qt Quick 的 GPU 加速渲染和流畅动画非常适合汽车 HMI(人机交互界面)开发,许多主流车企的仪表盘和中控系统都基于 Qt Quick 构建。
  • 智能家居与物联网设备:从智能音箱到工业控制面板,Qt Quick 能够在资源受限的嵌入式设备上提供流畅的界面体验。
  • 工业自动化与监控系统:Qt Quick 支持实时数据可视化,适合构建 SCADA 系统、设备监控面板等工业应用。
  • 桌面应用:通过 Qt Quick Controls 2,开发者可以构建现代化的桌面应用,并支持自定义主题和样式。

1.4 版本演进

Qt Quick 自 Qt 5 引入以来持续演进,每个版本都带来了重要的能力增强:

  • Qt 5.x:Qt Quick 2 引入场景图渲染架构,大幅提升渲染性能;Qt Quick Controls 2 提供更现代的控件风格。
  • Qt 6.x:引入 Qt Quick 3D,支持在 QML 中直接创建 3D 场景;改进渲染后端,支持 RHI(Rendering Hardware Interface),统一了不同图形 API(OpenGL、Vulkan、Direct3D、Metal)的渲染路径。
  • Qt Quick 3D:作为 Qt 6 的重要扩展,允许开发者在 QML 中声明式地构建 3D 场景,并与 2D 界面无缝混合,适用于产品展示、数据可视化和游戏开发。

在后续章节中,我们将深入探讨 QML 的基础语法、布局系统、信号处理、组件复用、动画机制以及如何与 C++ 后端高效交互,帮助读者全面掌握 Qt Quick 开发的核心技能。

2. QML

2.1 QML 概念

QML(Qt Modeling Language)是 Qt 框架中用于构建用户界面的一种声明式编程语言。它基于 JavaScript 语法,允许开发者通过描述界面元素及其属性来快速创建流畅、可交互的应用程序界面。QML 通常与 Qt Quick 模块配合使用,后者提供了一组丰富的可视化组件和布局系统。

与传统的命令式编程(如 C++、Java)不同,QML 采用声明式风格:开发者只需描述“界面应该是什么样”,而无需关心“如何一步步绘制界面”。这种特性使得 QML 特别适合开发需要频繁更新界面状态的应用,例如仪表盘、移动应用和嵌入式设备界面。

2.2 基础语法

QML 文件以.qml为扩展名,其基本结构由对象声明、属性绑定和信号处理三部分组成。下面是一个最简单的 QML 文件示例:

import QtQuick 2.15 Rectangle { width: 400 height: 300 color: "lightblue" Text { anchors.centerIn: parent text: "Hello, QML!" font.pixelSize: 24 } }

在上述代码中,Rectangle是一个可视元素,width、height和color是其属性;Text是嵌套的子元素,通过anchors.centerIn实现居中布局。QML 中的属性绑定是动态的,当某个属性值发生变化时,依赖它的其他属性会自动更新。

2.3 属性绑定

属性是 QML 元素的核心组成部分。每个元素都有一组预定义的属性,例如width、height、color、visible等。开发者也可以使用property关键字自定义属性:

Item { property int count: 0 property string name: "QML" onCountChanged: { console.log("Count changed to:", count) } }

属性绑定是 QML 最强大的特性之一。通过绑定,一个属性的值可以依赖于其他属性或表达式,并在依赖变化时自动更新。例如:

Rectangle { width: parent.width / 2 height: width * 0.6 }

当父元素的宽度变化时,子矩形的宽度和高度会自动重新计算,无需手动刷新。

2.4 布局与定位

QML 提供了多种布局方式,最常用的是锚点布局(Anchors)和定位器(Positioners)。锚点布局通过anchors属性将元素与父元素或其他兄弟元素对齐:

Rectangle { width: 300 height: 200 Rectangle { id: leftBox width: 100 height: 100 color: "red" anchors.left: parent.left anchors.top: parent.top } Rectangle { width: 100 height: 100 color: "green" anchors.left: leftBox.right anchors.top: parent.top } }

定位器包括Row、Column、Grid和Flow,它们会自动排列子元素。例如,使用Column垂直排列多个按钮:

Column { spacing: 10 Button { text: "按钮 1" } Button { text: "按钮 2" } Button { text: "按钮 3" } }

为了更直观地理解两种布局方式的差异,下表从适用场景、性能特点和代码复杂度三个维度对锚点布局与定位器进行对比:

对比维度锚点布局(Anchors)定位器(Row/Column/Grid/Flow)
适用场景适合元素之间需要精确对齐、相对定位的场景,例如将子元素锚定到父元素边缘、居中或与兄弟元素相邻排列。适合按固定方向或规则批量排列子元素的场景,例如垂直列表、水平按钮组、网格布局或自动换行排列。
性能特点依赖属性绑定和依赖关系计算,元素较多或锚点链较深时,布局计算开销相对较高。由定位器统一管理子元素的位置计算,结构简单时性能更优,但复杂嵌套或动态增删子元素时仍需关注重排成本。
代码复杂度需要为每个元素显式声明锚点关系,代码较为繁琐,但控制粒度细,适合精细布局。只需声明定位器类型和间距等少量属性,子元素自动排列,代码简洁,适合批量布局。

2.5 信号与事件

QML 使用信号(Signal)和槽(Slot)机制处理用户交互。每个元素都可以发出信号,开发者通过onSignalName语法连接信号处理器。例如,处理按钮点击事件:

Button { text: "点击我" onClicked: { console.log("按钮被点击了") } }

开发者也可以自定义信号。自定义信号使用signal关键字声明,并通过emit或直接调用信号名来触发:

Item { signal messageSent(string msg) function sendMessage() { messageSent("Hello from QML") } onMessageSent: { console.log("收到消息:", msg) } }

2.6 组件与复用

组件(Component)是 QML 中实现代码复用的重要机制。开发者可以将一组元素封装为一个组件,然后在多个地方重复使用。组件可以通过单独的文件定义,也可以使用Component关键字内联定义:

// MyButton.qml import QtQuick 2.15 import QtQuick.Controls 2.15 Button { property color bgColor: "lightgray" background: Rectangle { color: parent.bgColor radius: 5 } }

在其他文件中使用该组件:

import QtQuick 2.15 Column { MyButton { text: "保存"; bgColor: "green" } MyButton { text: "取消"; bgColor: "red" } }

2.7 动画与过渡

QML 内置了强大的动画系统,支持属性动画、状态过渡和缓动效果。使用Animation系列元素可以轻松实现平滑的界面动效。例如,实现一个颜色渐变动画:

Rectangle { id: box width: 100 height: 100 color: "blue" ColorAnimation on color { from: "blue" to: "red" duration: 1000 running: true } }

状态(State)和过渡(Transition)是 QML 动画的进阶用法。通过定义不同的状态,并在状态之间切换时应用过渡效果,可以实现复杂的界面变化:

Rectangle { id: rect width: 200 height: 200 color: "gray" states: [ State { name: "expanded" PropertyChanges { target: rect; width: 400; color: "yellow" } } ] transitions: [ Transition { NumberAnimation { properties: "width"; duration: 500 } ColorAnimation { duration: 500 } } ] MouseArea { anchors.fill: parent onClicked: rect.state = (rect.state === "expanded") ? "" : "expanded" } }

2.8 模型与视图

QML 通过模型(Model)和视图(View)机制展示列表、网格等数据集合。常用的视图包括ListView、GridView和Repeater。下面是一个使用ListView展示数据的示例:

ListView { width: 200 height: 300 model: ["苹果", "香蕉", "橙子", "葡萄"] delegate: Item { width: parent.width height: 40 Text { text: modelData anchors.centerIn: parent } } }

对于更复杂的数据结构,可以使用ListModel和ListElement定义模型:

ListModel { id: fruitModel ListElement { name: "苹果"; price: 5 } ListElement { name: "香蕉"; price: 3 } ListElement { name: "橙子"; price: 4 } } ListView { model: fruitModel delegate: Row { spacing: 10 Text { text: name } Text { text: "¥" + price } } }

2.9 与 C++ 交互

QML 通常与 C++ 后端配合使用,以实现高性能计算或访问系统资源。通过QObject派生类注册到 QML 上下文,可以在 QML 中直接调用 C++ 方法或访问属性。以下是一个简单的 C++ 类注册示例:

#include <QObject> class Counter : public QObject { Q_OBJECT Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged) public: explicit Counter(QObject *parent = nullptr) : QObject(parent), m_count(0) {} int count() const { return m_count; } void setCount(int value) { if (m_count != value) { m_count = value; emit countChanged(); } } Q_INVOKABLE void increment() { setCount(m_count + 1); } signals: void countChanged(); private: int m_count; };

在 C++ 中注册该类型:

qmlRegisterType<Counter>("MyApp", 1, 0, "Counter");

然后在 QML 中使用:

import MyApp 1.0 Column { Counter { id: counter } Text { text: "计数: " + counter.count } Button { text: "增加" onClicked: counter.increment() } }

2.10 综合实战

下面通过一个完整的待办事项应用,综合运用前面介绍的 ListModel 数据管理、信号与事件、组件复用、动画过渡以及 C++ 交互等知识。应用支持添加待办项、删除待办项、切换完成状态,并在状态切换时播放平滑的动画过渡。

首先定义一个 C++ 数据模型类,用于管理待办事项的增删改查。这里使用QAbstractListModel派生类,将数据逻辑放在 C++ 侧,便于后续扩展持久化或数据库支持:

#include <QAbstractListModel> #include <QStringList> struct TodoItem { QString text; bool done = false; }; class TodoModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged) public: enum Roles { TextRole = Qt::UserRole + 1, DoneRole }; explicit TodoModel(QObject *parent = nullptr) : QAbstractListModel(parent) {} int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return m_items.size(); } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid() || index.row() < 0 || index.row() >= m_items.size()) return QVariant(); const TodoItem &item = m_items.at(index.row()); switch (role) { case TextRole: return item.text; case DoneRole: return item.done; default: return QVariant(); } } QHash<int, QByteArray> roleNames() const override { QHash<int, QByteArray> roles; roles[TextRole] = "text"; roles[DoneRole] = "done"; return roles; } Q_INVOKABLE void addItem(const QString &text) { if (text.trimmed().isEmpty()) return; beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); m_items.append({text.trimmed(), false}); endInsertRows(); emit countChanged(); } Q_INVOKABLE void removeItem(int row) { if (row < 0 || row >= m_items.size()) return; beginRemoveRows(QModelIndex(), row, row); m_items.removeAt(row); endRemoveRows(); emit countChanged(); } Q_INVOKABLE void toggleDone(int row) { if (row < 0 || row >= m_items.size()) return; m_items[row].done = !m_items[row].done; emit dataChanged(index(row), index(row), {DoneRole}); } int count() const { return m_items.size(); } signals: void countChanged(); private: QList<TodoItem> m_items; };

在 C++ 入口中注册该类型,使其可以在 QML 中直接使用:

qmlRegisterType<TodoModel>("MyApp", 1, 0, "TodoModel");

接下来编写主界面main.qml。界面顶部是输入框和「添加」按钮,中间用ListView展示待办列表,底部显示未完成数量。每个列表项通过CheckBox切换完成状态,通过「删除」按钮移除该项,并在状态切换时使用Behavior和NumberAnimation实现平滑过渡:

import QtQuick 2.15 import QtQuick.Controls 2.15 import MyApp 1.0 ApplicationWindow { visible: true width: 420 height: 560 title: "待办事项" TodoModel { id: todoModel } Column { anchors.fill: parent anchors.margins: 16 spacing: 12 // 顶部输入区:输入框 + 添加按钮 Row { width: parent.width spacing: 8 TextField { id: inputField width: parent.width - addButton.width - parent.spacing placeholderText: "输入新的待办事项..." onAccepted: addTodo() } Button { id: addButton text: "添加" onClicked: addTodo() } } // 待办列表 ListView { id: todoList width: parent.width height: parent.height - inputRowHeight - summaryHeight - 2 * parent.spacing model: todoModel clip: true spacing: 8 delegate: Rectangle { id: itemRect width: todoList.width height: 48 radius: 6 color: done ? "#e8f5e9" : "#f5f5f5" // 状态切换时平滑过渡背景色 Behavior on color { ColorAnimation { duration: 200 } } Row { anchors.fill: parent anchors.leftMargin: 12 anchors.rightMargin: 12 spacing: 8 CheckBox { id: doneBox anchors.verticalCenter: parent.verticalCenter checked: done onClicked: todoModel.toggleDone(index) } Text { width: parent.width - doneBox.width - deleteBtn.width - 2 * parent.spacing anchors.verticalCenter: parent.verticalCenter text: model.text font.pixelSize: 16 // 完成项显示删除线 color: done ? "#9e9e9e" : "#212121" font.strikeout: done Behavior on color { ColorAnimation { duration: 200 } } } Button { id: deleteBtn anchors.verticalCenter: parent.verticalCenter text: "删除" onClicked: todoModel.removeItem(index) } } } // 空列表提示 Text { anchors.centerIn: parent text: "暂无待办事项,添加一个吧!" color: "#9e9e9e" visible: todoModel.count === 0 } } // 底部统计:未完成数量 Text { id: summaryText width: parent.width text: "未完成:" + (todoModel.count - doneCount) + " 项" horizontalAlignment: Text.AlignRight color: "#616161" } } // 添加待办项 function addTodo() { if (inputField.text.trim() !== "") { todoModel.addItem(inputField.text) inputField.clear() } } // 计算已完成数量 property int doneCount: 0 Connections { target: todoModel onDataChanged: updateDoneCount() onCountChanged: updateDoneCount() } function updateDoneCount() { var count = 0 for (var i = 0; i < todoModel.count; i++) { if (todoModel.data(todoModel.index(i, 0), 258)) // DoneRole = 258 count++ } doneCount = count } }

运行效果说明:启动应用后,在顶部输入框中输入文字并点击「添加」或按回车,即可在列表底部新增一条待办项;点击待办项左侧的复选框,该项会变为浅绿色背景、文字变为灰色并加上删除线,同时底部「未完成」数量自动更新;点击「删除」按钮可移除对应待办项。整个交互过程流畅,状态切换带有 200 毫秒的颜色过渡动画,体现了 QML 声明式绑定与 C++ 数据模型结合的优势。

3. 调试与优化

QML 提供了多种调试工具。开发者可以使用console.log()输出日志,使用qmlscene或 Qt Creator 的调试器进行断点调试。此外,Qt Quick还提供了性能分析工具,用于检测渲染瓶颈和内存占用。

在性能优化方面,以下几点值得关注:

  • 减少属性绑定层级:过深的绑定链会增加计算开销。
  • 合理使用缓存:对于不频繁变化的界面,可以缓存渲染结果。
  • 避免过度使用动画:大量同时运行的动画会消耗 GPU 资源。
  • 使用Loader延迟加载:对于非首屏组件,可以延迟实例化以加快启动速度。

常见调试技巧

除了基础的console.log(),QML 还提供了多种进阶日志方法,帮助开发者定位问题。例如,console.trace()可以打印当前调用栈,console.time()与console.timeEnd()可以测量代码片段的执行耗时:

function processData() { console.time("processData"); // 模拟耗时操作 var sum = 0; for (var i = 0; i < 100000; i++) { sum += i; } console.timeEnd("processData"); console.trace("processData 执行完毕"); }

在命令行中,qmlscene支持多个常用参数,便于调试和性能分析:

  • --quit:加载完成后立即退出,适合自动化测试。
  • --frame-count N:渲染指定帧数后退出,用于性能基准测试。
  • --max-ms-per-frame N:设置每帧最大耗时阈值,超出时输出警告。
  • --verbose:输出更详细的日志信息,便于排查加载问题。

使用示例:

qmlscene --quit --frame-count 300 --max-ms-per-frame 16 main.qml

在 Qt Creator 中,开发者可以通过以下步骤设置断点并监视变量:

  1. 在 QML 编辑器中,点击行号左侧的空白区域即可添加断点,断点会以红色圆点标记。
  2. 启动调试会话(按F5或点击调试按钮),程序会在断点处暂停。
  3. 在「调试器」面板的「局部变量」视图中,可以查看当前作用域内的所有变量及其值。
  4. 右键变量并选择「添加监视表达式」,即可在「监视」视图中持续跟踪该变量的变化。
  5. 使用「单步执行」(F10)和「步入」(F11)逐行检查代码执行流程。

下面是一个带断点调试的完整示例,演示如何在属性变化时观察变量:

import QtQuick 2.15 Rectangle { id: debugBox width: 200 height: 200 color: "lightgray" property int clickCount: 0 Text { anchors.centerIn: parent text: "点击次数: " + debugBox.clickCount } MouseArea { anchors.fill: parent onClicked: { debugBox.clickCount++; // 在此行设置断点,观察 clickCount 的变化 console.log("clickCount =", debugBox.clickCount); } } }

在调试器暂停时,可以结合「表达式求值」窗口输入任意表达式(如debugBox.clickCount * 2)即时查看计算结果,从而更高效地定位逻辑问题。

4. 总结

本文系统梳理了 Qt Quick 与 QML 的核心知识。Qt Quick 作为 Qt 框架中构建现代用户界面的模块,凭借声明式开发范式、GPU 加速的场景图渲染和跨平台能力,在移动应用、车载系统、智能家居和工业监控等领域得到广泛应用,与传统的 Qt Widgets 相比,更适合需要丰富动效和流畅交互的界面场景。

QML 语言是 Qt Quick 的基石,其关键特性贯穿整个开发流程:属性绑定让界面状态自动同步,信号与事件机制简化了交互处理,组件复用提升了代码的可维护性,动画与过渡增强了用户体验,模型与视图实现了数据的高效展示,而与 C++ 的交互则兼顾了高性能计算与系统资源访问。

在调试与性能优化方面,开发者可以借助console系列日志方法、qmlscene命令行参数和 Qt Creator 调试器快速定位问题,同时通过减少绑定层级、合理缓存、控制动画数量和延迟加载等手段提升应用性能。

建议读者从基础语法入手,逐步实践属性绑定、布局定位和信号处理,再结合组件复用、动画和模型视图构建完整界面,最后通过 C++ 交互和性能优化打磨出高效、可维护的 QML 应用。

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

做了17年游戏本地化,最后还是自己写了个工具

游戏本地化里这些重复活&#xff0c;我实在不想再手搓了 我做中日韩游戏本地化差不多17年了。 做久了以后有个感觉&#xff1a; 真正费时间的&#xff0c;很多时候不是“翻译一句话”本身。 而是翻译前后那一堆重复工作。 比如&#xff1a; 文本怎么整理术语怎么统一几千…

作者头像 李华
网站建设 2026/10/11 1:42:22

2026最新百度网盘高速解析网站:比PanDownload更加稳定

网盘已经成了现代人数字生活里不可或缺的文件柜&#xff0c;无论是工作交接还是生活记录&#xff0c;大家都离不开它。但每次面对动辄几个吉字节的文件&#xff0c;如果传输进度停滞不前&#xff0c;确实非常消耗人的耐心。 在探讨如何提升获取效率的圈子里&#xff0c;PanDow…

作者头像 李华
网站建设 2026/10/11 1:41:39

基于STM32单片机智能台灯手势识别控制PWM调光光照蓝牙/WiFi/视频监控/云平台无线APP-DIY设计S460

S460-手势识别LED灯光光敏PWM调光开关手动自动OLED屏声光提醒按键蓝牙/WiFi/视频监控/云平台APP本系统由STM32F103C8T6单片机核心板、OLED屏、无线蓝牙/WIFI/视频监控/云平台模块-可选、USB灯模块电路、手势模块、光敏电阻电路、蜂鸣器报警、电源电路、按键电路组成。【1】OLED…

作者头像 李华
网站建设 2026/10/11 1:41:22

基于DQN的导弹目标选择毕设实战:环境搭建、调参与避坑指南

简介&#xff1a;这份资源是面向计算机、通信工程、人工智能及自动化等专业师生与从业者的Python与深度Q网络&#xff08;DQN&#xff09;算法实践项目包&#xff0c;以导弹目标识别与选择为核心场景&#xff0c;可用于课程设计、期末大作业或毕业设计参考。项目为个人毕业设计…

作者头像 李华
网站建设 2026/10/11 1:41:05

生产制造数字孪生建模与开发实战:从数据链路到Unity驱动

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

作者头像 李华