add markdown 编辑器

This commit is contained in:
2025-09-30 15:53:47 +08:00
parent 01bd8e0f5e
commit 5ac40ed1cd
6 changed files with 878 additions and 1 deletions
+3
View File
@@ -18,6 +18,7 @@ from .students.student_interface import StudentInterface
from .video.video_interface import VideoInterface
from .camera.camera_interface import CameraInterface
from .web_view.web_view_interface import WebViewInterface
from .markdown.markdown_interface import MarkdownInterface
def resource_path(relative_path):
@@ -35,6 +36,7 @@ class Window(SplitFluentWindow):
self.video_interface = VideoInterface()
self.camera_interface = CameraInterface()
self.web_view_interface = WebViewInterface(self)
self.markdown_interface = MarkdownInterface()
self.initNavigation()
self.resize(900, 700)
@@ -61,6 +63,7 @@ class Window(SplitFluentWindow):
self.addSubInterface(self.video_interface, FIF.VIDEO, "Video")
self.addSubInterface(self.camera_interface, FIF.CAMERA, "Camera")
self.addSubInterface(self.web_view_interface, FIF.FIT_PAGE, "Web View")
self.addSubInterface(self.markdown_interface, FIF.DOCUMENT, "Markdown Editor")
# 使用资源路径函数
self.navigationInterface.addWidget(
View File
+775
View File
@@ -0,0 +1,775 @@
import os
import sys
import markdown
from pathlib import Path
from PySide6.QtCore import Qt, QTimer, Slot
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QSplitter,
QFileDialog,
QMessageBox,
)
from PySide6.QtGui import (
QFont,
QTextCursor,
QSyntaxHighlighter,
QKeySequence,
QShortcut,
QTextCharFormat,
)
from qfluentwidgets import (
CardWidget,
PushButton,
TextEdit,
ScrollArea,
setTheme,
Theme,
InfoBar,
InfoBarPosition,
StrongBodyLabel,
BodyLabel,
ToolButton,
)
from qfluentwidgets import FluentIcon as FIF
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWebChannel import QWebChannel
def resource_path(relative_path):
"""获取资源的绝对路径"""
if hasattr(sys, "_MEIPASS"):
return os.path.join(sys._MEIPASS, relative_path) # type: ignore
return os.path.join(os.path.abspath("."), relative_path)
class MarkdownHighlighter(QSyntaxHighlighter):
"""Markdown语法高亮"""
def __init__(self, parent=None):
super().__init__(parent) # type: ignore
self.highlighting_rules = []
# 标题规则
header_format = QTextCharFormat()
header_format.setForeground(Qt.GlobalColor.darkBlue)
header_format.setFontWeight(QFont.Weight.Bold)
# 不同级别的标题
for pattern in [
r"^# .+$",
r"^## .+$",
r"^### .+$",
r"^#### .+$",
r"^##### .+$",
r"^###### .+$",
]:
self.highlighting_rules.append((pattern, header_format))
# 粗体
bold_format = QTextCharFormat()
bold_format.setFontWeight(QFont.Weight.Bold)
self.highlighting_rules.append((r"\*\*.+?\*\*", bold_format))
self.highlighting_rules.append((r"__.+?__", bold_format))
# 斜体
italic_format = QTextCharFormat()
italic_format.setFontItalic(True)
self.highlighting_rules.append((r"\*.+?\*", italic_format))
self.highlighting_rules.append((r"_.+?_", italic_format))
# 代码
code_format = QTextCharFormat()
code_format.setForeground(Qt.GlobalColor.darkGreen)
code_format.setFontFamily("Consolas")
self.highlighting_rules.append((r"`.+?`", code_format))
# 链接
link_format = QTextCharFormat()
link_format.setForeground(Qt.GlobalColor.blue)
link_format.setUnderlineStyle(QTextCharFormat.UnderlineStyle.SingleUnderline)
self.highlighting_rules.append((r"\[.+?\]\(.+?\)", link_format))
# 列表
list_format = QTextCharFormat()
list_format.setForeground(Qt.GlobalColor.darkMagenta)
self.highlighting_rules.append((r"^[\*\-\+] .+$", list_format))
self.highlighting_rules.append((r"^\d+\. .+$", list_format))
def highlightBlock(self, text):
for pattern, format in self.highlighting_rules:
import re
expression = re.compile(pattern)
for match in expression.finditer(text):
start, end = match.span()
self.setFormat(start, end - start, format)
class MarkdownPreview(ScrollArea):
"""Markdown预览组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("markdownPreview")
self.setMinimumWidth(300)
self.parent = parent # type: ignore
self.current_scroll_percent = 0
# 创建预览容器
self.preview_widget = QWidget()
self.preview_layout = QVBoxLayout(self.preview_widget)
# 预览内容区域
self.web_view = QWebEngineView()
self.web_view.setHtml(self.get_preview_html(""))
self.channel = QWebChannel()
self.web_view.page().setWebChannel(self.channel)
self.channel.registerObject("preview", self)
self.preview_layout.addWidget(self.web_view)
self.setWidget(self.preview_widget)
self.setWidgetResizable(True)
# 设置样式
self.setStyleSheet(
"""
MarkdownPreview {
background-color: white;
border: 1px solid #e1e5e9;
border-radius: 8px;
}
"""
)
self.web_view.loadFinished.connect(self.on_load_finished)
@Slot(bool)
def on_load_finished(self):
"""页面加载完成后注入JavaScript代码"""
js_code = """
// 监听预览区域的滚动事件
let isScrolling = false;
window.addEventListener('scroll', function() {
if (isScrolling) return;
var scrollPercent = (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
if (window.pyHandler && window.pyHandler.onPreviewScroll) {
window.pyHandler.onPreviewScroll(scrollPercent);
}
});
// 用于从Python端设置滚动位置的函数
window.scrollToPercent = function(percent) {
isScrolling = true;
var scrollY = (percent / 100) * (document.documentElement.scrollHeight - window.innerHeight);
window.scrollTo(0, scrollY);
setTimeout(() => { isScrolling = false; }, 100);
};
// 保存当前滚动位置
window.saveScrollPosition = function() {
return (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100;
};
// 恢复滚动位置
window.restoreScrollPosition = function(percent) {
window.scrollToPercent(percent);
};
"""
self.web_view.page().runJavaScript(js_code)
# 加载完成后恢复滚动位置
if self.current_scroll_percent > 0:
QTimer.singleShot(
100, lambda: self.scroll_to_percent(self.current_scroll_percent)
)
@Slot(float)
def onPreviewScroll(self, percent):
"""从JavaScript接收预览区域的滚动位置"""
self.current_scroll_percent = percent
if self.parent and hasattr(self.parent, "sync_editor_to_preview"):
self.parent.sync_editor_to_preview(percent) # type: ignore
def scroll_to_percent(self, percent):
"""滚动预览到指定百分比位置"""
js_code = f"if (window.scrollToPercent) window.scrollToPercent({percent});"
self.web_view.page().runJavaScript(js_code)
def _on_save_scroll_position(self, result):
"""保存滚动位置的回调"""
if result is not None:
self.current_scroll_percent = int(result)
def restore_scroll_position(self):
"""恢复保存的滚动位置"""
if self.current_scroll_percent > 0:
self.scroll_to_percent(self.current_scroll_percent)
def save_scroll_position(self):
"""保存当前滚动位置"""
js_code = "if (window.saveScrollPosition) window.saveScrollPosition();"
self.web_view.page().runJavaScript(js_code, self._on_save_scroll_position)
def get_preview_html(self, markdown_text):
"""使用markdown库生成预览HTML"""
try:
# 使用markdown库转换Markdown
html_content = markdown.markdown(
markdown_text,
extensions=[
"extra", # 支持表格、缩写等
"codehilite", # 代码高亮
"toc", # 目录
"nl2br", # 换行转<br>
"sane_lists", # 更合理的列表
"smarty", # 智能引号等
],
extension_configs={
"codehilite": {"css_class": "highlight", "linenums": False}
},
)
except Exception as e:
html_content = f"<p style='color: red;'>Markdown解析错误: {str(e)}</p>"
return f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {{
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
line-height: 1.6;
padding: 20px;
background-color: #f8f9fa;
color: #333;
}}
.markdown-body {{
max-width: 100%;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
h1, h2, h3, h4, h5, h6 {{
color: #2c3e50;
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}}
h1 {{ font-size: 2em; border-bottom: 2px solid #eaecef; padding-bottom: 10px; }}
h2 {{ font-size: 1.5em; border-bottom: 1px solid #eaecef; padding-bottom: 8px; }}
h3 {{ font-size: 1.25em; }}
h4 {{ font-size: 1em; }}
h5 {{ font-size: 0.875em; }}
h6 {{ font-size: 0.85em; color: #6a737d; }}
p {{ margin-bottom: 16px; }}
code {{
background-color: rgba(175,184,193,0.2);
padding: 2px 6px;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 0.9em;
}}
pre {{
background-color: #f6f8fa;
padding: 16px;
border-radius: 6px;
overflow: auto;
margin-bottom: 16px;
}}
pre code {{
background: none;
padding: 0;
}}
blockquote {{
border-left: 4px solid #dfe2e5;
padding-left: 16px;
color: #6a737d;
margin-left: 0;
margin-bottom: 16px;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-bottom: 16px;
}}
th, td {{
border: 1px solid #dfe2e5;
padding: 8px 12px;
text-align: left;
}}
th {{
background-color: #f6f8fa;
font-weight: 600;
}}
ul, ol {{
padding-left: 2em;
margin-bottom: 16px;
}}
li {{
margin-bottom: 4px;
}}
a {{
color: #0366d6;
text-decoration: none;
}}
a:hover {{
text-decoration: underline;
}}
img {{
max-width: 100%;
height: auto;
}}
hr {{
height: 1px;
background-color: #eaecef;
border: none;
margin: 24px 0;
}}
/* 代码高亮样式 */
.highlight .hll {{ background-color: #ffc; }}
.highlight .c {{ color: #999988; font-style: italic; }}
.highlight .err {{ color: #a61717; background-color: #e3d2d2; }}
.highlight .k {{ color: #000000; font-weight: bold; }}
.highlight .o {{ color: #000000; font-weight: bold; }}
.highlight .cm {{ color: #999988; font-style: italic; }}
.highlight .cp {{ color: #999999; font-weight: bold; }}
.highlight .c1 {{ color: #999988; font-style: italic; }}
.highlight .cs {{ color: #999999; font-weight: bold; font-style: italic; }}
.highlight .gd {{ color: #000000; background-color: #ffdddd; }}
.highlight .ge {{ color: #000000; font-style: italic; }}
.highlight .gr {{ color: #aa0000; }}
.highlight .gh {{ color: #999999; }}
.highlight .gi {{ color: #000000; background-color: #ddffdd; }}
.highlight .go {{ color: #888888; }}
.highlight .gp {{ color: #555555; }}
.highlight .gs {{ font-weight: bold; }}
.highlight .gu {{ color: #aaaaaa; }}
.highlight .gt {{ color: #aa0000; }}
.highlight .kc {{ color: #000000; font-weight: bold; }}
.highlight .kd {{ color: #000000; font-weight: bold; }}
.highlight .kn {{ color: #000000; font-weight: bold; }}
.highlight .kp {{ color: #000000; font-weight: bold; }}
.highlight .kr {{ color: #000000; font-weight: bold; }}
.highlight .kt {{ color: #445588; }}
.highlight .m {{ color: #009999; }}
.highlight .s {{ color: #d14; }}
.highlight .na {{ color: #008080; }}
.highlight .nb {{ color: #0086B3; }}
.highlight .nc {{ color: #445588; font-weight: bold; }}
.highlight .no {{ color: #008080; }}
.highlight .ni {{ color: #800080; }}
.highlight .ne {{ color: #990000; font-weight: bold; }}
.highlight .nf {{ color: #990000; }}
.highlight .nn {{ color: #555555; }}
.highlight .nt {{ color: #000080; }}
.highlight .nv {{ color: #008080; }}
.highlight .ow {{ color: #000000; font-weight: bold; }}
.highlight .w {{ color: #bbbbbb; }}
.highlight .mf {{ color: #009999; }}
.highlight .mh {{ color: #009999; }}
.highlight .mi {{ color: #009999; }}
.highlight .mo {{ color: #009999; }}
.highlight .sb {{ color: #d14; }}
.highlight .sc {{ color: #d14; }}
.highlight .sd {{ color: #d14; }}
.highlight .s2 {{ color: #d14; }}
.highlight .se {{ color: #d14; }}
.highlight .sh {{ color: #d14; }}
.highlight .si {{ color: #d14; }}
.highlight .sx {{ color: #d14; }}
.highlight .sr {{ color: #009926; }}
.highlight .s1 {{ color: #d14; }}
.highlight .ss {{ color: #990073; }}
.highlight .bp {{ color: #999999; }}
.highlight .vc {{ color: #008080; }}
.highlight .vg {{ color: #008080; }}
.highlight .vi {{ color: #008080; }}
.highlight .il {{ color: #009999; }}
</style>
</head>
<body>
<div class="markdown-body">
{html_content}
</div>
</body>
</html>
"""
def update_preview(self, markdown_text):
"""更新预览内容"""
# 保存当前滚动位置
self.save_scroll_position()
# 更新HTML内容
self.web_view.setHtml(self.get_preview_html(markdown_text))
class MarkdownEditor(TextEdit):
"""增强的Markdown编辑器"""
def __init__(self, parent=None):
super().__init__(parent)
self.setPlaceholderText("在此输入Markdown内容...")
self.setFont(QFont("Consolas", 11))
# 启用语法高亮
self.highlighter = MarkdownHighlighter(self.document())
# 设置制表符宽度
self.setTabStopDistance(40)
class MarkdownInterface(QWidget):
"""Markdown编辑器主界面"""
def __init__(self):
super().__init__()
self.setObjectName("markdown_interface")
self.current_file = None
# self.editor = None
self.is_syncing = False
self.text_change_timer = QTimer() # 用于延迟更新预览
self.text_change_timer.setSingleShot(True)
self.text_change_timer.timeout.connect(self.delayed_preview_update)
self.setup_ui()
self.setup_shortcuts()
def setup_ui(self):
"""设置界面布局"""
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(20, 50, 20, 20)
main_layout.setSpacing(15)
# 标题
title_label = StrongBodyLabel("Markdown编辑器")
title_label.setStyleSheet("font-size: 24px; padding: 10px 0;")
main_layout.addWidget(title_label)
# 编辑器和预览器
editor_preview_layout = QHBoxLayout()
# 编辑器
self.editor = MarkdownEditor()
self.editor.textChanged.connect(self.on_text_changed)
# 连接编辑器的滚动事件
self.editor.verticalScrollBar().valueChanged.connect(self.on_editor_scroll)
# 工具栏
toolbar = self.create_toolbar()
main_layout.addWidget(toolbar)
# 预览器
self.preview = MarkdownPreview(self)
# 分割器
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.addWidget(self.editor)
splitter.addWidget(self.preview)
splitter.setSizes([400, 400])
editor_preview_layout.addWidget(splitter)
main_layout.addLayout(editor_preview_layout, 1)
# 状态栏
self.status_label = BodyLabel("就绪")
self.status_label.setStyleSheet("color: #666; font-size: 12px;")
main_layout.addWidget(self.status_label)
def create_toolbar(self):
"""创建工具栏"""
toolbar_card = CardWidget()
toolbar_layout = QHBoxLayout(toolbar_card)
# 文件操作按钮
self.new_btn = PushButton(FIF.ADD, "新建", self)
self.new_btn.clicked.connect(self.new_file)
self.open_btn = PushButton(FIF.FOLDER, "打开", self)
self.open_btn.clicked.connect(self.open_file)
self.save_btn = PushButton(FIF.SAVE, "保存", self)
self.save_btn.clicked.connect(self.save_file)
self.save_as_btn = PushButton(FIF.SAVE_AS, "另存为", self)
self.save_as_btn.clicked.connect(self.save_as_file)
# 编辑操作按钮
self.undo_btn = PushButton(FIF.LEFT_ARROW, "撤销", self)
self.undo_btn.clicked.connect(self.editor.undo)
self.redo_btn = PushButton(FIF.RIGHT_ARROW, "反撤销", self)
self.redo_btn.clicked.connect(self.editor.redo)
# 格式化按钮
self.bold_btn = ToolButton(self)
self.bold_btn.setText("B")
self.bold_btn.setToolTip("粗体")
self.bold_btn.clicked.connect(self.insert_bold)
self.italic_btn = ToolButton(self)
self.italic_btn.setText("I")
self.italic_btn.setToolTip("斜体")
self.italic_btn.clicked.connect(self.insert_italic)
self.code_btn = ToolButton(self)
self.code_btn.setText("</>")
self.code_btn.setToolTip("代码")
self.code_btn.clicked.connect(self.insert_code)
# 添加按钮到工具栏
toolbar_layout.addWidget(self.new_btn)
toolbar_layout.addWidget(self.open_btn)
toolbar_layout.addWidget(self.save_btn)
toolbar_layout.addWidget(self.save_as_btn)
toolbar_layout.addWidget(self.undo_btn)
toolbar_layout.addWidget(self.redo_btn)
toolbar_layout.addWidget(self.bold_btn)
toolbar_layout.addWidget(self.italic_btn)
toolbar_layout.addWidget(self.code_btn)
toolbar_layout.addStretch()
return toolbar_card
def setup_shortcuts(self):
"""设置快捷键"""
# 文件操作
QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self.save_file)
QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.open_file)
QShortcut(QKeySequence("Ctrl+N"), self).activated.connect(self.new_file)
# 编辑操作
QShortcut(QKeySequence("Ctrl+Z"), self).activated.connect(self.editor.undo)
QShortcut(QKeySequence("Ctrl+Y"), self).activated.connect(self.editor.redo)
QShortcut(QKeySequence("Ctrl+Shift+Z"), self).activated.connect(
self.editor.redo
)
# 格式化快捷键
QShortcut(QKeySequence("Ctrl+B"), self).activated.connect(self.insert_bold)
QShortcut(QKeySequence("Ctrl+I"), self).activated.connect(self.insert_italic)
QShortcut(QKeySequence("Ctrl+K"), self).activated.connect(self.insert_code)
def on_editor_scroll(self):
"""编辑器滚动时同步预览"""
if self.is_syncing:
return
self.is_syncing = True
# 计算编辑器的滚动百分比
scroll_bar = self.editor.verticalScrollBar()
max_scroll = scroll_bar.maximum()
if max_scroll > 0:
scroll_percent = (scroll_bar.value() / max_scroll) * 100
# 同步预览滚动
self.preview.scroll_to_percent(scroll_percent)
self.is_syncing = False
def sync_editor_to_preview(self, preview_percent):
"""从预览同步到编辑器"""
if self.is_syncing:
return
self.is_syncing = True
# 同步编辑器滚动
scroll_bar = self.editor.verticalScrollBar()
max_scroll = scroll_bar.maximum()
if max_scroll > 0:
scroll_value = (preview_percent / 100) * max_scroll
scroll_bar.setValue(int(scroll_value))
self.is_syncing = False
def insert_bold(self):
"""插入粗体"""
cursor = self.editor.textCursor()
selected_text = cursor.selectedText()
if selected_text:
cursor.insertText(f"**{selected_text}**")
else:
cursor.insertText("****")
cursor.movePosition(
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 2
)
self.editor.setTextCursor(cursor)
def insert_italic(self):
"""插入斜体"""
cursor = self.editor.textCursor()
selected_text = cursor.selectedText()
if selected_text:
cursor.insertText(f"*{selected_text}*")
else:
cursor.insertText("**")
cursor.movePosition(
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1
)
self.editor.setTextCursor(cursor)
def insert_code(self):
"""插入代码"""
cursor = self.editor.textCursor()
selected_text = cursor.selectedText()
if selected_text:
cursor.insertText(f"`{selected_text}`")
else:
cursor.insertText("``")
cursor.movePosition(
QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.MoveAnchor, 1
)
self.editor.setTextCursor(cursor)
def on_text_changed(self):
"""文本变化时延迟更新预览"""
# 停止之前的计时器
self.text_change_timer.stop()
# 重新启动计时器(延迟300毫秒更新预览)
self.text_change_timer.start(300)
# 更新状态
text = self.editor.toPlainText()
self.update_status("已修改" if text else "就绪")
def delayed_preview_update(self):
"""延迟更新预览,避免频繁刷新"""
text = self.editor.toPlainText()
self.preview.update_preview(text)
def new_file(self):
"""新建文件"""
if self.current_file or self.editor.toPlainText():
reply = QMessageBox.question(
self,
"确认",
"是否保存当前文件的更改?",
QMessageBox.StandardButton.Yes
| QMessageBox.StandardButton.No
| QMessageBox.StandardButton.Cancel,
)
if reply == QMessageBox.StandardButton.Yes:
if not self.save_file():
return
elif reply == QMessageBox.StandardButton.Cancel:
return
if self.editor:
self.editor.clear()
self.current_file = None
self.update_status("新建文件")
self.show_info("已创建新文件", "success")
def open_file(self):
"""打开文件"""
file_path, _ = QFileDialog.getOpenFileName(
self,
"打开Markdown文件",
"",
"Markdown文件 (*.md *.markdown);;所有文件 (*.*)",
)
if file_path:
try:
with open(file_path, "r", encoding="utf-8") as file:
content = file.read()
self.editor.setPlainText(content)
self.current_file = file_path
self.update_status(f"已打开: {Path(file_path).name}")
self.show_info(f"已打开文件: {Path(file_path).name}", "success")
except Exception as e:
self.show_info(f"打开文件失败: {str(e)}", "error")
def save_file(self):
"""保存文件"""
if self.current_file:
return self._save_to_file(self.current_file)
else:
return self.save_as_file()
def save_as_file(self):
"""另存为文件"""
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存Markdown文件",
self.current_file or "未命名文档.md",
"Markdown文件 (*.md *.markdown);;所有文件 (*.*)",
)
if file_path:
return self._save_to_file(file_path)
return False
def _save_to_file(self, file_path):
"""保存内容到文件"""
try:
with open(file_path, "w", encoding="utf-8") as file:
file.write(self.editor.toPlainText())
self.current_file = file_path
self.update_status(f"已保存: {Path(file_path).name}")
self.show_info(f"文件已保存: {Path(file_path).name}", "success")
return True
except Exception as e:
self.show_info(f"保存文件失败: {str(e)}", "error")
return False
def update_status(self, message):
"""更新状态栏"""
self.status_label.setText(message)
def show_info(self, content, type="info"):
"""显示信息条"""
if type == "success":
InfoBar.success(
"", content, parent=self, duration=2000, position=InfoBarPosition.TOP
)
elif type == "warning":
InfoBar.warning(
"", content, parent=self, duration=2000, position=InfoBarPosition.TOP
)
elif type == "error":
InfoBar.error(
"", content, parent=self, duration=3000, position=InfoBarPosition.TOP
)
else:
InfoBar.info(
"", content, parent=self, duration=2000, position=InfoBarPosition.TOP
)
if __name__ == "__main__":
import sys
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
setTheme(Theme.LIGHT)
window = MarkdownInterface()
window.resize(1000, 700)
window.show()
sys.exit(app.exec())
+1 -1
View File
@@ -1,7 +1,7 @@
from PySide6.QtCore import QUrl, Qt
from PySide6.QtGui import QFont # 导入QFont
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QHBoxLayout
from qfluentwidgets import setTheme, Theme, FluentIcon, PushButton, PrimaryPushButton
from qfluentwidgets import FluentIcon, PushButton
from qfluentwidgets.multimedia import VideoWidget