diff --git a/APP Store.zip b/APP Store.zip index f0b6be4..9c7c7be 100644 Binary files a/APP Store.zip and b/APP Store.zip differ diff --git a/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc b/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc index 37fca0e..4b88728 100644 Binary files a/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc and b/pyqt5fluentdesign/__pycache__/app_detail_window.cpython-312.pyc differ diff --git a/pyqt5fluentdesign/leonapp_gui.py b/pyqt5fluentdesign/leonapp_gui.py index c7e0449..3c0687f 100644 --- a/pyqt5fluentdesign/leonapp_gui.py +++ b/pyqt5fluentdesign/leonapp_gui.py @@ -5,13 +5,22 @@ LeonApp GUI - 基于PyQt5和Fluent Design的App Store API图形界面工具 """ +# APP版本号 +APP_VERSION = "Beta 0.2" + import sys import json import requests +import traceback +import os +import datetime +import markdown +from enum import Enum from PyQt5.QtWidgets import ( QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout, - QTableWidgetItem + QTableWidgetItem, QTableWidget, QTextEdit, QFrame, QHeaderView ) +from PyQt5.QtGui import QTextOption from PyQt5.QtCore import Qt, pyqtSignal, QThread from qfluentwidgets import ( CardWidget, TitleLabel, SubtitleLabel, CaptionLabel, PushButton, @@ -79,6 +88,248 @@ class WorkerThread(QThread): except Exception as e: self.error.emit(f"执行错误: {str(e)}") +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) + + # 创建最新应用表格 + self.latest_apps_table = TableWidget() + self.latest_apps_table.setColumnCount(4) + self.latest_apps_table.setHorizontalHeaderLabels(["ID", "应用名称", "版本", "添加时间"]) + self.latest_apps_table.horizontalHeader().setSectionResizeMode(1, 3) + self.latest_apps_table.cellDoubleClicked.connect(self.show_app_detail) + self.latest_apps_table.setMaximumHeight(250) + apps_layout.addWidget(self.latest_apps_table) + + + + 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) + + # 创建滚动区域放置公告列表 + scroll_area = ScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setWidget(self.latest_announcements_list) + scroll_area.setMaximumHeight(250) + 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): + """最新应用加载完成处理""" + # 清空表格 + self.latest_apps_table.setRowCount(0) + + if data and isinstance(data, dict) and 'apps' in data and isinstance(data['apps'], list): + # 填充表格 + for app in data['apps']: + if not isinstance(app, dict): + continue + + row_pos = self.latest_apps_table.rowCount() + self.latest_apps_table.insertRow(row_pos) + + self.latest_apps_table.setItem(row_pos, 0, QTableWidgetItem(str(app.get('id', '')))) + self.latest_apps_table.setItem(row_pos, 1, QTableWidgetItem(app.get('name', ''))) + self.latest_apps_table.setItem(row_pos, 2, QTableWidgetItem(app.get('version', ''))) + self.latest_apps_table.setItem(row_pos, 3, QTableWidgetItem(app.get('created_at', ''))) + + # 自动调整列宽 + self.latest_apps_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + + # 检查是否还有其他数据正在加载 + 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() + + def show_app_detail(self, row, column): + """显示应用详情""" + app_id = self.latest_apps_table.item(row, 0).text() + # 调用父窗口的show_app_detail方法 + if hasattr(self.parent(), 'show_app_detail'): + self.parent().show_app_detail(app_id) + + 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 + ) + class AppTab(QWidget): """应用列表标签页""" def __init__(self, api_client, parent=None): @@ -276,7 +527,14 @@ class TagTab(QWidget): def __init__(self, api_client, parent=None): super().__init__(parent) self.api_client = api_client + self.parent = parent + # 初始化分页相关变量 + self.current_page = 1 + self.items_per_page = 20 + self.total_pages = 1 self.init_ui() + # 加载所有标签 + self.load_tags() def init_ui(self): """初始化界面""" @@ -288,25 +546,67 @@ class TagTab(QWidget): title = TitleLabel("标签管理") layout.addWidget(title) + # 创建标签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) + # 创建标签列表 self.tag_list = TableWidget() - self.tag_list.setColumnCount(2) - self.tag_list.setHorizontalHeaderLabels(["ID", "标签名称"]) + self.tag_list.setColumnCount(3) + self.tag_list.setHorizontalHeaderLabels(["ID", "标签名称", "应用数量"]) + self.tag_list.horizontalHeader().setSectionResizeMode(1, 3) self.tag_list.cellDoubleClicked.connect(self.show_tag_apps) layout.addWidget(self.tag_list) + # 创建分页控制 + 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.progress_bar = ProgressBar() self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) - # 加载标签数据 - self.load_tags() - def load_tags(self): """加载标签数据""" self.show_progress() - self.worker = WorkerThread(self.api_client, 'getalltags') + self.worker = WorkerThread( + self.api_client, + 'getalltags', + {'page': self.current_page, 'limit': self.items_per_page} + ) self.worker.finished.connect(self.on_tags_loaded) self.worker.progress.connect(self.update_progress) self.worker.error.connect(self.show_error) @@ -316,29 +616,148 @@ class TagTab(QWidget): """标签数据加载完成处理""" self.hide_progress() - if not data: - self.show_error("数据加载失败") + # 增强的数据格式验证,提供更具体的错误信息 + 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__}") return # 清空表格 self.tag_list.setRowCount(0) - # 填充表格 - for tag in data: + # 填充表格,添加更健壮的数据处理 + 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 + row_pos = self.tag_list.rowCount() self.tag_list.insertRow(row_pos) - self.tag_list.setItem(row_pos, 0, QTableWidgetItem(str(tag.get('id', '')))) - self.tag_list.setItem(row_pos, 1, QTableWidgetItem(tag.get('name', ''))) + # 安全获取字段值,避免KeyError + tag_id = str(tag.get('id', '未知ID')) + tag_name = tag.get('name', '无名称') + app_count = str(tag.get('app_count', 0)) + + self.tag_list.setItem(row_pos, 0, QTableWidgetItem(tag_id)) + self.tag_list.setItem(row_pos, 1, QTableWidgetItem(tag_name)) + self.tag_list.setItem(row_pos, 2, QTableWidgetItem(app_count)) + # 更新分页信息,增加异常处理 + 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() + 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() - # 直接创建并显示标签应用窗口,而不是调用parent方法 + # 直接创建并显示标签应用窗口 tag_apps_window = TagAppsWindow(self.api_client, tag_id, tag_name, self) tag_apps_window.show() + 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("未找到该标签的信息") + def show_progress(self): """显示进度条""" self.progress_bar.setVisible(True) @@ -352,44 +771,6 @@ class TagTab(QWidget): """隐藏进度条""" self.progress_bar.setVisible(False) - def download_version(self, version_id): - """下载指定版本""" - import webbrowser - - # 查找对应版本的文件路径 - file_path = None - for i in range(self.versions_table.rowCount()): - # 这里假设我们在填充表格时存储了版本ID作为item的data - version_item = self.versions_table.item(i, 0) - if version_item and hasattr(version_item, 'version_id') and version_item.version_id == version_id: - # 假设我们在填充表格时存储了文件路径 - if hasattr(version_item, 'file_path'): - file_path = version_item.file_path - break - - if file_path: - # 构建直接的文件URL - download_url = f"http://leonmmcoset.jjxmm.win:8010/{file_path}" - - try: - # 使用系统默认浏览器打开下载链接 - webbrowser.open(download_url) - - # 显示下载成功提示 - InfoBar.success( - title="下载开始", - content=f"下载已开始,请稍候...", - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.BOTTOM_RIGHT, - duration=3000, - parent=self - ) - except Exception as e: - self.show_error(f"下载失败: {str(e)}") - else: - self.show_error("无法获取下载文件路径") - def show_error(self, message): """显示错误消息""" self.hide_progress() @@ -408,7 +789,14 @@ class DeveloperTab(QWidget): def __init__(self, api_client, parent=None): super().__init__(parent) self.api_client = api_client + self.parent = parent + # 初始化分页相关变量 + self.current_page = 1 + self.items_per_page = 20 + self.total_pages = 1 self.init_ui() + # 加载所有开发者 + self.load_developers() def init_ui(self): """初始化界面""" @@ -440,18 +828,165 @@ class DeveloperTab(QWidget): info_button.clicked.connect(self.show_developer_info) input_layout.addWidget(info_button) + input_layout.addSpacing(10) + + # 添加刷新按钮 + refresh_button = PushButton("刷新开发者列表") + refresh_button.clicked.connect(self.load_developers) + input_layout.addWidget(refresh_button) + layout.addLayout(input_layout) + # 创建开发者列表 + 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) + # 创建进度条 self.progress_bar = ProgressBar() self.progress_bar.setVisible(False) layout.addWidget(self.progress_bar) + 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) + def show_developer_apps(self): """查看开发者的应用列表""" developer_id = self.developer_id_input.text().strip() if developer_id: - # 直接创建并显示开发者应用窗口,而不是调用parent方法 + # 直接创建并显示开发者应用窗口 developer_apps_window = DeveloperAppsWindow(self.api_client, developer_id, self) developer_apps_window.show() else: @@ -468,6 +1003,50 @@ class DeveloperTab(QWidget): def show_developer_info(self): """查看开发者信息""" developer_id = self.developer_id_input.text().strip() + 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 + ) if developer_id: # 直接创建并显示开发者信息窗口,而不是调用parent方法 developer_info_window = DeveloperInfoWindow(self.api_client, developer_id, self) @@ -549,10 +1128,13 @@ class AnnouncementTab(QWidget): # 创建公告列表 self.announcement_list = TableWidget() - self.announcement_list.setColumnCount(4) - self.announcement_list.setHorizontalHeaderLabels(["ID", "标题", "发布时间", "管理员ID"]) + self.announcement_list.setColumnCount(3) + self.announcement_list.setHorizontalHeaderLabels(["ID", "标题", "发布时间"]) self.announcement_list.horizontalHeader().setSectionResizeMode(1, 3) self.announcement_list.cellDoubleClicked.connect(self.show_announcement_detail) + + # 设置表格为只读 + self.announcement_list.setEditTriggers(QTableWidget.NoEditTriggers) layout.addWidget(self.announcement_list) # 创建进度条 @@ -640,7 +1222,6 @@ class AnnouncementTab(QWidget): 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', '未知时间'))) - self.announcement_list.setItem(row_pos, 3, QTableWidgetItem(str(announcement.get('admin_id', '未知管理员')))) # 更新分页信息,增加异常处理 try: @@ -735,16 +1316,9 @@ class AnnouncementTab(QWidget): self.show_error(f"查找公告详情时发生错误: {str(e)}") return - # 使用Sweet Alert风格的弹窗显示公告详情 - InfoBar.info( - title="公告详情", - content=f"标题: {title}\n\n发布时间: {created_at}\n\n内容:\n{content}", - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.BOTTOM_RIGHT, - duration=10000, # 增加显示时间以便阅读 - parent=self - ) + # 创建并显示公告详情窗口 + detail_window = AnnouncementDetailWindow(self.api_client, announcement_id, title, created_at, content, self) + detail_window.show() def show_progress(self): """显示进度条""" @@ -978,8 +1552,24 @@ class AppDetailWindow(QMainWindow): description_label = SubtitleLabel("描述") self.info_layout.addWidget(description_label) - description_text = CaptionLabel(data.get('description', '暂无描述')) - description_text.setWordWrap(True) + # 使用QTextEdit替代CaptionLabel以支持富文本显示 + description_text = QTextEdit() + description_text.setReadOnly(True) + description_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) + description_text.setLineWrapMode(QTextEdit.WidgetWidth) + description_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + # 尝试将markdown格式的描述转换为HTML + app_description = data.get('description', '暂无描述') + try: + html_description = markdown.markdown(app_description) + description_text.setHtml(html_description) + except Exception: + # 如果markdown解析失败,使用纯文本 + description_text.setPlainText(app_description) + + # 设置文本框的高度 + description_text.setMinimumHeight(120) self.info_layout.addWidget(description_text) # 添加标签信息 @@ -1383,6 +1973,83 @@ class DeveloperAppsWindow(QMainWindow): parent=self ) +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 + + # 将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.setWindowTitle(f"公告详情 - {title}") + self.resize(600, 500) + 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) + + # 创建标题 + title_label = TitleLabel(f"{self.title}") + main_layout.addWidget(title_label) + + # 创建信息卡片 + self.info_card = CardWidget() + self.info_layout = QVBoxLayout(self.info_card) + main_layout.addWidget(self.info_card) + + # 添加公告信息 + info_items = [ + ("ID", self.announcement_id), + ("发布时间", self.created_at) + ] + + for label_text, value_text in info_items: + label = CaptionLabel(f"{label_text}: {value_text}") + self.info_layout.addWidget(label) + + # 添加分隔线 + separator = QWidget() + separator.setMinimumHeight(10) + self.info_layout.addWidget(separator) + + # 添加内容标题 + content_title = SubtitleLabel("公告内容") + self.info_layout.addWidget(content_title) + + # 添加内容文本框,支持Markdown渲染 + self.content_text = QTextEdit() + self.content_text.setHtml(self.content_html) + self.content_text.setReadOnly(True) + self.content_text.setMinimumHeight(200) + self.content_text.setLineWrapMode(QTextEdit.WidgetWidth) + self.content_text.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere) + self.info_layout.addWidget(self.content_text) + + # 添加关闭按钮 + button_layout = QHBoxLayout() + close_button = PushButton("关闭") + close_button.clicked.connect(self.close) + button_layout.addWidget(close_button, alignment=Qt.AlignCenter) + main_layout.addLayout(button_layout) + class DeveloperInfoWindow(QMainWindow): """开发者信息窗口""" def __init__(self, api_client, developer_id, parent=None): @@ -1446,7 +2113,6 @@ class DeveloperInfoWindow(QMainWindow): info_items = [ ("ID", data.get('id', '--')), ("用户名", data.get('username', '--')), - ("邮箱", data.get('email', '--')), ("注册时间", data.get('created_at', '--')), ("是否验证", "是" if data.get('is_verified', False) else "否"), ("应用数量", data.get('app_count', 0)) @@ -1649,6 +2315,42 @@ class AppVersionsWindow(QMainWindow): parent=self ) +class AppInfoTab(QWidget): + """APP信息标签页""" + def __init__(self, parent=None): + super().__init__(parent) + 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) + + # 添加版本号信息 + version_label = SubtitleLabel(f"版本号: {APP_VERSION}") + card_layout.addWidget(version_label, alignment=Qt.AlignCenter) + + # 添加描述信息 + description_label = CaptionLabel("这是LeonApp应用商店的PC端管理工具,用于查看和管理应用、标签和开发者信息。") + description_label.setWordWrap(True) + card_layout.addWidget(description_label, alignment=Qt.AlignCenter) + + # 添加信息卡片到主布局 + layout.addWidget(card, alignment=Qt.AlignCenter) + + # 填充空白,使内容居中 + layout.addStretch() + class LeonAppGUI(FluentWindow): """主应用窗口""" def __init__(self): @@ -1664,6 +2366,8 @@ class LeonAppGUI(FluentWindow): def init_ui(self): """初始化界面""" # 创建各个标签页 + self.homepage_tab = HomepageTab(self.api_client, self) + self.homepage_tab.setObjectName("homepage") self.app_tab = AppTab(self.api_client, self) self.app_tab.setObjectName("app") self.tag_tab = TagTab(self.api_client, self) @@ -1676,14 +2380,20 @@ class LeonAppGUI(FluentWindow): self.stats_tab.setObjectName("stats") # 添加子界面到主窗口 + self.addSubInterface(self.homepage_tab, FluentIcon.HOME, "首页") 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, "统计信息") + # 添加APP信息标签页 + self.info_tab = AppInfoTab(self) + self.info_tab.setObjectName("info") + self.addSubInterface(self.info_tab, FluentIcon.INFO, "应用信息") + # 设置默认选中的标签页 - self.navigationInterface.setCurrentItem("app") + self.navigationInterface.setCurrentItem("homepage") # 构建状态栏 # self.init_status_bar() @@ -1713,21 +2423,77 @@ class LeonAppGUI(FluentWindow): versions_window = AppVersionsWindow(self.api_client, app_id, app_name, self) versions_window.show() +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) + + # 设置字体大小 + font = QFont() + font.setPointSize(10) + msg_box.setFont(font) + + # 设置消息内容 + msg_box.setText(message) + msg_box.setStandardButtons(QMessageBox.Ok) + + # 显示弹窗 + 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(): """主函数""" - # 创建应用实例 - app = QApplication(sys.argv) - - # 添加中文支持 - translator = FluentTranslator() - app.installTranslator(translator) - - # 创建并显示主窗口 - window = LeonAppGUI() - window.show() - - # 运行应用 - sys.exit(app.exec_()) + 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) if __name__ == "__main__": main() \ No newline at end of file diff --git a/pyqt5fluentdesign/requirements.txt b/pyqt5fluentdesign/requirements.txt index 98e381d..60b272d 100644 --- a/pyqt5fluentdesign/requirements.txt +++ b/pyqt5fluentdesign/requirements.txt @@ -2,4 +2,5 @@ PyQt5==5.15.7 PyQt5-Qt5==5.15.2 PyQt5-sip==12.11.0 requests==2.31.0 -PyQt-Fluent-Widgets==1.8.7 \ No newline at end of file +PyQt-Fluent-Widgets==1.8.7 +markdown==3.5.2 \ No newline at end of file