diff --git a/README.md b/README.md index 2d8f669..a4baee5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -<<<<<<< HEAD # YwYMusic 🎵 一款基于 **Tauri 2 + Vue 3** 的轻量在线音乐播放器。前端负责界面与播放,音乐数据、音频直链解析、扫码登录等能力由独立的音乐聚合后端提供。 @@ -34,7 +33,7 @@ | 路由 | Vue Router(Hash 模式,兼容 Tauri `file://` 协议) | | 网络请求 | Axios(统一拦截器 + 业务码处理) | | 二维码 | `qrcode`(本地生成登录二维码) | -| 状态管理 | 轻量 `reactive` store(无 Pinia 依赖) | +| 状态管理 | Pinia + pinia-plugin-persistedstate(持久化设置与搜索历史) | | 工程化 | pnpm workspace、unplugin-auto-import、unplugin-vue-components | ## 📁 项目结构 @@ -46,13 +45,18 @@ YwYMusic │ ├─ components/ # 通用组件(PlayerBar、SongTable、MusicCard、QrLogin 等) │ ├─ layouts/ # 主布局:侧边栏导航 + 内容区 + 底部播放条 │ ├─ router/ # 路由定义 -│ ├─ stores/ # 轻量状态:player(播放器)、settings(音乐源设置) +│ ├─ stores/ # Pinia 状态:player(播放器)、settings(音乐源设置)、searchHistory(搜索历史) │ ├─ utils/ # 请求封装、封面防盗链代理、时间/数量格式化 │ ├─ views/ # 页面:发现 / 排行榜 / 我的音乐 / 搜索 / 歌单详情 / 设置 │ ├─ mocks/ # 本地兜底数据(排行榜、新歌、我的音乐) │ ├─ styles/ # 全局样式 │ └─ types/ # 全局类型与自动生成声明 ├─ src-tauri/ # Tauri(Rust)壳与打包配置 +│ ├─ src/ +│ │ ├─ commands/ # Tauri 命令(music_url 等) +│ │ ├─ services/ # HTTP 客户端(支持 GO_MUSIC_URL / METING_API_URL 切换) +│ │ └─ models.rs # 数据模型与 API 地址常量 +│ └─ Cargo.toml ├─ index.html └─ package.json ``` @@ -92,6 +96,25 @@ pnpm tauri build VITE_BASE_API_URL = "http://127.0.0.1:8080" ``` +Rust 后端同时配置了两个 API 地址常量(`src-tauri/src/models.rs`): + +```rust +pub const GO_MUSIC_URL: &str = "http://127.0.0.1:8080"; // 音乐聚合后端 +pub const METING_API_URL: &str = "http://127.0.0.1:81"; // Meting API +``` + +`HttpClient` 支持通过 `ApiEndpoint` 枚举在请求时选择目标地址: + +```rust +use crate::services::http::{ApiEndpoint, HttpClient}; + +// 默认请求 GO_MUSIC_URL +state.get("/api/v1/playlist/recommend", ¶ms).await?; + +// 显式请求 METING_API_URL +state.get_at(ApiEndpoint::Meting, "/api", ¶ms).await?; +``` + 请先启动后端服务,否则页面将回退到本地 mock 数据或提示网络异常。 ## 🔌 后端接口概览 @@ -107,6 +130,7 @@ VITE_BASE_API_URL = "http://127.0.0.1:8080" | 专辑 | `/api/v1/album/detail` | 专辑详情 | | 系统 | `/api/v1/system/qr_login/*` | 扫码登录会话与状态轮询 | | 系统 | `/api/v1/system/cookies` | 平台 Cookies 读取 / 写入 | +| Meting | `/api` | Meting API(通过 `music_url` 命令调用) | > 多源参数使用重复键形式传递,例如 `sources=kugou&sources=netease`。 diff --git a/app-icon.png b/app-icon.png new file mode 100644 index 0000000..13784fd Binary files /dev/null and b/app-icon.png differ diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 6be5e50..e09bc86 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e81bece..7439319 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a437dd5..9884097 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..163891a Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 0ca4f27..69e8653 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b81f820..e4e9f70 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 624c7bf..172033a 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index c021d2b..355ae8d 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 6219700..fd292ef 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index f9bc048..7c7622d 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d5fbfb2..d1a611b 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 63440d7..c4b2678 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index f3f705a..d4a2160 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 4556388..6ca54cd 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..7351d9b Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..73e420d Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..779a051 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..d240056 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5f01d37 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..52c4453 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..be8c912 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..5e5b877 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9c482a5 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..3875c6c Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..0671731 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..78b4d26 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..eb493a2 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..64a4294 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da24ee1 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 12a5bce..ac9c601 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index b3636e4..66e1801 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index e1cd261..923a926 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..5d30cb3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..73553a3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..73553a3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..a2e3276 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..dcf2ee9 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..3f24fd3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..3f24fd3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..f7b05ca Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..73553a3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..023ae85 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..023ae85 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..d603273 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..20aa6ae Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..d603273 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..0527cc2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..62e3265 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..1d77af9 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..547f269 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/src/commands/album.rs b/src-tauri/src/commands/album.rs index e69de29..9383b9a 100644 --- a/src-tauri/src/commands/album.rs +++ b/src-tauri/src/commands/album.rs @@ -0,0 +1,19 @@ +use crate::{ + models::ApiResponse, + services::http::{ApiEndpoint, HttpClient}, +}; +use tauri::State; +use tauri_helper::auto_collect_command; + +// 获取专辑详情 +#[tauri::command] +#[auto_collect_command] +pub async fn album_detail( + http: State<'_, HttpClient>, + id: String, + source: String, +) -> Result, String> { + let params = [("id".to_string(), id), ("source".to_string(), source)]; + let data = http.get_at(ApiEndpoint::GoMusic, "/api/v1/album/detail", ¶ms).await.map_err(|e| e.to_string())?; + Ok(ApiResponse::success(data)) +} \ No newline at end of file diff --git a/src-tauri/src/commands/music.rs b/src-tauri/src/commands/music.rs index 5473208..2a73810 100644 --- a/src-tauri/src/commands/music.rs +++ b/src-tauri/src/commands/music.rs @@ -7,26 +7,185 @@ use crate::{ services::http::{ApiEndpoint, HttpClient}, }; +// 代理请求并下载封面图 +#[tauri::command] +#[auto_collect_command] +pub async fn music_cover( + http: State<'_, HttpClient>, + url: String, + artist: String, +) -> Result, String> { + let params: Vec<(String, String)> = + vec![("url".to_string(), url), ("artist".to_string(), artist)]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/cover", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 探测音频大小与码率 +#[tauri::command] +#[auto_collect_command] +pub async fn music_inspect( + http: State<'_, HttpClient>, + id: String, + source: String, + duration: u32, +) -> Result, String> { + let params = vec![ + ("id".to_string(), id), + ("source".to_string(), source), + ("duration".to_string(), duration.to_string()), + ]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/inspect", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 获取 JSON 格式歌词 +#[tauri::command] +#[auto_collect_command] +pub async fn music_lyric( + http: State<'_, HttpClient>, + id: String, + source: String, +) -> Result, String> { + let params = vec![("id".to_string(), id), ("source".to_string(), source)]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/lyric", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 下载 LRC 歌词文件 +#[tauri::command] +#[auto_collect_command] +pub async fn music_lrc_file( + http: State<'_, HttpClient>, + id: String, + source: String, + name: String, + artist: String, +) -> Result, String> { + let params = vec![ + ("id".to_string(), id), + ("source".to_string(), source), + ("name".to_string(), name), + ("artist".to_string(), artist), + ]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/lyric/file", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 综合搜索与链接解析 +#[tauri::command] +#[auto_collect_command] +pub async fn music_search( + http: State<'_, HttpClient>, + keyword: String, + type_: String, + sources: Vec, +) -> Result, String> { + let mut params = vec![("q".to_string(), keyword), ("type".to_string(), type_)]; + // 追加动态 sources 参数 + for source in sources { + params.push(("source".to_string(), source)); + } + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/search", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 串流代理与下载音频 +#[tauri::command] +#[auto_collect_command] +pub async fn music_stream( + http: State<'_, HttpClient>, + id: String, + source: String, + name: String, + artist: String, +) -> Result, String> { + let params = vec![ + ("id".to_string(), id), + ("source".to_string(), source), + ("name".to_string(), name), + ("artist".to_string(), artist), + ]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/stream", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 智能切换可用的平替音源 +pub async fn music_switch( + http: State<'_, HttpClient>, + name: String, + artist: String, + source: String, + target: String, + duration: u32, +) -> Result, String> { + let params = vec![ + ("name".to_string(), name), + ("artist".to_string(), artist), + ("source".to_string(), source), + ("target".to_string(), target), + ("duration".to_string(), duration.to_string()), + ]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/switch", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +// 获取音频裸直链 #[tauri::command] #[auto_collect_command] pub async fn music_url( - state: State<'_, HttpClient>, + http: State<'_, HttpClient>, id: String, - server: String, - type_: String, + source: String, ) -> Result, String> { - let client = reqwest::Client::new(); - let params = vec![ - ("id".to_string(), id), - ("server".to_string(), server), - ("type".to_string(), type_), - ]; - let resp = client - .get(format!("{}/api", METING_API_URL)) - .query(¶ms) - .send() + let params = vec![("id".to_string(), id), ("source".to_string(), source)]; + let data = http + .get_at(ApiEndpoint::GoMusic, "/api/v1/music/url", ¶ms) .await .map_err(|e| e.to_string())?; - let data = resp.json().await.map_err(|e| e.to_string())?; Ok(ApiResponse::success(data)) } + +// #[tauri::command] +// #[auto_collect_command] +// pub async fn music_url( +// id: String, +// server: String, +// type_: String, +// ) -> Result, String> { +// let client = reqwest::Client::new(); +// let params = vec![ +// ("id".to_string(), id), +// ("server".to_string(), server), +// ("type".to_string(), type_), +// ]; +// let resp = client +// .get(format!("{}/api", METING_API_URL)) +// .query(¶ms) +// .send() +// .await +// .map_err(|e| e.to_string())?; +// let data = resp.json().await.map_err(|e| e.to_string())?; +// Ok(ApiResponse::success(data)) +// } diff --git a/src-tauri/src/commands/playlist.rs b/src-tauri/src/commands/playlist.rs index 01521a2..73bb171 100644 --- a/src-tauri/src/commands/playlist.rs +++ b/src-tauri/src/commands/playlist.rs @@ -3,6 +3,61 @@ use tauri_helper::auto_collect_command; use crate::{models::ApiResponse, services::http::HttpClient}; +#[tauri::command] +#[auto_collect_command] +pub async fn playlist_categories( + state: State<'_, HttpClient>, + sources: Vec, +) -> Result, String> { + let params: Vec<(String, String)> = sources + .iter() + .map(|source| ("sources".to_string(), source.clone())) + .collect(); + let data = state + .get("/api/v1/playlist/categories", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +#[tauri::command] +#[auto_collect_command] +pub async fn playlist_category_list( + state: State<'_, HttpClient>, + category_id: String, + source: String, + page: u32, + limit: u32, +) -> Result, String> { + let params: Vec<(String, String)> = vec![ + ("category_id".to_string(), category_id), + ("source".to_string(), source), + ("page".to_string(), page.to_string()), + ("limit".to_string(), limit.to_string()), + ]; + let data = state + .get("/api/v1/playlist/category", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + +#[tauri::command] +#[auto_collect_command] +pub async fn playlist_detail( + state: State<'_, HttpClient>, + id: String, + source: String, +) -> Result, String> { + let params: Vec<(String, String)> = + vec![("id".to_string(), id), ("source".to_string(), source)]; + let data = state + .get("/api/v1/playlist/detail", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} + #[tauri::command] #[auto_collect_command] pub async fn playlist_recommand( @@ -21,3 +76,23 @@ pub async fn playlist_recommand( Ok(ApiResponse::success(data?)) } + +#[tauri::command] +#[auto_collect_command] +pub async fn playlist_user( + state: State<'_, HttpClient>, + source: String, + page: u32, + limit: u32, +) -> Result, String> { + let params: Vec<(String, String)> = vec![ + ("source".to_string(), source), + ("page".to_string(), page.to_string()), + ("limit".to_string(), limit.to_string()), + ]; + let data = state + .get("/api/v1/playlist/user", ¶ms) + .await + .map_err(|e| e.to_string()); + Ok(ApiResponse::success(data?)) +} diff --git a/src/api/album.ts b/src/api/album.ts index 38d6b1c..e31a9ae 100644 --- a/src/api/album.ts +++ b/src/api/album.ts @@ -1,5 +1,5 @@ -import { get } from "@/utils/request" -import Source from "@/types/global" +import Source, { ApiResponse } from "@/types/global" +import { invoke } from "@tauri-apps/api/core" const AlbumApi = { /** @@ -10,7 +10,8 @@ const AlbumApi = { * @returns */ detail: (id: string, source: Source) => { - return get(`/api/v1/album/detail?id=${id}&source=${source}`) + // return get(`/api/v1/album/detail?id=${id}&source=${source}`) + return invoke>("album_detail", { id, source }) }, } diff --git a/src/api/music.ts b/src/api/music.ts index a36360e..7decfcc 100644 --- a/src/api/music.ts +++ b/src/api/music.ts @@ -1,4 +1,3 @@ -import { get } from "@/utils/request" import Source, { ApiResponse } from "@/types/global" import { invoke } from "@tauri-apps/api/core" @@ -72,7 +71,8 @@ const MusicApi = { * @returns */ cover: (url: string, name?: string, artist?: string) => { - return get(`/api/v1/music/cover?url=${url}&name=${name}&artist=${artist}`) + // return get(`/api/v1/music/cover?url=${url}&name=${name}&artist=${artist}`) + return invoke>("music_cover", { url, name, artist }) }, /** * 探测音频大小与码率 @@ -82,7 +82,8 @@ const MusicApi = { * @param duration 音乐时长(秒),提供可精确预估码率(kbps) */ inspect: (id: string, source: Source, duration?: string) => { - return get(`/api/v1/music/inspect?id=${id}&source=${source}&duration=${duration}`) + // return get(`/api/v1/music/inspect?id=${id}&source=${source}&duration=${duration}`) + return invoke>("music_inspect", { id, source, duration }) }, /** * 获取 JSON 格式歌词 @@ -92,7 +93,8 @@ const MusicApi = { * @returns */ lyric: (id: string, source: Source) => { - return get(`/api/v1/music/lyric?id=${id}&source=${source}`) + // return get(`/api/v1/music/lyric?id=${id}&source=${source}`) + return invoke>("music_lyric", { id, source }) }, /** * 下载 LRC 歌词文件 @@ -104,7 +106,8 @@ const MusicApi = { * @returns */ lyricFile: (id: string, source: Source, name?: string, artist?: string) => { - return get(`/api/v1/music/lyric/file?id=${id}&source=${source}&name=${name}&artist=${artist}`) + // return get(`/api/v1/music/lyric/file?id=${id}&source=${source}&name=${name}&artist=${artist}`) + return invoke>("music_lyric_file", { id, source, name, artist }) }, /** * 综合搜索与链接解析 @@ -115,8 +118,9 @@ const MusicApi = { * @returns */ search: (keyword: string, type: string, sources?: Source[]) => { - const sourceParams = (sources ?? []).map((s) => `&sources=${encodeURIComponent(s)}`).join("") - return get(`/api/v1/music/search?q=${encodeURIComponent(keyword)}&type=${type}${sourceParams}`) + // const sourceParams = (sources ?? []).map((s) => `&sources=${encodeURIComponent(s)}`).join("") + // return get(`/api/v1/music/search?q=${encodeURIComponent(keyword)}&type=${type}${sourceParams}`) + return invoke>("music_search", { keyword, type, sources }) }, /** * 串流代理与下载音频 @@ -128,7 +132,8 @@ const MusicApi = { * @returns */ stream: (id: string, source: Source, name?: string, artist?: string) => { - return get(`/api/v1/music/stream?id=${id}&source=${source}&name=${name}&artist=${artist}`) + // return get(`/api/v1/music/stream?id=${id}&source=${source}&name=${name}&artist=${artist}`) + return invoke>("music_stream", { id, source, name, artist }) }, /** * 智能切换可用的平替音源 @@ -141,7 +146,8 @@ const MusicApi = { * @returns */ switch: (name: string, source: Source, artist?: string, target?: string, duration?: string) => { - return get(`/api/v1/music/switch?name=${name}&source=${source}&artist=${artist}&target=${target}&duration=${duration}`) + // return get(`/api/v1/music/switch?name=${name}&source=${source}&artist=${artist}&target=${target}&duration=${duration}`) + return invoke>("music_switch", { name, source, artist, target, duration }) }, /** * 获取音频裸直链 @@ -151,7 +157,8 @@ const MusicApi = { * @returns */ url: (id: string, source: Source) => { - return get(`/api/v1/music/url?id=${id}&source=${source}`) + // return get(`/api/v1/music/url?id=${id}&source=${source}`) + return invoke>("music_url", { id, source }) }, musicUrl: (id: string, source: Source) => { diff --git a/src/api/playlist.ts b/src/api/playlist.ts index 4522042..d0b4da6 100644 --- a/src/api/playlist.ts +++ b/src/api/playlist.ts @@ -1,4 +1,3 @@ -import { get } from "@/utils/request" import Source, { type ApiResponse } from "@/types/global" import { invoke } from "@tauri-apps/api/core" @@ -101,8 +100,9 @@ const PlayListApi = { * @returns */ categoryList: (sources?: Source[]) => { - const sourceParams = (sources ?? []).map((s) => `sources=${encodeURIComponent(s)}`).join("&") - return get(`/api/v1/playlist/categories${sourceParams ? `?${sourceParams}` : ""}`) + // const sourceParams = (sources ?? []).map((s) => `sources=${encodeURIComponent(s)}`).join("&") + // return get(`/api/v1/playlist/categories${sourceParams ? `?${sourceParams}` : ""}`) + return invoke>("playlist_categories", { sources }) }, /** * 获取分类歌单 @@ -114,7 +114,8 @@ const PlayListApi = { * @returns */ categoryPlayList: (categoryId: string, source?: Source, page?: number, limit?: number) => { - return get(`/api/v1/playlist/category?source=${source}&category_id=${categoryId}&page=${page}&limit=${limit}`) + // return get(`/api/v1/playlist/category?source=${source}&category_id=${categoryId}&page=${page}&limit=${limit}`) + return invoke>("playlist_category_list", { categoryId, source, page, limit }) }, /** * 获取歌单详情 @@ -123,7 +124,8 @@ const PlayListApi = { * @returns */ detail: (source: Source, id: string) => { - return get(`/api/v1/playlist/detail?source=${source}&id=${id}`) + // return get(`/api/v1/playlist/detail?source=${source}&id=${id}`) + return invoke>("playlist_detail", { source, id }) }, /** diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..13784fd Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo1.png b/src/assets/logo1.png new file mode 100644 index 0000000..ab2df46 Binary files /dev/null and b/src/assets/logo1.png differ diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 37672a3..1b53b6c 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -2,8 +2,9 @@
@@ -46,7 +59,6 @@ const navItems = [ { path: "/", label: "歌单", icon: "home" }, { path: "/ranking", label: "排行榜", icon: "trophy" }, { path: "/library", label: "我的音乐", icon: "music" }, - { path: "/settings", label: "设置", icon: "settings" }, { path: "/demo", label: "演示", icon: "play" }, ] as const @@ -74,7 +86,7 @@ watch( .sidebar { display: flex; flex-direction: column; - width: clamp(200px, 16vw, 232px); + width: clamp(168px, 13vw, 196px); height: 100vh; flex-shrink: 0; padding: 20px 14px 14px; @@ -84,8 +96,9 @@ watch( .brand { display: flex; + flex-direction: column; align-items: center; - gap: 10px; + gap: 6px; padding: 0 10px 18px; } @@ -100,6 +113,14 @@ watch( box-shadow: 0 6px 14px rgba(34, 197, 94, 0.35); } +.brand-logo { + display: block; + width: 64px; + height: 64px; + object-fit: contain; + margin: 0 auto; +} + .brand-name { font-size: 17px; font-weight: 700; @@ -112,6 +133,15 @@ watch( gap: 4px; } +.nav-settings { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: auto; + padding-top: 12px; + border-top: 1px solid var(--app-border); +} + .nav-item { display: flex; align-items: center; @@ -236,6 +266,11 @@ watch( width: 100%; } + .nav-settings { + width: 100%; + margin-top: auto; + } + .nav-item { justify-content: center; padding: 10px 0; diff --git a/src/views/DiscoverView.vue b/src/views/DiscoverView.vue index f3bf3b9..1ba4be2 100644 --- a/src/views/DiscoverView.vue +++ b/src/views/DiscoverView.vue @@ -60,7 +60,7 @@ let loadSeq = 0 /** 获取歌单分类 */ const list = async () => { const res = await PlayListApi.categoryList([settingsStore.defaultSource]) - categories.value = res[0].categories + categories.value = res.data[0].categories if (categories.value.length > 0) { activeCategory.value = categories.value[1].id playList(activeCategory.value) @@ -75,7 +75,7 @@ async function loadCategoryPlayList(categoryId: string, p: number) { const res = await PlayListApi.categoryPlayList(categoryId, settingsStore.defaultSource, p, pageSize) console.log(res) if (seq !== loadSeq) return - categoryPlaylists.value = res.playlists + categoryPlaylists.value = res.data.playlists const n = categoryPlaylists.value.length if (n === pageSize) { // 满页:推测还有下一页 diff --git a/src/views/PlaylistDetailView.vue b/src/views/PlaylistDetailView.vue index c8d634c..7192d34 100644 --- a/src/views/PlaylistDetailView.vue +++ b/src/views/PlaylistDetailView.vue @@ -86,7 +86,7 @@ const playlistSource = computed( onMounted(async () => { try { const res = await PlayListApi.detail(playlistSource.value, props.id) - songs.value = res.map((song) => ({ + songs.value = res.data.map((song) => ({ id: song.id, title: song.name, artist: song.artist, @@ -113,7 +113,8 @@ function playAll() { async function playWithUrl(song: Song, queue?: Song[]) { try { const source = (song.source ?? playlistSource.value) as Source - const { url } = await MusicApi.url(String(song.id), source) + const res = await MusicApi.url(String(song.id), source) + const url = res.data.url playerStore.playSong(song, queue, url) } catch { // 拿不到直链时仍切歌,后续可尝试 MusicApi.stream 代理播放 diff --git a/src/views/SearchView.vue b/src/views/SearchView.vue index eae285d..f7fced4 100644 --- a/src/views/SearchView.vue +++ b/src/views/SearchView.vue @@ -136,12 +136,12 @@ async function doSearch(q: string) { ]) if (seq !== requestSeq) return if (songRes.status === "fulfilled") { - songResults.value = (songRes.value.songs ?? []).map(toSong) + songResults.value = (songRes.value.data.songs ?? []).map(toSong) } else { songResults.value = [] } if (playlistRes.status === "fulfilled") { - playlistResults.value = (playlistRes.value.playlists ?? []).map(toPlaylist) + playlistResults.value = (playlistRes.value.data.playlists ?? []).map(toPlaylist) } else { playlistResults.value = [] } @@ -179,8 +179,8 @@ async function playSearchSong(song: Song) { try { // const res = await MusicApi.musicUrl(String(song.id), source) // const url = "http://127.0.0.1:81" + res.data[0].url - const { url } = await MusicApi.url(String(song.id), source) - // const url = "http://127.0.0.1:81" + res.data[0].url + const res = await MusicApi.url(String(song.id), source) + const url = res.data.url console.log("url: ", url) playerStore.playSong(song, songResults.value, url) } catch {