mirror of
https://github.com/atdunbg/Nekosonic-Music.git
synced 2026-06-22 00:58:51 +08:00
v0.5.1: 修复缓存/FM/翻译/Linux等问题
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
|||||||
|
## v0.5.1
|
||||||
|
|
||||||
|
### 🐛 修复
|
||||||
|
- 修复页面缓存不刷新的问题:切换回已缓存的页面时数据永远不更新,现在超过 5 分钟会自动重新加载
|
||||||
|
- 修复本地音乐页面空列表时刷新按钮不显示的问题
|
||||||
|
- 修复修改下载路径后本地音乐列表不更新的问题,现在会自动刷新
|
||||||
|
- 修复私人 FM 播放约二三十首后循环重复的问题:新增听歌打卡上报,服务端推荐不再重复
|
||||||
|
- 修复歌词界面切换翻译开关时歌词未居中的问题
|
||||||
|
- 修复 Linux 下从外部控制暂停时进度条跳回 0 的问题:MPRIS 现在正确报告播放进度位置
|
||||||
|
|
||||||
|
### ⚡ 优化
|
||||||
|
- 私人 FM 预取队列优化,队列剩余不足时自动后台拉取下一批
|
||||||
|
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
|
||||||
### ✨ 新功能
|
### ✨ 新功能
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "nekosonic",
|
"name": "nekosonic",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.0",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "Nekosonic"
|
name = "Nekosonic"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"cpal",
|
"cpal",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "Nekosonic"
|
name = "Nekosonic"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
description = "A Simple music app"
|
description = "A Simple music app"
|
||||||
authors = ["atdunbg"]
|
authors = ["atdunbg"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -318,6 +318,21 @@ pub async fn personal_fm(state: State<'_, ApiController>) -> Result<String, Stri
|
|||||||
api_call!(state, personal_fm)
|
api_call!(state, personal_fm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 听歌打卡查询参数
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ScrobbleQuery {
|
||||||
|
pub id: u64,
|
||||||
|
pub sourceid: Option<String>,
|
||||||
|
pub time: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 听歌打卡
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn scrobble(query: ScrobbleQuery, state: State<'_, ApiController>) -> Result<String, String> {
|
||||||
|
api_call!(state, scrobble, params: [("id", &query.id.to_string()), ("sourceid", query.sourceid.as_deref().unwrap_or("")), ("time", &query.time.to_string())])
|
||||||
|
}
|
||||||
|
|
||||||
/// 获取歌曲详情
|
/// 获取歌曲详情
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_song_detail(id: String, state: State<'_, ApiController>) -> Result<String, String> {
|
pub async fn get_song_detail(id: String, state: State<'_, ApiController>) -> Result<String, String> {
|
||||||
|
|||||||
@ -145,6 +145,7 @@ pub fn run() {
|
|||||||
api::recommend_resource,
|
api::recommend_resource,
|
||||||
api::recommend_songs,
|
api::recommend_songs,
|
||||||
api::personal_fm,
|
api::personal_fm,
|
||||||
|
api::scrobble,
|
||||||
api::get_song_detail,
|
api::get_song_detail,
|
||||||
api::get_qr_key,
|
api::get_qr_key,
|
||||||
api::create_qr,
|
api::create_qr,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use tauri::{AppHandle, Emitter, Listener};
|
use tauri::{AppHandle, Emitter, Listener};
|
||||||
use souvlaki::{
|
use souvlaki::{
|
||||||
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback,
|
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback,
|
||||||
PlatformConfig, SeekDirection,
|
MediaPosition, PlatformConfig, SeekDirection,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MediaState {
|
struct MediaState {
|
||||||
@ -79,9 +79,15 @@ pub fn start_media_controls(app_handle: AppHandle, hwnd: Option<*mut std::ffi::c
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(status) = data.get("status").and_then(|v| v.as_str()) {
|
if let Some(status) = data.get("status").and_then(|v| v.as_str()) {
|
||||||
|
let position_us = data.get("positionUs").and_then(|v| v.as_i64()).unwrap_or(0);
|
||||||
|
let progress = if position_us > 0 {
|
||||||
|
Some(MediaPosition(std::time::Duration::from_micros(position_us as u64)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
let playback = match status {
|
let playback = match status {
|
||||||
"playing" => MediaPlayback::Playing { progress: None },
|
"playing" => MediaPlayback::Playing { progress },
|
||||||
"paused" => MediaPlayback::Paused { progress: None },
|
"paused" => MediaPlayback::Paused { progress },
|
||||||
_ => MediaPlayback::Stopped,
|
_ => MediaPlayback::Stopped,
|
||||||
};
|
};
|
||||||
let _ = s.controls.set_playback(playback);
|
let _ = s.controls.set_playback(playback);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Nekosonic",
|
"productName": "Nekosonic",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"identifier": "com.atdunbg.Nekosonic",
|
"identifier": "com.atdunbg.Nekosonic",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@ -375,6 +375,12 @@ watch(currentLyricIdx, () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(showTranslation, () => {
|
||||||
|
if (player.showRoamDrawer && !roamLyricHovering.value) {
|
||||||
|
nextTick(() => scrollToRoamActiveLyric());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function scrollToRoamActiveLyric() {
|
function scrollToRoamActiveLyric() {
|
||||||
if (!lyricScrollContainer.value || roamLyricHovering.value) return;
|
if (!lyricScrollContainer.value || roamLyricHovering.value) return;
|
||||||
const active = lyricScrollContainer.value.querySelector('.roam-lyric-active') as HTMLElement | null;
|
const active = lyricScrollContainer.value.querySelector('.roam-lyric-active') as HTMLElement | null;
|
||||||
|
|||||||
@ -22,3 +22,9 @@ export function pageCacheDelete(key: string) {
|
|||||||
export function pageCacheInvalidate(key: string) {
|
export function pageCacheInvalidate(key: string) {
|
||||||
cache.delete(key);
|
cache.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pageCacheIsStale(key: string): boolean {
|
||||||
|
const entry = cache.get(key);
|
||||||
|
if (!entry) return true;
|
||||||
|
return Date.now() - entry.ts > TTL;
|
||||||
|
}
|
||||||
|
|||||||
@ -111,8 +111,34 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
const isFmMode = ref(false);
|
const isFmMode = ref(false);
|
||||||
|
const fmQueue: Song[] = [];
|
||||||
let fmNextCallback: (() => void) | null = null;
|
let fmNextCallback: (() => void) | null = null;
|
||||||
|
|
||||||
|
let lastScrobbleId: number | null = null;
|
||||||
|
let lastScrobbleStartTime: number = 0;
|
||||||
|
|
||||||
|
function reportScrobble() {
|
||||||
|
const song = currentSong.value;
|
||||||
|
if (!song || song.localPath || song.id == null) {
|
||||||
|
lastScrobbleId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastScrobbleId === song.id && lastScrobbleStartTime > 0) {
|
||||||
|
const playedSec = Math.round((Date.now() - lastScrobbleStartTime) / 1000);
|
||||||
|
if (playedSec > 5) {
|
||||||
|
invoke('scrobble', {
|
||||||
|
query: {
|
||||||
|
id: song.id,
|
||||||
|
sourceid: isFmMode.value ? String(song.id) : '',
|
||||||
|
time: playedSec,
|
||||||
|
},
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastScrobbleId = song.id;
|
||||||
|
lastScrobbleStartTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
function enableFmMode(onNext: () => void) {
|
function enableFmMode(onNext: () => void) {
|
||||||
isFmMode.value = true;
|
isFmMode.value = true;
|
||||||
fmNextCallback = onNext;
|
fmNextCallback = onNext;
|
||||||
@ -121,6 +147,15 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
function disableFmMode() {
|
function disableFmMode() {
|
||||||
isFmMode.value = false;
|
isFmMode.value = false;
|
||||||
fmNextCallback = null;
|
fmNextCallback = null;
|
||||||
|
fmQueue.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFmBatch(): Promise<Song[]> {
|
||||||
|
const jsonStr: string = await invoke('personal_fm');
|
||||||
|
const data = JSON.parse(jsonStr);
|
||||||
|
const raw = data.data || data;
|
||||||
|
if (!Array.isArray(raw) || raw.length === 0) return [];
|
||||||
|
return raw.map((s: any) => normalizeSong(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fmVipSkipCount = 0;
|
let fmVipSkipCount = 0;
|
||||||
@ -128,6 +163,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
async function playFmSong(song: Song) {
|
async function playFmSong(song: Song) {
|
||||||
if (tickInterval) { clearInterval(tickInterval); setTickInterval(null); }
|
if (tickInterval) { clearInterval(tickInterval); setTickInterval(null); }
|
||||||
|
reportScrobble();
|
||||||
if (!song.dt || song.dt === 0) {
|
if (!song.dt || song.dt === 0) {
|
||||||
try {
|
try {
|
||||||
const jsonStr: string = await invoke('get_song_detail', { id: String(song.id) });
|
const jsonStr: string = await invoke('get_song_detail', { id: String(song.id) });
|
||||||
@ -248,6 +284,7 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
async function playCurrent() {
|
async function playCurrent() {
|
||||||
if (tickInterval) { clearInterval(tickInterval); setTickInterval(null); }
|
if (tickInterval) { clearInterval(tickInterval); setTickInterval(null); }
|
||||||
|
reportScrobble();
|
||||||
const song = queue.value[currentIndex.value];
|
const song = queue.value[currentIndex.value];
|
||||||
if (!song?.id) {
|
if (!song?.id) {
|
||||||
console.error('无效的歌曲数据', song);
|
console.error('无效的歌曲数据', song);
|
||||||
@ -494,12 +531,11 @@ export const usePlayerStore = defineStore('player', () => {
|
|||||||
|
|
||||||
async function loadFirstFmSong() {
|
async function loadFirstFmSong() {
|
||||||
try {
|
try {
|
||||||
const jsonStr: string = await invoke('personal_fm');
|
const batch = await fetchFmBatch();
|
||||||
const data = JSON.parse(jsonStr);
|
if (batch.length > 0) {
|
||||||
const songs = data.data || data;
|
fmQueue.push(...batch);
|
||||||
if (songs && songs.length > 0) {
|
const song = fmQueue.shift()!;
|
||||||
const song = normalizeSong(songs[0]);
|
enableFmMode(nextFm);
|
||||||
enableFmMode(() => loadFirstFmSong());
|
|
||||||
await playFmSong(song);
|
await playFmSong(song);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -516,15 +552,18 @@ const fmPlaying = ref(false);
|
|||||||
|
|
||||||
async function loadFm() {
|
async function loadFm() {
|
||||||
try {
|
try {
|
||||||
const jsonStr: string = await invoke('personal_fm');
|
if (fmQueue.length === 0) {
|
||||||
const data = JSON.parse(jsonStr);
|
const batch = await fetchFmBatch();
|
||||||
const songs = data.data || data;
|
if (batch.length === 0) return;
|
||||||
if (songs && songs.length > 0) {
|
fmQueue.push(...batch);
|
||||||
const song = normalizeSong(songs[0]);
|
}
|
||||||
fmSong.value = song;
|
const song = fmQueue.shift()!;
|
||||||
enableFmMode(nextFm);
|
fmSong.value = song;
|
||||||
await playFmSong(song);
|
enableFmMode(nextFm);
|
||||||
fmPlaying.value = true;
|
await playFmSong(song);
|
||||||
|
fmPlaying.value = true;
|
||||||
|
if (fmQueue.length <= 1) {
|
||||||
|
fetchFmBatch().then(batch => { fmQueue.push(...batch); }).catch(() => {});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('FM加载失败', e);
|
console.error('FM加载失败', e);
|
||||||
@ -565,6 +604,7 @@ listen('audio-started', () => {
|
|||||||
listen('audio-ended', () => {
|
listen('audio-ended', () => {
|
||||||
if (_tickInterval) { clearInterval(_tickInterval); _tickInterval = null; }
|
if (_tickInterval) { clearInterval(_tickInterval); _tickInterval = null; }
|
||||||
const player = usePlayerStore();
|
const player = usePlayerStore();
|
||||||
|
player.reportScrobble();
|
||||||
player.next();
|
player.next();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -597,6 +637,9 @@ listen<string>('mpris-command', (event) => {
|
|||||||
} else if (cmd.startsWith('SetPosition:')) {
|
} else if (cmd.startsWith('SetPosition:')) {
|
||||||
const posUs = parseInt(cmd.slice(13), 10);
|
const posUs = parseInt(cmd.slice(13), 10);
|
||||||
const posSec = posUs / 1_000_000;
|
const posSec = posUs / 1_000_000;
|
||||||
|
if (posSec < 1 && player.currentTime > 5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
player.seek(posSec);
|
player.seek(posSec);
|
||||||
} else if (cmd === 'Raise') {
|
} else if (cmd === 'Raise') {
|
||||||
getCurrentWindow().show().catch(() => {});
|
getCurrentWindow().show().catch(() => {});
|
||||||
@ -666,6 +709,8 @@ watch(playing, (val) => {
|
|||||||
toggleRoamDrawer,
|
toggleRoamDrawer,
|
||||||
loadFirstFmSong,
|
loadFirstFmSong,
|
||||||
|
|
||||||
|
reportScrobble,
|
||||||
|
|
||||||
fmSong,
|
fmSong,
|
||||||
fmPlaying,
|
fmPlaying,
|
||||||
loadFm,
|
loadFm,
|
||||||
|
|||||||
@ -51,11 +51,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import SongListItem from '../components/SongListItem.vue';
|
import SongListItem from '../components/SongListItem.vue';
|
||||||
import { usePlayerStore } from '../stores/player';
|
import { usePlayerStore } from '../stores/player';
|
||||||
import { pageCacheGet, pageCacheSet, pageCacheInvalidate } from '../composables/usePageCache';
|
import { pageCacheGet, pageCacheSet, pageCacheInvalidate, pageCacheIsStale } from '../composables/usePageCache';
|
||||||
import { normalizeSong, type Song } from '../utils/song';
|
import { normalizeSong, type Song } from '../utils/song';
|
||||||
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
||||||
|
|
||||||
@ -92,6 +92,10 @@ async function loadData() {
|
|||||||
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (pageCacheIsStale('dailySongs')) loadData();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isOnline, (val, old) => {
|
watch(isOnline, (val, old) => {
|
||||||
if (val && !old && songs.value.length === 0) {
|
if (val && !old && songs.value.length === 0) {
|
||||||
pageCacheInvalidate('dailySongs');
|
pageCacheInvalidate('dailySongs');
|
||||||
|
|||||||
@ -44,13 +44,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineOptions({ name: 'DiscoverView' });
|
defineOptions({ name: 'DiscoverView' });
|
||||||
|
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { usePlayerStore } from '../stores/player';
|
import { usePlayerStore } from '../stores/player';
|
||||||
import SongListItem from '../components/SongListItem.vue';
|
import SongListItem from '../components/SongListItem.vue';
|
||||||
import { normalizeSong, type Song } from '../utils/song';
|
import { normalizeSong, type Song } from '../utils/song';
|
||||||
import { pageCacheGet, pageCacheSet, pageCacheInvalidate } from '../composables/usePageCache';
|
import { pageCacheGet, pageCacheSet, pageCacheInvalidate, pageCacheIsStale } from '../composables/usePageCache';
|
||||||
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -89,6 +89,10 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (pageCacheIsStale('discover_hotTags')) loadHotTags();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isOnline, (val, old) => {
|
watch(isOnline, (val, old) => {
|
||||||
if (val && !old && hotTags.value.length === 0) {
|
if (val && !old && hotTags.value.length === 0) {
|
||||||
pageCacheInvalidate('discover_hotTags');
|
pageCacheInvalidate('discover_hotTags');
|
||||||
|
|||||||
@ -36,13 +36,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import SongListItem from '../components/SongListItem.vue';
|
import SongListItem from '../components/SongListItem.vue';
|
||||||
import { usePlayerStore } from '../stores/player';
|
import { usePlayerStore } from '../stores/player';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { normalizeSong, type Song } from '../utils/song';
|
import { normalizeSong, type Song } from '../utils/song';
|
||||||
import { pageCacheGet, pageCacheSet, pageCacheInvalidate } from '../composables/usePageCache';
|
import { pageCacheGet, pageCacheSet, pageCacheInvalidate, pageCacheIsStale } from '../composables/usePageCache';
|
||||||
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
||||||
|
|
||||||
defineOptions({ name: 'FavoriteSongsView' });
|
defineOptions({ name: 'FavoriteSongsView' });
|
||||||
@ -87,6 +87,10 @@ async function loadData() {
|
|||||||
|
|
||||||
onMounted(loadData);
|
onMounted(loadData);
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (pageCacheIsStale('favoriteSongs')) loadData();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isOnline, (val, old) => {
|
watch(isOnline, (val, old) => {
|
||||||
if (val && !old && userStore.isLoggedIn && songs.value.length === 0) {
|
if (val && !old && userStore.isLoggedIn && songs.value.length === 0) {
|
||||||
pageCacheInvalidate('favoriteSongs');
|
pageCacheInvalidate('favoriteSongs');
|
||||||
|
|||||||
@ -109,12 +109,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, onActivated, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { usePlayerStore } from '../stores/player';
|
import { usePlayerStore } from '../stores/player';
|
||||||
import { pageCacheGet, pageCacheSet, pageCacheInvalidate } from '../composables/usePageCache';
|
import { pageCacheGet, pageCacheSet, pageCacheInvalidate, pageCacheIsStale } from '../composables/usePageCache';
|
||||||
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
import { useOnlineStatus } from '../composables/useOnlineStatus';
|
||||||
import { getCoverUrl } from '../utils/song';
|
import { getCoverUrl } from '../utils/song';
|
||||||
|
|
||||||
@ -199,6 +199,10 @@ onMounted(async () => {
|
|||||||
await loadData();
|
await loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (pageCacheIsStale('home')) loadData();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isOnline, (val, old) => {
|
watch(isOnline, (val, old) => {
|
||||||
if (val && !old && rankPlaylists.value.length === 0 && recPlaylists.value.length === 0) {
|
if (val && !old && rankPlaylists.value.length === 0 && recPlaylists.value.length === 0) {
|
||||||
pageCacheInvalidate('home');
|
pageCacheInvalidate('home');
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
<h1 class="text-2xl font-bold">本地音乐</h1>
|
<h1 class="text-2xl font-bold">本地音乐</h1>
|
||||||
<span v-if="songs.length" class="text-xs text-content-3">{{ songs.length }} 首</span>
|
<span v-if="songs.length" class="text-xs text-content-3">{{ songs.length }} 首</span>
|
||||||
<button
|
<button
|
||||||
v-if="songs.length"
|
|
||||||
@click="refresh"
|
@click="refresh"
|
||||||
class="px-3 py-1 bg-muted hover:bg-emphasis rounded-full text-xs transition"
|
class="px-3 py-1 bg-muted hover:bg-emphasis rounded-full text-xs transition"
|
||||||
>
|
>
|
||||||
@ -75,13 +74,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, computed, onMounted, onActivated, onBeforeUnmount, watch } from 'vue';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { usePlayerStore } from '../stores/player';
|
import { usePlayerStore } from '../stores/player';
|
||||||
import { useDownload } from '../composables/useDownload';
|
import { useDownload } from '../composables/useDownload';
|
||||||
import { useSettingsStore } from '../stores/settings';
|
import { useSettingsStore } from '../stores/settings';
|
||||||
import { showToast } from '../composables/useToast';
|
import { showToast } from '../composables/useToast';
|
||||||
import { pageCacheSet, pageCacheInvalidate } from '../composables/usePageCache';
|
import { pageCacheSet, pageCacheInvalidate, pageCacheIsStale } from '../composables/usePageCache';
|
||||||
import SongListItem from '../components/SongListItem.vue';
|
import SongListItem from '../components/SongListItem.vue';
|
||||||
import type { Song } from '../utils/song';
|
import type { Song } from '../utils/song';
|
||||||
|
|
||||||
@ -159,6 +158,12 @@ async function fetchMissingCovers() {
|
|||||||
|
|
||||||
onMounted(refresh);
|
onMounted(refresh);
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
if (pageCacheIsStale('localMusic')) refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => settings.downloadPath, () => { refresh(); });
|
||||||
|
|
||||||
function formatFileSize(bytes: number): string {
|
function formatFileSize(bytes: number): string {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
const units = ['B', 'KB', 'MB', 'GB'];
|
const units = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
|||||||
Reference in New Issue
Block a user