Files
Sunset-Music-ForPC/Beta-0.1.1.py
2025-09-25 12:47:28 +00:00

284 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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