2025-09-20 22:20:08 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
LeonApp GUI - 基于PyQt5和Fluent Design的App Store API图形界面工具
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-09-26 09:37:20 +00:00
|
|
|
|
#
|
|
|
|
|
|
# ┏┓ ┏┓
|
|
|
|
|
|
# ┏┛┻━━━┛┻┓
|
|
|
|
|
|
# ┃ ┃
|
|
|
|
|
|
# ┃ ━ ┃
|
|
|
|
|
|
# ┃ ┳┛ ┗┳ ┃
|
|
|
|
|
|
# ┃ ┃
|
|
|
|
|
|
# ┃ ┻ ┃
|
|
|
|
|
|
# ┃ ┃
|
|
|
|
|
|
# ┗━┓ ┏━┛Codes are far away from bugs with the animal protecting
|
|
|
|
|
|
# ┃ ┃ 神兽保佑,代码无bug
|
|
|
|
|
|
# ┃ ┃
|
|
|
|
|
|
# ┃ ┗━━━┓
|
|
|
|
|
|
# ┃ ┣┓
|
|
|
|
|
|
# ┃ ┏┛
|
|
|
|
|
|
# ┗┓┓┏━┳┓┏┛
|
|
|
|
|
|
# ┃┫┫ ┃┫┫
|
|
|
|
|
|
# ┗┻┛ ┗┻┛
|
|
|
|
|
|
#
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# APP版本号
|
2025-09-24 21:58:25 +08:00
|
|
|
|
APP_VERSION = "Beta 0.4"
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
import json
|
|
|
|
|
|
import requests
|
2025-09-21 21:26:55 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
import os
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
import markdown
|
|
|
|
|
|
from enum import Enum
|
2025-09-20 22:20:08 +08:00
|
|
|
|
from PyQt5.QtWidgets import (
|
|
|
|
|
|
QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout,
|
2025-09-22 22:24:56 +08:00
|
|
|
|
QTableWidgetItem, QTableWidget, QTextEdit, QFrame, QHeaderView, QLabel
|
2025-09-20 22:20:08 +08:00
|
|
|
|
)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
from PyQt5.QtGui import QTextOption
|
2025-09-20 22:20:08 +08:00
|
|
|
|
from PyQt5.QtCore import Qt, pyqtSignal, QThread
|
|
|
|
|
|
from qfluentwidgets import (
|
2025-09-22 22:24:56 +08:00
|
|
|
|
CardWidget, TitleLabel, SubtitleLabel, CaptionLabel, BodyLabel, PushButton,
|
2025-09-20 22:20:08 +08:00
|
|
|
|
PrimaryPushButton, LineEdit, ComboBox, ProgressBar, TableWidget,
|
|
|
|
|
|
ScrollArea, InfoBar, InfoBarPosition, NavigationInterface, NavigationItemPosition,
|
2025-09-23 20:33:28 +08:00
|
|
|
|
FluentWindow, MSFluentWindow, FluentIcon, SimpleCardWidget, PrimaryPushSettingCard
|
2025-09-20 22:20:08 +08:00
|
|
|
|
)
|
|
|
|
|
|
from qfluentwidgets import FluentTranslator
|
2025-09-22 22:24:56 +08:00
|
|
|
|
from app_detail_window import AppDetailWindow
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
class APIClient:
|
|
|
|
|
|
"""API客户端类,处理与API的通信"""
|
|
|
|
|
|
def __init__(self, api_base_url="http://leonmmcoset.jjxmm.win:8010/api.php"):
|
|
|
|
|
|
self.api_base_url = api_base_url
|
|
|
|
|
|
|
|
|
|
|
|
def make_request(self, endpoint_type, params=None):
|
|
|
|
|
|
"""基础API请求函数"""
|
|
|
|
|
|
if params is None:
|
|
|
|
|
|
params = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 添加API类型参数
|
|
|
|
|
|
params['t'] = endpoint_type
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
response = requests.get(self.api_base_url, params=params, timeout=30)
|
|
|
|
|
|
response.raise_for_status() # 抛出HTTP错误
|
|
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
|
|
|
|
|
|
|
if data.get('status') == 'error':
|
|
|
|
|
|
return {'error': data.get('message', '未知错误')}
|
|
|
|
|
|
|
|
|
|
|
|
return {'success': True, 'data': data.get('data')}
|
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
|
return {'error': f"请求异常: {str(e)}"}
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
return {'error': "无法解析响应"}
|
|
|
|
|
|
|
|
|
|
|
|
class WorkerThread(QThread):
|
|
|
|
|
|
"""工作线程,用于在后台执行API请求"""
|
|
|
|
|
|
# 使用object类型以接受任何数据类型(dict、list等)
|
|
|
|
|
|
finished = pyqtSignal(object)
|
|
|
|
|
|
progress = pyqtSignal(int)
|
|
|
|
|
|
error = pyqtSignal(str)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, api_client, endpoint_type, params=None):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.endpoint_type = endpoint_type
|
|
|
|
|
|
self.params = params or {}
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
|
"""线程运行函数"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.progress.emit(10)
|
|
|
|
|
|
result = self.api_client.make_request(self.endpoint_type, self.params)
|
|
|
|
|
|
self.progress.emit(100)
|
|
|
|
|
|
if 'error' in result:
|
|
|
|
|
|
self.error.emit(result['error'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 确保数据是可序列化的对象
|
|
|
|
|
|
if isinstance(result['data'], (dict, list, str, int, float, bool, type(None))):
|
|
|
|
|
|
self.finished.emit(result['data'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.error.emit(f"API返回的数据类型不支持: {type(result['data'])}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.error.emit(f"执行错误: {str(e)}")
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
class HomepageTab(QWidget):
|
|
|
|
|
|
"""主页标签页 - 显示最新增加的APP和最新的公告"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_data()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("欢迎使用 LeonApp")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加副标题
|
|
|
|
|
|
subtitle = SubtitleLabel("这是应用商店的管理工具,用于查看和管理应用、标签和开发者信息。")
|
|
|
|
|
|
layout.addWidget(subtitle)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加分隔符
|
|
|
|
|
|
layout.addSpacing(20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建最新应用部分
|
|
|
|
|
|
latest_apps_title = SubtitleLabel("最新添加的应用")
|
|
|
|
|
|
layout.addWidget(latest_apps_title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建最新应用卡片
|
|
|
|
|
|
self.latest_apps_card = CardWidget()
|
|
|
|
|
|
apps_layout = QVBoxLayout(self.latest_apps_card)
|
|
|
|
|
|
apps_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 创建水平滚动区域来放置应用卡片 - 设置为隐形样式
|
|
|
|
|
|
self.apps_scroll_area = ScrollArea()
|
|
|
|
|
|
self.apps_scroll_area.setWidgetResizable(True)
|
|
|
|
|
|
self.apps_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
|
|
|
|
self.apps_scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置隐形样式
|
|
|
|
|
|
self.apps_scroll_area.setStyleSheet("""
|
|
|
|
|
|
QScrollArea {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollArea > QWidget > QWidget {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar:horizontal {
|
|
|
|
|
|
height: 0px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar::handle:horizontal {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
min-width: 0px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建水平布局来放置卡片
|
|
|
|
|
|
self.apps_container = QWidget()
|
|
|
|
|
|
self.apps_layout = QHBoxLayout(self.apps_container)
|
|
|
|
|
|
self.apps_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
self.apps_layout.setSpacing(15)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置滚动区域的内容
|
|
|
|
|
|
self.apps_scroll_area.setWidget(self.apps_container)
|
|
|
|
|
|
self.apps_scroll_area.setMaximumHeight(1000)
|
|
|
|
|
|
self.apps_scroll_area.setMaximumWidth(1000)
|
|
|
|
|
|
self.apps_scroll_area.setMinimumWidth(1000)
|
|
|
|
|
|
apps_layout.addWidget(self.apps_scroll_area)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
layout.addWidget(self.latest_apps_card)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加分隔符
|
|
|
|
|
|
layout.addSpacing(20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建最新公告部分
|
|
|
|
|
|
latest_announcements_title = SubtitleLabel("最新公告")
|
|
|
|
|
|
layout.addWidget(latest_announcements_title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建最新公告卡片
|
|
|
|
|
|
self.latest_announcements_card = CardWidget()
|
|
|
|
|
|
announcements_layout = QVBoxLayout(self.latest_announcements_card)
|
|
|
|
|
|
announcements_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建最新公告列表
|
|
|
|
|
|
self.latest_announcements_list = QWidget()
|
|
|
|
|
|
self.latest_announcements_list_layout = QVBoxLayout(self.latest_announcements_list)
|
|
|
|
|
|
self.latest_announcements_list_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 创建滚动区域放置公告列表 - 设置为隐形样式
|
2025-09-21 21:26:55 +08:00
|
|
|
|
scroll_area = ScrollArea()
|
|
|
|
|
|
scroll_area.setWidgetResizable(True)
|
2025-09-22 22:24:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置隐形样式
|
|
|
|
|
|
scroll_area.setStyleSheet("""
|
|
|
|
|
|
QScrollArea {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollArea > QWidget > QWidget {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar:vertical {
|
|
|
|
|
|
width: 0px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar::handle:vertical {
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
min-height: 0px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
2025-09-21 21:26:55 +08:00
|
|
|
|
scroll_area.setWidget(self.latest_announcements_list)
|
2025-09-22 22:24:56 +08:00
|
|
|
|
scroll_area.setMaximumHeight(300)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
announcements_layout.addWidget(scroll_area)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
layout.addWidget(self.latest_announcements_card)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加填充,使内容上移
|
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
|
|
def load_data(self):
|
|
|
|
|
|
"""加载最新应用和公告数据"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
|
|
|
|
|
|
# 加载最新应用
|
|
|
|
|
|
self.apps_worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getallapps',
|
|
|
|
|
|
{'page': 1, 'limit': 5, 'sort': 'latest'} # 获取最新的5个应用
|
|
|
|
|
|
)
|
|
|
|
|
|
self.apps_worker.finished.connect(self.on_latest_apps_loaded)
|
|
|
|
|
|
self.apps_worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.apps_worker.error.connect(self.show_error)
|
|
|
|
|
|
self.apps_worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
# 加载最新公告
|
|
|
|
|
|
self.announcements_worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getacc',
|
|
|
|
|
|
{'page': 1, 'limit': 3} # 获取最新的3个公告
|
|
|
|
|
|
)
|
|
|
|
|
|
self.announcements_worker.finished.connect(self.on_latest_announcements_loaded)
|
|
|
|
|
|
self.announcements_worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.announcements_worker.error.connect(self.show_error)
|
|
|
|
|
|
self.announcements_worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_latest_apps_loaded(self, data):
|
|
|
|
|
|
"""最新应用加载完成处理"""
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 清空现有卡片
|
|
|
|
|
|
while self.apps_layout.count() > 0:
|
|
|
|
|
|
item = self.apps_layout.takeAt(0)
|
|
|
|
|
|
widget = item.widget()
|
|
|
|
|
|
if widget:
|
|
|
|
|
|
widget.deleteLater()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
if data and isinstance(data, dict) and 'apps' in data and isinstance(data['apps'], list):
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 为每个应用创建卡片
|
|
|
|
|
|
index = 0
|
2025-09-21 21:26:55 +08:00
|
|
|
|
for app in data['apps']:
|
|
|
|
|
|
if not isinstance(app, dict):
|
|
|
|
|
|
continue
|
2025-09-22 22:24:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 只显示前三个应用
|
|
|
|
|
|
if index >= 3:
|
|
|
|
|
|
break
|
|
|
|
|
|
index += 1
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 创建卡片 - 微软商店风格
|
|
|
|
|
|
app_card = CardWidget()
|
|
|
|
|
|
app_card.setFixedWidth(160) # 固定卡片宽度
|
|
|
|
|
|
app_card.setFixedHeight(220) # 固定卡片高度
|
|
|
|
|
|
# 使用样式表设置圆角并移除边框
|
|
|
|
|
|
app_card.setStyleSheet("""
|
|
|
|
|
|
QWidget {
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建卡片内容布局
|
|
|
|
|
|
card_layout = QVBoxLayout(app_card)
|
|
|
|
|
|
card_layout.setContentsMargins(12, 12, 12, 12)
|
|
|
|
|
|
card_layout.setSpacing(8) # 调整间距
|
|
|
|
|
|
|
|
|
|
|
|
# 添加应用ID(隐藏)
|
|
|
|
|
|
app_id_label = QLabel(str(app.get('id', '')))
|
|
|
|
|
|
app_id_label.setVisible(False)
|
|
|
|
|
|
card_layout.addWidget(app_id_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加应用图标占位区 - 微软商店风格的图标位置
|
|
|
|
|
|
icon_widget = QWidget()
|
|
|
|
|
|
icon_widget.setFixedSize(100, 100)
|
|
|
|
|
|
icon_layout = QVBoxLayout(icon_widget)
|
|
|
|
|
|
icon_layout.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建图标占位符(实际应用中可以替换为真实图标)
|
|
|
|
|
|
placeholder_label = QLabel()
|
|
|
|
|
|
placeholder_label.setFixedSize(80, 80)
|
|
|
|
|
|
placeholder_label.setStyleSheet("""
|
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
""")
|
|
|
|
|
|
placeholder_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
# 使用应用名称的第一个字符作为图标占位符
|
|
|
|
|
|
first_char = app.get('name', 'A')[0].upper() if app.get('name') else 'A'
|
|
|
|
|
|
placeholder_label.setText(first_char)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
icon_layout.addWidget(placeholder_label)
|
|
|
|
|
|
card_layout.addWidget(icon_widget, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加应用名称 - 限制为2行
|
|
|
|
|
|
name_label = SubtitleLabel(app.get('name', '无名称'))
|
|
|
|
|
|
name_label.setWordWrap(True)
|
|
|
|
|
|
name_label.setMaximumHeight(40) # 限制高度,最多显示2行
|
|
|
|
|
|
name_label.setStyleSheet("""
|
|
|
|
|
|
QLabel {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
card_layout.addWidget(name_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加应用版本和添加时间(合并显示)
|
|
|
|
|
|
version = app.get('version', '未知')
|
|
|
|
|
|
created_at = app.get('created_at', '未知')
|
|
|
|
|
|
# 简化时间显示
|
|
|
|
|
|
if created_at != '未知':
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 假设时间格式为ISO格式,提取日期部分
|
|
|
|
|
|
if 'T' in created_at:
|
|
|
|
|
|
created_at = created_at.split('T')[0]
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
info_label = CaptionLabel(f"版本 {version} · {created_at}")
|
|
|
|
|
|
info_label.setStyleSheet("""
|
|
|
|
|
|
QLabel {
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
card_layout.addWidget(info_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加获取按钮(模拟微软商店的获取按钮)- 使用PrimaryPushButton获得标准的蓝色按钮
|
|
|
|
|
|
get_button = PrimaryPushButton("查看详情")
|
|
|
|
|
|
get_button.setFixedHeight(32)
|
|
|
|
|
|
|
|
|
|
|
|
get_button.clicked.connect(lambda checked, app_id=app.get('id', ''): self.show_app_detail(app_id))
|
|
|
|
|
|
card_layout.addWidget(get_button)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加点击事件到整个卡片
|
|
|
|
|
|
app_card.mousePressEvent = lambda event, app_id=app.get('id', ''): self.show_app_detail(app_id)
|
|
|
|
|
|
|
|
|
|
|
|
# 将卡片添加到水平布局
|
|
|
|
|
|
self.apps_layout.addWidget(app_card)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 添加一个占位符,确保卡片不会被拉伸
|
|
|
|
|
|
self.apps_layout.addStretch()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 检查是否还有其他数据正在加载
|
|
|
|
|
|
if not self.apps_worker.isRunning() and not self.announcements_worker.isRunning():
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
def on_latest_announcements_loaded(self, data):
|
|
|
|
|
|
"""最新公告加载完成处理"""
|
|
|
|
|
|
# 清空公告列表
|
|
|
|
|
|
while self.latest_announcements_list_layout.count() > 0:
|
|
|
|
|
|
item = self.latest_announcements_list_layout.takeAt(0)
|
|
|
|
|
|
widget = item.widget()
|
|
|
|
|
|
if widget:
|
|
|
|
|
|
widget.deleteLater()
|
|
|
|
|
|
|
|
|
|
|
|
announcements = []
|
|
|
|
|
|
if data and isinstance(data, dict) and 'announcements' in data and isinstance(data['announcements'], list):
|
|
|
|
|
|
announcements = data['announcements']
|
|
|
|
|
|
elif data and isinstance(data, list):
|
|
|
|
|
|
announcements = data
|
|
|
|
|
|
|
|
|
|
|
|
if not announcements:
|
|
|
|
|
|
# 如果没有公告,显示提示信息
|
|
|
|
|
|
no_announcement_label = CaptionLabel("暂无公告")
|
|
|
|
|
|
no_announcement_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.latest_announcements_list_layout.addWidget(no_announcement_label)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 只显示最新的公告
|
|
|
|
|
|
announcement = announcements[0] # 只取最新的一个公告
|
|
|
|
|
|
if not isinstance(announcement, dict):
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 创建公告卡片
|
|
|
|
|
|
announcement_item = QWidget()
|
|
|
|
|
|
announcement_layout = QVBoxLayout(announcement_item)
|
|
|
|
|
|
announcement_layout.setContentsMargins(0, 0, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加标题
|
|
|
|
|
|
title_label = SubtitleLabel(announcement.get('title', '无标题'))
|
|
|
|
|
|
announcement_layout.addWidget(title_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加发布时间
|
|
|
|
|
|
time_label = CaptionLabel(f"发布时间: {announcement.get('created_at', '未知时间')}")
|
|
|
|
|
|
announcement_layout.addWidget(time_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加内容 - 支持Markdown格式
|
|
|
|
|
|
content_text_edit = QTextEdit()
|
|
|
|
|
|
content_text_edit.setReadOnly(True) # 设置为只读
|
|
|
|
|
|
content_text_edit.setFrameShape(QFrame.NoFrame) # 无边框
|
|
|
|
|
|
content_text_edit.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # 必要时显示滚动条
|
|
|
|
|
|
content_text_edit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 不显示水平滚动条
|
|
|
|
|
|
|
|
|
|
|
|
# 将Markdown内容转换为HTML并显示
|
|
|
|
|
|
content = announcement.get('content', '无内容')
|
|
|
|
|
|
try:
|
|
|
|
|
|
html_content = markdown.markdown(content)
|
|
|
|
|
|
content_text_edit.setHtml(html_content)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 如果Markdown解析失败,显示原始文本
|
|
|
|
|
|
content_text_edit.setPlainText(content)
|
|
|
|
|
|
print(f"Markdown解析错误: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
# 设置最大高度,避免内容过长占用过多空间
|
|
|
|
|
|
content_text_edit.setMaximumHeight(200)
|
|
|
|
|
|
announcement_layout.addWidget(content_text_edit)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加到布局
|
|
|
|
|
|
self.latest_announcements_list_layout.addWidget(announcement_item)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查是否还有其他数据正在加载
|
|
|
|
|
|
if not self.apps_worker.isRunning() and not self.announcements_worker.isRunning():
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
def show_app_detail(self, app_id):
|
2025-09-21 21:26:55 +08:00
|
|
|
|
"""显示应用详情"""
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 直接创建并显示应用详情窗口
|
|
|
|
|
|
detail_window = AppDetailWindow(self.api_client, app_id, self)
|
|
|
|
|
|
detail_window.show()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
def show_announcement_detail(self, announcement_id):
|
|
|
|
|
|
"""显示公告详情方法已被移除,现在主页直接显示最新公告内容"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
class AppTab(QWidget):
|
|
|
|
|
|
"""应用列表标签页"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
# 先初始化分页相关变量,因为init_ui()内部会调用load_apps()
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("应用列表")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建搜索和过滤器区域
|
|
|
|
|
|
filter_layout = QHBoxLayout()
|
|
|
|
|
|
filter_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.search_input = LineEdit()
|
|
|
|
|
|
self.search_input.setPlaceholderText("搜索应用...")
|
|
|
|
|
|
filter_layout.addWidget(self.search_input, 3)
|
|
|
|
|
|
|
|
|
|
|
|
filter_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_size_combo = ComboBox()
|
|
|
|
|
|
self.page_size_combo.addItems(["10", "20", "50", "100"])
|
|
|
|
|
|
self.page_size_combo.setCurrentText("20")
|
|
|
|
|
|
self.page_size_combo.currentTextChanged.connect(self.on_page_size_changed)
|
|
|
|
|
|
filter_layout.addWidget(CaptionLabel("每页显示:"))
|
|
|
|
|
|
filter_layout.addWidget(self.page_size_combo)
|
|
|
|
|
|
|
|
|
|
|
|
filter_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
search_button = PrimaryPushButton("搜索")
|
|
|
|
|
|
search_button.clicked.connect(self.search_apps)
|
|
|
|
|
|
filter_layout.addWidget(search_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(filter_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建表格
|
|
|
|
|
|
self.table = TableWidget()
|
|
|
|
|
|
self.table.setColumnCount(5)
|
|
|
|
|
|
self.table.setHorizontalHeaderLabels(["ID", "应用名称", "版本", "评分", "下载量"])
|
|
|
|
|
|
self.table.horizontalHeader().setSectionResizeMode(1, 3)
|
|
|
|
|
|
self.table.cellDoubleClicked.connect(self.show_app_detail)
|
|
|
|
|
|
layout.addWidget(self.table)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建分页控制
|
|
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
pagination_layout.setContentsMargins(0, 10, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
# 加载初始数据
|
|
|
|
|
|
self.load_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def load_apps(self):
|
|
|
|
|
|
"""加载应用列表"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getallapps',
|
|
|
|
|
|
{'page': self.current_page, 'limit': self.items_per_page}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_apps_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_apps_loaded(self, data):
|
|
|
|
|
|
"""应用列表加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data or 'apps' not in data or 'pagination' not in data:
|
|
|
|
|
|
self.show_error("数据格式错误")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.table.setRowCount(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 填充表格
|
|
|
|
|
|
for app in data['apps']:
|
|
|
|
|
|
row_pos = self.table.rowCount()
|
|
|
|
|
|
self.table.insertRow(row_pos)
|
|
|
|
|
|
|
|
|
|
|
|
self.table.setItem(row_pos, 0, QTableWidgetItem(str(app.get('id', ''))))
|
|
|
|
|
|
self.table.setItem(row_pos, 1, QTableWidgetItem(app.get('name', '')))
|
|
|
|
|
|
self.table.setItem(row_pos, 2, QTableWidgetItem(app.get('version', '')))
|
|
|
|
|
|
self.table.setItem(row_pos, 3, QTableWidgetItem(str(app.get('avg_rating', '暂无'))))
|
|
|
|
|
|
self.table.setItem(row_pos, 4, QTableWidgetItem(str(app.get('total_downloads', 0))))
|
|
|
|
|
|
|
|
|
|
|
|
# 更新分页信息
|
|
|
|
|
|
self.total_pages = data['pagination']['totalPages']
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def search_apps(self):
|
|
|
|
|
|
"""搜索应用"""
|
|
|
|
|
|
search_text = self.search_input.text().strip()
|
|
|
|
|
|
if search_text:
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getallapps',
|
|
|
|
|
|
{'page': 1, 'limit': self.items_per_page, 'search': search_text}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_apps_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.load_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def on_page_size_changed(self, value):
|
|
|
|
|
|
"""每页显示数量变化"""
|
|
|
|
|
|
self.items_per_page = int(value)
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.load_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def show_app_detail(self, row, column):
|
|
|
|
|
|
"""显示应用详情"""
|
|
|
|
|
|
app_id = self.table.item(row, 0).text()
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 直接创建应用详情窗口,使用文件顶部已导入的AppDetailWindow类
|
|
|
|
|
|
self.app_detail_window = AppDetailWindow(self.api_client, app_id, self)
|
|
|
|
|
|
self.app_detail_window.show()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
# 使用Sweet Alert风格的弹窗
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class TagTab(QWidget):
|
|
|
|
|
|
"""标签管理标签页"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.parent = parent
|
|
|
|
|
|
# 初始化分页相关变量
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.init_ui()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 加载所有标签
|
|
|
|
|
|
self.load_tags()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("标签管理")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 创建标签ID输入框和按钮
|
|
|
|
|
|
input_layout = QHBoxLayout()
|
|
|
|
|
|
input_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.tag_id_input = LineEdit()
|
|
|
|
|
|
self.tag_id_input.setPlaceholderText("输入标签ID...")
|
|
|
|
|
|
input_layout.addWidget(self.tag_id_input, 3)
|
|
|
|
|
|
|
|
|
|
|
|
input_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
app_list_button = PrimaryPushButton("查看应用列表")
|
|
|
|
|
|
app_list_button.clicked.connect(self.show_tag_apps_by_id_input)
|
|
|
|
|
|
input_layout.addWidget(app_list_button)
|
|
|
|
|
|
|
|
|
|
|
|
input_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加刷新按钮
|
|
|
|
|
|
refresh_button = PushButton("刷新标签列表")
|
|
|
|
|
|
refresh_button.clicked.connect(self.load_tags)
|
|
|
|
|
|
input_layout.addWidget(refresh_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(input_layout)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 创建标签列表
|
|
|
|
|
|
self.tag_list = TableWidget()
|
2025-09-23 20:33:28 +08:00
|
|
|
|
self.tag_list.setColumnCount(2)
|
|
|
|
|
|
self.tag_list.setHorizontalHeaderLabels(["ID", "标签名称"])
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.tag_list.horizontalHeader().setSectionResizeMode(1, 3)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.tag_list.cellDoubleClicked.connect(self.show_tag_apps)
|
|
|
|
|
|
layout.addWidget(self.tag_list)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 创建分页控制
|
|
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
pagination_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
def load_tags(self):
|
|
|
|
|
|
"""加载标签数据"""
|
|
|
|
|
|
self.show_progress()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getalltags',
|
|
|
|
|
|
{'page': self.current_page, 'limit': self.items_per_page}
|
|
|
|
|
|
)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.worker.finished.connect(self.on_tags_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_tags_loaded(self, data):
|
|
|
|
|
|
"""标签数据加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 增强的数据格式验证,提供更具体的错误信息
|
|
|
|
|
|
if data is None:
|
|
|
|
|
|
self.show_error("API返回数据为空")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 处理数据 - 如果是列表类型,转换为预期的字典格式
|
|
|
|
|
|
processed_data = {}
|
|
|
|
|
|
if isinstance(data, list):
|
|
|
|
|
|
# API返回了列表,我们将其转换为预期的字典结构
|
|
|
|
|
|
processed_data['tags'] = data
|
|
|
|
|
|
# 构建简单的分页信息
|
|
|
|
|
|
total_items = len(data)
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif isinstance(data, dict):
|
|
|
|
|
|
# 保留原有的字典处理逻辑,但添加更灵活的验证
|
|
|
|
|
|
processed_data = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证必要字段是否存在,如果不存在则使用默认值
|
|
|
|
|
|
if 'tags' not in processed_data:
|
|
|
|
|
|
processed_data['tags'] = []
|
|
|
|
|
|
elif not isinstance(processed_data['tags'], list):
|
|
|
|
|
|
# 如果tags不是列表,尝试转换或使用空列表
|
|
|
|
|
|
try:
|
|
|
|
|
|
processed_data['tags'] = [processed_data['tags']]
|
|
|
|
|
|
except:
|
|
|
|
|
|
processed_data['tags'] = []
|
|
|
|
|
|
|
|
|
|
|
|
if 'pagination' not in processed_data:
|
|
|
|
|
|
# 如果没有分页信息,计算简单的分页
|
|
|
|
|
|
total_items = len(processed_data['tags'])
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif not isinstance(processed_data['pagination'], dict):
|
|
|
|
|
|
# 如果pagination不是字典,创建默认分页信息
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': 1}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保分页信息中有totalPages
|
|
|
|
|
|
if 'totalPages' not in processed_data['pagination']:
|
|
|
|
|
|
processed_data['pagination']['totalPages'] = 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.show_error(f"数据格式错误: 期望字典或列表类型,实际为{type(data).__name__}")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.tag_list.setRowCount(0)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 填充表格,添加更健壮的数据处理
|
|
|
|
|
|
tags_to_display = processed_data['tags'] if isinstance(processed_data['tags'], list) else []
|
|
|
|
|
|
for tag in tags_to_display:
|
|
|
|
|
|
# 确保tag是字典类型
|
|
|
|
|
|
if not isinstance(tag, dict):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
row_pos = self.tag_list.rowCount()
|
|
|
|
|
|
self.tag_list.insertRow(row_pos)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 安全获取字段值,避免KeyError
|
|
|
|
|
|
tag_id = str(tag.get('id', '未知ID'))
|
|
|
|
|
|
tag_name = tag.get('name', '无名称')
|
|
|
|
|
|
|
|
|
|
|
|
self.tag_list.setItem(row_pos, 0, QTableWidgetItem(tag_id))
|
|
|
|
|
|
self.tag_list.setItem(row_pos, 1, QTableWidgetItem(tag_name))
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 更新分页信息,增加异常处理
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.total_pages = int(processed_data['pagination']['totalPages'])
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_tags()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_tags()
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
def show_tag_apps(self, row, column):
|
|
|
|
|
|
"""显示标签下的应用"""
|
|
|
|
|
|
tag_id = self.tag_list.item(row, 0).text()
|
|
|
|
|
|
tag_name = self.tag_list.item(row, 1).text()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 直接创建并显示标签应用窗口
|
2025-09-20 22:20:08 +08:00
|
|
|
|
tag_apps_window = TagAppsWindow(self.api_client, tag_id, tag_name, self)
|
|
|
|
|
|
tag_apps_window.show()
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
def show_tag_apps_by_id_input(self):
|
|
|
|
|
|
"""通过输入框中的ID查看标签下的应用列表"""
|
|
|
|
|
|
tag_id = self.tag_id_input.text().strip()
|
|
|
|
|
|
if tag_id:
|
|
|
|
|
|
# 先获取标签名称
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'gettagapps',
|
|
|
|
|
|
{'id': tag_id, 'page': 1, 'limit': 1}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(lambda data, tid=tag_id: self.on_tag_info_loaded(data, tid))
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
else:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
title="警告",
|
|
|
|
|
|
content="请输入标签ID",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=3000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def on_tag_info_loaded(self, data, tag_id):
|
|
|
|
|
|
"""标签信息加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if data and 'apps' in data and data['apps']:
|
|
|
|
|
|
# 从第一个应用中获取标签名称
|
|
|
|
|
|
tag_name = "标签名称未知"
|
|
|
|
|
|
if data['apps'] and isinstance(data['apps'], list):
|
|
|
|
|
|
first_app = data['apps'][0]
|
|
|
|
|
|
if 'tags' in first_app and isinstance(first_app['tags'], list) and first_app['tags']:
|
|
|
|
|
|
tag_name = first_app['tags'][0].get('name', f"标签{tag_id}")
|
|
|
|
|
|
|
|
|
|
|
|
# 打开标签应用列表窗口
|
|
|
|
|
|
tag_apps_window = TagAppsWindow(self.api_client, tag_id, tag_name, self)
|
|
|
|
|
|
tag_apps_window.show()
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.show_error("未找到该标签的信息")
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class DeveloperTab(QWidget):
|
|
|
|
|
|
"""开发者管理标签页"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.parent = parent
|
|
|
|
|
|
# 初始化分页相关变量
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.init_ui()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 加载所有开发者
|
|
|
|
|
|
self.load_developers()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("开发者管理")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建开发者ID输入框和按钮
|
|
|
|
|
|
input_layout = QHBoxLayout()
|
|
|
|
|
|
input_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.developer_id_input = LineEdit()
|
|
|
|
|
|
self.developer_id_input.setPlaceholderText("输入开发者ID...")
|
|
|
|
|
|
input_layout.addWidget(self.developer_id_input, 3)
|
|
|
|
|
|
|
|
|
|
|
|
input_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
app_list_button = PrimaryPushButton("查看应用列表")
|
|
|
|
|
|
app_list_button.clicked.connect(self.show_developer_apps)
|
|
|
|
|
|
input_layout.addWidget(app_list_button)
|
|
|
|
|
|
|
|
|
|
|
|
input_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
info_button = PushButton("查看开发者信息")
|
|
|
|
|
|
info_button.clicked.connect(self.show_developer_info)
|
|
|
|
|
|
input_layout.addWidget(info_button)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
input_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加刷新按钮
|
|
|
|
|
|
refresh_button = PushButton("刷新开发者列表")
|
|
|
|
|
|
refresh_button.clicked.connect(self.load_developers)
|
|
|
|
|
|
input_layout.addWidget(refresh_button)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
layout.addLayout(input_layout)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 创建开发者列表
|
|
|
|
|
|
self.developers_table = TableWidget()
|
|
|
|
|
|
self.developers_table.setColumnCount(4)
|
|
|
|
|
|
self.developers_table.setHorizontalHeaderLabels(["ID", "开发者名称", "应用数量", "注册时间"])
|
|
|
|
|
|
self.developers_table.horizontalHeader().setSectionResizeMode(1, 3)
|
|
|
|
|
|
self.developers_table.cellDoubleClicked.connect(self.on_developer_double_clicked)
|
|
|
|
|
|
layout.addWidget(self.developers_table)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建分页控制
|
|
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
pagination_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
def load_developers(self):
|
|
|
|
|
|
"""加载所有开发者"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getalldevelopers',
|
|
|
|
|
|
{'page': self.current_page, 'limit': self.items_per_page}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_developers_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_developers_loaded(self, data):
|
|
|
|
|
|
"""开发者列表加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
# 增强的数据格式验证
|
|
|
|
|
|
if data is None:
|
|
|
|
|
|
self.show_error("API返回数据为空")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 处理数据 - 如果是列表类型,转换为预期的字典格式
|
|
|
|
|
|
processed_data = {}
|
|
|
|
|
|
if isinstance(data, list):
|
|
|
|
|
|
# API返回了列表,我们将其转换为预期的字典结构
|
|
|
|
|
|
processed_data['developers'] = data
|
|
|
|
|
|
# 构建简单的分页信息
|
|
|
|
|
|
total_items = len(data)
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif isinstance(data, dict):
|
|
|
|
|
|
# 保留原有的字典处理逻辑,但添加更灵活的验证
|
|
|
|
|
|
processed_data = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证必要字段是否存在,如果不存在则使用默认值
|
|
|
|
|
|
if 'developers' not in processed_data:
|
|
|
|
|
|
processed_data['developers'] = []
|
|
|
|
|
|
elif not isinstance(processed_data['developers'], list):
|
|
|
|
|
|
# 如果developers不是列表,尝试转换或使用空列表
|
|
|
|
|
|
try:
|
|
|
|
|
|
processed_data['developers'] = [processed_data['developers']]
|
|
|
|
|
|
except:
|
|
|
|
|
|
processed_data['developers'] = []
|
|
|
|
|
|
|
|
|
|
|
|
if 'pagination' not in processed_data:
|
|
|
|
|
|
# 如果没有分页信息,计算简单的分页
|
|
|
|
|
|
total_items = len(processed_data['developers'])
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif not isinstance(processed_data['pagination'], dict):
|
|
|
|
|
|
# 如果pagination不是字典,创建默认分页信息
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': 1}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保分页信息中有totalPages
|
|
|
|
|
|
if 'totalPages' not in processed_data['pagination']:
|
|
|
|
|
|
processed_data['pagination']['totalPages'] = 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.show_error(f"数据格式错误: 期望字典或列表类型,实际为{type(data).__name__}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.developers_table.setRowCount(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 填充表格,添加更健壮的数据处理
|
|
|
|
|
|
developers_to_display = processed_data['developers'] if isinstance(processed_data['developers'], list) else []
|
|
|
|
|
|
for developer in developers_to_display:
|
|
|
|
|
|
# 确保developer是字典类型
|
|
|
|
|
|
if not isinstance(developer, dict):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
row_pos = self.developers_table.rowCount()
|
|
|
|
|
|
self.developers_table.insertRow(row_pos)
|
|
|
|
|
|
|
|
|
|
|
|
# 安全获取字段值,避免KeyError
|
|
|
|
|
|
dev_id = str(developer.get('id', '未知ID'))
|
|
|
|
|
|
dev_name = developer.get('username', '无名称')
|
|
|
|
|
|
app_count = str(developer.get('app_count', 0))
|
|
|
|
|
|
created_at = developer.get('created_at', '未知时间')
|
|
|
|
|
|
|
|
|
|
|
|
self.developers_table.setItem(row_pos, 0, QTableWidgetItem(dev_id))
|
|
|
|
|
|
self.developers_table.setItem(row_pos, 1, QTableWidgetItem(dev_name))
|
|
|
|
|
|
self.developers_table.setItem(row_pos, 2, QTableWidgetItem(app_count))
|
|
|
|
|
|
self.developers_table.setItem(row_pos, 3, QTableWidgetItem(created_at))
|
|
|
|
|
|
|
|
|
|
|
|
# 更新分页信息,增加异常处理
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.total_pages = int(processed_data['pagination']['totalPages'])
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_developers()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_developers()
|
|
|
|
|
|
|
|
|
|
|
|
def on_developer_double_clicked(self, row, column):
|
|
|
|
|
|
"""双击开发者行查看详情"""
|
|
|
|
|
|
developer_id = self.developers_table.item(row, 0).text()
|
|
|
|
|
|
# 显示开发者信息
|
|
|
|
|
|
self.show_developer_info_by_id(developer_id)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
def show_developer_apps(self):
|
|
|
|
|
|
"""查看开发者的应用列表"""
|
|
|
|
|
|
developer_id = self.developer_id_input.text().strip()
|
|
|
|
|
|
if developer_id:
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 直接创建并显示开发者应用窗口
|
2025-09-20 22:20:08 +08:00
|
|
|
|
developer_apps_window = DeveloperAppsWindow(self.api_client, developer_id, self)
|
|
|
|
|
|
developer_apps_window.show()
|
|
|
|
|
|
else:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
title="警告",
|
|
|
|
|
|
content="请输入开发者ID",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=3000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def show_developer_info(self):
|
|
|
|
|
|
"""查看开发者信息"""
|
|
|
|
|
|
developer_id = self.developer_id_input.text().strip()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
if developer_id:
|
|
|
|
|
|
self.show_developer_info_by_id(developer_id)
|
|
|
|
|
|
else:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
title="警告",
|
|
|
|
|
|
content="请输入开发者ID",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=3000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def show_developer_info_by_id(self, developer_id):
|
|
|
|
|
|
"""根据ID显示开发者信息"""
|
|
|
|
|
|
# 直接创建并显示开发者信息窗口
|
|
|
|
|
|
developer_info_window = DeveloperInfoWindow(self.api_client, developer_id, self)
|
|
|
|
|
|
developer_info_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
if developer_id:
|
|
|
|
|
|
# 直接创建并显示开发者信息窗口,而不是调用parent方法
|
|
|
|
|
|
developer_info_window = DeveloperInfoWindow(self.api_client, developer_id, self)
|
|
|
|
|
|
developer_info_window.show()
|
|
|
|
|
|
else:
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
title="警告",
|
|
|
|
|
|
content="请输入开发者ID",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=3000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class AnnouncementTab(QWidget):
|
|
|
|
|
|
"""公告管理标签页"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
# 先初始化分页相关变量,因为init_ui()内部会调用load_announcements()
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("公告管理")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建分页控制
|
|
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
pagination_layout.setContentsMargins(0, 10, 0, 10)
|
|
|
|
|
|
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建公告列表
|
|
|
|
|
|
self.announcement_list = TableWidget()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.announcement_list.setColumnCount(3)
|
|
|
|
|
|
self.announcement_list.setHorizontalHeaderLabels(["ID", "标题", "发布时间"])
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.announcement_list.horizontalHeader().setSectionResizeMode(1, 3)
|
|
|
|
|
|
self.announcement_list.cellDoubleClicked.connect(self.show_announcement_detail)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置表格为只读
|
|
|
|
|
|
self.announcement_list.setEditTriggers(QTableWidget.NoEditTriggers)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
layout.addWidget(self.announcement_list)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
# 加载初始数据
|
|
|
|
|
|
self.load_announcements()
|
|
|
|
|
|
|
|
|
|
|
|
def load_announcements(self):
|
|
|
|
|
|
"""加载公告列表"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getacc',
|
|
|
|
|
|
{'page': self.current_page, 'limit': self.items_per_page}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_announcements_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_announcements_loaded(self, data):
|
|
|
|
|
|
"""公告列表加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 增强的数据格式验证,提供更具体的错误信息
|
|
|
|
|
|
if data is None:
|
|
|
|
|
|
self.show_error("API返回数据为空")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 处理数据 - 如果是列表类型,转换为预期的字典格式
|
|
|
|
|
|
processed_data = {}
|
|
|
|
|
|
if isinstance(data, list):
|
|
|
|
|
|
# API返回了列表,我们将其转换为预期的字典结构
|
|
|
|
|
|
processed_data['announcements'] = data
|
|
|
|
|
|
# 构建简单的分页信息
|
|
|
|
|
|
total_items = len(data)
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif isinstance(data, dict):
|
|
|
|
|
|
# 保留原有的字典处理逻辑,但添加更灵活的验证
|
|
|
|
|
|
processed_data = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证必要字段是否存在,如果不存在则使用默认值
|
|
|
|
|
|
if 'announcements' not in processed_data:
|
|
|
|
|
|
processed_data['announcements'] = []
|
|
|
|
|
|
elif not isinstance(processed_data['announcements'], list):
|
|
|
|
|
|
# 如果announcements不是列表,尝试转换或使用空列表
|
|
|
|
|
|
try:
|
|
|
|
|
|
processed_data['announcements'] = [processed_data['announcements']]
|
|
|
|
|
|
except:
|
|
|
|
|
|
processed_data['announcements'] = []
|
|
|
|
|
|
|
|
|
|
|
|
if 'pagination' not in processed_data:
|
|
|
|
|
|
# 如果没有分页信息,计算简单的分页
|
|
|
|
|
|
total_items = len(processed_data['announcements'])
|
|
|
|
|
|
total_pages = (total_items + self.items_per_page - 1) // self.items_per_page
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': total_pages}
|
|
|
|
|
|
elif not isinstance(processed_data['pagination'], dict):
|
|
|
|
|
|
# 如果pagination不是字典,创建默认分页信息
|
|
|
|
|
|
processed_data['pagination'] = {'totalPages': 1}
|
|
|
|
|
|
|
|
|
|
|
|
# 确保分页信息中有totalPages
|
|
|
|
|
|
if 'totalPages' not in processed_data['pagination']:
|
|
|
|
|
|
processed_data['pagination']['totalPages'] = 1
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.show_error(f"数据格式错误: 期望字典或列表类型,实际为{type(data).__name__}")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.announcement_list.setRowCount(0)
|
|
|
|
|
|
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 填充表格,添加更健壮的数据处理
|
|
|
|
|
|
for announcement in processed_data['announcements']:
|
|
|
|
|
|
# 确保announcement是字典类型
|
|
|
|
|
|
if not isinstance(announcement, dict):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
row_pos = self.announcement_list.rowCount()
|
|
|
|
|
|
self.announcement_list.insertRow(row_pos)
|
|
|
|
|
|
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 安全获取字段值,避免KeyError
|
|
|
|
|
|
self.announcement_list.setItem(row_pos, 0, QTableWidgetItem(str(announcement.get('id', '未知ID'))))
|
|
|
|
|
|
self.announcement_list.setItem(row_pos, 1, QTableWidgetItem(announcement.get('title', '无标题')))
|
|
|
|
|
|
self.announcement_list.setItem(row_pos, 2, QTableWidgetItem(announcement.get('created_at', '未知时间')))
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 更新分页信息,增加异常处理
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.total_pages = int(processed_data['pagination']['totalPages'])
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_announcements()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_announcements()
|
|
|
|
|
|
|
|
|
|
|
|
def show_announcement_detail(self, row, column):
|
|
|
|
|
|
"""显示公告详情"""
|
|
|
|
|
|
announcement_id = self.announcement_list.item(row, 0).text()
|
|
|
|
|
|
title = self.announcement_list.item(row, 1).text()
|
2025-09-21 18:17:37 +08:00
|
|
|
|
created_at = self.announcement_list.item(row, 2).text()
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 查找完整的公告数据
|
|
|
|
|
|
for i in range(self.announcement_list.rowCount()):
|
|
|
|
|
|
if self.announcement_list.item(i, 0).text() == announcement_id:
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 获取完整的公告内容
|
|
|
|
|
|
# 由于表格中没有直接存储content,我们需要重新获取
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.detail_worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getacc',
|
|
|
|
|
|
{'page': 1, 'limit': 100} # 获取足够多的公告以确保找到目标公告
|
2025-09-20 22:20:08 +08:00
|
|
|
|
)
|
2025-09-21 18:17:37 +08:00
|
|
|
|
self.detail_worker.finished.connect(lambda data, aid=announcement_id, t=title, ca=created_at:
|
|
|
|
|
|
self.on_announcement_detail_loaded(data, aid, t, ca))
|
|
|
|
|
|
self.detail_worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.detail_worker.error.connect(self.show_error)
|
|
|
|
|
|
self.detail_worker.start()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
break
|
2025-09-21 18:17:37 +08:00
|
|
|
|
|
|
|
|
|
|
def on_announcement_detail_loaded(self, data, announcement_id, title, created_at):
|
|
|
|
|
|
"""公告详情加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
# 增强的数据格式验证
|
|
|
|
|
|
if data is None:
|
|
|
|
|
|
self.show_error("获取公告详情失败: API返回数据为空")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 处理数据 - 支持字典或列表类型
|
|
|
|
|
|
processed_data = {}
|
|
|
|
|
|
if isinstance(data, list):
|
|
|
|
|
|
# API返回了列表,转换为预期的字典结构
|
|
|
|
|
|
processed_data['announcements'] = data
|
|
|
|
|
|
elif isinstance(data, dict):
|
|
|
|
|
|
# 保留字典处理逻辑,但添加更灵活的验证
|
|
|
|
|
|
processed_data = data.copy()
|
|
|
|
|
|
|
|
|
|
|
|
# 验证必要字段是否存在
|
|
|
|
|
|
if 'announcements' not in processed_data:
|
|
|
|
|
|
processed_data['announcements'] = []
|
|
|
|
|
|
elif not isinstance(processed_data['announcements'], list):
|
|
|
|
|
|
# 尝试转换announcements为列表类型
|
|
|
|
|
|
try:
|
|
|
|
|
|
processed_data['announcements'] = [processed_data['announcements']]
|
|
|
|
|
|
except:
|
|
|
|
|
|
processed_data['announcements'] = []
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.show_error(f"获取公告详情失败: 数据格式错误,期望字典或列表类型,实际为{type(data).__name__}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 查找特定的公告,增加异常处理
|
|
|
|
|
|
content = "无内容"
|
|
|
|
|
|
try:
|
|
|
|
|
|
for announcement in processed_data['announcements']:
|
|
|
|
|
|
if not isinstance(announcement, dict):
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if str(announcement.get('id', '')) == announcement_id:
|
|
|
|
|
|
content = announcement.get('content', '无内容')
|
|
|
|
|
|
break
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.show_error(f"查找公告详情时发生错误: {str(e)}")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 创建并显示公告详情窗口
|
|
|
|
|
|
detail_window = AnnouncementDetailWindow(self.api_client, announcement_id, title, created_at, content, self)
|
|
|
|
|
|
detail_window.show()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class StatsTab(QWidget):
|
|
|
|
|
|
"""统计信息标签页"""
|
|
|
|
|
|
def __init__(self, api_client, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("应用商店统计信息")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建统计卡片容器
|
|
|
|
|
|
cards_layout = QVBoxLayout()
|
|
|
|
|
|
cards_layout.setSpacing(20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建统计卡片
|
|
|
|
|
|
self.app_count_card = CardWidget()
|
|
|
|
|
|
self.app_count_card.setMinimumHeight(100)
|
|
|
|
|
|
self.app_count_layout = QVBoxLayout(self.app_count_card)
|
|
|
|
|
|
self.app_count_title = SubtitleLabel("应用总数")
|
|
|
|
|
|
self.app_count_value = TitleLabel("--")
|
|
|
|
|
|
self.app_count_layout.addWidget(self.app_count_title)
|
|
|
|
|
|
self.app_count_layout.addWidget(self.app_count_value, alignment=Qt.AlignCenter)
|
|
|
|
|
|
cards_layout.addWidget(self.app_count_card)
|
|
|
|
|
|
|
|
|
|
|
|
self.developer_count_card = CardWidget()
|
|
|
|
|
|
self.developer_count_card.setMinimumHeight(100)
|
|
|
|
|
|
self.developer_count_layout = QVBoxLayout(self.developer_count_card)
|
|
|
|
|
|
self.developer_count_title = SubtitleLabel("开发者总数")
|
|
|
|
|
|
self.developer_count_value = TitleLabel("--")
|
|
|
|
|
|
self.developer_count_layout.addWidget(self.developer_count_title)
|
|
|
|
|
|
self.developer_count_layout.addWidget(self.developer_count_value, alignment=Qt.AlignCenter)
|
|
|
|
|
|
cards_layout.addWidget(self.developer_count_card)
|
|
|
|
|
|
|
|
|
|
|
|
self.tag_count_card = CardWidget()
|
|
|
|
|
|
self.tag_count_card.setMinimumHeight(100)
|
|
|
|
|
|
self.tag_count_layout = QVBoxLayout(self.tag_count_card)
|
|
|
|
|
|
self.tag_count_title = SubtitleLabel("标签总数")
|
|
|
|
|
|
self.tag_count_value = TitleLabel("--")
|
|
|
|
|
|
self.tag_count_layout.addWidget(self.tag_count_title)
|
|
|
|
|
|
self.tag_count_layout.addWidget(self.tag_count_value, alignment=Qt.AlignCenter)
|
|
|
|
|
|
cards_layout.addWidget(self.tag_count_card)
|
|
|
|
|
|
|
|
|
|
|
|
self.announcement_count_card = CardWidget()
|
|
|
|
|
|
self.announcement_count_card.setMinimumHeight(100)
|
|
|
|
|
|
self.announcement_count_layout = QVBoxLayout(self.announcement_count_card)
|
|
|
|
|
|
self.announcement_count_title = SubtitleLabel("公告总数")
|
|
|
|
|
|
self.announcement_count_value = TitleLabel("--")
|
|
|
|
|
|
self.announcement_count_layout.addWidget(self.announcement_count_title)
|
|
|
|
|
|
self.announcement_count_layout.addWidget(self.announcement_count_value, alignment=Qt.AlignCenter)
|
|
|
|
|
|
cards_layout.addWidget(self.announcement_count_card)
|
|
|
|
|
|
|
|
|
|
|
|
self.download_count_card = CardWidget()
|
|
|
|
|
|
self.download_count_card.setMinimumHeight(100)
|
|
|
|
|
|
self.download_count_layout = QVBoxLayout(self.download_count_card)
|
|
|
|
|
|
self.download_count_title = SubtitleLabel("总下载量")
|
|
|
|
|
|
self.download_count_value = TitleLabel("--")
|
|
|
|
|
|
self.download_count_layout.addWidget(self.download_count_title)
|
|
|
|
|
|
self.download_count_layout.addWidget(self.download_count_value, alignment=Qt.AlignCenter)
|
|
|
|
|
|
cards_layout.addWidget(self.download_count_card)
|
|
|
|
|
|
|
|
|
|
|
|
layout.addLayout(cards_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建刷新按钮
|
|
|
|
|
|
refresh_button = PrimaryPushButton("刷新统计数据")
|
|
|
|
|
|
refresh_button.clicked.connect(self.load_stats)
|
|
|
|
|
|
layout.addWidget(refresh_button, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
# 加载初始数据
|
|
|
|
|
|
self.load_stats()
|
|
|
|
|
|
|
|
|
|
|
|
def load_stats(self):
|
|
|
|
|
|
"""加载统计数据"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(self.api_client, 'getcount')
|
|
|
|
|
|
self.worker.finished.connect(self.on_stats_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_stats_loaded(self, data):
|
|
|
|
|
|
"""统计数据加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data:
|
|
|
|
|
|
self.show_error("数据加载失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 更新统计数据
|
|
|
|
|
|
self.app_count_value.setText(str(data.get('total_apps', '--')))
|
|
|
|
|
|
self.developer_count_value.setText(str(data.get('total_developers', '--')))
|
|
|
|
|
|
self.tag_count_value.setText(str(data.get('total_tags', '--')))
|
|
|
|
|
|
self.announcement_count_value.setText(str(data.get('total_announcements', '--')))
|
|
|
|
|
|
self.download_count_value.setText(str(data.get('total_downloads', '--')))
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-24 21:58:25 +08:00
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
class TagAppsWindow(QMainWindow):
|
|
|
|
|
|
"""标签下的应用列表窗口"""
|
|
|
|
|
|
def __init__(self, api_client, tag_id, tag_name, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.tag_id = tag_id
|
|
|
|
|
|
self.tag_name = tag_name
|
|
|
|
|
|
self.setWindowTitle(f"标签 '{tag_name}' 下的应用")
|
|
|
|
|
|
self.resize(800, 600)
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_tag_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建中心部件
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel(f"标签 '{self.tag_name}' 下的应用")
|
|
|
|
|
|
main_layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建应用列表
|
|
|
|
|
|
self.app_table = TableWidget()
|
|
|
|
|
|
self.app_table.setColumnCount(5)
|
|
|
|
|
|
self.app_table.setHorizontalHeaderLabels(["ID", "应用名称", "版本", "评分", "下载量"])
|
|
|
|
|
|
self.app_table.horizontalHeader().setSectionResizeMode(1, 3)
|
|
|
|
|
|
self.app_table.cellDoubleClicked.connect(self.show_app_detail)
|
|
|
|
|
|
main_layout.addWidget(self.app_table)
|
|
|
|
|
|
|
2025-09-21 17:41:55 +08:00
|
|
|
|
# 创建分页控件
|
2025-09-20 22:20:08 +08:00
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
2025-09-21 17:41:55 +08:00
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
pagination_layout.addStretch()
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label)
|
|
|
|
|
|
pagination_layout.addStretch()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
main_layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
def load_tag_apps(self):
|
|
|
|
|
|
"""加载标签下的应用"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
params = {
|
|
|
|
|
|
'id': self.tag_id,
|
|
|
|
|
|
'page': self.current_page,
|
|
|
|
|
|
'limit': self.items_per_page
|
|
|
|
|
|
}
|
|
|
|
|
|
self.worker = WorkerThread(self.api_client, 'gettagapp', params)
|
|
|
|
|
|
self.worker.finished.connect(self.on_tag_apps_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_tag_apps_loaded(self, data):
|
|
|
|
|
|
"""标签应用加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data or 'apps' not in data:
|
|
|
|
|
|
self.show_error("应用列表加载失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.app_table.setRowCount(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加数据
|
|
|
|
|
|
for app in data['apps']:
|
|
|
|
|
|
row_position = self.app_table.rowCount()
|
|
|
|
|
|
self.app_table.insertRow(row_position)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加应用数据到表格
|
|
|
|
|
|
self.app_table.setItem(row_position, 0, QTableWidgetItem(str(app.get('id', '--'))))
|
|
|
|
|
|
self.app_table.setItem(row_position, 1, QTableWidgetItem(app.get('name', '未知应用')))
|
|
|
|
|
|
self.app_table.setItem(row_position, 2, QTableWidgetItem(app.get('version', '未知')))
|
|
|
|
|
|
self.app_table.setItem(row_position, 3, QTableWidgetItem(str(app.get('avg_rating', '暂无'))))
|
|
|
|
|
|
self.app_table.setItem(row_position, 4, QTableWidgetItem(str(app.get('total_downloads', 0))))
|
|
|
|
|
|
|
|
|
|
|
|
# 更新分页信息
|
|
|
|
|
|
self.total_pages = data.get('total_pages', 1)
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_tag_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_tag_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def show_app_detail(self, row, column):
|
|
|
|
|
|
"""显示应用详情"""
|
|
|
|
|
|
# 获取应用ID
|
|
|
|
|
|
app_id_item = self.app_table.item(row, 0)
|
|
|
|
|
|
if app_id_item:
|
|
|
|
|
|
app_id = int(app_id_item.text())
|
|
|
|
|
|
# 打开应用详情窗口
|
|
|
|
|
|
self.app_detail_window = AppDetailWindow(self.api_client, app_id, self)
|
|
|
|
|
|
self.app_detail_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class DeveloperAppsWindow(QMainWindow):
|
|
|
|
|
|
"""开发者的应用列表窗口"""
|
|
|
|
|
|
def __init__(self, api_client, developer_id, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.developer_id = developer_id
|
|
|
|
|
|
self.setWindowTitle(f"开发者ID {developer_id} 的应用")
|
|
|
|
|
|
self.resize(800, 600)
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_developer_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建中心部件
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel(f"开发者ID {self.developer_id} 的应用")
|
|
|
|
|
|
main_layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建应用列表
|
|
|
|
|
|
self.app_table = TableWidget()
|
|
|
|
|
|
self.app_table.setColumnCount(5)
|
|
|
|
|
|
self.app_table.setHorizontalHeaderLabels(["ID", "应用名称", "版本", "评分", "下载量"])
|
|
|
|
|
|
self.app_table.horizontalHeader().setSectionResizeMode(1, 3)
|
|
|
|
|
|
self.app_table.cellDoubleClicked.connect(self.show_app_detail)
|
|
|
|
|
|
main_layout.addWidget(self.app_table)
|
|
|
|
|
|
|
2025-09-21 17:41:55 +08:00
|
|
|
|
# 创建分页控件
|
2025-09-20 22:20:08 +08:00
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
pagination_layout.setContentsMargins(0, 10, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
|
|
|
|
|
|
main_layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
def load_developer_apps(self):
|
|
|
|
|
|
"""加载开发者的应用"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getdeveloperapp',
|
|
|
|
|
|
{'id': self.developer_id, 'page': self.current_page, 'limit': self.items_per_page}
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_developer_apps_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_developer_apps_loaded(self, data):
|
|
|
|
|
|
"""开发者应用加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data or 'apps' not in data or 'pagination' not in data:
|
|
|
|
|
|
self.show_error("数据格式错误")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.app_table.setRowCount(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 填充表格
|
|
|
|
|
|
for app in data['apps']:
|
|
|
|
|
|
row_pos = self.app_table.rowCount()
|
|
|
|
|
|
self.app_table.insertRow(row_pos)
|
|
|
|
|
|
|
|
|
|
|
|
self.app_table.setItem(row_pos, 0, QTableWidgetItem(str(app.get('id', ''))))
|
|
|
|
|
|
self.app_table.setItem(row_pos, 1, QTableWidgetItem(app.get('name', '')))
|
|
|
|
|
|
self.app_table.setItem(row_pos, 2, QTableWidgetItem(app.get('version', '')))
|
|
|
|
|
|
self.app_table.setItem(row_pos, 3, QTableWidgetItem(str(app.get('avg_rating', '暂无'))))
|
|
|
|
|
|
self.app_table.setItem(row_pos, 4, QTableWidgetItem(str(app.get('total_downloads', 0))))
|
|
|
|
|
|
|
|
|
|
|
|
# 更新分页信息
|
|
|
|
|
|
self.total_pages = data['pagination']['totalPages']
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_developer_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_developer_apps()
|
|
|
|
|
|
|
|
|
|
|
|
def show_app_detail(self, row, column):
|
|
|
|
|
|
"""显示应用详情"""
|
|
|
|
|
|
app_id = self.app_table.item(row, 0).text()
|
|
|
|
|
|
detail_window = AppDetailWindow(self.api_client, app_id, self)
|
|
|
|
|
|
detail_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
class AnnouncementDetailWindow(QMainWindow):
|
|
|
|
|
|
"""公告详情窗口"""
|
|
|
|
|
|
def __init__(self, api_client, announcement_id, title, created_at, content, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.announcement_id = announcement_id
|
|
|
|
|
|
self.title = title
|
|
|
|
|
|
self.created_at = created_at
|
|
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 设置窗口属性
|
|
|
|
|
|
self.setWindowTitle(f"公告详情 - {title}")
|
|
|
|
|
|
self.resize(700, 550)
|
|
|
|
|
|
self.setObjectName("AnnouncementDetailWindow")
|
|
|
|
|
|
|
|
|
|
|
|
# 添加全局样式
|
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
|
#AnnouncementDetailWindow {
|
|
|
|
|
|
background-color: #F2F3F5;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 将Markdown内容转换为HTML
|
|
|
|
|
|
try:
|
|
|
|
|
|
import markdown
|
|
|
|
|
|
self.content_html = markdown.markdown(content)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 如果转换失败,使用原始内容
|
|
|
|
|
|
self.content_html = content
|
|
|
|
|
|
print(f"Markdown转换失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
self.content = content
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建中心部件
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
2025-09-22 22:24:56 +08:00
|
|
|
|
main_layout.setSpacing(12)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
2025-09-22 22:24:56 +08:00
|
|
|
|
self.title_label = TitleLabel(f"{self.title}")
|
|
|
|
|
|
self.title_label.setObjectName("AnnouncementTitle")
|
|
|
|
|
|
main_layout.addWidget(self.title_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建滚动区域 - 使用QFluentWidgets的ScrollArea
|
|
|
|
|
|
self.scroll_area = ScrollArea()
|
|
|
|
|
|
self.scroll_area.setWidgetResizable(True)
|
|
|
|
|
|
self.scroll_area.setFrameShape(0)
|
|
|
|
|
|
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置滚动区域样式
|
|
|
|
|
|
self.scroll_area.setStyleSheet("""
|
|
|
|
|
|
ScrollArea {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar:vertical {
|
|
|
|
|
|
width: 8px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar::handle:vertical {
|
|
|
|
|
|
background: rgba(142, 142, 147, 0.3);
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
min-height: 40px;
|
|
|
|
|
|
}
|
|
|
|
|
|
QScrollBar::handle:vertical:hover {
|
|
|
|
|
|
background: rgba(142, 142, 147, 0.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建滚动内容部件
|
|
|
|
|
|
self.scroll_content = QWidget()
|
|
|
|
|
|
self.scroll_layout = QVBoxLayout(self.scroll_content)
|
|
|
|
|
|
self.scroll_layout.setContentsMargins(0, 0, 0, 20)
|
|
|
|
|
|
self.scroll_layout.setSpacing(16)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加滚动区域到主布局
|
|
|
|
|
|
self.scroll_area.setWidget(self.scroll_content)
|
|
|
|
|
|
main_layout.addWidget(self.scroll_area)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建信息卡片
|
2025-09-22 22:24:56 +08:00
|
|
|
|
self.create_info_card()
|
|
|
|
|
|
|
|
|
|
|
|
# 创建内容卡片
|
|
|
|
|
|
self.create_content_card()
|
|
|
|
|
|
|
|
|
|
|
|
# 添加关闭按钮
|
|
|
|
|
|
self.create_close_button(main_layout)
|
|
|
|
|
|
|
|
|
|
|
|
def create_info_card(self):
|
|
|
|
|
|
"""创建公告信息卡片"""
|
|
|
|
|
|
info_card = SimpleCardWidget()
|
|
|
|
|
|
info_card.setObjectName("InfoCard")
|
|
|
|
|
|
|
|
|
|
|
|
card_layout = QHBoxLayout(info_card)
|
|
|
|
|
|
card_layout.setContentsMargins(16, 12, 16, 12)
|
|
|
|
|
|
card_layout.setSpacing(20)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加公告信息
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
("ID", self.announcement_id),
|
|
|
|
|
|
("发布时间", self.created_at)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label_text, value_text in info_items:
|
2025-09-22 22:24:56 +08:00
|
|
|
|
info_layout = QVBoxLayout()
|
|
|
|
|
|
label = CaptionLabel(label_text)
|
|
|
|
|
|
value = BodyLabel(value_text)
|
|
|
|
|
|
|
|
|
|
|
|
info_layout.addWidget(label)
|
|
|
|
|
|
info_layout.addWidget(value)
|
|
|
|
|
|
card_layout.addLayout(info_layout)
|
|
|
|
|
|
|
|
|
|
|
|
card_layout.addStretch()
|
|
|
|
|
|
self.scroll_layout.addWidget(info_card)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
def create_content_card(self):
|
|
|
|
|
|
"""创建公告内容卡片"""
|
|
|
|
|
|
content_card = CardWidget()
|
|
|
|
|
|
content_card.setObjectName("ContentCard")
|
|
|
|
|
|
|
|
|
|
|
|
card_layout = QVBoxLayout(content_card)
|
|
|
|
|
|
card_layout.setContentsMargins(16, 16, 16, 16)
|
|
|
|
|
|
card_layout.setSpacing(12)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加内容标题
|
|
|
|
|
|
content_title = SubtitleLabel("公告内容")
|
2025-09-22 22:24:56 +08:00
|
|
|
|
card_layout.addWidget(content_title)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-24 21:58:25 +08:00
|
|
|
|
# 添加内容文本框,支持Markdown渲染和链接点击
|
|
|
|
|
|
from PyQt5.QtWidgets import QTextBrowser
|
|
|
|
|
|
self.content_text = QTextBrowser()
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.content_text.setHtml(self.content_html)
|
|
|
|
|
|
self.content_text.setReadOnly(True)
|
2025-09-22 22:24:56 +08:00
|
|
|
|
self.content_text.setMinimumHeight(250)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.content_text.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
|
|
|
|
self.content_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
2025-09-24 21:58:25 +08:00
|
|
|
|
self.content_text.setOpenExternalLinks(False) # 禁用自动打开外部链接,由我们自己处理
|
|
|
|
|
|
|
|
|
|
|
|
# QTextBrowser组件有anchorClicked信号,可以连接到处理函数
|
|
|
|
|
|
self.content_text.anchorClicked.connect(self.handle_link_clicked)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
# 设置Fluent风格样式
|
|
|
|
|
|
self.content_text.setStyleSheet("""
|
|
|
|
|
|
TextEdit {
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
card_layout.addWidget(self.content_text)
|
|
|
|
|
|
self.scroll_layout.addWidget(content_card)
|
|
|
|
|
|
|
2025-09-24 21:58:25 +08:00
|
|
|
|
def handle_link_clicked(self, url):
|
|
|
|
|
|
"""处理链接点击事件,使用系统默认浏览器打开外部链接"""
|
|
|
|
|
|
from PyQt5.QtCore import QUrl
|
|
|
|
|
|
from PyQt5.QtGui import QDesktopServices
|
|
|
|
|
|
|
|
|
|
|
|
# 检查URL是否为http或https协议
|
|
|
|
|
|
if url.scheme() in ['http', 'https']:
|
|
|
|
|
|
# 使用系统默认浏览器打开链接
|
|
|
|
|
|
QDesktopServices.openUrl(url)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 处理特殊的leonapp链接
|
|
|
|
|
|
elif url.toString().startswith('leonapp://'):
|
|
|
|
|
|
# 提取应用ID或其他参数
|
|
|
|
|
|
# 示例: leonapp://app/123
|
|
|
|
|
|
path = url.toString()[10:] # 移除 'leonapp://'
|
|
|
|
|
|
if path.startswith('app/'):
|
|
|
|
|
|
app_id = path[4:] # 提取应用ID
|
|
|
|
|
|
# 这里可以实现打开特定应用详情的逻辑
|
|
|
|
|
|
from app_detail_window import AppDetailWindow
|
|
|
|
|
|
self.app_detail_window = AppDetailWindow(self.api_client, app_id, parent=self)
|
|
|
|
|
|
self.app_detail_window.show()
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 显示不支持的链接类型提示
|
|
|
|
|
|
InfoBar.warning(
|
|
|
|
|
|
title="不支持的链接类型",
|
|
|
|
|
|
content=f"无法打开链接: {url.toString()}",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=3000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-22 22:24:56 +08:00
|
|
|
|
def create_close_button(self, parent_layout):
|
|
|
|
|
|
"""创建关闭按钮"""
|
|
|
|
|
|
button_card = SimpleCardWidget()
|
|
|
|
|
|
button_card.setObjectName("ButtonCard")
|
|
|
|
|
|
button_card.setMinimumHeight(80)
|
|
|
|
|
|
|
|
|
|
|
|
button_layout = QHBoxLayout(button_card)
|
|
|
|
|
|
button_layout.setContentsMargins(16, 16, 16, 16)
|
|
|
|
|
|
button_layout.addStretch()
|
|
|
|
|
|
|
2025-09-24 21:58:25 +08:00
|
|
|
|
# 关闭按钮
|
2025-09-21 21:26:55 +08:00
|
|
|
|
close_button = PushButton("关闭")
|
2025-09-22 22:24:56 +08:00
|
|
|
|
close_button.setFixedWidth(100)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
close_button.clicked.connect(self.close)
|
2025-09-22 22:24:56 +08:00
|
|
|
|
button_layout.addWidget(close_button)
|
|
|
|
|
|
|
|
|
|
|
|
parent_layout.addWidget(button_card)
|
2025-09-24 21:58:25 +08:00
|
|
|
|
|
|
|
|
|
|
def copy_to_clipboard(self, text):
|
|
|
|
|
|
"""复制文本到剪贴板"""
|
|
|
|
|
|
from qfluentwidgets import InfoBar
|
|
|
|
|
|
clipboard = QApplication.clipboard()
|
|
|
|
|
|
clipboard.setText(text)
|
|
|
|
|
|
InfoBar.success(
|
|
|
|
|
|
title="复制成功",
|
|
|
|
|
|
content="链接已复制到剪贴板!",
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.TOP,
|
|
|
|
|
|
duration=2000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
class DeveloperInfoWindow(QMainWindow):
|
|
|
|
|
|
"""开发者信息窗口"""
|
|
|
|
|
|
def __init__(self, api_client, developer_id, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.developer_id = developer_id
|
|
|
|
|
|
self.setWindowTitle(f"开发者信息")
|
|
|
|
|
|
self.resize(600, 400)
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_developer_info()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建中心部件
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("开发者信息")
|
|
|
|
|
|
main_layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建信息卡片
|
|
|
|
|
|
self.info_card = CardWidget()
|
|
|
|
|
|
self.info_layout = QVBoxLayout(self.info_card)
|
|
|
|
|
|
main_layout.addWidget(self.info_card)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
def load_developer_info(self):
|
|
|
|
|
|
"""加载开发者信息"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(self.api_client, 'getdeveloperinfo', {'id': self.developer_id})
|
|
|
|
|
|
self.worker.finished.connect(self.on_developer_info_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_developer_info_loaded(self, data):
|
|
|
|
|
|
"""开发者信息加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data:
|
|
|
|
|
|
self.show_error("开发者信息加载失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空之前的信息
|
|
|
|
|
|
while self.info_layout.count():
|
|
|
|
|
|
item = self.info_layout.takeAt(0)
|
|
|
|
|
|
widget = item.widget()
|
|
|
|
|
|
if widget:
|
|
|
|
|
|
widget.deleteLater()
|
|
|
|
|
|
|
|
|
|
|
|
# 添加开发者信息
|
|
|
|
|
|
info_items = [
|
|
|
|
|
|
("ID", data.get('id', '--')),
|
|
|
|
|
|
("用户名", data.get('username', '--')),
|
|
|
|
|
|
("注册时间", data.get('created_at', '--')),
|
|
|
|
|
|
("是否验证", "是" if data.get('is_verified', False) else "否"),
|
|
|
|
|
|
("应用数量", data.get('app_count', 0))
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for label_text, value_text in info_items:
|
|
|
|
|
|
label = CaptionLabel(f"{label_text}: {value_text}")
|
|
|
|
|
|
self.info_layout.addWidget(label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加验证时间(如果已验证)
|
|
|
|
|
|
if data.get('is_verified', False) and data.get('verified_at'):
|
|
|
|
|
|
verified_at_label = CaptionLabel(f"验证时间: {data.get('verified_at')}")
|
|
|
|
|
|
self.info_layout.addWidget(verified_at_label)
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-21 17:41:55 +08:00
|
|
|
|
class AppVersionsWindow(QMainWindow):
|
|
|
|
|
|
"""应用版本列表窗口"""
|
|
|
|
|
|
def __init__(self, api_client, app_id, app_name, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.api_client = api_client
|
|
|
|
|
|
self.app_id = app_id
|
|
|
|
|
|
self.app_name = app_name
|
|
|
|
|
|
self.setWindowTitle(f"{app_name} - 版本历史")
|
|
|
|
|
|
self.resize(800, 600)
|
|
|
|
|
|
self.current_page = 1
|
|
|
|
|
|
self.items_per_page = 20
|
|
|
|
|
|
self.total_pages = 1
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
self.load_versions()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建中心部件
|
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel(f"{self.app_name} - 版本历史")
|
|
|
|
|
|
main_layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建版本列表
|
|
|
|
|
|
self.versions_table = TableWidget()
|
2025-09-21 18:17:37 +08:00
|
|
|
|
self.versions_table.setColumnCount(6)
|
|
|
|
|
|
self.versions_table.setHorizontalHeaderLabels(["版本号", "发布日期", "操作系统", "文件大小", "下载量", "操作"])
|
2025-09-21 17:41:55 +08:00
|
|
|
|
main_layout.addWidget(self.versions_table)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建分页控件
|
|
|
|
|
|
pagination_layout = QHBoxLayout()
|
|
|
|
|
|
self.prev_button = PushButton("上一页")
|
|
|
|
|
|
self.prev_button.clicked.connect(self.prev_page)
|
|
|
|
|
|
self.page_label = CaptionLabel("第 1 页,共 1 页")
|
|
|
|
|
|
self.next_button = PushButton("下一页")
|
|
|
|
|
|
self.next_button.clicked.connect(self.next_page)
|
|
|
|
|
|
|
|
|
|
|
|
pagination_layout.addWidget(self.prev_button)
|
|
|
|
|
|
pagination_layout.addStretch()
|
|
|
|
|
|
pagination_layout.addWidget(self.page_label)
|
|
|
|
|
|
pagination_layout.addStretch()
|
|
|
|
|
|
pagination_layout.addWidget(self.next_button)
|
|
|
|
|
|
main_layout.addLayout(pagination_layout)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建进度条
|
|
|
|
|
|
self.progress_bar = ProgressBar()
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
main_layout.addWidget(self.progress_bar)
|
|
|
|
|
|
|
|
|
|
|
|
def load_versions(self):
|
|
|
|
|
|
"""加载版本列表"""
|
|
|
|
|
|
self.show_progress()
|
|
|
|
|
|
self.worker = WorkerThread(self.api_client, 'getappversions',
|
|
|
|
|
|
{'id': self.app_id,
|
|
|
|
|
|
'page': self.current_page,
|
|
|
|
|
|
'limit': self.items_per_page})
|
|
|
|
|
|
self.worker.finished.connect(self.on_versions_loaded)
|
|
|
|
|
|
self.worker.progress.connect(self.update_progress)
|
|
|
|
|
|
self.worker.error.connect(self.show_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_versions_loaded(self, data):
|
|
|
|
|
|
"""版本列表加载完成处理"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
|
|
|
|
|
|
if not data:
|
|
|
|
|
|
self.show_error("版本列表加载失败")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 清空表格
|
|
|
|
|
|
self.versions_table.setRowCount(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新分页信息
|
|
|
|
|
|
pagination = data.get('pagination', {})
|
|
|
|
|
|
self.total_pages = pagination.get('totalPages', 1)
|
|
|
|
|
|
self.page_label.setText(f"第 {self.current_page} 页,共 {self.total_pages} 页")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新按钮状态
|
|
|
|
|
|
self.prev_button.setEnabled(self.current_page > 1)
|
|
|
|
|
|
self.next_button.setEnabled(self.current_page < self.total_pages)
|
|
|
|
|
|
|
|
|
|
|
|
# 填充表格
|
|
|
|
|
|
versions = data.get('versions', [])
|
|
|
|
|
|
for version in versions:
|
|
|
|
|
|
row_position = self.versions_table.rowCount()
|
|
|
|
|
|
self.versions_table.insertRow(row_position)
|
|
|
|
|
|
|
2025-09-21 18:17:37 +08:00
|
|
|
|
# 添加版本数据并存储版本ID和文件路径
|
|
|
|
|
|
version_item = QTableWidgetItem(version.get('version', '未知'))
|
|
|
|
|
|
# 存储版本ID和文件路径信息
|
|
|
|
|
|
version_item.version_id = version.get('id', '')
|
|
|
|
|
|
version_item.file_path = version.get('file_path', '')
|
|
|
|
|
|
self.versions_table.setItem(row_position, 0, version_item)
|
|
|
|
|
|
|
2025-09-21 17:41:55 +08:00
|
|
|
|
self.versions_table.setItem(row_position, 1, QTableWidgetItem(version.get('created_at', '未知')))
|
|
|
|
|
|
self.versions_table.setItem(row_position, 2, QTableWidgetItem(version.get('platform', '未知')))
|
|
|
|
|
|
self.versions_table.setItem(row_position, 3, QTableWidgetItem(version.get('file_size', '未知')))
|
|
|
|
|
|
self.versions_table.setItem(row_position, 4, QTableWidgetItem(str(version.get('download_count', 0))))
|
2025-09-21 18:17:37 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加下载按钮
|
|
|
|
|
|
download_button = PushButton("下载")
|
|
|
|
|
|
download_button.setIcon(FluentIcon.DOWNLOAD)
|
|
|
|
|
|
# 将版本ID绑定到按钮上
|
|
|
|
|
|
version_id = version.get('id', '')
|
|
|
|
|
|
download_button.clicked.connect(lambda checked, vid=version_id: self.download_version(vid))
|
|
|
|
|
|
|
|
|
|
|
|
# 创建按钮容器并添加按钮
|
|
|
|
|
|
button_container = QWidget()
|
|
|
|
|
|
button_layout = QHBoxLayout(button_container)
|
|
|
|
|
|
button_layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
|
button_layout.addWidget(download_button)
|
|
|
|
|
|
button_container.setLayout(button_layout)
|
|
|
|
|
|
|
|
|
|
|
|
self.versions_table.setCellWidget(row_position, 5, button_container)
|
2025-09-21 17:41:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 自动调整列宽
|
|
|
|
|
|
self.versions_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
|
|
|
|
|
|
|
|
|
|
def prev_page(self):
|
|
|
|
|
|
"""上一页"""
|
|
|
|
|
|
if self.current_page > 1:
|
|
|
|
|
|
self.current_page -= 1
|
|
|
|
|
|
self.load_versions()
|
|
|
|
|
|
|
|
|
|
|
|
def next_page(self):
|
|
|
|
|
|
"""下一页"""
|
|
|
|
|
|
if self.current_page < self.total_pages:
|
|
|
|
|
|
self.current_page += 1
|
|
|
|
|
|
self.load_versions()
|
|
|
|
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
|
|
"""显示进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(True)
|
|
|
|
|
|
self.progress_bar.setValue(0)
|
|
|
|
|
|
|
|
|
|
|
|
def update_progress(self, value):
|
|
|
|
|
|
"""更新进度条"""
|
|
|
|
|
|
self.progress_bar.setValue(value)
|
|
|
|
|
|
|
|
|
|
|
|
def hide_progress(self):
|
|
|
|
|
|
"""隐藏进度条"""
|
|
|
|
|
|
self.progress_bar.setVisible(False)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
self.hide_progress()
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
class AppInfoTab(QWidget):
|
|
|
|
|
|
"""APP信息标签页"""
|
2025-09-23 20:33:28 +08:00
|
|
|
|
def __init__(self, api_client=None, parent=None):
|
2025-09-21 21:26:55 +08:00
|
|
|
|
super().__init__(parent)
|
2025-09-23 20:33:28 +08:00
|
|
|
|
self.api_client = api_client
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建主布局
|
|
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建标题
|
|
|
|
|
|
title = TitleLabel("应用信息")
|
|
|
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建信息卡片
|
|
|
|
|
|
card = CardWidget()
|
|
|
|
|
|
card_layout = QVBoxLayout(card)
|
|
|
|
|
|
card_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
2025-09-23 20:33:28 +08:00
|
|
|
|
# 添加版本号信息 - 使用PrimaryPushSettingCard
|
|
|
|
|
|
self.version_card = PrimaryPushSettingCard(
|
|
|
|
|
|
title="版本号",
|
|
|
|
|
|
text="检查更新",
|
|
|
|
|
|
content="目前APP版本号:" + APP_VERSION,
|
|
|
|
|
|
icon=FluentIcon.INFO,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
self.version_card.clicked.connect(self.check_update)
|
|
|
|
|
|
card_layout.addWidget(self.version_card)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加描述信息
|
|
|
|
|
|
description_label = CaptionLabel("这是LeonApp应用商店的PC端管理工具,用于查看和管理应用、标签和开发者信息。")
|
|
|
|
|
|
description_label.setWordWrap(True)
|
2025-09-23 20:33:28 +08:00
|
|
|
|
description_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
description_label.setMinimumHeight(60)
|
|
|
|
|
|
card_layout.addWidget(description_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加分隔符
|
|
|
|
|
|
card_layout.addSpacing(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加QFluentDesign版权信息
|
|
|
|
|
|
qfluentdesign_label = CaptionLabel("界面设计基于QFluentWidgets - PyQt5的Fluent Design风格组件库")
|
|
|
|
|
|
qfluentdesign_label.setWordWrap(True)
|
|
|
|
|
|
qfluentdesign_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
card_layout.addWidget(qfluentdesign_label)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加GPLv3许可证信息
|
|
|
|
|
|
license_label = CaptionLabel("本应用采用GNU General Public License v3.0 (GPLv3) 许可协议发布")
|
|
|
|
|
|
license_label.setWordWrap(True)
|
|
|
|
|
|
license_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
card_layout.addWidget(license_label)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加信息卡片到主布局
|
|
|
|
|
|
layout.addWidget(card, alignment=Qt.AlignCenter)
|
|
|
|
|
|
|
|
|
|
|
|
# 填充空白,使内容居中
|
|
|
|
|
|
layout.addStretch()
|
2025-09-23 20:33:28 +08:00
|
|
|
|
|
|
|
|
|
|
def check_update(self):
|
|
|
|
|
|
"""检查更新功能"""
|
|
|
|
|
|
# 显示加载中的提示
|
|
|
|
|
|
self.show_info("正在检查更新...")
|
|
|
|
|
|
|
|
|
|
|
|
# 创建工作线程来异步获取版本信息
|
|
|
|
|
|
self.worker = WorkerThread(
|
|
|
|
|
|
self.api_client,
|
|
|
|
|
|
'getappversions',
|
|
|
|
|
|
{'id': 15} # LeonAPP的ID为15
|
|
|
|
|
|
)
|
|
|
|
|
|
self.worker.finished.connect(self.on_update_checked)
|
|
|
|
|
|
self.worker.error.connect(self.on_update_error)
|
|
|
|
|
|
self.worker.start()
|
|
|
|
|
|
|
|
|
|
|
|
def on_update_checked(self, data):
|
|
|
|
|
|
"""更新检查完成后的处理"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 检查API返回的数据格式
|
|
|
|
|
|
if not isinstance(data, dict) or 'versions' not in data:
|
|
|
|
|
|
self.show_error("获取版本信息失败:数据格式不正确")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
versions = data.get('versions', [])
|
|
|
|
|
|
if not versions:
|
|
|
|
|
|
self.show_error("未找到任何版本信息")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 最新版本通常在列表的第一个
|
|
|
|
|
|
latest_version = versions[0].get('version', '未知')
|
|
|
|
|
|
current_version = APP_VERSION
|
|
|
|
|
|
|
|
|
|
|
|
# 比较版本号
|
|
|
|
|
|
if latest_version != current_version:
|
|
|
|
|
|
# 版本不一致,显示更新提示
|
|
|
|
|
|
self.show_info(
|
|
|
|
|
|
f"发现新版本:{latest_version}",
|
|
|
|
|
|
f"当前版本:{current_version}\n建议前往应用商店下载最新版本。"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 已是最新版本
|
|
|
|
|
|
self.show_info("已是最新版本", f"当前版本 {current_version} 是最新版本")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.show_error(f"处理版本信息时发生错误:{str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_update_error(self, error_message):
|
|
|
|
|
|
"""处理更新检查时的错误"""
|
|
|
|
|
|
self.show_error(f"检查更新失败:{error_message}")
|
|
|
|
|
|
|
|
|
|
|
|
def show_info(self, title, content=None):
|
|
|
|
|
|
"""显示信息消息"""
|
|
|
|
|
|
if content is None:
|
|
|
|
|
|
content = ""
|
|
|
|
|
|
InfoBar.info(
|
|
|
|
|
|
title=title,
|
|
|
|
|
|
content=content,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def show_error(self, message):
|
|
|
|
|
|
"""显示错误消息"""
|
|
|
|
|
|
InfoBar.error(
|
|
|
|
|
|
title="错误",
|
|
|
|
|
|
content=message,
|
|
|
|
|
|
orient=Qt.Horizontal,
|
|
|
|
|
|
isClosable=True,
|
|
|
|
|
|
position=InfoBarPosition.BOTTOM_RIGHT,
|
|
|
|
|
|
duration=5000,
|
|
|
|
|
|
parent=self
|
|
|
|
|
|
)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
|
2025-09-23 20:33:28 +08:00
|
|
|
|
class LeonAppGUI(MSFluentWindow):
|
2025-09-20 22:20:08 +08:00
|
|
|
|
"""主应用窗口"""
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
# 初始化API客户端
|
|
|
|
|
|
self.api_client = APIClient()
|
|
|
|
|
|
# 设置窗口标题和大小
|
2025-09-21 18:17:37 +08:00
|
|
|
|
self.setWindowTitle("LeonApp For PC")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.resize(1000, 700)
|
2025-09-23 20:33:28 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置窗口图标
|
|
|
|
|
|
from PyQt5.QtGui import QIcon
|
|
|
|
|
|
import os
|
|
|
|
|
|
icon_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "icon.jpeg")
|
|
|
|
|
|
if os.path.exists(icon_path):
|
|
|
|
|
|
self.setWindowIcon(QIcon(icon_path))
|
|
|
|
|
|
|
|
|
|
|
|
# 启用亚克力效果
|
|
|
|
|
|
from qfluentwidgets import isDarkTheme
|
|
|
|
|
|
if hasattr(self, 'setAcrylicEffect'):
|
|
|
|
|
|
self.setAcrylicEffect(True)
|
|
|
|
|
|
elif hasattr(self, 'setWindowOpacity'):
|
|
|
|
|
|
# 如果没有直接的亚克力效果方法,设置窗口透明度作为替代
|
|
|
|
|
|
self.setWindowOpacity(0.95)
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 初始化UI
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化界面"""
|
|
|
|
|
|
# 创建各个标签页
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.homepage_tab = HomepageTab(self.api_client, self)
|
|
|
|
|
|
self.homepage_tab.setObjectName("homepage")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.app_tab = AppTab(self.api_client, self)
|
|
|
|
|
|
self.app_tab.setObjectName("app")
|
|
|
|
|
|
self.tag_tab = TagTab(self.api_client, self)
|
|
|
|
|
|
self.tag_tab.setObjectName("tag")
|
|
|
|
|
|
self.developer_tab = DeveloperTab(self.api_client, self)
|
|
|
|
|
|
self.developer_tab.setObjectName("developer")
|
|
|
|
|
|
self.announcement_tab = AnnouncementTab(self.api_client, self)
|
|
|
|
|
|
self.announcement_tab.setObjectName("announcement")
|
|
|
|
|
|
self.stats_tab = StatsTab(self.api_client, self)
|
|
|
|
|
|
self.stats_tab.setObjectName("stats")
|
|
|
|
|
|
|
|
|
|
|
|
# 添加子界面到主窗口
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.addSubInterface(self.homepage_tab, FluentIcon.HOME, "首页")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
self.addSubInterface(self.app_tab, FluentIcon.APPLICATION, "应用管理")
|
|
|
|
|
|
self.addSubInterface(self.tag_tab, FluentIcon.TAG, "标签管理")
|
|
|
|
|
|
self.addSubInterface(self.developer_tab, FluentIcon.DEVELOPER_TOOLS, "开发者管理")
|
|
|
|
|
|
self.addSubInterface(self.announcement_tab, FluentIcon.MEGAPHONE, "公告管理")
|
|
|
|
|
|
self.addSubInterface(self.stats_tab, FluentIcon.PIE_SINGLE, "统计信息")
|
|
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 添加APP信息标签页
|
2025-09-23 20:33:28 +08:00
|
|
|
|
self.info_tab = AppInfoTab(self.api_client, self)
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.info_tab.setObjectName("info")
|
|
|
|
|
|
self.addSubInterface(self.info_tab, FluentIcon.INFO, "应用信息")
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
# 设置默认选中的标签页
|
2025-09-21 21:26:55 +08:00
|
|
|
|
self.navigationInterface.setCurrentItem("homepage")
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
# 构建状态栏
|
|
|
|
|
|
# self.init_status_bar()
|
|
|
|
|
|
|
|
|
|
|
|
def show_app_detail(self, app_id):
|
|
|
|
|
|
"""显示应用详情"""
|
|
|
|
|
|
detail_window = AppDetailWindow(self.api_client, app_id, self)
|
|
|
|
|
|
detail_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_tag_apps(self, tag_id, tag_name):
|
|
|
|
|
|
"""显示标签下的应用"""
|
|
|
|
|
|
tag_apps_window = TagAppsWindow(self.api_client, tag_id, tag_name, self)
|
|
|
|
|
|
tag_apps_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_developer_apps(self, developer_id):
|
|
|
|
|
|
"""显示开发者的应用"""
|
|
|
|
|
|
developer_apps_window = DeveloperAppsWindow(self.api_client, developer_id, self)
|
|
|
|
|
|
developer_apps_window.show()
|
|
|
|
|
|
|
|
|
|
|
|
def show_developer_info(self, developer_id):
|
|
|
|
|
|
"""显示开发者信息"""
|
|
|
|
|
|
developer_info_window = DeveloperInfoWindow(self.api_client, developer_id, self)
|
|
|
|
|
|
developer_info_window.show()
|
2025-09-21 17:41:55 +08:00
|
|
|
|
|
|
|
|
|
|
def show_app_versions(self, app_id, app_name):
|
|
|
|
|
|
"""显示应用的版本列表"""
|
|
|
|
|
|
versions_window = AppVersionsWindow(self.api_client, app_id, app_name, self)
|
|
|
|
|
|
versions_window.show()
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
def show_error_dialog(title, message):
|
|
|
|
|
|
"""显示错误弹窗"""
|
|
|
|
|
|
from PyQt5.QtWidgets import QMessageBox
|
|
|
|
|
|
from PyQt5.QtGui import QFont
|
|
|
|
|
|
|
|
|
|
|
|
# 创建错误消息框,模拟Sweet Alert风格
|
|
|
|
|
|
msg_box = QMessageBox()
|
|
|
|
|
|
msg_box.setIcon(QMessageBox.Critical)
|
|
|
|
|
|
msg_box.setWindowTitle(title)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 设置字体大小
|
|
|
|
|
|
font = QFont()
|
|
|
|
|
|
font.setPointSize(10)
|
|
|
|
|
|
msg_box.setFont(font)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 设置消息内容
|
|
|
|
|
|
msg_box.setText(message)
|
|
|
|
|
|
msg_box.setStandardButtons(QMessageBox.Ok)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-21 21:26:55 +08:00
|
|
|
|
# 显示弹窗
|
|
|
|
|
|
msg_box.exec_()
|
|
|
|
|
|
|
|
|
|
|
|
def log_error(error_message):
|
|
|
|
|
|
"""记录错误日志到文件"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建logs目录(如果不存在)
|
|
|
|
|
|
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
|
|
|
|
|
|
if not os.path.exists(log_dir):
|
|
|
|
|
|
os.makedirs(log_dir)
|
|
|
|
|
|
|
|
|
|
|
|
# 生成日志文件名(使用当前日期)
|
|
|
|
|
|
today = datetime.date.today().strftime("%Y-%m-%d")
|
|
|
|
|
|
log_file = os.path.join(log_dir, f"error_{today}.log")
|
|
|
|
|
|
|
|
|
|
|
|
# 写入日志
|
|
|
|
|
|
with open(log_file, "a", encoding="utf-8") as f:
|
|
|
|
|
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
f.write(f"[{timestamp}]\n{error_message}\n\n")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
# 如果日志记录失败,不影响程序运行
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
"""主函数"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 创建应用实例
|
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加中文支持
|
|
|
|
|
|
translator = FluentTranslator()
|
|
|
|
|
|
app.installTranslator(translator)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建并显示主窗口
|
|
|
|
|
|
window = LeonAppGUI()
|
|
|
|
|
|
window.show()
|
|
|
|
|
|
|
|
|
|
|
|
# 运行应用
|
|
|
|
|
|
sys.exit(app.exec_())
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 获取完整的错误堆栈
|
|
|
|
|
|
error_traceback = traceback.format_exc()
|
|
|
|
|
|
|
|
|
|
|
|
# 记录错误日志
|
|
|
|
|
|
log_error(error_traceback)
|
|
|
|
|
|
|
|
|
|
|
|
# 显示错误弹窗
|
|
|
|
|
|
error_msg = f"程序崩溃了!错误信息:\n{e}\n\n详细日志已保存到logs目录。"
|
|
|
|
|
|
show_error_dialog("程序崩溃", error_msg)
|
|
|
|
|
|
|
|
|
|
|
|
# 退出程序
|
|
|
|
|
|
sys.exit(1)
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|