|
|
@@ -7,12 +7,15 @@ const state = {
|
|
|
albums: [],
|
|
|
currentView: "home",
|
|
|
activeAlbumPath: "",
|
|
|
+ featuredAlbums: [],
|
|
|
+ featuredIndex: 0,
|
|
|
};
|
|
|
|
|
|
const audio = document.getElementById("audioPlayer");
|
|
|
const progressBar = document.getElementById("progressBar");
|
|
|
const audioCacheName = "musicweb-audio-cache-v1";
|
|
|
let toastTimer = null;
|
|
|
+let featuredTimer = null;
|
|
|
|
|
|
const iconMarkup = {
|
|
|
play: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7-11-7z"/></svg>',
|
|
|
@@ -100,6 +103,8 @@ async function fetchLibrary() {
|
|
|
const response = await fetch("/api/library");
|
|
|
state.library = await response.json();
|
|
|
state.albums = buildAlbums(state.library.tree);
|
|
|
+ state.featuredAlbums = buildFeaturedAlbums(state.albums);
|
|
|
+ state.featuredIndex = 0;
|
|
|
|
|
|
document.getElementById("albumCount").textContent = state.albums.length;
|
|
|
document.getElementById("trackCount").textContent = state.library.all_tracks.length;
|
|
|
@@ -116,22 +121,27 @@ async function fetchLibrary() {
|
|
|
state.currentQueue = [...state.library.all_tracks];
|
|
|
renderQueue();
|
|
|
}
|
|
|
+
|
|
|
+ startFeaturedCarousel();
|
|
|
}
|
|
|
|
|
|
function buildAlbums(tree) {
|
|
|
const albums = [];
|
|
|
|
|
|
- function visit(node) {
|
|
|
+ function visit(node, ancestors = []) {
|
|
|
const tracks = flattenNodeTracks(node);
|
|
|
- if (node.path && tracks.length) {
|
|
|
+ const childAlbums = node.folders.filter((child) => flattenNodeTracks(child).length);
|
|
|
+ if (node.path && tracks.length && !childAlbums.length) {
|
|
|
+ const nameParts = [...ancestors, node.name].filter(Boolean);
|
|
|
albums.push({
|
|
|
- name: node.name || "未命名专辑",
|
|
|
+ name: nameParts.join("") || "未命名专辑",
|
|
|
path: node.path,
|
|
|
cover: node.cover || null,
|
|
|
tracks,
|
|
|
});
|
|
|
}
|
|
|
- node.folders.forEach(visit);
|
|
|
+ const nextAncestors = node.path ? [...ancestors, node.name] : ancestors;
|
|
|
+ node.folders.forEach((child) => visit(child, nextAncestors));
|
|
|
}
|
|
|
|
|
|
visit(tree);
|
|
|
@@ -191,11 +201,10 @@ function buildCoverMarkup(album, className) {
|
|
|
}
|
|
|
|
|
|
function renderHome() {
|
|
|
- const featured = [...state.albums].sort((a, b) => b.tracks.length - a.tracks.length).slice(0, 2);
|
|
|
const latestTracks = [...state.library.all_tracks].slice(-8).reverse();
|
|
|
const rankingAlbums = [...state.albums].sort((a, b) => b.tracks.length - a.tracks.length).slice(0, 6);
|
|
|
|
|
|
- renderFeaturedAlbums(featured);
|
|
|
+ renderFeaturedAlbums(state.featuredAlbums);
|
|
|
renderAlbumGrid(state.albums);
|
|
|
renderLatestList(latestTracks);
|
|
|
renderRankingList(rankingAlbums);
|
|
|
@@ -224,28 +233,46 @@ function renderFeaturedAlbums(items) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- items.forEach((album) => {
|
|
|
- const card = document.createElement("article");
|
|
|
- card.className = "feature-card";
|
|
|
- card.innerHTML = `
|
|
|
- <div class="feature-card-inner">
|
|
|
- ${buildCoverMarkup(album, "feature-cover")}
|
|
|
- <div class="feature-body">
|
|
|
- <div>
|
|
|
- <h3>${album.name}</h3>
|
|
|
- <p class="meta-row">${album.tracks.length} 首歌曲 · ${album.path}</p>
|
|
|
- </div>
|
|
|
- <p class="subtext">点击进入详细目录页,查看歌曲列表并播放。</p>
|
|
|
- <div class="card-actions">
|
|
|
- <button class="primary-btn" data-action="detail">进入目录</button>
|
|
|
- <button class="secondary-btn" data-action="play">播放</button>
|
|
|
- </div>
|
|
|
+ const album = items[state.featuredIndex] || items[0];
|
|
|
+ container.appendChild(buildFeaturedAlbumCard(album));
|
|
|
+}
|
|
|
+
|
|
|
+function buildFeaturedAlbums(albums) {
|
|
|
+ const sorted = [...albums].sort((a, b) => b.tracks.length - a.tracks.length);
|
|
|
+ if (sorted.length <= 5) return sorted;
|
|
|
+ return sorted.slice(0, 8);
|
|
|
+}
|
|
|
+
|
|
|
+function startFeaturedCarousel() {
|
|
|
+ if (featuredTimer) clearInterval(featuredTimer);
|
|
|
+ if (state.featuredAlbums.length <= 1) return;
|
|
|
+ featuredTimer = setInterval(() => {
|
|
|
+ state.featuredIndex = (state.featuredIndex + 1) % state.featuredAlbums.length;
|
|
|
+ renderFeaturedAlbums(state.featuredAlbums);
|
|
|
+ }, 3000);
|
|
|
+}
|
|
|
+
|
|
|
+function buildFeaturedAlbumCard(album) {
|
|
|
+ const card = document.createElement("article");
|
|
|
+ card.className = "feature-card";
|
|
|
+ card.innerHTML = `
|
|
|
+ <div class="feature-card-inner">
|
|
|
+ ${buildCoverMarkup(album, "feature-cover")}
|
|
|
+ <div class="feature-body">
|
|
|
+ <div>
|
|
|
+ <h3>${album.name}</h3>
|
|
|
+ <p class="meta-row">${album.tracks.length} 首歌曲 · ${album.path}</p>
|
|
|
+ </div>
|
|
|
+ <p class="subtext">点击进入详细目录页,查看歌曲列表并播放。</p>
|
|
|
+ <div class="card-actions">
|
|
|
+ <button class="primary-btn" data-action="detail">进入目录</button>
|
|
|
+ <button class="secondary-btn" data-action="play">播放</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
- `;
|
|
|
- buildAlbumCardActions(card, album);
|
|
|
- container.appendChild(card);
|
|
|
- });
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ buildAlbumCardActions(card, album);
|
|
|
+ return card;
|
|
|
}
|
|
|
|
|
|
function renderAlbumGrid(items) {
|
|
|
@@ -565,6 +592,32 @@ function updateDock(track) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function clearCurrentTrack() {
|
|
|
+ audio.pause();
|
|
|
+ audio.removeAttribute("src");
|
|
|
+ audio.load();
|
|
|
+ state.currentTrackIndex = -1;
|
|
|
+ state.activeAlbumPath = "";
|
|
|
+ document.getElementById("nowTitle").textContent = "选择一个专辑目录开始播放";
|
|
|
+ document.getElementById("nowMeta").textContent = "点击专辑进入详情页,或直接在卡片上快速播放。";
|
|
|
+ document.getElementById("dockTitle").textContent = "未开始播放";
|
|
|
+ document.getElementById("dockPath").textContent = "点击专辑或歌曲开始播放";
|
|
|
+ document.getElementById("currentTimeLabel").textContent = "00:00";
|
|
|
+ document.getElementById("durationLabel").textContent = "00:00";
|
|
|
+ progressBar.value = 0;
|
|
|
+
|
|
|
+ const coverImg = document.getElementById("dockCoverImage");
|
|
|
+ const fallback = document.getElementById("dockCoverFallback");
|
|
|
+ coverImg.removeAttribute("src");
|
|
|
+ coverImg.classList.add("is-hidden");
|
|
|
+ fallback.classList.remove("is-hidden");
|
|
|
+ fallback.textContent = "乐";
|
|
|
+
|
|
|
+ renderQueue();
|
|
|
+ syncPlayButton();
|
|
|
+ savePlaybackState();
|
|
|
+}
|
|
|
+
|
|
|
function syncPlayButton() {
|
|
|
document.getElementById("playToggleBtn").classList.toggle("is-playing", !audio.paused && !audio.ended);
|
|
|
document.querySelector(".icon-play").classList.toggle("is-hidden", !audio.paused && !audio.ended);
|
|
|
@@ -680,6 +733,7 @@ document.getElementById("closeAlbumMenuBtn").onclick = () => toggleAlbumMenu(fal
|
|
|
document.getElementById("albumMenuHomeBtn").onclick = () => {
|
|
|
toggleAlbumMenu(false);
|
|
|
showView("home");
|
|
|
+ clearCurrentTrack();
|
|
|
scrollToPageTop();
|
|
|
};
|
|
|
document.getElementById("albumMenuOverlay").onclick = (event) => {
|