mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
✨ feat: 重构心动模式与私人FM播放逻辑
- 心动模式从播放模式循环中独立,移至 SearchBar 作为独立按钮 - 新增私人FM自动续播:播放结束后自动获取下一首 - 播放列表设置时自动清除FM模式标志 - 顺序播放模式播放到最后一首后正确停止 - 新增获取关注歌手新歌 API - 补充心动模式相关 i18n 翻译
This commit is contained in:
@@ -194,7 +194,9 @@ export default {
|
||||
tabPlaylist: 'Playlist',
|
||||
tabMv: 'MV',
|
||||
tabCharts: 'Charts',
|
||||
cancelSearch: 'Cancel'
|
||||
cancelSearch: 'Cancel',
|
||||
intelligenceMode: 'Intelligence Mode',
|
||||
exitIntelligence: 'Exit Intelligence Mode'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: 'Choose how to close',
|
||||
|
||||
@@ -194,7 +194,9 @@ export default {
|
||||
tabPlaylist: 'プレイリスト',
|
||||
tabMv: 'MV',
|
||||
tabCharts: 'チャート',
|
||||
cancelSearch: 'キャンセル'
|
||||
cancelSearch: 'キャンセル',
|
||||
intelligenceMode: '心動モード',
|
||||
exitIntelligence: '心動モードを終了'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '閉じる方法を選択してください',
|
||||
|
||||
@@ -193,7 +193,9 @@ export default {
|
||||
tabPlaylist: '플레이리스트',
|
||||
tabMv: 'MV',
|
||||
tabCharts: '차트',
|
||||
cancelSearch: '취소'
|
||||
cancelSearch: '취소',
|
||||
intelligenceMode: '심쿵 모드',
|
||||
exitIntelligence: '심쿵 모드 종료'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '닫기 방법을 선택해주세요',
|
||||
|
||||
@@ -187,7 +187,9 @@ export default {
|
||||
tabPlaylist: '播放列表',
|
||||
tabMv: 'MV',
|
||||
tabCharts: '排行榜',
|
||||
cancelSearch: '取消'
|
||||
cancelSearch: '取消',
|
||||
intelligenceMode: '心动模式',
|
||||
exitIntelligence: '退出心动模式'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '请选择关闭方式',
|
||||
|
||||
@@ -187,7 +187,9 @@ export default {
|
||||
tabPlaylist: '播放清單',
|
||||
tabMv: 'MV',
|
||||
tabCharts: '排行榜',
|
||||
cancelSearch: '取消'
|
||||
cancelSearch: '取消',
|
||||
intelligenceMode: '心動模式',
|
||||
exitIntelligence: '退出心動模式'
|
||||
},
|
||||
titleBar: {
|
||||
closeTitle: '請選擇關閉方式',
|
||||
|
||||
@@ -19,3 +19,8 @@ export const getArtistTopSongs = (params) => {
|
||||
export const getArtistAlbums = (params) => {
|
||||
return request.get('/artist/album', { params });
|
||||
};
|
||||
|
||||
// 获取关注歌手新歌
|
||||
export const getArtistNewSongs = (limit: number = 20) => {
|
||||
return request.get<any>('/artist/new/song', { params: { limit } });
|
||||
};
|
||||
|
||||
@@ -467,13 +467,44 @@ const setupAudioListeners = () => {
|
||||
};
|
||||
|
||||
// 监听结束
|
||||
audioService.on('end', () => {
|
||||
audioService.on('end', async () => {
|
||||
console.log('音频播放结束事件触发');
|
||||
clearInterval();
|
||||
|
||||
if (getPlayerStore().playMode === 1) {
|
||||
// 单曲循环模式
|
||||
replayMusic();
|
||||
} else if (getPlayerStore().isFmPlaying) {
|
||||
// 私人FM模式:自动获取下一首
|
||||
try {
|
||||
const { getPersonalFM } = await import('@/api/home');
|
||||
const res = await getPersonalFM();
|
||||
const songs = res.data?.data;
|
||||
if (Array.isArray(songs) && songs.length > 0) {
|
||||
const song = songs[0];
|
||||
const fmSong = {
|
||||
id: song.id,
|
||||
name: song.name,
|
||||
picUrl: song.al?.picUrl || song.album?.picUrl,
|
||||
ar: song.artists || song.ar,
|
||||
al: song.al || song.album,
|
||||
source: 'netease' as const,
|
||||
song,
|
||||
...song,
|
||||
playLoading: false
|
||||
} as any;
|
||||
const { usePlaylistStore } = await import('@/store/modules/playlist');
|
||||
const playlistStore = usePlaylistStore();
|
||||
playlistStore.setPlayList([fmSong], false, false);
|
||||
getPlayerStore().isFmPlaying = true; // setPlayList 会清除,需重设
|
||||
await getPlayerStore().handlePlayMusic(fmSong, true);
|
||||
} else {
|
||||
getPlayerStore().setIsPlay(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('FM自动播放下一首失败:', error);
|
||||
getPlayerStore().setIsPlay(false);
|
||||
}
|
||||
} else {
|
||||
// 顺序播放、列表循环、随机播放模式都使用统一的nextPlay方法
|
||||
getPlayerStore().nextPlay();
|
||||
|
||||
@@ -14,7 +14,7 @@ export function usePlayMode() {
|
||||
// 当前播放模式
|
||||
const playMode = computed(() => playerStore.playMode);
|
||||
|
||||
// 播放模式图标
|
||||
// 播放模式图标(心动模式已移至 SearchBar,不参与循环切换)
|
||||
const playModeIcon = computed(() => {
|
||||
switch (playMode.value) {
|
||||
case 0:
|
||||
@@ -23,8 +23,6 @@ export function usePlayMode() {
|
||||
return 'ri-repeat-one-line';
|
||||
case 2:
|
||||
return 'ri-shuffle-line';
|
||||
case 3:
|
||||
return 'ri-heart-pulse-line';
|
||||
default:
|
||||
return 'ri-repeat-2-line';
|
||||
}
|
||||
@@ -39,8 +37,6 @@ export function usePlayMode() {
|
||||
return t('player.playBar.playMode.loop');
|
||||
case 2:
|
||||
return t('player.playBar.playMode.random');
|
||||
case 3:
|
||||
return t('player.playBar.intelligenceMode.title');
|
||||
default:
|
||||
return t('player.playBar.playMode.sequence');
|
||||
}
|
||||
|
||||
@@ -105,6 +105,24 @@
|
||||
</n-badge>
|
||||
</button>
|
||||
|
||||
<!-- 心动模式按钮 -->
|
||||
<n-tooltip v-if="showIntelligenceBtn" trigger="hover">
|
||||
<template #trigger>
|
||||
<button
|
||||
class="action-btn"
|
||||
:class="{ 'intelligence-active': isIntelligenceMode }"
|
||||
@click="toggleIntelligenceMode"
|
||||
>
|
||||
<i class="ri-heart-pulse-line" />
|
||||
</button>
|
||||
</template>
|
||||
{{
|
||||
isIntelligenceMode
|
||||
? t('comp.searchBar.exitIntelligence')
|
||||
: t('comp.searchBar.intelligenceMode')
|
||||
}}
|
||||
</n-tooltip>
|
||||
|
||||
<!-- 用户 -->
|
||||
<n-popover trigger="hover" placement="bottom-end" :show-arrow="false" raw>
|
||||
<template #trigger>
|
||||
@@ -205,6 +223,7 @@ import Coffee from '@/components/Coffee.vue';
|
||||
import { SEARCH_TYPES, USER_SET_OPTIONS } from '@/const/bar-const';
|
||||
import { useDownloadStatus } from '@/hooks/useDownloadStatus';
|
||||
import { useZoom } from '@/hooks/useZoom';
|
||||
import { useIntelligenceModeStore } from '@/store/modules/intelligenceMode';
|
||||
import { useNavTitleStore } from '@/store/modules/navTitle';
|
||||
import { useSearchStore } from '@/store/modules/search';
|
||||
import { useSettingsStore } from '@/store/modules/settings';
|
||||
@@ -223,6 +242,7 @@ const userStore = useUserStore();
|
||||
const userSetOptions = ref(USER_SET_OPTIONS);
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const intelligenceModeStore = useIntelligenceModeStore();
|
||||
const { downloadingCount, navigateToDownloads } = useDownloadStatus();
|
||||
const showDownloadButton = computed(
|
||||
() =>
|
||||
@@ -230,6 +250,17 @@ const showDownloadButton = computed(
|
||||
);
|
||||
const { zoomFactor, initZoomFactor, increaseZoom, decreaseZoom, resetZoom, isZoom100 } = useZoom();
|
||||
|
||||
// ── 心动模式 ─────────────────────────────────────────
|
||||
const isIntelligenceMode = computed(() => intelligenceModeStore.isIntelligenceMode);
|
||||
const showIntelligenceBtn = computed(() => userStore.user && userStore.loginType === 'cookie');
|
||||
const toggleIntelligenceMode = async () => {
|
||||
if (isIntelligenceMode.value) {
|
||||
intelligenceModeStore.clearIntelligenceMode();
|
||||
} else {
|
||||
await intelligenceModeStore.playIntelligenceMode();
|
||||
}
|
||||
};
|
||||
|
||||
// ── Back button ───────────────────────────────────────
|
||||
const showBackButton = computed(() => {
|
||||
const meta = router.currentRoute.value.meta;
|
||||
@@ -681,6 +712,16 @@ onMounted(() => {
|
||||
background: rgba(34, 197, 94, 0.08);
|
||||
color: #22c55e;
|
||||
}
|
||||
.action-btn.intelligence-active {
|
||||
color: #ec4899;
|
||||
border-color: #fbcfe8;
|
||||
background: #fdf2f8;
|
||||
}
|
||||
.dark .action-btn.intelligence-active {
|
||||
color: #ec4899;
|
||||
border-color: #831843;
|
||||
background: rgba(236, 72, 153, 0.1);
|
||||
}
|
||||
|
||||
/* ── User button ─────────────────────────────────────── */
|
||||
.user-btn {
|
||||
|
||||
@@ -33,8 +33,17 @@ export const usePlayerStore = defineStore('player', () => {
|
||||
const intelligenceMode = useIntelligenceModeStore();
|
||||
|
||||
// 使用 storeToRefs 获取响应式引用
|
||||
const { play, isPlay, playMusic, playMusicUrl, musicFull, playbackRate, volume, userPlayIntent } =
|
||||
storeToRefs(playerCore);
|
||||
const {
|
||||
play,
|
||||
isPlay,
|
||||
playMusic,
|
||||
playMusicUrl,
|
||||
musicFull,
|
||||
playbackRate,
|
||||
volume,
|
||||
userPlayIntent,
|
||||
isFmPlaying
|
||||
} = storeToRefs(playerCore);
|
||||
|
||||
const { playList, playListIndex, playMode, originalPlayList, playListDrawerVisible } =
|
||||
storeToRefs(playlist);
|
||||
@@ -88,6 +97,7 @@ export const usePlayerStore = defineStore('player', () => {
|
||||
playbackRate,
|
||||
volume,
|
||||
userPlayIntent,
|
||||
isFmPlaying,
|
||||
|
||||
// PlayerCore - Computed
|
||||
currentSong,
|
||||
|
||||
@@ -35,6 +35,7 @@ export const usePlayerCoreStore = defineStore(
|
||||
const playbackRate = ref(1.0);
|
||||
const volume = ref(1);
|
||||
const userPlayIntent = ref(false); // 用户是否想要播放
|
||||
const isFmPlaying = ref(false); // 是否正在播放私人FM
|
||||
|
||||
// 音频输出设备
|
||||
const audioOutputDeviceId = ref<string>(
|
||||
@@ -689,6 +690,7 @@ export const usePlayerCoreStore = defineStore(
|
||||
playbackRate,
|
||||
volume,
|
||||
userPlayIntent,
|
||||
isFmPlaying,
|
||||
audioOutputDeviceId,
|
||||
availableAudioDevices,
|
||||
|
||||
|
||||
@@ -260,6 +260,12 @@ export const usePlaylistStore = defineStore(
|
||||
}
|
||||
}
|
||||
|
||||
// 当新播放列表长度>1时,清除FM模式标志(FM播放列表只有1首)
|
||||
if (list.length > 1) {
|
||||
const playerCore = usePlayerCoreStore();
|
||||
playerCore.isFmPlaying = false;
|
||||
}
|
||||
|
||||
if (list.length === 0) {
|
||||
playList.value = [];
|
||||
playListIndex.value = 0;
|
||||
@@ -373,21 +379,14 @@ export const usePlaylistStore = defineStore(
|
||||
* 切换播放模式
|
||||
*/
|
||||
const togglePlayMode = async () => {
|
||||
const { useUserStore } = await import('./user');
|
||||
const userStore = useUserStore();
|
||||
const wasRandom = playMode.value === 2;
|
||||
const wasIntelligence = playMode.value === 3;
|
||||
|
||||
let newMode = (playMode.value + 1) % 4;
|
||||
|
||||
// 如果要切换到心动模式,但用户未使用cookie登录,则跳过
|
||||
if (newMode === 3 && (!userStore.user || userStore.loginType !== 'cookie')) {
|
||||
console.log('跳过心动模式:需要cookie登录');
|
||||
newMode = 0;
|
||||
}
|
||||
// 心动模式(3)不参与循环切换,仅通过 SearchBar 入口进入
|
||||
// 如果当前是心动模式,切换回顺序播放
|
||||
const newMode = wasIntelligence ? 0 : (playMode.value + 1) % 3;
|
||||
|
||||
const isRandom = newMode === 2;
|
||||
const isIntelligence = newMode === 3;
|
||||
|
||||
console.log(`[PlaylistStore] togglePlayMode: ${playMode.value} -> ${newMode}`);
|
||||
playMode.value = newMode;
|
||||
@@ -404,15 +403,8 @@ export const usePlaylistStore = defineStore(
|
||||
console.log('切换出随机模式,恢复原始顺序');
|
||||
}
|
||||
|
||||
// 切换到心动模式
|
||||
if (isIntelligence && !wasIntelligence) {
|
||||
console.log('切换到心动模式');
|
||||
const intelligenceStore = useIntelligenceModeStore();
|
||||
await intelligenceStore.playIntelligenceMode();
|
||||
}
|
||||
|
||||
// 从心动模式切换出去
|
||||
if (!isIntelligence && wasIntelligence) {
|
||||
if (wasIntelligence) {
|
||||
console.log('退出心动模式');
|
||||
const intelligenceStore = useIntelligenceModeStore();
|
||||
intelligenceStore.clearIntelligenceMode(true);
|
||||
@@ -441,13 +433,15 @@ export const usePlaylistStore = defineStore(
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是播放列表的最后一首且设置了播放列表结束定时
|
||||
if (
|
||||
playMode.value === 0 &&
|
||||
playListIndex.value === playList.value.length - 1 &&
|
||||
sleepTimerStore.sleepTimer.type === 'end'
|
||||
) {
|
||||
sleepTimerStore.stopPlayback();
|
||||
// 顺序播放模式:播放到最后一首后停止
|
||||
if (playMode.value === 0 && playListIndex.value >= playList.value.length - 1) {
|
||||
if (sleepTimerStore.sleepTimer.type === 'end') {
|
||||
sleepTimerStore.stopPlayback();
|
||||
}
|
||||
console.log('[nextPlay] 顺序播放模式:已播放到最后一首,停止播放');
|
||||
playerCore.setIsPlay(false);
|
||||
const { audioService } = await import('@/services/audioService');
|
||||
audioService.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user