Files
Sunset-Music-ForPC/Beta-0.1.1.py

284 lines
11 KiB
Python
Raw Normal View History

2025-09-25 12:47:28 +00:00
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_())