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 from app.view.widgets.add_tag_messageBox import AddTagMessageBox 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._onTagDeleteError(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 _onTagDeleteError(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) self.addPushButton = PushButton(lang("添加标签"), self) logger.debug("初始化标签管理组件") self.setupUi() self.connectSignals() def setupUi(self): """初始化UI""" self.hBoxLayout = QHBoxLayout(self) self.hBoxLayout.addWidget(self.tagScrollArea) self.hBoxLayout.addSpacing(10) self.hBoxLayout.addWidget(self.addPushButton, Qt.AlignmentFlag.AlignRight) def connectSignals(self): """连接信号与槽""" logger.debug("连接标签管理组件信号") self.addPushButton.clicked.connect(self.addTag) def addTag(self): w = AddTagMessageBox(self.window()) w.successAddTagSignal.connect(self.onAddTagSuccess) if w.exec(): ... def onAddTagSuccess(self, name, result): """处理标签添加成功事件""" InfoBar.success( "成功", "标签添加成功", Qt.Orientation.Horizontal, True, 1000, InfoBarPosition.TOP_RIGHT, self.window(), ) self.tagScrollArea.addTag(result["data"], name) class SearchWidget(QWidget): """搜索组件""" # 信号:搜索请求时发出,传递搜索关键词 searchRequested = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self.searchLineEdit = LineEdit(self) self.searchButton = PrimarySplitPushButton(lang("仓内搜索"), self) self.menu = RoundMenu(parent=self) self.menu.addAction( Action( lang("仓内搜索"), triggered=lambda: self.changeButtonText(lang("仓内搜索")), ) ) self.menu.addAction( Action( lang("站内搜索"), triggered=lambda: self.changeButtonText(lang("站内搜索")), ) ) self.searchButton.setFlyout(self.menu) logger.debug("初始化搜索组件") self.setupUi() def changeButtonText(self, text): self.searchButton.setText(text) 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)