news 2026/9/13 5:30:30

PySide6+Qt Designer:拖拽UI与信号槽联动,搞懂桌面应用布局与性能优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PySide6+Qt Designer:拖拽UI与信号槽联动,搞懂桌面应用布局与性能优化

第一次打开Qt Designer的时候,我其实是有点不屑的——拖控件谁不会,代码党表示不能忍。后来项目从三天一小改变成一天三改,我才老老实实把这套“拖拽式UI”捡回来。用PySide6写Python桌面应用,UI部分真不建议全手码,尤其是表单多、字段多、老板还动不动要加一个输入框的时候,纯代码写布局就是在给自己挖坑。

这篇文章把我从环境搭建、Designer出图、再把.ui文件接进PySide6代码的完整流程捋一遍,顺带解决几个高频问题:控件找不到、界面卡顿、打包出来没有界面等。适合刚接触PySide6、或者一直被布局管理器搞得心烦的Python开发者。读完你就能搭出一个能跑、能改、不卡顿的桌面应用骨架。

1. 先把这盘棋想清楚:Designer在PySide6里的定位

1.1 Qt Designer到底帮你省了什么

Qt Designer是Qt官方出品的可视化界面设计工具,它把“手工排版”变成了“拖拽控件后保存一个XML文件”,这个文件就是.ui。Python程序运行的时候,再把这个.ui文件读进来,翻译成对应的Qt控件对象。

省下来的时间非常可观。一个带表单、列表、按钮、布局的窗口,手工写布局代码少说要三四百行,还要反复调间距、调对齐。用Designer拖出来,加布局管理器,保存,转换,然后专心写业务逻辑,半小时内就能把界面框架定下来。

更重要的是后期维护。用户提需求说“把这个按钮放到旁边去”,你在Designer里拖一下,重新生成一下Python文件就行;手工布局就得去找那段代码,还要小心别把信号槽连接改坏。这个差距在项目后期极其明显。

1.2 PySide6和PyQt5怎么选

这两套都是Qt的Python绑定,很多老教程还在用PyQt5,但新项目我个人更推荐PySide6。

对比项PySide6PyQt5
维护方Qt官方(Qt for Python)Riverbank Computing
许可证LGPL,更宽松GPL/商业授权
信号槽写法Signal/SlotpyqtSignal/pyqtSlot
枚举风格贴近C++ Qt6老式Qt5风格
内置工具pyside6-designer、pyside6-uic、pyside6-rccpyuic5、pyrcc5等

如果你是个人开发者做工具、做练习,License的区别可能感觉不明显;但如果将来要闭源分发、或者在公司里用,PySide6的LGPL会省去很多授权上的麻烦。语法上两者几乎通用,你只要盯着PySide6的写法去查就行。

注意:PySide6对应用户装的Python版本最好在3.8以上,Windows、macOS、Linux都支持。如果你之前接触过PyQt5,转到PySide6时最容易踩的坑就是导包从PyQt5.QtWidgets变成PySide6.QtWidgets,以及pyqtSignal变成Signal。

2. 环境准备:让Qt Designer在你的电脑上跑起来

2.1 安装PySide6,自带Designer

很多朋友跑去搜“qt designer下载”,下载了一个独立版Designer,结果版本和PySide6不匹配,控件生成后代码各种报错。其实完全没必要,用pip安装PySide6时,Designer和配套工具都会一起装好。

创建一个虚拟环境:

python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install pyside6

装完后,在虚拟环境的Scripts目录(Windows)或bin目录(Linux/macOS)下,会多出几个可执行文件:

pyside6-designer # 打开Qt Designer pyside6-uic # .ui转.py pyside6-rcc # .qrc资源文件转.py

直接在终端输入pyside6-designer就能打开图形界面。如果你找不着可执行文件,可以先看一下安装路径:

pip show pyside6

然后去site-packages同级的Scripts目录里找。Windows下如果已经激活了虚拟环境,命令直接可用,不用配环境变量。

实操心得:我习惯把pyside6-designerpyside6-uic的路径记下来,因为在VS Code或PyCharm外部工具里配置时要用到绝对路径。用where pyside6-uic(Windows)或which pyside6-uic(Linux/macOS)能快速拿到。

2.2 VS Code里配置PySide6开发环境

VS Code写Python+PySide6是很顺手的,但默认状态下,.ui文件就是一堆XML,你没法右键“一键转成.py”。我推荐装一个扩展叫“PYQT Integration”,装完之后它能识别.ui文件,并提供“Compile”的动作。

具体配置思路大概是:

  1. 安装Python扩展和PYQT Integration扩展。
  2. 打开扩展设置,填入pyside6-uic的完整路径。
  3. 之后再打开.ui文件,右键可以看到“Compile .ui File”之类的选项。
  4. 如果扩展的默认命令是pyuic5,你需要调整配置为pyside6-uic

这个扩展对PyQt和PySide都兼容,只是转换命令的路径要自己确认好。VS Code终端也可以直接跑命令:

pyside6-uic main.ui -o main_ui.py

不需要什么花哨配置,一行命令就能把界面文件变成Python类。我把这条命令放在习惯里之后,几乎不再用图形化的“一键编译”,因为命令行可重复、可脚本化,保存一个sh/bat脚本就能批量处理整个项目的ui文件。

2.3 认识Designer界面:三块核心区域

打开Designer后,看到的界面大致分成三块:

  • 左侧是控件面板(Widget Box):按钮、标签、输入框、表格、布局等分类摆放。
  • 中间是画布,也就是你正在编辑的窗口。
  • 右侧是属性编辑器(Property Editor)和对象查看器(Object Inspector)。

刚开始不用全部弄懂,先记住四个按钮:拖控件、改属性、切换布局、编辑信号槽。工具菜单里可以新建Main Window、Dialog、Widget三种窗口,一般主程序选Main Window,弹窗选Dialog。

实操心得:画布默认带参考网格,但这个网格只做辅助,不会显示在最终界面上。布局这种事别人工对网格,后面讲布局管理器会省掉你一大半调整工作。

3. 用Qt Designer画UI的关键操作与布局心得

3.1 新建窗口与常用控件拖放

打开Designer后,按Ctrl+N新建一个Main Window。左侧控件面板里,最常用的是这几类:

  • Display Widgets:QLabel、QProgressBar、QLCDNumber
  • Input Widgets:QLineEdit、QTextEdit、QSpinBox、QComboBox
  • Buttons:QPushButton、QRadioButton、QCheckBox
  • Item Views:QTableView、QListView、QTreeView

拖拽方式就是字面意思:鼠标点住控件,拖到画布上松开。每个控件都有默认的objectName,比如pushButtonlineEdit,在属性编辑器里改成有意义的名称非常重要。后面代码里找控件,全靠这个名称。

举个例子,你要放一个账号输入框,拖一个QLineEdit过去,然后在属性编辑器里找到objectName,改成accountEdit。密码输入框同理,改成passwordEdit,再把echoMode属性设为Password,输入时就会显示成圆点。

3.2 布局管理器用对了,窗口自由拉伸也不乱

新手最容易犯的错误,是把控件按坐标一个个摆在窗口上,窗口一拉伸就全乱了。Qt的控件默认是“绝对定位”,这在Designer里看很整齐,运行起来一放大就露馅。

正确做法:在画布上选中多个控件,然后点击工具栏里的“水平布局”“垂直布局”或“栅格布局”按钮,把它们套进布局管理器。布局管理器会自动计算控件的位置和大小,窗口缩放时控件会跟着自适应。

我的工作经验里,下面几条选型规律很少出错:

  • 表单类(标签+输入框交替):用QGridLayout栅格布局,分两列,左边标签右对齐,右边输入框。
  • 单列按钮:用QVBoxLayout垂直布局。
  • 顶部按钮栏、底部状态栏、中间内容区:外层用QVBoxLayout,中间放一个QHBoxLayout或QGridLayout。

布局之后,最外层还需要把“布局”作用到中央窗口。选中一个容器控件,右键选择“布局”,或者使用“栅格布局”按钮。Main Window的中央区域会有一个centralwidget,右键它,选择布局方式,整个窗口结构就成立了。

注意:布局是可以嵌套的。一个窗口可以外层是垂直布局,里面左边是一个表单栅格布局,右边是一个垂直布局放按钮。选中多个控件后,用“水平布局”等按钮把它们包进去。想取消布局,就右键选“打破布局”。

3.3 属性面板:objectName、尺寸与透明度

属性编辑器里经常要调的属性不多:

  • objectName:代码里访问控件的唯一标识,必须认真命名。
  • geometry:控件的坐标和尺寸。这个在布局管理器下意义不大,因为布局会自动处理,但如果做自定义固定大小控件,可以设置minimumSize/maximumSize。
  • minimumSize / maximumSize:限制控件最小和最大尺寸,防止布局时被压缩。
  • enabled / visible:控制控件是否可用、是否可见,可以在代码里动态切换。
  • styleSheet:给控件单独设置QSS样式,做简单美化时很方便。
  • windowTitle:主窗口标题。

另外一个细节容易被忽略:控件的accessibilityNameaccessibleName对于UI自动化测试脚本很有用。如果你后面需要给项目写自动化用例,可以在Designer里顺手把这些属性填好,运行时的控件树里会好认很多。

3.4 自定义控件:“提升”操作让Designer认识你的Python类

Qt自带的控件库毕竟有限,大家都会自己封装一些控件,比如带图标的按钮、可翻页的表格。Designer本身不认识这些自定义类,但提供了一个叫“提升为(Promote)”的功能。

操作流程:

  1. 在画布上拖一个QWidget或者QPushButton,作为自定义控件的占位。
  2. 右键它,选择“提升为(Promote to)”。
  3. 弹出的对话框里填写提升的类名,比如MyButton,头文件填mybutton
  4. 点击“提升(Promote)”,这个占位控件在Designer里会显示成“MyButton”。

关键在头文件这里。假设你有一个模块文件mybutton.py,里面定义了类MyButton,那么头文件填的就是模块名mybutton。转换py代码的时候,pyside6-uic会生成类似from mybutton import MyButton的导入语句,所以你写的这个模块必须在Python的搜索路径里。

实操心得:我用过最顺的组织方式,是把所有自定义控件放在一个widgets/包目录下,转换后的ui代码直接importwidgets.mybutton。如果import失败,优先检查当前运行目录是否在sys.path里,通常把工程根目录加到PYTHONPATH就能解决。

4. 把.ui放进Python:两种接入方式与信号槽联动

4.1 动态加载(uic.loadUi)与静态转换(pyside6-uic)怎么选

.ui文件做好后,接入Python有两种主流方式,很多人纠结,其实各有适用场景。

第一种,动态加载。运行时用QUiLoader把.ui文件直接读进来:

from PySide6.QtWidgets import QApplication from PySide6.QtUiTools import QUiLoader import sys app = QApplication(sys.argv) loader = QUiLoader() window = loader.load("main.ui") window.show() sys.exit(app.exec())

第二种,静态转换。用命令先把.ui转成.py,再在代码里import:

pyside6-uic main.ui -o main_ui.py
from main_ui import Ui_MainWindow

转出来的Ui_MainWindow是一个界面类,通常再写一个业务类继承它:

from PySide6.QtWidgets import QMainWindow from main_ui import Ui_MainWindow class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) if __name__ == "__main__": import sys from PySide6.QtWidgets import QApplication app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())

这两种方式我都用过,说说我的判断:

  • 动态加载适合快速原型、ui文件频繁调整、不想每次改完ui都运行一次转换命令的场景。缺点是运行时解析.xml,启动稍慢一点,而且控件访问要通过window.findChild.属性去取,IDE的代码补全几乎没用。
  • 静态转换适合正式项目、代码量大的场景。生成的Python类可以静态分析,IDE补全很准确,运行速度也更快。

我自己的习惯是开发前期用动态加载,界面结构基本稳定后转成静态.py,再开始写业务逻辑。这样既能保持迭代速度,后期又能吃到静态检查的红利。

4.2 Designer里连信号槽 vs 代码里connect

Designer里有一个“编辑信号/槽”模式(快捷键F4),你可以把按钮拖到窗口上,弹出信号槽连接对话框,选一个信号和一个槽。这样保存下来的.ui文件里会带有连接信息。

但我强烈建议:信号槽连接放到代码里写,不要在Designer里连。原因很简单:

  1. Designer里只能连接“内置信号和内置槽”,比如clickedclose。你要连到自定义方法上,根本连不了。
  2. 信号槽关系写在代码里,搜索和维护都方便,改起来不用重新打开Designer。
  3. 代码里连接的时候,你还能顺手做一些参数处理。

在PySide6里,信号槽连接基本是这样:

self.ui.loginButton.clicked.connect(self.on_login_clicked) def on_login_clicked(self): text = self.ui.accountEdit.text() self.ui.tipLabel.setText(f"当前输入:{text}")

自定义信号用Signal定义:

from PySide6.QtCore import Signal, QObject class WorkWorker(QObject): finished = Signal(str) def run(self): self.finished.emit("done")

注意PySide6的Signal来自PySide6.QtCore,在类定义内部声明,不能作为局部变量。

4.3 资源文件.qrc与图标处理

界面难免要用图标、背景图。最规范的做法是创建一个.qrc资源文件,用XML描述资源路径,然后通过pyside6-rcc转换成Python文件。

qrc文件内容大致长这样:

<RCC> <qresource prefix="/"> <file>icons/logo.png</file> <file>style/app.qss</file> </qresource> </RCC>

转换命令:

pyside6-rcc resources.qrc -o resources_rc.py

然后在主程序入口import resources_rc,这个模块就会被加载,资源路径:/icons/logo.png就能被Qt识别。

在代码里给按钮设置图标:

from PySide6.QtGui import QIcon self.ui.logoButton.setIcon(QIcon(":/icons/logo.png"))

注意:很多人转换成功但图片显示不出来,十有八九是忘了import resources_rc。如果你用pyinstaller打包,也不要漏掉这个生成的资源模块。

4.4 一个完整的登录表单示例

把上面的知识点串成一个能跑的小例子。假设你在Designer里画了一个登录窗口,界面包含两个QLineEdit(accountEdit、passwordEdit)、两个QPushButton(loginButton、cancelButton)、一个QLabel(tipLabel),然后导出成main_ui.py。

业务代码可以这么写:

from PySide6.QtWidgets import QMessageBox from main_ui import Ui_MainWindow class LoginWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.loginButton.clicked.connect(self.handle_login) self.ui.cancelButton.clicked.connect(self.close) def handle_login(self): account = self.ui.accountEdit.text() password = self.ui.passwordEdit.text() if account == "admin" and password == "123456": self.ui.tipLabel.setText("登录成功") QMessageBox.information(self, "提示", "欢迎回来") else: self.ui.tipLabel.setText("账号或密码错误")

这就是一个完整的、可运行的登录界面逻辑。界面上所有控件的文本、大小、位置都由Designer里的.ui文件决定,代码只负责数据和交互。这也正好呼应了“python用pyside6设计的界面如何实现交互”这个问题——交互的本质,就是拿到控件对象,连接信号,然后更新控件内容。

5. 常见问题与卡顿排查实录

5.1 打开报错、控件找不到、路径问题速查

我遇到的、以及身边同事遇到的高频问题,基本都能对应到下面这张表:

问题现象常见原因解决方法
运行时报ModuleNotFoundError: No module named 'main_ui'转换后的.py文件不在当前路径确认工作目录、或用from package.main_ui import Ui_MainWindow
控件对象不存在,访问window.ui.accountEdit报AttributeErrorobjectName没改;或者setupUi没调用先在Designer里确认objectName;调用setupUi(self)后再访问
QUiLoader加载失败,报错找不到ui文件相对路径问题用绝对路径:os.path.join(os.path.dirname(__file__), "main.ui")
窗口一闪而过/没有界面忘了window.show(),或事件循环没启动确保有app.exec()并调用sys.exit
图标、图片加载不出来资源模块没import,或路径错误检查import resources_rc;检查qrc里的路径是相对qrc文件的

路径问题是我见过占比最大的坑。Python的工作目录不一定是.py文件所在目录,尤其从VS Code、PyCharm不同入口启动时,相对路径的基准点完全不同。不偷懒的话,一律用__file__拼绝对路径最稳。

5.2 UI卡顿背后其实是线程问题

热词里有“ui界面卡顿”和“c# 循环数据采集和ui刷新卡顿”,这类问题在PySide6里同样高频。原因基本一致:把耗时任务放在了UI主线程里。

Qt的主线程负责绘制和事件分发,如果你在主线程里做循环、sleep、下载数据,界面就会像没响应一样。解决思路是用QThread把耗时任务扔到后台,通过信号把结果传回主线程,再由主线程更新控件。

一个典型的后台采集逻辑:

from PySide6.QtCore import QThread, Signal import time class DataWorker(QThread): result = Signal(str) def run(self): for i in range(100): time.sleep(0.1) self.result.emit(f"第{i}条数据") # 使用 self.worker = DataWorker() self.worker.result.connect(self.ui.textEdit.append) self.worker.start()

注意两点:一是不要在子线程里直接操作UI控件,只能在信号槽里间接更新;二是worker对象要保存为实例属性,或者用parent管理,否则局部变量可能被垃圾回收,线程直接没了。

如果刷新的频率太高,比如传感器每10毫秒来一条数据,UI刷新也会应接不暇。这种情况下可以引入一个定时合并机制:子线程发来的数据先存队列,主线程用QTimer每隔100毫秒统一刷新一次,把多次数据合并成一次界面更新,卡顿感会明显改善。这个思路和“C#循环数据采集和UI刷新卡顿”的优化方向是一致的,核心就是抛弃高频直更。

5.3 打包exe时PySide6的坑

用PyInstaller打包PySide6程序,最常见的坑是打包后漏插件、漏动态库,打开exe报“无法定位程序输入点”或直接闪退。

我的建议是不要用默认的pyinstaller -F xxx.py,而是加上插件收集参数:

pyinstaller -w -F main.py --collect-all PySide6

--collect-all PySide6会把PySide6相关插件、翻译文件、依赖一网打尽,代价是打包体积变大。对于界面程序,我个人更推荐用目录模式(不加-F),启动速度快,排查问题也方便。首次打包可以先试试,把运行日志打开,缺什么补什么。

实操心得:打包完之后,如果exe运行提示缺少Qt platform插件“xcb”或“windows”,多半是PySide6的plugins目录没有被复制过去。直接检查打包产物里的PySide6目录是否包含完整的plugins文件夹,不齐全就手动拷。

6. 进阶方向:从“能跑”到“好看好用”

6.1 QSS样式表,几十行换一套皮肤

很多人问“pyside6炫酷界面”怎么做。答案很简单:Designer负责骨架,QSS负责皮肤。

QSS语法和CSS非常像,给设置样式之后,界面的观感会完全不同:

app.setStyleSheet(""" QMainWindow { background-color: #f5f6fa; } QLineEdit { border: 1px solid #d4d7e3; border-radius: 6px; padding: 6px 10px; background-color: #ffffff; } QPushButton { background-color: #4f8ff7; color: #ffffff; border: none; border-radius: 6px; padding: 8px 16px; } QPushButton:hover { background-color: #3a7be0; } QPushButton:pressed { background-color: #2f6fce; } """)

这些代码放在main.py入口处,全局生效。如果你想让不同控件有不同的样式,就在Designer里给控件单独设置styleSheet属性,或者在代码里用选择器指定objectName:

QPushButton#loginButton { background-color: #f76c6c; }

实际项目里,我更推荐把QSS单独存成一个.qss文件,通过资源系统加载:

with open("style.qss", "r", encoding="utf-8") as f: app.setStyleSheet(f.read())

这样换主题就是从参数切换而已,不用改逻辑代码。想做一个帅气侧边栏、圆角卡片、渐变按钮,都是QSS的范畴。

6.2 多窗口跳转与页面切换

业务稍微复杂一点,就会遇到多窗口问题。登录成功后跳主界面、点击按钮弹出设置页,这是最常见的两种。

多个独立窗口,用信号槽来协调:

self.ui.loginButton.clicked.connect(self.open_main) def open_main(self): self.main_window = MainWindow() self.main_window.show() self.close()

注意要保存self.main_window的引用,否则窗口被垃圾回收,闪烁一下就消失。

如果你做的是单窗口多页面,比如左侧导航右侧内容区,推荐用QStackedWidget。在Designer里拖一个QStackedWidget,添加几个页面,每个页面放不同的控件集合,然后代码里切换索引:

self.ui.stackedWidget.setCurrentIndex(1)

这种模式的体验比弹多个窗口流畅很多,不会出现窗口重叠、任务栏混乱的问题。

6.3 把PySide6界面接到本地AI模型上

最近“免费ai模型ollama ui”热度很高,其实用PySide6做一个本地模型的聊天客户端非常顺手。Designer画一个输入框、一个消息列表、一个发送按钮,Python里通过requests调用本地服务接口,把返回内容显示到界面上。

import requests def send_message(self): question = self.ui.inputEdit.text() self.ui.msgList.append(f"我:{question}") resp = requests.post( "http://localhost:11434/api/generate", json={"model": "qwen2.5:7b", "prompt": question, "stream": False}, timeout=60, ) answer = resp.json().get("response", "") self.ui.msgList.append(f"AI:{answer}")

这里有个很重要的经验:如果本地模型显存不够、推理很慢,请求会阻塞主线程,界面必然“假死”。所以访问模型的调用也要放到QThread或者异步线程中。我看到很多新手在这里踩坑,把模型推理直接写在按钮槽函数里,点完发送按钮,窗口直接变成“未响应”。记住一句话:凡是可能超过几十毫秒的操作,都要出主线程。

这样一套PySide6+本地模型的组合,既不需要联网,也不用担心数据隐私,界面用Designer调整起来又灵活。我最近做的小工具就是这么干的,整体开发效率比我以前手写布局的时候高了一倍都不止。

最后再分享一个小技巧:Designer里做出来的界面,如果后期需求变动频繁,尽量保持“界面文件”和“业务代码”彻底分离。.ui或者转换出的代码只负责界面结构,业务逻辑全写在另一个类里面。这样每次改UI,只需要重新转换ui文件,不会影响你已经写好的逻辑。我在实际使用中发现,凡是能做到这一点的项目,后面维护起来都特别轻松。

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

MATLAB 综合能源系统容量配置-运行调度双层优化模型

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长毕业设计辅导、数学建模、数据处理、算法改进、程序设计科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;完整代码获取 定制创新 论文复现私信&#x1f34a;个人信条&#xff1a;做科研&#xff0c…

作者头像 李华
网站建设 2026/9/13 5:28:14

基于Matlab的答题卡识别:图像预处理与Hough变换透视校正

简介&#xff1a;基于Matlab实现的答题卡识别系统毕业设计资源&#xff0c;面向计算机、电子信息、数学等专业学生&#xff0c;适用于课程设计、期末大作业或毕业设计参考。项目包含完整GUI界面与图像处理流程&#xff0c;涵盖高斯滤波、图像归一化、中值中心定位、Hough变换、…

作者头像 李华
网站建设 2026/9/13 5:27:31

GPT Image 2实战评测:文本渲染、API调用与工作流集成指南

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

作者头像 李华
网站建设 2026/9/13 5:27:05

CAN总线G代码传输实战:用C语言和SocketCAN实现稳定运动控制

简介&#xff1a;这是一份基于C语言的CAN通信协议实现源码包&#xff0c;适合嵌入式开发者、汽车电子或工业控制领域的初学者学习参考。资源围绕DSP2833x平台搭建&#xff0c;覆盖ECan模块驱动、报文收发、中断服务、过滤器配置及错误处理等关键环节&#xff0c;并附带完整工程…

作者头像 李华
网站建设 2026/9/13 5:24:20

Shottr:macOS原生截图标注与OCR一体化工具

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

作者头像 李华
网站建设 2026/9/13 5:24:18

达梦数据库与MySQL的MERGE语法对比及迁移踩坑指南

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

作者头像 李华