@@ -63,32 +63,146 @@ export class PlayModeManager {
6363 }
6464
6565 /**
66- * 切换随机模式
67- * @param mode 可选,直接设置目标模式。如果不传则按 Off -> On -> Heartbeat -> Off 顺序轮转
68- * @param playAction 回调函数,用于在心动模式下触发播放。通常你应该传入 PlayerController.play,或者其他类似的方法
66+ * 中止之前的请求并清除 Loading 消息
67+ * @returns 新的 AbortSignal
6968 */
70- public async toggleShuffle ( mode ?: ShuffleModeType , playAction ?: ( ) => Promise < void > ) {
69+ private resetCurrentTask ( ) : AbortSignal {
70+ if ( this . currentAbortController ) {
71+ this . currentAbortController . abort ( ) ;
72+ }
73+ this . clearLoadingMessage ( ) ;
74+ this . currentAbortController = new AbortController ( ) ;
75+ return this . currentAbortController . signal ;
76+ }
77+
78+ /**
79+ * 计算下一个随机模式
80+ */
81+ private calculateNextShuffleMode (
82+ currentMode : ShuffleModeType ,
83+ targetMode ?: ShuffleModeType ,
84+ ) : ShuffleModeType {
85+ if ( targetMode ) return targetMode ;
86+ if ( currentMode === "off" ) return "on" ;
87+ if ( currentMode === "on" ) return "heartbeat" ;
88+ return "off" ;
89+ }
90+
91+ /**
92+ * 执行开启随机模式的操作
93+ */
94+ private async applyShuffleOn ( signal : AbortSignal ) {
7195 const dataStore = useDataStore ( ) ;
7296 const statusStore = useStatusStore ( ) ;
7397 const musicStore = useMusicStore ( ) ;
7498
75- if ( this . currentAbortController ) {
76- this . currentAbortController . abort ( ) ;
99+ const currentList = [ ...dataStore . playList ] ;
100+ // 备份原始列表
101+ await dataStore . setOriginalPlayList ( currentList ) ;
102+
103+ if ( signal . aborted ) return ;
104+
105+ // 打乱列表
106+ const shuffled = shuffleArray ( currentList ) ;
107+ await dataStore . setPlayList ( shuffled ) ;
108+
109+ // 修正当前播放索引
110+ const idx = shuffled . findIndex ( ( s ) => s . id === musicStore . playSong ?. id ) ;
111+ if ( idx !== - 1 ) statusStore . playIndex = idx ;
112+ }
113+
114+ /**
115+ * 执行开启心动模式的操作
116+ */
117+ private async applyHeartbeatMode (
118+ signal : AbortSignal ,
119+ previousMode : ShuffleModeType ,
120+ playAction ?: ( ) => Promise < void > ,
121+ ) {
122+ const statusStore = useStatusStore ( ) ;
123+ const musicStore = useMusicStore ( ) ;
124+ const dataStore = useDataStore ( ) ;
125+
126+ if ( isLogin ( ) !== 1 ) {
127+ // 未登录,回滚状态
128+ statusStore . shuffleMode = previousMode ;
129+ if ( isLogin ( ) === 0 ) {
130+ openUserLogin ( true ) ;
131+ } else {
132+ window . $message . warning ( "该登录模式暂不支持该操作" ) ;
133+ }
134+ return ;
135+ }
136+
137+ if ( previousMode === "heartbeat" ) {
138+ if ( playAction ) await playAction ( ) ;
139+ statusStore . showFullPlayer = true ;
140+ return ;
77141 }
142+
143+ this . loadingMessage = window . $message . loading ( "心动模式开启中..." , {
144+ duration : 0 , // 不自动关闭,必须手动 destroy
145+ } ) ;
146+
147+ const pid =
148+ musicStore . playPlaylistId || ( await dataStore . getUserLikePlaylist ( ) ) ?. detail ?. id || 0 ;
149+ const currentSongId = musicStore . playSong ?. id || 0 ;
150+
151+ if ( ! currentSongId ) throw new Error ( "无播放歌曲" ) ;
152+
153+ const res = await heartRateList ( currentSongId , pid , undefined , signal ) ;
154+ if ( res . code !== 200 ) throw new Error ( "获取推荐失败" ) ;
155+
156+ const recList = formatSongsList ( res . data ) ;
157+
158+ // 混合列表
159+ const currentList = [ ...dataStore . playList ] ;
160+ const mixedList = interleaveLists ( currentList , recList ) ;
161+
162+ await dataStore . setPlayList ( mixedList ) ;
163+
164+ const idx = mixedList . findIndex ( ( s ) => s . id === currentSongId ) ;
165+ if ( idx !== - 1 ) statusStore . playIndex = idx ;
166+
78167 this . clearLoadingMessage ( ) ;
79- this . currentAbortController = new AbortController ( ) ;
80- const { signal } = this . currentAbortController ;
168+ window . $message . success ( "心动模式已开启" ) ;
169+ }
81170
82- const currentMode = statusStore . shuffleMode ;
83- let nextMode : ShuffleModeType ;
171+ /**
172+ * 执行关闭随机模式的操作
173+ *
174+ * 会恢复原始列表 和/或 清理推荐歌曲
175+ */
176+ private async applyShuffleOff ( ) {
177+ const dataStore = useDataStore ( ) ;
178+ const statusStore = useStatusStore ( ) ;
179+ const musicStore = useMusicStore ( ) ;
84180
85- if ( mode ) {
86- nextMode = mode ;
181+ // 恢复原始列表
182+ const original = await dataStore . getOriginalPlayList ( ) ;
183+
184+ if ( original && original . length > 0 ) {
185+ await dataStore . setPlayList ( original ) ;
186+ const idx = original . findIndex ( ( s ) => s . id === musicStore . playSong ?. id ) ;
187+ statusStore . playIndex = idx !== - 1 ? idx : 0 ;
188+ await dataStore . clearOriginalPlayList ( ) ;
87189 } else {
88- if ( currentMode === "off" ) nextMode = "on" ;
89- else if ( currentMode === "on" ) nextMode = "heartbeat" ;
90- else nextMode = "off" ;
190+ const cleaned = cleanRecommendations ( dataStore . playList ) ;
191+ await dataStore . setPlayList ( cleaned ) ;
91192 }
193+ }
194+
195+ /**
196+ * 切换随机模式
197+ * @param mode 可选,直接设置目标模式。如果不传则按 Off -> On -> Heartbeat -> Off 顺序轮转
198+ * @param playAction 回调函数,用于在心动模式下触发播放。通常你应该传入 PlayerController.play,或者其他类似的方法
199+ */
200+ public async toggleShuffle ( mode ?: ShuffleModeType , playAction ?: ( ) => Promise < void > ) {
201+ const statusStore = useStatusStore ( ) ;
202+ const signal = this . resetCurrentTask ( ) ;
203+
204+ const currentMode = statusStore . shuffleMode ;
205+ const nextMode = this . calculateNextShuffleMode ( currentMode , mode ) ;
92206
93207 if ( nextMode === currentMode ) return ;
94208
@@ -99,97 +213,31 @@ export class PlayModeManager {
99213 // 将耗时的数据处理扔到 UI 图标更新后再进行,避免打乱庞大列表导致点击延迟
100214 setTimeout ( async ( ) => {
101215 if ( signal . aborted ) return ;
216+
102217 try {
103- if ( nextMode === "on" ) {
104- const currentList = [ ...dataStore . playList ] ;
105- // 备份原始列表
106- await dataStore . setOriginalPlayList ( currentList ) ;
107- if ( signal . aborted ) return ;
108- // 打乱列表
109- const shuffled = shuffleArray ( currentList ) ;
110- await dataStore . setPlayList ( shuffled ) ;
111- // 修正当前播放索引
112- const idx = shuffled . findIndex ( ( s ) => s . id === musicStore . playSong ?. id ) ;
113- if ( idx !== - 1 ) statusStore . playIndex = idx ;
114- } else if ( nextMode === "heartbeat" ) {
115- const loginStatus = isLogin ( ) ;
116- if ( loginStatus !== 1 ) {
117- // 未登录,回滚状态
118- statusStore . shuffleMode = previousMode ;
119- if ( loginStatus === 0 ) {
120- openUserLogin ( true ) ;
121- } else {
122- window . $message . warning ( "该登录模式暂不支持该操作" ) ;
123- }
124- return ;
125- }
126-
127- if ( previousMode === "heartbeat" ) {
128- if ( playAction ) await playAction ( ) ;
129- statusStore . showFullPlayer = true ;
130- return ;
131- }
132-
133- this . loadingMessage = window . $message . loading ( "心动模式开启中..." , {
134- duration : 0 , // 不自动关闭,必须手动 destroy
135- } ) ;
136-
137- try {
138- const pid =
139- musicStore . playPlaylistId || ( await dataStore . getUserLikePlaylist ( ) ) ?. detail ?. id || 0 ;
140- const currentSongId = musicStore . playSong ?. id || 0 ;
141-
142- if ( ! currentSongId ) throw new Error ( "无播放歌曲" ) ;
143-
144- const res = await heartRateList ( currentSongId , pid , undefined , signal ) ;
145- if ( res . code !== 200 ) throw new Error ( "获取推荐失败" ) ;
146-
147- const recList = formatSongsList ( res . data ) ;
148-
149- // 混合列表
150- const currentList = [ ...dataStore . playList ] ;
151- const mixedList = interleaveLists ( currentList , recList ) ;
152-
153- await dataStore . setPlayList ( mixedList ) ;
154-
155- const idx = mixedList . findIndex ( ( s ) => s . id === currentSongId ) ;
156- if ( idx !== - 1 ) statusStore . playIndex = idx ;
157-
158- this . clearLoadingMessage ( ) ;
159- window . $message . success ( "心动模式已开启" ) ;
160- } catch ( e ) {
161- this . clearLoadingMessage ( ) ;
162-
163- if ( signal . aborted || axios . isCancel ( e ) ) {
164- return ;
165- }
166-
167- console . error ( e ) ;
168- window . $message . error ( "心动模式开启失败" ) ;
169- // 失败回滚
170- statusStore . shuffleMode = previousMode ;
171- }
172- } else {
173- // 恢复原始列表
174- const original = await dataStore . getOriginalPlayList ( ) ;
175-
176- if ( original && original . length > 0 ) {
177- await dataStore . setPlayList ( original ) ;
178- const idx = original . findIndex ( ( s ) => s . id === musicStore . playSong ?. id ) ;
179- statusStore . playIndex = idx !== - 1 ? idx : 0 ;
180- await dataStore . clearOriginalPlayList ( ) ;
181- } else {
182- const cleaned = cleanRecommendations ( dataStore . playList ) ;
183- await dataStore . setPlayList ( cleaned ) ;
184- }
218+ switch ( nextMode ) {
219+ case "on" :
220+ await this . applyShuffleOn ( signal ) ;
221+ break ;
222+ case "heartbeat" :
223+ await this . applyHeartbeatMode ( signal , previousMode , playAction ) ;
224+ break ;
225+ default :
226+ await this . applyShuffleOff ( ) ;
227+ break ;
185228 }
186229 } catch ( e ) {
187- if ( signal . aborted ) return ;
230+ if ( signal . aborted || axios . isCancel ( e ) ) return ;
231+
188232 this . clearLoadingMessage ( ) ;
233+
189234 console . error ( "切换模式时发生错误:" , e ) ;
235+
190236 // 失败回滚
191237 statusStore . shuffleMode = previousMode ;
192- window . $message . error ( "模式切换出错" ) ;
238+
239+ const errorMsg = ( e as Error ) . message || "模式切换出错" ;
240+ window . $message . error ( errorMsg ) ;
193241 }
194242 } , 10 ) ;
195243 }
0 commit comments