298 lines
9.7 KiB
Python
298 lines
9.7 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
|
|
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)
|