284 lines
11 KiB
Python
284 lines
11 KiB
Python
import sys
|
||
import requests
|
||
from PyQt5.QtWidgets import (
|
||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||
QTreeWidget, QTreeWidgetItem, QLineEdit, QLabel, QComboBox,
|
||
QPushButton, QSplitter, QTabWidget, QTextEdit, QMessageBox
|
||
)
|
||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QThread
|
||
from PyQt5.QtGui import QColor, QBrush
|
||
|
||
# --- 配置区 ---
|
||
# 你的 API 基础地址
|
||
BASE_URL = "https://shanwogou.cn/audio/"
|
||
API_URL = BASE_URL + "api.php"
|
||
USER_API_URL = BASE_URL + "user_info_api.php"
|
||
PLAY_URL_TEMPLATE = BASE_URL + "play.php?play={music_id}"
|
||
|
||
# --- API 交互模块 ---
|
||
def fetch_api_data(url, params=None):
|
||
"""通用函数,用于发送 GET 请求并解析 JSON 响应"""
|
||
try:
|
||
response = requests.get(url, params=params, timeout=15)
|
||
response.raise_for_status()
|
||
return response.json()
|
||
except requests.exceptions.RequestException as e:
|
||
print(f"API 请求失败: {e}")
|
||
return None
|
||
|
||
def get_all_music():
|
||
"""获取所有音乐"""
|
||
return fetch_api_data(API_URL, params={"action": "getAllMusic"})
|
||
|
||
def get_music_tags():
|
||
"""获取所有音乐标签"""
|
||
return fetch_api_data(API_URL, params={"action": "getMusicTags"})
|
||
|
||
def get_announcements():
|
||
"""获取公告"""
|
||
return fetch_api_data(API_URL, params={"action": "getAnnouncements"})
|
||
|
||
# --- 后台数据加载线程 ---
|
||
class DataLoaderThread(QThread):
|
||
"""后台线程,用于加载数据,防止UI卡顿"""
|
||
music_loaded_signal = pyqtSignal(dict)
|
||
tags_loaded_signal = pyqtSignal(dict)
|
||
announcements_loaded_signal = pyqtSignal(dict)
|
||
|
||
def run(self):
|
||
"""线程执行函数"""
|
||
# 顺序加载数据
|
||
music_data = get_all_music()
|
||
self.music_loaded_signal.emit(music_data)
|
||
|
||
tags_data = get_music_tags()
|
||
self.tags_loaded_signal.emit(tags_data)
|
||
|
||
announcements_data = get_announcements()
|
||
self.announcements_loaded_signal.emit(announcements_data)
|
||
|
||
# --- 主窗口类 ---
|
||
class SunsetMusicApp(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle("落日音乐 for PC")
|
||
self.setGeometry(100, 100, 1200, 700)
|
||
|
||
# 数据存储
|
||
self.all_music = []
|
||
self.filtered_music = []
|
||
|
||
# 初始化过滤条件属性(修复错误的关键)
|
||
self.current_keyword = ""
|
||
self.current_tag = "所有标签"
|
||
|
||
self._init_ui()
|
||
self._load_data_in_background()
|
||
|
||
def _init_ui(self):
|
||
"""初始化用户界面"""
|
||
central_widget = QWidget()
|
||
self.setCentralWidget(central_widget)
|
||
main_layout = QVBoxLayout(central_widget)
|
||
|
||
# 1. 顶部控制区
|
||
top_control_layout = QHBoxLayout()
|
||
|
||
# 搜索框
|
||
self.search_input = QLineEdit()
|
||
self.search_input.setPlaceholderText("输入关键词搜索...")
|
||
self.search_input.textChanged.connect(self._filter_music_by_search)
|
||
top_control_layout.addWidget(self.search_input)
|
||
|
||
# 标签筛选下拉框
|
||
self.tag_combo = QComboBox()
|
||
self.tag_combo.addItem("所有标签")
|
||
self.tag_combo.currentIndexChanged.connect(self._filter_music_by_tag)
|
||
top_control_layout.addWidget(self.tag_combo)
|
||
|
||
# 刷新按钮
|
||
refresh_button = QPushButton("刷新列表")
|
||
refresh_button.clicked.connect(self._load_data_in_background)
|
||
top_control_layout.addWidget(refresh_button)
|
||
|
||
main_layout.addLayout(top_control_layout)
|
||
|
||
# 2. 中部主内容区(分割窗口)
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
|
||
# 左侧音乐列表
|
||
self.music_tree = QTreeWidget()
|
||
self.music_tree.setColumnCount(4)
|
||
self.music_tree.setHeaderLabels(["ID", "标题", "艺术家", "标签"])
|
||
# 设置列宽
|
||
self.music_tree.setColumnWidth(0, 60)
|
||
self.music_tree.setColumnWidth(1, 350)
|
||
self.music_tree.setColumnWidth(2, 200)
|
||
self.music_tree.setColumnWidth(3, 120)
|
||
# 设置为单选
|
||
self.music_tree.setSelectionMode(QTreeWidget.SingleSelection)
|
||
self.music_tree.itemClicked.connect(self._on_music_item_clicked)
|
||
splitter.addWidget(self.music_tree)
|
||
|
||
# 右侧内容区(标签页)
|
||
self.right_tab_widget = QTabWidget()
|
||
|
||
# 播放页签
|
||
self.play_web_view = QWebEngineView()
|
||
# 初始加载一个占位页面
|
||
self.play_web_view.setUrl(QUrl("about:blank"))
|
||
self.right_tab_widget.addTab(self.play_web_view, "播放器")
|
||
|
||
# 公告页签
|
||
self.announcements_text = QTextEdit()
|
||
self.announcements_text.setReadOnly(True)
|
||
self.right_tab_widget.addTab(self.announcements_text, "公告")
|
||
|
||
splitter.addWidget(self.right_tab_widget)
|
||
# 设置拉伸比例,让右侧占更多空间
|
||
splitter.setSizes([600, 800])
|
||
|
||
main_layout.addWidget(splitter)
|
||
|
||
def _load_data_in_background(self):
|
||
"""启动后台线程加载数据"""
|
||
self._show_loading_indicator(True)
|
||
|
||
self.loader_thread = DataLoaderThread()
|
||
self.loader_thread.music_loaded_signal.connect(self._on_music_data_loaded)
|
||
self.loader_thread.tags_loaded_signal.connect(self._on_tags_data_loaded)
|
||
self.loader_thread.announcements_loaded_signal.connect(self._on_announcements_data_loaded)
|
||
self.loader_thread.finished.connect(lambda: self._show_loading_indicator(False))
|
||
self.loader_thread.start()
|
||
|
||
def _show_loading_indicator(self, is_loading):
|
||
"""显示或隐藏加载状态"""
|
||
if is_loading:
|
||
self.statusBar().showMessage("正在加载数据...")
|
||
self.setEnabled(False) # 禁用UI
|
||
else:
|
||
self.statusBar().clearMessage()
|
||
self.setEnabled(True) # 启用UI
|
||
|
||
def _on_music_data_loaded(self, data):
|
||
"""处理加载完成的音乐数据"""
|
||
if data and data.get("success", False):
|
||
self.all_music = data.get("data", [])
|
||
self.filtered_music = self.all_music.copy()
|
||
self._populate_music_tree()
|
||
print(f"成功加载 {len(self.all_music)} 首音乐。")
|
||
else:
|
||
QMessageBox.warning(self, "警告", "音乐数据加载失败!")
|
||
print("音乐数据加载失败。")
|
||
|
||
def _on_tags_data_loaded(self, data):
|
||
"""处理加载完成的标签数据"""
|
||
if data and data.get("success", False):
|
||
tags = data.get("data", [])
|
||
self.tag_combo.clear()
|
||
self.tag_combo.addItem("所有标签")
|
||
self.tag_combo.addItems(tags)
|
||
print(f"成功加载 {len(tags)} 个标签。")
|
||
else:
|
||
QMessageBox.warning(self, "警告", "标签数据加载失败!")
|
||
print("标签数据加载失败。")
|
||
|
||
def _on_announcements_data_loaded(self, data):
|
||
"""处理加载完成的公告数据"""
|
||
self.announcements_text.clear()
|
||
if data and data.get("success", False):
|
||
announcements = data.get("data", [])
|
||
for ann in announcements:
|
||
# 假设字段是 'nr' (内容) 和 'time' (时间)
|
||
content = ann.get("nr", "无内容")
|
||
time_str = ann.get("time", "未知时间")
|
||
self.announcements_text.append(f"【{time_str}】\n{content}\n")
|
||
print(f"成功加载 {len(announcements)} 条公告。")
|
||
else:
|
||
self.announcements_text.setText("公告加载失败或暂无公告。")
|
||
print("公告数据加载失败。")
|
||
|
||
def _populate_music_tree(self):
|
||
"""将音乐数据填充到 TreeWidget 中"""
|
||
self.music_tree.clear()
|
||
for music in self.filtered_music:
|
||
item = QTreeWidgetItem([
|
||
str(music.get("id", "")),
|
||
music.get("title", "未知标题"),
|
||
music.get("artist", "未知艺术家"),
|
||
music.get("category", "未知标签")
|
||
])
|
||
# 存储完整音乐对象,方便后续使用
|
||
item.setData(0, Qt.UserRole, music)
|
||
self.music_tree.addTopLevelItem(item)
|
||
|
||
def _filter_music_by_search(self):
|
||
"""根据搜索框内容过滤音乐列表"""
|
||
keyword = self.search_input.text().lower()
|
||
self._apply_filters(keyword=keyword)
|
||
|
||
def _filter_music_by_tag(self):
|
||
"""根据选择的标签过滤音乐列表"""
|
||
selected_tag = self.tag_combo.currentText()
|
||
self._apply_filters(tag=selected_tag)
|
||
|
||
def _apply_filters(self, keyword=None, tag=None):
|
||
"""应用所有过滤器(搜索词和标签)"""
|
||
# 使用类变量存储当前过滤条件
|
||
if keyword is not None:
|
||
self.current_keyword = keyword
|
||
if tag is not None:
|
||
self.current_tag = tag
|
||
|
||
filtered = [
|
||
music for music in self.all_music
|
||
if (self.current_tag == "所有标签" or music.get("category", "") == self.current_tag) and
|
||
(self.current_keyword == "" or self.current_keyword in music.get("title", "").lower() or
|
||
self.current_keyword in music.get("artist", "").lower() or
|
||
self.current_keyword in music.get("category", "").lower())
|
||
]
|
||
|
||
self.filtered_music = filtered
|
||
self._populate_music_tree()
|
||
self._highlight_search_results()
|
||
|
||
def _highlight_search_results(self):
|
||
"""高亮显示搜索结果中的关键词"""
|
||
keyword = self.current_keyword
|
||
if not keyword:
|
||
return
|
||
|
||
# 注意:QTreeWidget本身不直接支持HTML格式,这里我们使用简单的颜色标记方式
|
||
for i in range(self.music_tree.topLevelItemCount()):
|
||
item = self.music_tree.topLevelItem(i)
|
||
for col in range(item.columnCount()):
|
||
text = item.text(col)
|
||
if keyword.lower() in text.lower():
|
||
# 找到匹配项,设置背景色
|
||
item.setBackground(col, QBrush(QColor(255, 255, 153))) # 浅黄色背景
|
||
else:
|
||
# 没有匹配项,恢复默认背景
|
||
item.setBackground(col, QBrush(QColor(255, 255, 255)))
|
||
|
||
def _on_music_item_clicked(self, item, column):
|
||
"""当音乐列表项被点击时,在播放器中加载对应的播放页面"""
|
||
music = item.data(0, Qt.UserRole)
|
||
if not music:
|
||
return
|
||
|
||
music_id = music.get("id")
|
||
if music_id:
|
||
play_url = PLAY_URL_TEMPLATE.format(music_id=music_id)
|
||
self.play_web_view.setUrl(QUrl(play_url))
|
||
# 切换到播放器标签页
|
||
self.right_tab_widget.setCurrentIndex(0)
|
||
print(f"正在播放: {music.get('title')} URL: {play_url}")
|
||
|
||
# --- 程序入口 ---
|
||
if __name__ == '__main__':
|
||
app = QApplication(sys.argv)
|
||
window = SunsetMusicApp()
|
||
window.show()
|
||
sys.exit(app.exec_())
|