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_())
|