Skip to content

Commit 2ef0cc9

Browse files
support import playlist in local cache
1 parent 339b583 commit 2ef0cc9

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

index.html

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,7 +1395,9 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
13951395
<select id="playlist-select"></select>
13961396
<button id="add-current-btn" class="btn btn-secondary btn-icon ripple-target" title="把当前歌曲加入歌单"></button>
13971397
<button id="new-playlist-btn" class="btn btn-ghost ripple-target" data-i18n="newPlaylist">新建歌单</button>
1398+
<button id="import-playlist-btn" class="btn btn-ghost ripple-target" data-i18n="importPlaylist">导入歌单</button>
13981399
<button id="export-playlist-btn" class="btn btn-ghost ripple-target" data-i18n="exportPlaylist">导出歌单</button>
1400+
<input id="import-playlist-input" type="file" accept="application/json,.json" style="display:none;" />
13991401
</div>
14001402
<div class="playmode-row">
14011403
<button class="playmode-btn active ripple-target" data-mode="list" title="列表循环">🔁</button>
@@ -1532,6 +1534,7 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
15321534
playlistInfoFavorites:"收藏列表",
15331535
playlistInfoPlaylist:"歌单",
15341536
newPlaylist:"新建歌单",
1537+
importPlaylist:"导入歌单",
15351538
exportPlaylist:"导出歌单",
15361539
footerText:"本站仅作为学习演示,音乐版权归各平台与原作者所有。",
15371540
toastAddedFavorite:"已添加到收藏",
@@ -1545,6 +1548,9 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
15451548
toastLyricStyleSwitched:"已切换歌词炫酷效果。",
15461549
toastDownloadNotReady:"当前歌曲还未加载完成,稍后再试。",
15471550
toastPlaylistCreated:"歌单创建成功。",
1551+
toastPlaylistImported:"导入完成",
1552+
toastPlaylistImportEmpty:"导入文件里没有可用歌单或收藏。",
1553+
toastPlaylistImportError:"导入失败,请确认文件是本站导出的 JSON。",
15481554
toastPlaylistExported:"已导出歌单文件。",
15491555
toastPlaylistExportEmpty:"暂无可导出的歌单。",
15501556
toastPlaylistEmpty:"当前歌单为空,先添加几首歌吧~",
@@ -1603,6 +1609,7 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
16031609
playlistInfoFavorites:"Favorites List",
16041610
playlistInfoPlaylist:"Playlist",
16051611
newPlaylist:"Create",
1612+
importPlaylist:"Import",
16061613
exportPlaylist:"Export",
16071614
footerText:"For demo only. All music copyrights belong to original owners.",
16081615
toastAddedFavorite:"Added to favorites",
@@ -1616,6 +1623,9 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
16161623
toastLyricStyleSwitched:"Lyrics FX toggled.",
16171624
toastDownloadNotReady:"Song not fully loaded yet. Try again later.",
16181625
toastPlaylistCreated:"Playlist created.",
1626+
toastPlaylistImported:"Import completed",
1627+
toastPlaylistImportEmpty:"No usable playlists or favorites found in this file.",
1628+
toastPlaylistImportError:"Import failed. Please choose a JSON file exported by this site.",
16191629
toastPlaylistExported:"Playlist file exported.",
16201630
toastPlaylistExportEmpty:"No playlist to export.",
16211631
toastPlaylistEmpty:"Playlist is empty. Add some songs first.",
@@ -1846,6 +1856,104 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
18461856
showToast(t('toastPlaylistExported'));
18471857
}
18481858

1859+
function mergeImportedTracks(targetList, rawTracks){
1860+
let added=0;
1861+
if(!Array.isArray(rawTracks)) return added;
1862+
1863+
rawTracks.forEach(raw=>{
1864+
const imported=deserializeTrack(raw);
1865+
if(!imported || !imported.uid) return;
1866+
1867+
const track=state.trackMap.get(imported.uid) || imported;
1868+
if(!state.trackMap.has(track.uid)) state.trackMap.set(track.uid, track);
1869+
1870+
if(!targetList.some(item=>item.uid===track.uid)){
1871+
targetList.push(track);
1872+
added++;
1873+
}
1874+
});
1875+
1876+
return added;
1877+
}
1878+
1879+
function importPlaylistData(data){
1880+
if(!data || typeof data!=='object') throw new Error('invalid import data');
1881+
1882+
let addedFavorites=0;
1883+
let addedPlaylists=0;
1884+
let addedPlaylistTracks=0;
1885+
1886+
addedFavorites=mergeImportedTracks(state.favorites, data.favorites);
1887+
1888+
const importedPlaylists=Array.isArray(data.playlists) ? data.playlists : [];
1889+
importedPlaylists.forEach((pl,idx)=>{
1890+
if(!pl || typeof pl!=='object') return;
1891+
1892+
const fallbackName=state.language==='zh' ? '导入歌单' : 'Imported Playlist';
1893+
const name=(pl.name || fallbackName).toString().trim() || fallbackName;
1894+
const rawId=(pl.id || '').toString().trim();
1895+
1896+
let target=rawId ? state.playlists.find(item=>item.id===rawId) : null;
1897+
if(!target) target=state.playlists.find(item=>item.name===name);
1898+
1899+
if(!target){
1900+
let id=rawId || ('pl-import-'+Date.now()+'-'+idx+'-'+Math.random().toString(16).slice(2));
1901+
if(state.playlists.some(item=>item.id===id)){
1902+
id='pl-import-'+Date.now()+'-'+idx+'-'+Math.random().toString(16).slice(2);
1903+
}
1904+
target={id,name,tracks:[]};
1905+
state.playlists.push(target);
1906+
addedPlaylists++;
1907+
}
1908+
1909+
addedPlaylistTracks += mergeImportedTracks(target.tracks, pl.tracks);
1910+
});
1911+
1912+
const totalAdded=addedFavorites + addedPlaylists + addedPlaylistTracks;
1913+
const hasUsableData=(Array.isArray(data.favorites) && data.favorites.length) || importedPlaylists.length;
1914+
if(!hasUsableData) return {empty:true, addedFavorites, addedPlaylists, addedPlaylistTracks, totalAdded};
1915+
1916+
rebuildLibraryTrackMap();
1917+
renderPlaylistOptions();
1918+
saveLibraryToStorage();
1919+
renderPlaylistList();
1920+
updateMainFavButton();
1921+
1922+
return {empty:false, addedFavorites, addedPlaylists, addedPlaylistTracks, totalAdded};
1923+
}
1924+
1925+
function handleImportPlaylistFile(e){
1926+
const input=e.target;
1927+
const file=input.files && input.files[0];
1928+
if(!file) return;
1929+
1930+
const reader=new FileReader();
1931+
reader.onload=()=>{
1932+
try{
1933+
const data=JSON.parse(reader.result);
1934+
const stat=importPlaylistData(data);
1935+
if(stat.empty){
1936+
showToast(t('toastPlaylistImportEmpty'));
1937+
}else{
1938+
const msg=state.language==='zh'
1939+
? `${t('toastPlaylistImported')}:新增 ${stat.addedPlaylists} 个歌单,${stat.addedFavorites} 首收藏,${stat.addedPlaylistTracks} 首歌单歌曲。`
1940+
: `${t('toastPlaylistImported')}: ${stat.addedPlaylists} playlists, ${stat.addedFavorites} favorites, ${stat.addedPlaylistTracks} playlist tracks added.`;
1941+
showToast(msg);
1942+
}
1943+
}catch(err){
1944+
console.error('import playlist failed', err);
1945+
showToast(t('toastPlaylistImportError'));
1946+
}finally{
1947+
input.value='';
1948+
}
1949+
};
1950+
reader.onerror=()=>{
1951+
showToast(t('toastPlaylistImportError'));
1952+
input.value='';
1953+
};
1954+
reader.readAsText(file, 'utf-8');
1955+
}
1956+
18491957
function renderPlaylistOptions(){
18501958
if(!dom.playlistSelect) return;
18511959
const prev=dom.playlistSelect.value || state.playContext.playlistId;
@@ -2947,6 +3055,8 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
29473055
dom.playlistSelectRow=$('playlist-select-row');
29483056
dom.playlistSelect=$('playlist-select');
29493057
dom.newPlaylistBtn=$('new-playlist-btn');
3058+
dom.importPlaylistBtn=$('import-playlist-btn');
3059+
dom.importPlaylistInput=$('import-playlist-input');
29503060
dom.exportPlaylistBtn=$('export-playlist-btn');
29513061
dom.addCurrentBtn=$('add-current-btn');
29523062

@@ -3090,6 +3200,8 @@ <h1 data-i18n="appTitle">皮卡丘的音乐站</h1>
30903200
});
30913201

30923202
dom.newPlaylistBtn.addEventListener('click',openPlaylistModal);
3203+
dom.importPlaylistBtn.addEventListener('click',()=>dom.importPlaylistInput.click());
3204+
dom.importPlaylistInput.addEventListener('change',handleImportPlaylistFile);
30933205
dom.exportPlaylistBtn.addEventListener('click',exportPlaylistData);
30943206
dom.playlistConfirmBtn.addEventListener('click',createPlaylist);
30953207
dom.playlistCancelBtn.addEventListener('click',closePlaylistModal);

0 commit comments

Comments
 (0)