Files
leonpan-pc/app/view/widgets/ownFiled_widgets.py
2025-11-03 22:28:58 +08:00

255 lines
8.4 KiB
Python

from loguru import logger
from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtWidgets import (
QHBoxLayout,
QWidget,
)
from qfluentwidgets import (Action, InfoBar, InfoBarPosition, LineEdit, MenuAnimationType, PillPushButton,
PrimarySplitPushButton, PushButton, RoundMenu, ScrollArea)
from app.core import DeleteTagThread, userConfig, lang
class TagsScrollArea(ScrollArea):
"""标签滚动区域组件,支持动态添加和移除标签,支持单选模式"""
# 信号:标签被点击时发出,传递标签文本
tagClicked = pyqtSignal(str, str)
TAG_TYPES = {"video": "视频", "doc": "文档", "image": "图片", "audio": "音乐"}
def __init__(self, parent=None):
super().__init__(parent)
self.tagsDict = {} # 存储所有标签按钮
self.currentCheckedTag = None # 当前选中的标签ID
self.setupUi()
logger.debug("初始化标签滚动区域组件")
def setupUi(self):
"""初始化UI"""
self.widgets = QWidget()
self.layouts = QHBoxLayout(self.widgets)
self.setWidget(self.widgets)
self.setWidgetResizable(True)
self.setMaximumWidth(400)
# 设置布局属性
self.layouts.setContentsMargins(0, 0, 0, 0)
self.layouts.setSpacing(10)
# 初始化默认标签
self.initDefaultTags()
# 设置样式
self.widgets.setStyleSheet("background-color: transparent; border: none;")
self.setStyleSheet("background-color: transparent; border: none;")
# 设置滚动策略
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
# 安全地获取 tags 字段,如果不存在则使用空列表
self.tags = userConfig.userData.get("data", {}).get("tags", [])
for tag in self.tags:
self.addTag(tag["id"], tag["name"])
self.tagsDict[tag["id"]] = tag["name"]
def initDefaultTags(self):
"""初始化默认标签"""
logger.debug("初始化默认标签")
for tagId, tagText in self.TAG_TYPES.items():
self.addTag(tagId, tagText)
def addTag(self, tagId, text):
"""添加一个新标签
Args:
tagId: 标签的唯一标识符
text: 标签显示的文本
Returns:
创建的标签按钮对象
"""
self.tagsDict[tagId] = text
logger.debug(f"添加新标签: {tagId} - {text}")
tagBtn = PillPushButton(text, self.widgets)
tagBtn.setObjectName(f"tag_{tagId}")
tagBtn.setCheckable(True) # 设置为可选中状态
tagBtn.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu
) # 启用自定义上下文菜单
# 连接点击信号,实现单选逻辑
tagBtn.clicked.connect(
lambda checked, tid=tagId: self.onTagClicked(tid, checked)
)
# 连接右键菜单信号
tagBtn.customContextMenuRequested.connect(
lambda pos, tid=tagId: self.onTagRightClicked(tid, pos)
)
self.layouts.addWidget(tagBtn)
return tagBtn
def onTagClicked(self, tagId, checked):
"""处理标签点击事件,实现单选逻辑"""
if checked:
# 如果当前点击的标签被选中,取消之前选中的标签
if self.currentCheckedTag and self.currentCheckedTag != tagId:
# 找到之前选中的标签并取消选中
previousTagBtn = self.findChild(
QWidget, f"tag_{self.currentCheckedTag}"
)
if previousTagBtn:
previousTagBtn.setChecked(False)
# 更新当前选中的标签
self.currentCheckedTag = tagId
if tagId in ["video", "doc", "image", "audio"]:
self.tagClicked.emit("internalTag", tagId)
else:
self.tagClicked.emit("externalTag", tagId)
logger.debug(f"选中标签: {tagId}")
else:
# 如果取消选中当前标签,清空当前选中的标签
if self.currentCheckedTag == tagId:
self.currentCheckedTag = None
logger.debug(f"取消选中标签: {tagId}")
# 发出标签点击信号
def onTagRightClicked(self, tagId, pos):
"""处理标签右键点击事件"""
logger.debug(f"标签被右键点击: {tagId}")
tagBtn = self.findChild(QWidget, f"tag_{tagId}")
if tagBtn:
global_pos = tagBtn.mapToGlobal(pos)
if tagBtn.text() in ["视频", "文档", "图片", "音乐"]:
return
menu = RoundMenu(parent=self)
menu.addAction(Action(lang("删除"), triggered=lambda: self.deleteTag(tagId)))
menu.exec(global_pos, aniType=MenuAnimationType.DROP_DOWN)
def deleteTag(self, tagId):
self.deleteTagThread = DeleteTagThread(tagId)
self.deleteTagThread.successDeleteSignal.connect(
lambda: self._onTagDeleteSuccess(tagId)
)
self.deleteTagThread.errorSignal.connect(self._onTagDeleteError)
self.deleteTagThread.start()
def _onTagDeleteError(self, msg):
InfoBar.error(
"失败",
msg,
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
def _onTagDeleteSuccess(self, tagId):
self.removeTag(tagId)
InfoBar.success(
"成功",
"标签删除成功",
Qt.Orientation.Horizontal,
True,
1000,
InfoBarPosition.TOP_RIGHT,
self.window(),
)
def removeTag(self, tagId):
"""移除指定标签"""
if tagId in self.tagsDict:
logger.debug(f"移除标签: {tagId}")
# 如果移除的是当前选中的标签,清空选中状态
if self.currentCheckedTag == tagId:
self.currentCheckedTag = None
tagBtn = self.findChild(QWidget, f"tag_{tagId}")
if tagBtn:
self.layouts.removeWidget(tagBtn)
tagBtn.deleteLater()
del self.tagsDict[tagId]
else:
logger.warning(f"尝试移除不存在的标签: {tagId}")
def getCheckedTag(self):
"""获取当前选中的标签ID"""
return self.currentCheckedTag
def setCheckedTag(self, tagId):
"""设置指定标签为选中状态"""
if tagId in self.tagsDict:
tagBtn = self.findChild(QWidget, f"tag_{tagId}")
if tagBtn:
tagBtn.setChecked(True)
# onTagClicked 方法会自动处理单选逻辑
else:
logger.warning(f"尝试选中不存在的标签: {tagId}")
def clearChecked(self):
"""清除所有选中状态"""
if self.currentCheckedTag:
tagBtn = self.findChild(QWidget, f"tag_{self.currentCheckedTag}")
if tagBtn:
tagBtn.setChecked(False)
self.currentCheckedTag = None
class TagWidget(QWidget):
"""标签管理组件"""
def __init__(self, parent=None):
super().__init__(parent)
self.tagScrollArea = TagsScrollArea(self)
# 移除添加标签按钮
logger.debug("初始化标签管理组件")
self.setupUi()
# 移除信号连接
def setupUi(self):
"""初始化UI"""
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.addWidget(self.tagScrollArea)
# 移除添加标签按钮的布局设置
# 确保标签区域能够充分利用空间
self.hBoxLayout.addStretch()
class SearchWidget(QWidget):
"""搜索组件"""
# 信号:搜索请求时发出,传递搜索关键词
searchRequested = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.searchLineEdit = LineEdit(self)
self.searchButton = PillPushButton(lang("搜索"), self) # 使用已导入的PillPushButton
logger.debug("初始化搜索组件")
self.setupUi()
def setupUi(self):
"""初始化UI"""
self.searchLineEdit.setPlaceholderText(lang("搜索文件"))
self.hBoxLayout = QHBoxLayout(self)
self.hBoxLayout.setContentsMargins(0, 24, 0, 0)
self.hBoxLayout.addWidget(self.searchLineEdit)
self.hBoxLayout.addWidget(self.searchButton)