""" 中间二级菜单组件 - 分类标题 - 搜索框(带图标) - 工具列表(图标+名称+描述) - 用户信息 """ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QButtonGroup, QScrollArea, QFrame, QGraphicsDropShadowEffect ) from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QFont, QColor, QPainter, QLinearGradient from PySide6.QtSvg import QSvgRenderer from PySide6.QtCore import QByteArray # 工具图标 SVG TOOL_ICONS = { "ph-arrows-in-line-horizontal": """""", "ph-arrows-left-right": """""", "ph-stamp": """""", "ph-scissors": """""", "ph-files": """""", "ph-microsoft-word-logo": """""", "ph-eye": """""", "ph-chart-bar": """""", "ph-magnifying-glass": """""", } # 工具数据定义 TOOLS_DATA = { "image": { "title": "图片工具", "items": [ {"id": "img-compress", "name": "图片压缩", "icon": "ph-arrows-in-line-horizontal", "desc": "智能无损压缩"}, {"id": "img-convert", "name": "格式转换", "icon": "ph-arrows-left-right", "desc": "JPG/PNG/WEBP"}, {"id": "img-watermark", "name": "图片加水印", "icon": "ph-stamp", "desc": "批量添加水印"}, ] }, "pdf": { "title": "PDF 工具箱", "items": [ {"id": "pdf-split", "name": "PDF 拆分", "icon": "ph-scissors", "desc": "提取指定页面"}, {"id": "pdf-merge", "name": "PDF 合并", "icon": "ph-files", "desc": "多文件合并"}, {"id": "pdf-word", "name": "PDF 转 Word", "icon": "ph-microsoft-word-logo", "desc": "保持排版转换"}, ] }, "excel": { "title": "Excel 表格", "items": [ {"id": "xls-view", "name": "Excel 预览", "icon": "ph-eye", "desc": "在线查看表格"}, {"id": "xls-chart", "name": "图表生成", "icon": "ph-chart-bar", "desc": "数据可视化"}, ] } } def render_svg_icon(svg_data: str, size: int = 18, color: str = "#94a3b8") -> 'QPixmap': """渲染SVG图标""" from PySide6.QtGui import QPixmap svg_data = svg_data.replace('currentColor', color) renderer = QSvgRenderer(QByteArray(svg_data.encode())) pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) renderer.render(painter) painter.end() return pixmap class ToolButton(QFrame): """工具按钮 - 还原HTML设计样式""" clicked = Signal() def __init__(self, tool_data: dict, parent=None): super().__init__(parent) self.tool_data = tool_data self._checked = False self._hovered = False self.setFixedHeight(56) self.setCursor(Qt.CursorShape.PointingHandCursor) self.setup_ui() def setup_ui(self): layout = QHBoxLayout(self) layout.setContentsMargins(12, 8, 12, 8) layout.setSpacing(12) # 图标容器 self.icon_frame = QFrame() self.icon_frame.setFixedSize(32, 32) self.icon_frame.setStyleSheet(""" background: #0f172a; border: 1px solid #334155; border-radius: 6px; """) icon_layout = QVBoxLayout(self.icon_frame) icon_layout.setContentsMargins(0, 0, 0, 0) icon_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) # 图标 self.icon_label = QLabel() self.icon_label.setFixedSize(18, 18) self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) svg_data = TOOL_ICONS.get(self.tool_data.get('icon', ''), '') if svg_data: self.icon_label.setPixmap(render_svg_icon(svg_data, 18, "#94a3b8")) icon_layout.addWidget(self.icon_label) layout.addWidget(self.icon_frame) # 文字区域 text_layout = QVBoxLayout() text_layout.setSpacing(2) self.name_label = QLabel(self.tool_data.get('name', '')) self.name_label.setStyleSheet("color: #e2e8f0; font-size: 13px; font-weight: 500;") text_layout.addWidget(self.name_label) self.desc_label = QLabel(self.tool_data.get('desc', '')) self.desc_label.setStyleSheet("color: #64748b; font-size: 10px;") text_layout.addWidget(self.desc_label) layout.addLayout(text_layout, 1) self.update_style() def setChecked(self, checked: bool): self._checked = checked self.update_style() def isChecked(self) -> bool: return self._checked def update_style(self): if self._checked: self.setStyleSheet(""" ToolButton { background: #334155; border-radius: 8px; border-left: 2px solid #fbbf24; } """) self.icon_frame.setStyleSheet(""" background: #0f172a; border: 1px solid rgba(251, 191, 36, 0.3); border-radius: 6px; """) svg_data = TOOL_ICONS.get(self.tool_data.get('icon', ''), '') if svg_data: self.icon_label.setPixmap(render_svg_icon(svg_data, 18, "#fbbf24")) self.name_label.setStyleSheet("color: white; font-size: 13px; font-weight: 500;") elif self._hovered: self.setStyleSheet(""" ToolButton { background: rgba(51, 65, 85, 0.5); border-radius: 8px; } """) self.icon_frame.setStyleSheet(""" background: #0f172a; border: 1px solid rgba(251, 191, 36, 0.3); border-radius: 6px; """) svg_data = TOOL_ICONS.get(self.tool_data.get('icon', ''), '') if svg_data: self.icon_label.setPixmap(render_svg_icon(svg_data, 18, "#fbbf24")) self.name_label.setStyleSheet("color: white; font-size: 13px; font-weight: 500;") else: self.setStyleSheet(""" ToolButton { background: transparent; border-radius: 8px; } """) self.icon_frame.setStyleSheet(""" background: #0f172a; border: 1px solid #334155; border-radius: 6px; """) svg_data = TOOL_ICONS.get(self.tool_data.get('icon', ''), '') if svg_data: self.icon_label.setPixmap(render_svg_icon(svg_data, 18, "#94a3b8")) self.name_label.setStyleSheet("color: #e2e8f0; font-size: 13px; font-weight: 500;") def enterEvent(self, event): self._hovered = True self.update_style() def leaveEvent(self, event): self._hovered = False self.update_style() def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: self.clicked.emit() class SearchInput(QWidget): """搜索框 - 带前置图标""" textChanged = Signal(str) def __init__(self, parent=None): super().__init__(parent) self.setup_ui() def setup_ui(self): layout = QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # 容器 container = QFrame() container.setStyleSheet(""" QFrame { background: #0f172a; border: 1px solid #334155; border-radius: 8px; } QFrame:focus-within { border-color: rgba(251, 191, 36, 0.5); } """) container_layout = QHBoxLayout(container) container_layout.setContentsMargins(12, 8, 12, 8) container_layout.setSpacing(8) # 搜索图标 icon_label = QLabel() icon_label.setFixedSize(16, 16) svg_data = TOOL_ICONS.get("ph-magnifying-glass", "") if svg_data: icon_label.setPixmap(render_svg_icon(svg_data, 16, "#64748b")) container_layout.addWidget(icon_label) # 输入框 self.input = QLineEdit() self.input.setPlaceholderText("搜索功能...") self.input.setStyleSheet(""" QLineEdit { background: transparent; border: none; color: #e2e8f0; font-size: 13px; } QLineEdit::placeholder { color: #475569; } """) self.input.textChanged.connect(self.textChanged.emit) container_layout.addWidget(self.input, 1) layout.addWidget(container) def text(self) -> str: return self.input.text() class UserInfoWidget(QFrame): """用户信息区 - 渐变头像""" def __init__(self, parent=None): super().__init__(parent) self.setFixedHeight(70) self.setStyleSheet(""" UserInfoWidget { background: rgba(30, 41, 59, 0.3); border-top: 1px solid rgba(51, 65, 85, 0.5); } """) self.setup_ui() def setup_ui(self): layout = QHBoxLayout(self) layout.setContentsMargins(16, 12, 16, 12) layout.setSpacing(12) # 头像 - 渐变背景 avatar = QFrame() avatar.setFixedSize(36, 36) avatar.setStyleSheet(""" background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #8b5cf6, stop:1 #6366f1); border-radius: 18px; """) avatar_layout = QVBoxLayout(avatar) avatar_layout.setContentsMargins(0, 0, 0, 0) avatar_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) avatar_text = QLabel("SV") avatar_text.setStyleSheet("color: white; font-size: 12px; font-weight: bold; background: transparent;") avatar_text.setAlignment(Qt.AlignmentFlag.AlignCenter) avatar_layout.addWidget(avatar_text) layout.addWidget(avatar) # 信息 info_layout = QVBoxLayout() info_layout.setSpacing(2) name_label = QLabel("超级会员") name_label.setStyleSheet("color: white; font-size: 12px; font-weight: 500;") info_layout.addWidget(name_label) expire_label = QLabel("有效期至 2026-10") expire_label.setStyleSheet("color: #64748b; font-size: 10px;") info_layout.addWidget(expire_label) layout.addLayout(info_layout, 1) class SecondarySidebar(QWidget): """中间二级菜单""" tool_selected = Signal(dict) def __init__(self, parent=None): super().__init__(parent) self.setObjectName("secondary_sidebar") self.setFixedWidth(256) self.current_category = "image" self.tool_buttons = [] self._stretch_item = None # 保存stretch引用 self.setup_ui() self.load_tools("image") def setup_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # 标题区域 title_widget = QWidget() title_widget.setFixedHeight(80) title_widget.setStyleSheet("border-bottom: 1px solid rgba(51, 65, 85, 0.5);") title_layout = QVBoxLayout(title_widget) title_layout.setContentsMargins(24, 0, 24, 0) title_layout.setAlignment(Qt.AlignmentFlag.AlignVCenter) self.category_title = QLabel("图片工具") self.category_title.setStyleSheet("color: white; font-size: 18px; font-weight: 600; letter-spacing: 1px;") title_layout.addWidget(self.category_title) layout.addWidget(title_widget) # 搜索区域 search_widget = QWidget() search_layout = QVBoxLayout(search_widget) search_layout.setContentsMargins(16, 16, 16, 8) self.search_input = SearchInput() self.search_input.textChanged.connect(self.filter_tools) search_layout.addWidget(self.search_input) layout.addWidget(search_widget) # 工具列表区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) scroll_area.setFrameShape(QFrame.Shape.NoFrame) scroll_area.setStyleSheet("background: transparent;") self.tools_container = QWidget() self.tools_layout = QVBoxLayout(self.tools_container) self.tools_layout.setContentsMargins(12, 4, 12, 16) self.tools_layout.setSpacing(4) self.tools_layout.setAlignment(Qt.AlignmentFlag.AlignTop) scroll_area.setWidget(self.tools_container) layout.addWidget(scroll_area, 1) # 用户信息 layout.addWidget(UserInfoWidget()) def load_tools(self, category: str): self.current_category = category data = TOOLS_DATA.get(category, {}) self.category_title.setText(data.get("title", "")) # 清空所有内容(包括stretch) for btn in self.tool_buttons: self.tools_layout.removeWidget(btn) btn.deleteLater() self.tool_buttons.clear() # 移除旧的stretch while self.tools_layout.count() > 0: item = self.tools_layout.takeAt(0) if item.widget(): item.widget().deleteLater() # 添加工具按钮 for tool in data.get("items", []): btn = ToolButton(tool) btn.clicked.connect(lambda t=tool: self.on_tool_clicked(t)) self.tools_layout.addWidget(btn) self.tool_buttons.append(btn) # 添加新的stretch self.tools_layout.addStretch() def on_tool_clicked(self, tool_data: dict): # 更新选中状态 for btn in self.tool_buttons: btn.setChecked(btn.tool_data.get('id') == tool_data.get('id')) self.tool_selected.emit(tool_data) def filter_tools(self, text: str): text = text.lower() for btn in self.tool_buttons: name = btn.tool_data.get("name", "").lower() desc = btn.tool_data.get("desc", "").lower() btn.setVisible(text in name or text in desc or not text) def select_tool(self, tool_id: str): for btn in self.tool_buttons: if btn.tool_data.get("id") == tool_id: btn.setChecked(True) self.tool_selected.emit(btn.tool_data) break