mirror of
https://github.com/atdunbg/Nekosonic-Music.git
synced 2026-08-07 12:34:18 +08:00
feat: v0.8.0 详情页架构重构、皮肤系统、侧边栏交互升级、底层模块拆分
- 详情页统一 DetailLayout 组件(常驻头部 + 独立滚动 + 紧凑态动画) - 歌曲/评论 tab 切换(懒加载 + 保活),专辑模糊搜索 - 皮肤系统 skins.ts(19 语义色 + 14 预设 + 自定义皮肤 + 壁纸) - 设置页皮肤模块(外观/主题色/皮肤编辑器/壁纸选择) - 可折叠侧边栏 + TopBar + 折叠态歌单浮层(高度不越 PlayerBar) - 首页扩展至 6 区块 + 通用卡片组件 + 虚拟歌曲列表 - audio.rs 拆为 8 模块,api.ts 拆为 api/ 目录,player store 拆为 4 store - 修复播放竞态、seek 暂停、评论提前加载、紧凑态卡死等
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 专辑相关 API
|
||||
*/
|
||||
export const AlbumApi = {
|
||||
/** 获取专辑详情 */
|
||||
async albumDetail(id: number): Promise<string> {
|
||||
return invoke('album_detail', { id });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 应用级 API
|
||||
*/
|
||||
export const AppApi = {
|
||||
/** 退出应用 */
|
||||
exitApp(): Promise<void> {
|
||||
return invoke('exit_app');
|
||||
},
|
||||
|
||||
/** 读取图片为 data URL */
|
||||
async readImageAsDataUrl(path: string): Promise<string> {
|
||||
return invoke('read_image_as_data_url', { path });
|
||||
},
|
||||
|
||||
/** 在文件管理器中显示文件 */
|
||||
async showItemInFolder(path: string): Promise<void> {
|
||||
return invoke('show_item_in_folder', { path });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 歌手相关 API
|
||||
*/
|
||||
export const ArtistApi = {
|
||||
/** 获取歌手详情 */
|
||||
async artistDetail(id: number): Promise<string> {
|
||||
return invoke('artist_detail', { id });
|
||||
},
|
||||
|
||||
/** 获取歌手歌曲 */
|
||||
async artistSongs(query: { id: number; order: string; limit: number; offset: number }): Promise<string> {
|
||||
return invoke('artist_songs', { query });
|
||||
},
|
||||
|
||||
/** 获取歌手专辑 */
|
||||
async artistAlbum(id: number, limit: number, offset: number): Promise<string> {
|
||||
return invoke('artist_album', { id, limit, offset });
|
||||
},
|
||||
|
||||
/** 获取歌手描述 */
|
||||
async artistDesc(id: number): Promise<string> {
|
||||
return invoke('artist_desc', { id });
|
||||
},
|
||||
|
||||
/** 关注/取消关注歌手 */
|
||||
async artistSub(id: number, sub: boolean): Promise<string> {
|
||||
return invoke('artist_sub', { query: { id, sub } });
|
||||
},
|
||||
|
||||
/** 已关注的歌手列表 */
|
||||
async artistSublist(limit = 100, offset = 0): Promise<string> {
|
||||
return invoke('artist_sublist', { query: { limit, offset } });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 音频播放控制 API
|
||||
*/
|
||||
export const AudioApi = {
|
||||
/** 播放网络音频 */
|
||||
async playAudio(url: string): Promise<void> {
|
||||
return invoke('play_audio', { url });
|
||||
},
|
||||
|
||||
/** 播放本地音频文件 */
|
||||
async playLocalAudio(path: string): Promise<void> {
|
||||
return invoke('play_local_audio', { path });
|
||||
},
|
||||
|
||||
/** 暂停播放 */
|
||||
async pauseAudio(): Promise<void> {
|
||||
return invoke('pause_audio');
|
||||
},
|
||||
|
||||
/** 恢复播放 */
|
||||
async resumeAudio(): Promise<void> {
|
||||
return invoke('resume_audio');
|
||||
},
|
||||
|
||||
/** 停止播放 */
|
||||
async stopAudio(): Promise<void> {
|
||||
return invoke('stop_audio');
|
||||
},
|
||||
|
||||
/** 跳转到指定位置(秒) */
|
||||
async seekAudio(time: number): Promise<void> {
|
||||
return invoke('seek_audio', { time });
|
||||
},
|
||||
|
||||
/** 设置音量(0.0 - 1.0) */
|
||||
async setVolume(vol: number): Promise<void> {
|
||||
return invoke('set_volume', { vol });
|
||||
},
|
||||
|
||||
/** 获取当前播放位置(秒) */
|
||||
async getAudioPosition(): Promise<number> {
|
||||
return invoke('get_audio_position');
|
||||
},
|
||||
|
||||
/** 是否正在播放 */
|
||||
async isAudioPlaying(): Promise<boolean> {
|
||||
return invoke('is_audio_playing');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 云盘相关 API
|
||||
*/
|
||||
export const CloudApi = {
|
||||
/** 获取云盘歌曲列表 */
|
||||
async userCloud(limit = 30, offset = 0): Promise<string> {
|
||||
return invoke('user_cloud', { limit, offset });
|
||||
},
|
||||
|
||||
/** 删除云盘歌曲 */
|
||||
async userCloudDel(id: number): Promise<string> {
|
||||
return invoke('user_cloud_del', { id });
|
||||
},
|
||||
|
||||
/** 上传歌曲到云盘 */
|
||||
async cloudUpload(filePath: string): Promise<string> {
|
||||
return invoke('cloud_upload', { filePath });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 评论相关 API
|
||||
*/
|
||||
export const CommentApi = {
|
||||
/** 获取热门评论 */
|
||||
async commentHot(query: { type: number; id: number; limit: number; offset: number }): Promise<string> {
|
||||
return invoke('comment_hot', { query });
|
||||
},
|
||||
|
||||
/** 点赞/取消点赞评论 */
|
||||
async commentLike(query: { t: number; type: number; id: number; cid: number }): Promise<void> {
|
||||
return invoke('comment_like', { query });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 音频输出设备 API
|
||||
*/
|
||||
export const DeviceApi = {
|
||||
/** 获取所有输出设备 */
|
||||
async getOutputDevices(): Promise<string[]> {
|
||||
return invoke('get_output_devices');
|
||||
},
|
||||
|
||||
/** 设置输出设备(null 表示默认) */
|
||||
async setOutputDevice(device: string | null): Promise<void> {
|
||||
return invoke('set_output_device', { device });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 下载与本地音乐 API
|
||||
*/
|
||||
export const DownloadApi = {
|
||||
/** 下载歌曲 */
|
||||
async downloadSong(query: {
|
||||
id: number;
|
||||
name: string;
|
||||
artist: string;
|
||||
album: string | null;
|
||||
duration: number | null;
|
||||
coverUrl: string | null;
|
||||
level: string;
|
||||
downloadPath: string | null;
|
||||
}): Promise<void> {
|
||||
return invoke('download_song', { query });
|
||||
},
|
||||
|
||||
/** 列出已下载的歌曲 */
|
||||
async listLocalSongs(downloadPath: string | null): Promise<any[]> {
|
||||
return invoke('list_local_songs', { downloadPath });
|
||||
},
|
||||
|
||||
/** 扫描本地文件夹 */
|
||||
async scanLocalFolders(paths: string[]): Promise<any[]> {
|
||||
return invoke('scan_local_folders', { paths });
|
||||
},
|
||||
|
||||
/** 删除本地歌曲文件 */
|
||||
async deleteLocalSong(query: { id: number; filename: string; downloadPath: string | null }): Promise<void> {
|
||||
return invoke('delete_local_song', { query });
|
||||
},
|
||||
|
||||
/** 获取默认下载路径 */
|
||||
async getDefaultDownloadPath(): Promise<string> {
|
||||
return invoke('get_default_download_path');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 私人 FM 相关 API
|
||||
*/
|
||||
export const FmApi = {
|
||||
/** 获取私人 FM 歌曲 */
|
||||
async personalFm(): Promise<string> {
|
||||
return invoke('personal_fm');
|
||||
},
|
||||
|
||||
/** 切换私人 FM 模式 */
|
||||
async personalFmMode(query: { mode: string; subMode: string; limit: number }): Promise<string> {
|
||||
return invoke('personal_fm_mode', { query });
|
||||
},
|
||||
|
||||
/** 私人 FM 不喜欢(扔进垃圾桶) */
|
||||
async fmTrash(id: number, time: number): Promise<void> {
|
||||
return invoke('fm_trash', { query: { id, time } });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* API 统一入口
|
||||
*
|
||||
* 按业务域拆分到独立文件,此处重新组装为命名空间以保持向后兼容。
|
||||
* 新代码建议直接从具体域文件导入,例如:
|
||||
* import { SongApi } from '../api/song';
|
||||
*/
|
||||
import { LoginApi } from './login';
|
||||
import { SongApi } from './song';
|
||||
import { PlaylistApi } from './playlist';
|
||||
import { SearchApi } from './search';
|
||||
import { AlbumApi } from './album';
|
||||
import { ArtistApi } from './artist';
|
||||
import { CommentApi } from './comment';
|
||||
import { FmApi } from './fm';
|
||||
import { CloudApi } from './cloud';
|
||||
import { AudioApi } from './audio';
|
||||
import { DeviceApi } from './device';
|
||||
import { DownloadApi } from './download';
|
||||
import { AppApi } from './app';
|
||||
import { RecApi } from './rec';
|
||||
|
||||
export { LoginApi, SongApi, PlaylistApi, SearchApi, AlbumApi, ArtistApi, CommentApi, FmApi, CloudApi, AudioApi, DeviceApi, DownloadApi, AppApi, RecApi };
|
||||
|
||||
/**
|
||||
* 音乐业务 API(向后兼容命名空间)
|
||||
* @deprecated 建议直接使用具体域的 Api 对象,如 `SongApi`、`PlaylistApi` 等
|
||||
*/
|
||||
export const MusicApi = {
|
||||
// 登录
|
||||
getLoginStatus: LoginApi.getLoginStatus,
|
||||
logout: LoginApi.logout,
|
||||
getQrKey: LoginApi.getQrKey,
|
||||
checkQrStatus: LoginApi.checkQrStatus,
|
||||
|
||||
// 歌曲
|
||||
getSongDetail: SongApi.getSongDetail,
|
||||
getSongUrl: SongApi.getSongUrl,
|
||||
getLyric: SongApi.getLyric,
|
||||
likelist: SongApi.likelist,
|
||||
likeSong: SongApi.likeSong,
|
||||
scrobble: SongApi.scrobble,
|
||||
|
||||
// 歌单
|
||||
userPlaylist: PlaylistApi.userPlaylist,
|
||||
getPlaylistDetail: PlaylistApi.getPlaylistDetail,
|
||||
playlistTrackAll: PlaylistApi.playlistTrackAll,
|
||||
playlistSubscribe: PlaylistApi.playlistSubscribe,
|
||||
recommendResource: PlaylistApi.recommendResource,
|
||||
recommendSongs: PlaylistApi.recommendSongs,
|
||||
|
||||
// 搜索
|
||||
searchSuggest: SearchApi.searchSuggest,
|
||||
getHotSearch: SearchApi.getHotSearch,
|
||||
cloudsearch: SearchApi.cloudsearch,
|
||||
|
||||
// 专辑
|
||||
albumDetail: AlbumApi.albumDetail,
|
||||
|
||||
// 歌手
|
||||
artistDetail: ArtistApi.artistDetail,
|
||||
artistSongs: ArtistApi.artistSongs,
|
||||
artistAlbum: ArtistApi.artistAlbum,
|
||||
artistDesc: ArtistApi.artistDesc,
|
||||
artistSub: ArtistApi.artistSub,
|
||||
artistSublist: ArtistApi.artistSublist,
|
||||
|
||||
// 评论
|
||||
commentHot: CommentApi.commentHot,
|
||||
commentLike: CommentApi.commentLike,
|
||||
|
||||
// 私人 FM
|
||||
personalFm: FmApi.personalFm,
|
||||
personalFmMode: FmApi.personalFmMode,
|
||||
fmTrash: FmApi.fmTrash,
|
||||
|
||||
// 云盘
|
||||
userCloud: CloudApi.userCloud,
|
||||
userCloudDel: CloudApi.userCloudDel,
|
||||
cloudUpload: CloudApi.cloudUpload,
|
||||
|
||||
// 推荐
|
||||
personalized: RecApi.personalized,
|
||||
personalizedNewsong: RecApi.personalizedNewsong,
|
||||
topArtists: RecApi.topArtists,
|
||||
topSong: RecApi.topSong,
|
||||
albumNewest: RecApi.albumNewest,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 登录相关 API
|
||||
*/
|
||||
export const LoginApi = {
|
||||
/** 获取登录状态 */
|
||||
async getLoginStatus(): Promise<string> {
|
||||
return invoke('get_login_status');
|
||||
},
|
||||
|
||||
/** 退出登录 */
|
||||
async logout(): Promise<void> {
|
||||
return invoke('logout');
|
||||
},
|
||||
|
||||
/** 获取二维码登录 key */
|
||||
async getQrKey(): Promise<string> {
|
||||
return invoke('get_qr_key');
|
||||
},
|
||||
|
||||
/** 检查二维码扫描状态 */
|
||||
async checkQrStatus(key: string): Promise<string> {
|
||||
return invoke('check_qr_status', { query: { key } });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 歌单相关 API
|
||||
*/
|
||||
export const PlaylistApi = {
|
||||
/** 获取用户歌单列表 */
|
||||
async userPlaylist(uid: number): Promise<string> {
|
||||
return invoke('user_playlist', { uid });
|
||||
},
|
||||
|
||||
/** 获取歌单详情 */
|
||||
async getPlaylistDetail(id: number): Promise<string> {
|
||||
return invoke('get_playlist_detail', { id });
|
||||
},
|
||||
|
||||
/** 获取歌单全部曲目 */
|
||||
async playlistTrackAll(id: number): Promise<string> {
|
||||
return invoke('playlist_track_all', { query: { id } });
|
||||
},
|
||||
|
||||
/** 订阅/取消订阅歌单 */
|
||||
async playlistSubscribe(id: number, subscribe: boolean): Promise<void> {
|
||||
return invoke('playlist_subscribe', { query: { id, subscribe } });
|
||||
},
|
||||
|
||||
/** 每日推荐歌单 */
|
||||
async recommendResource(): Promise<string> {
|
||||
return invoke('recommend_resource');
|
||||
},
|
||||
|
||||
/** 每日推荐歌曲 */
|
||||
async recommendSongs(): Promise<string> {
|
||||
return invoke('recommend_songs');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 推荐相关 API
|
||||
*/
|
||||
export const RecApi = {
|
||||
/** 个性化推荐歌单(无需登录) */
|
||||
async personalized(limit: number = 30): Promise<string> {
|
||||
return invoke('personalized', { limit });
|
||||
},
|
||||
|
||||
/** 推荐新歌 */
|
||||
async personalizedNewsong(limit: number = 10): Promise<string> {
|
||||
return invoke('personalized_newsong', { limit });
|
||||
},
|
||||
|
||||
/** 热门歌手 */
|
||||
async topArtists(limit: number = 30, offset: number = 0): Promise<string> {
|
||||
return invoke('top_artists', { limit, offset });
|
||||
},
|
||||
|
||||
/** 新歌速递,type: 全部:0 / 华语:7 / 欧美:96 / 韩国:16 / 日本:8 */
|
||||
async topSong(areaType: number = 0): Promise<string> {
|
||||
return invoke('top_song', { areaType });
|
||||
},
|
||||
|
||||
/** 最新专辑(新碟上架) */
|
||||
async albumNewest(): Promise<string> {
|
||||
return invoke('album_newest');
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 搜索相关 API
|
||||
*/
|
||||
export const SearchApi = {
|
||||
/** 搜索建议 */
|
||||
async searchSuggest(keyword: string): Promise<string> {
|
||||
return invoke('search_suggest', { query: { keyword } });
|
||||
},
|
||||
|
||||
/** 热门搜索 */
|
||||
async getHotSearch(): Promise<string> {
|
||||
return invoke('get_hot_search');
|
||||
},
|
||||
|
||||
/** 云搜索 */
|
||||
async cloudsearch(query: { keyword: string; searchType: number; limit: number }): Promise<string> {
|
||||
return invoke('cloudsearch', { query });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* 歌曲相关 API
|
||||
*/
|
||||
export const SongApi = {
|
||||
/** 获取歌曲详情 */
|
||||
async getSongDetail(id: string): Promise<string> {
|
||||
return invoke('get_song_detail', { id });
|
||||
},
|
||||
|
||||
/** 获取歌曲播放 URL */
|
||||
async getSongUrl(query: { id: number; level: string; fm_mode?: boolean }): Promise<string> {
|
||||
return invoke('get_song_url', { query });
|
||||
},
|
||||
|
||||
/** 获取歌词 */
|
||||
async getLyric(id: number): Promise<string> {
|
||||
return invoke('get_lyric', { id });
|
||||
},
|
||||
|
||||
/** 获取喜欢列表 */
|
||||
async likelist(uid: number): Promise<string> {
|
||||
return invoke('likelist', { uid });
|
||||
},
|
||||
|
||||
/** 喜欢/取消喜欢歌曲 */
|
||||
async likeSong(id: number, like: boolean): Promise<void> {
|
||||
return invoke('like_song', { query: { id, like: like ? 'true' : 'false' } });
|
||||
},
|
||||
|
||||
/** 上报听歌记录(scrobble) */
|
||||
async scrobble(query: { id: number; sourceid: string; time: number; alg?: string; source?: string; bitrate?: number }): Promise<void> {
|
||||
return invoke('scrobble', { query });
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user