feat: 曲库索引 —— 曲库里已有的同名歌曲在列表中标记「曲库」
不是通过声匣下载的文件(其他工具 / 手动放入)以前一律显示未下载。 现在启动时扫描音乐目录,按「歌名 + 歌手文件夹 / 专辑文件夹」匹配, 下载完成增量更新,每 30 分钟或删除 / 补全标签后重建;设置页可手动重建。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,8 @@ watch(
|
||||
);
|
||||
|
||||
const allSelected = computed(() => props.tracks.length > 0 && selected.value.size === props.tracks.length);
|
||||
const pendingCount = computed(() => props.tracks.filter((t) => !isDownloaded(t)).length);
|
||||
const owned = (t: TrackSummary) => isDownloaded(t) || !!t.inLibrary;
|
||||
const pendingCount = computed(() => props.tracks.filter((t) => !owned(t)).length);
|
||||
|
||||
function toggleRow(index: number, e: MouseEvent) {
|
||||
const next = new Set(selected.value);
|
||||
@@ -56,7 +57,7 @@ function toggleAll() {
|
||||
}
|
||||
|
||||
function selectPending() {
|
||||
selected.value = new Set(props.tracks.filter((t) => !isDownloaded(t)).map((t) => t.id));
|
||||
selected.value = new Set(props.tracks.filter((t) => !owned(t)).map((t) => t.id));
|
||||
}
|
||||
|
||||
async function downloadSelected() {
|
||||
@@ -69,7 +70,14 @@ function downloadAll() {
|
||||
download(props.tracks, quality.value, props.group);
|
||||
}
|
||||
|
||||
function rowState(t: TrackSummary) {
|
||||
interface RowState {
|
||||
kind: string;
|
||||
text: string;
|
||||
pct: number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
function rowState(t: TrackSummary): RowState | null {
|
||||
const task = activeByTrack.value.get(trackKey(t.source, t.id));
|
||||
if (task) {
|
||||
if (task.status === 'running') {
|
||||
@@ -80,6 +88,7 @@ function rowState(t: TrackSummary) {
|
||||
return { kind: task.status, text: task.status === 'paused' ? '已暂停' : '排队中', pct: 0 };
|
||||
}
|
||||
if (isDownloaded(t)) return { kind: 'done', text: '已下载', pct: 1 };
|
||||
if (t.inLibrary) return { kind: 'library', text: '曲库中有', pct: 1, title: `曲库中有同名歌曲:${t.inLibrary}` };
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -158,9 +167,9 @@ const best = (t: TrackSummary) => bestQuality(t.qualities);
|
||||
</span>
|
||||
<span class="state">
|
||||
<template v-if="rowState(t)">
|
||||
<span class="st" :class="rowState(t)!.kind">
|
||||
<Icon v-if="rowState(t)!.kind === 'done'" name="check" :size="14" />
|
||||
{{ rowState(t)!.kind === 'done' ? '' : rowState(t)!.text }}
|
||||
<span class="st" :class="rowState(t)!.kind" :title="rowState(t)!.title ?? rowState(t)!.text">
|
||||
<Icon v-if="rowState(t)!.kind === 'done' || rowState(t)!.kind === 'library'" name="check" :size="14" />
|
||||
{{ rowState(t)!.kind === 'done' ? '' : rowState(t)!.kind === 'library' ? '曲库' : rowState(t)!.text }}
|
||||
</span>
|
||||
<span v-if="rowState(t)!.kind === 'running'" class="bar"><i :style="{ width: `${rowState(t)!.pct * 100}%` }" /></span>
|
||||
</template>
|
||||
@@ -392,6 +401,11 @@ const best = (t: TrackSummary) => bestQuality(t.qualities);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.st.library {
|
||||
color: var(--ink-3);
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.st.done {
|
||||
color: var(--ok);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user