Эх сурвалжийг харах

播放列表有一点问题

xiang 3 долоо хоног өмнө
parent
commit
14d0588e9c

+ 73 - 0
src/context/MusicPlayerContext.tsx

@@ -0,0 +1,73 @@
+// src/context/MusicPlayerContext.tsx
+import React, { createContext, useContext, useState, useRef } from 'react';
+import type { Song } from '@/type/Song';
+interface MusicPlayerContextType {
+  currentTrackIndex: number;
+  isPlaying: boolean;
+  playlist: Song[];
+  isPlaylistOpen: boolean;
+  setCurrentTrackIndex: (index: number) => void;
+  setIsPlaying: (playing: boolean) => void;
+  addToPlaylist: (song: Song) => void;
+  removeFromPlaylist: (id: number) => void;
+  setPlaylist: (songs: Song[]) => void;
+  setIsPlaylistOpen: (open: boolean) => void;
+  playNext: () => void;
+  playPrev: () => void;
+}
+
+const MusicPlayerContext = createContext<MusicPlayerContextType | undefined>(undefined);
+
+export const useMusicPlayer = () => {
+  const context = useContext(MusicPlayerContext);
+  if (!context) {
+    throw new Error('useMusicPlayer must be used within a MusicPlayerProvider');
+  }
+  return context;
+};
+
+export const MusicPlayerProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [currentTrackIndex, setCurrentTrackIndex] = useState<number>(0);
+  const [isPlaying, setIsPlaying] = useState<boolean>(false); // 默认不播放
+  const [playlist, setPlaylist] = useState<Song[]>([]);
+  const [isPlaylistOpen, setIsPlaylistOpen] = useState<boolean>(false);
+
+  const playNext = () => {
+    setCurrentTrackIndex((prev) => (prev + 1) % (playlist.length || 1));
+    setIsPlaying(true);
+  };
+
+  const playPrev = () => {
+    setCurrentTrackIndex((prev) => (prev - 1 + (playlist.length || 1)) % (playlist.length || 1));
+    setIsPlaying(true);
+  };
+
+  const addToPlaylist = (song: Song) => {
+    setPlaylist(prev => [...prev, song]);
+  };
+
+  const removeFromPlaylist = (id: number) => {
+    setPlaylist(prev => prev.filter(song => song.id !== id));
+  };
+
+  return (
+    <MusicPlayerContext.Provider
+      value={{
+        currentTrackIndex,
+        isPlaying,
+        playlist,
+        isPlaylistOpen,
+        setCurrentTrackIndex,
+        setIsPlaying,
+        addToPlaylist,
+        removeFromPlaylist,
+        setPlaylist,
+        setIsPlaylistOpen,
+        playNext,
+        playPrev
+      }}
+    >
+      {children}
+    </MusicPlayerContext.Provider>
+  );
+};

+ 1 - 1
src/pages/layout/components/LoginContent/index.tsx

@@ -70,7 +70,7 @@ const LoginContent: React.FC<LoginContentProps> = ({
       console.log(res);
       if (res && res.access_token) {
         setToken(res);
-        setUserinfo(res.user_info);
+        setUserinfo(res.userinfo);
         onLoginSuccess?.();
       } else {
         console.log('登录失败');

+ 3 - 0
src/pages/layout/index.tsx

@@ -7,6 +7,7 @@ import Wy_Footer from './components/Wy_Footer'
 import { ConfigProvider } from 'antd'
 import type { ThemeConfig } from 'antd/es/config-provider/context'
 import MusicPlayer from './pages/testPage'
+import { MusicPlayerProvider } from '@/context/MusicPlayerContext'
 
 // 定义红色主题
 const theme: ThemeConfig = {
@@ -17,6 +18,7 @@ const theme: ThemeConfig = {
 
 const WYLayout = () => {
   return (
+    <MusicPlayerProvider>
       <ConfigProvider theme={theme}>
         <Flex gap="middle" wrap>
           <Layout>
@@ -33,6 +35,7 @@ const WYLayout = () => {
         </Flex>
         <MusicPlayer />
       </ConfigProvider>
+    </MusicPlayerProvider>
   )
 }
 export default WYLayout

+ 108 - 0
src/pages/layout/pages/mine/components/PlaylistDetail/index.css

@@ -0,0 +1,108 @@
+.playlist-page {
+  padding: 20px;
+  font-family: 'Microsoft YaHei', sans-serif;
+}
+.playlist-page .playlist-header {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-bottom: 30px;
+}
+.playlist-page .playlist-header .cover-img {
+  width: 200px;
+  height: 200px;
+  border-radius: 8px;
+  object-fit: cover;
+}
+.playlist-page .playlist-header .playlist-info {
+  flex: 1;
+}
+.playlist-page .playlist-header .playlist-info h1 {
+  margin: 0;
+  font-size: 24px;
+  color: #333;
+}
+.playlist-page .playlist-header .playlist-info .meta {
+  color: #666;
+  font-size: 14px;
+  margin: 8px 0;
+}
+.playlist-page .playlist-header .playlist-info .actions {
+  display: flex;
+  gap: 10px;
+  margin: 10px 0;
+}
+.playlist-page .playlist-header .playlist-info .actions button {
+  padding: 6px 12px;
+  border: 1px solid #ccc;
+  background: #f5f5f5;
+  cursor: pointer;
+  font-size: 12px;
+}
+.playlist-page .playlist-header .playlist-info .actions .play-btn,
+.playlist-page .playlist-header .playlist-info .actions .add-btn {
+  background: linear-gradient(to right, #007bff, #0056b3);
+  color: white;
+  border: none;
+  border-radius: 4px;
+}
+.playlist-page .playlist-header .playlist-info .actions .add-btn {
+  background: #007bff;
+}
+.playlist-page .playlist-header .playlist-info .play-count {
+  color: #999;
+  font-size: 14px;
+}
+.playlist-page .song-list-container {
+  width: 100%;
+  border-collapse: collapse;
+  margin-top: 20px;
+}
+.playlist-page .song-list-container .song-list-header {
+  display: grid;
+  grid-template-columns: 60px 1fr 80px 120px 150px;
+  background-color: #f5f5f5;
+  border-bottom: 2px solid #d90000;
+  font-weight: bold;
+  color: #333;
+  padding: 10px 0;
+  text-align: center;
+}
+.playlist-page .song-list-container .song-list {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+.playlist-page .song-list-container .song-list .song-item {
+  display: grid;
+  grid-template-columns: 60px 1fr 80px 120px 150px;
+  padding: 8px 0;
+  border-bottom: 1px solid #eee;
+  transition: background-color 0.2s ease;
+}
+.playlist-page .song-list-container .song-list .song-item:hover {
+  background-color: #f9f9f9;
+}
+.playlist-page .song-list-container .song-list .song-number,
+.playlist-page .song-list-container .song-list .song-title,
+.playlist-page .song-list-container .song-list .song-duration,
+.playlist-page .song-list-container .song-list .song-artist,
+.playlist-page .song-list-container .song-list .song-album {
+  padding: 4px 0;
+  text-align: center;
+}
+.playlist-page .song-list-container .song-list .song-title {
+  text-align: left;
+  padding-left: 10px;
+  width: 230px;
+}
+.playlist-page .song-list-container .song-list .play-icon {
+  margin-right: 8px;
+  cursor: pointer;
+  color: #666;
+}
+.playlist-page .song-list-container .song-list .album-tag {
+  font-size: 12px;
+  color: #999;
+  margin-left: 4px;
+}

+ 126 - 0
src/pages/layout/pages/mine/components/PlaylistDetail/index.less

@@ -0,0 +1,126 @@
+.playlist-page {
+  padding: 20px;
+  font-family: 'Microsoft YaHei', sans-serif;
+
+  .playlist-header {
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    margin-bottom: 30px;
+
+    .cover-img {
+      width: 200px;
+      height: 200px;
+      border-radius: 8px;
+      object-fit: cover;
+    }
+
+    .playlist-info {
+      flex: 1;
+
+      h1 {
+        margin: 0;
+        font-size: 24px;
+        color: #333;
+      }
+
+      .meta {
+        color: #666;
+        font-size: 14px;
+        margin: 8px 0;
+      }
+
+      .actions {
+        display: flex;
+        gap: 10px;
+        margin: 10px 0;
+
+        button {
+          padding: 6px 12px;
+          border: 1px solid #ccc;
+          background: #f5f5f5;
+          cursor: pointer;
+          font-size: 12px;
+        }
+
+        .play-btn,
+        .add-btn {
+          background: linear-gradient(to right, #007bff, #0056b3);
+          color: white;
+          border: none;
+          border-radius: 4px;
+        }
+
+        .add-btn {
+          background: #007bff;
+        }
+      }
+
+      .play-count {
+        color: #999;
+        font-size: 14px;
+      }
+    }
+  }
+.song-list-container {
+  width: 100%;
+  border-collapse: collapse;
+  margin-top: 20px;
+
+  .song-list-header {
+    display: grid;
+    grid-template-columns: 60px 1fr 80px 120px 150px;
+    background-color: #f5f5f5;
+    border-bottom: 2px solid #d90000;
+    font-weight: bold;
+    color: #333;
+    padding: 10px 0;
+    text-align: center;
+  }
+
+  .song-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+
+    .song-item {
+      display: grid;
+      grid-template-columns: 60px 1fr 80px 120px 150px;
+      padding: 8px 0;
+      border-bottom: 1px solid #eee;
+      transition: background-color 0.2s ease;
+
+      &:hover {
+        background-color: #f9f9f9;
+      }
+    }
+
+    .song-number,
+    .song-title,
+    .song-duration,
+    .song-artist,
+    .song-album {
+      padding: 4px 0;
+      text-align: center;
+    }
+
+    .song-title {
+      text-align: left;
+      padding-left: 10px;
+      width: 230px;
+    }
+
+    .play-icon {
+      margin-right: 8px;
+      cursor: pointer;
+      color: #666;
+    }
+
+    .album-tag {
+      font-size: 12px;
+      color: #999;
+      margin-left: 4px;
+    }
+  }
+}
+}

+ 235 - 0
src/pages/layout/pages/mine/components/PlaylistDetail/index.tsx

@@ -0,0 +1,235 @@
+// src/pages/layout/pages/mine/components/PlaylistDetail/index.tsx
+import React, { useState, useEffect } from 'react';
+import { Image, Spin } from 'antd';
+import { LoadingOutlined } from '@ant-design/icons';
+import { getPayListDetail } from '@/apis/payList';
+import './index.css';
+import { useMusicPlayer } from '@/context/MusicPlayerContext';
+import type { Song } from '@/type/Song';
+
+interface Playlist {
+  id: number;
+  userId: number;
+  playlistName: string;
+  coverUrl: string | null;
+  description: string | null;
+  tag: string | null;
+  songCount: number;
+  playCount: number;
+  createTime: string;
+  updateTime: string;
+  status: boolean;
+}
+
+
+interface PlaylistData {
+  playlist: Playlist;
+  songs: Song[];
+}
+
+// 格式化时间为 YYYY-MM-DD 格式
+const formatDate = (dateString: string) => {
+  const date = new Date(dateString);
+  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+};
+
+// 将秒数转换为 mm:ss 格式
+const formatDuration = (seconds: number) => {
+  const mins = Math.floor(seconds / 60);
+  const secs = seconds % 60;
+  return `${mins}:${secs.toString().padStart(2, '0')}`;
+};
+
+interface PlaylistDetailProps {
+  id: number;
+  showCollect?: boolean;
+}
+
+
+
+const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true }) => {
+  const [playlistData, setPlaylistData] = useState<PlaylistData | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+  const { 
+    setPlaylist, 
+    setCurrentTrackIndex, 
+    setIsPlaying,
+    playlist: contextPlaylist
+  } = useMusicPlayer();
+
+  // 播放指定歌曲
+  const handlePlaySong = (index: number) => {
+    // 确保播放列表已经设置
+    if (contextPlaylist.length > 0) {
+      setCurrentTrackIndex(index);
+      setIsPlaying(true);
+    }
+  };
+
+  // 播放整个歌单
+  const handlePlayPlaylist = () => {
+    if (contextPlaylist.length > 0) {
+      setCurrentTrackIndex(0);
+      setIsPlaying(true);
+    }
+  };
+
+  // 请求歌单详情
+  useEffect(() => {
+    const fetchPlaylistDetails = async () => {
+      try {
+        setLoading(true);
+        setError(null);
+
+        if (!id || id <= 0) {
+          throw new Error('无效的歌单ID');
+        }
+
+        const response = await getPayListDetail(id);
+
+        if (response.code === 200) {
+          const formattedSongs = response.data.songs.map(song => ({
+            id: song.id,
+            title: song.songName,
+            artist: song.singerName || '未知歌手',
+            url: song.fileUrl,
+            cover: song.coverUrl,
+            duration: formatDuration(song.duration)
+          }));
+
+          // 先设置播放列表
+          setPlaylist(formattedSongs);
+          
+          // 延迟一小段时间后再设置播放状态,确保播放列表已更新
+          setTimeout(() => {
+            setCurrentTrackIndex(0);
+            setIsPlaying(true);
+          }, 100);
+
+          setPlaylistData(response.data);
+        } else {
+          throw new Error(response.message || '获取歌单失败');
+        }
+      } catch (err: any) {
+        console.error('获取歌单失败:', err);
+        setError(err.message || '获取歌单失败');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    if (id) {
+      fetchPlaylistDetails();
+    }
+  }, [id]);
+
+
+
+  // 如果没有ID,直接返回错误信息
+  if (!id) {
+    return <div className="error">无效的歌单ID</div>;
+  }
+
+  if (loading) {
+    return (
+      <div className="loading" style={{
+        display: 'flex',
+        justifyContent: 'center',
+        alignItems: 'center',
+        height: '100vh',
+        width: '100vw',
+        position: 'fixed',
+        top: 0,
+        left: 0,
+        backgroundColor: 'rgba(255, 255, 255, 0.8)',
+        zIndex: 9999
+      }}>
+        <Spin indicator={<LoadingOutlined style={{ fontSize: 48 }} spin />} />
+      </div>
+    );
+  }
+
+  if (error) {
+    return <div className="error">错误: {error}</div>;
+  }
+
+  if (!playlistData || !playlistData.playlist) {
+    return <div className="not-found">歌单不存在</div>;
+  }
+
+  const { playlist, songs } = playlistData;
+  const validSongs = Array.isArray(songs) ? songs : [];
+
+
+  return (
+    <div className="playlist-page">
+      {/* 歌单头部 */}
+      <div className="playlist-header">
+        <img
+          src={playlist.coverUrl || 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'}
+          alt={playlist.playlistName}
+          className="cover-img"
+        />
+        <div className="playlist-info">
+          <h1>{playlist.playlistName}</h1>
+          <div className="meta">
+            <span className="create-time"> {formatDate(playlist.createTime)} 创建</span>
+          </div>
+          <div className="actions">
+            <button 
+              className="play-btn" 
+              onClick={handlePlayPlaylist}
+            >
+              播放
+            </button>
+            <button className="add-btn">+</button>
+            {showCollect && <button className="collect-btn">收藏</button>}
+            <button className="share-btn">分享</button>
+            <button className="download-btn">下载</button>
+            <button className="comment-btn">评论</button>
+          </div>
+          <div className="play-count">
+            播放:<strong>{playlist.playCount}</strong>次
+          </div>
+        </div>
+      </div>
+
+      {/* 歌曲列表 */}
+      {validSongs.length > 0 ? (
+        <div className="song-list-container">
+          <div className="song-list-header">
+            <div className="song-number">序号</div>
+            <div className="song-title">歌曲标题</div>
+            <div className="song-duration">时长</div>
+            <div className="song-artist">歌手</div>
+            <div className="song-album">专辑</div>
+          </div>
+
+          <ul className="song-list">
+            {validSongs.map((song, index) => (
+              <li key={song.id} className="song-item">
+                <div className="song-number">{index + 1}</div>
+                <div 
+                  className="song-title" 
+                  onClick={() => handlePlaySong(index)}
+                >
+                  <span className="play-icon">▶</span>
+                  {song.songName}
+                </div>
+                <div className="song-duration">{formatDuration(song.duration)}</div>
+                <div className="song-artist">{song.singerName || '群星'}</div>
+                <div className="song-album">{song.albumName || '群星'}</div>
+              </li>
+            ))}
+          </ul>
+        </div>
+      ) : (
+        <div>
+          <Image preview={false} src="http://117.72.120.45:9000/public/musician/playlist/songNull.png" />
+        </div>
+      )}
+    </div>
+  );
+};
+export default PlaylistDetail;

+ 0 - 107
src/pages/layout/pages/mine/playlist/index.css

@@ -1,107 +0,0 @@
-.playlist-page {
-  padding: 20px;
-  font-family: 'Microsoft YaHei', sans-serif;
-}
-.playlist-page .playlist-header {
-  display: flex;
-  align-items: center;
-  gap: 20px;
-  margin-bottom: 30px;
-}
-.playlist-page .playlist-header .cover-img {
-  width: 200px;
-  height: 200px;
-  border-radius: 8px;
-  object-fit: cover;
-}
-.playlist-page .playlist-header .playlist-info {
-  flex: 1;
-}
-.playlist-page .playlist-header .playlist-info h1 {
-  margin: 0;
-  font-size: 24px;
-  color: #333;
-}
-.playlist-page .playlist-header .playlist-info .meta {
-  color: #666;
-  font-size: 14px;
-  margin: 8px 0;
-}
-.playlist-page .playlist-header .playlist-info .actions {
-  display: flex;
-  gap: 10px;
-  margin: 10px 0;
-}
-.playlist-page .playlist-header .playlist-info .actions button {
-  padding: 6px 12px;
-  border: 1px solid #ccc;
-  background: #f5f5f5;
-  cursor: pointer;
-  font-size: 12px;
-}
-.playlist-page .playlist-header .playlist-info .actions .play-btn,
-.playlist-page .playlist-header .playlist-info .actions .add-btn {
-  background: linear-gradient(to right, #007bff, #0056b3);
-  color: white;
-  border: none;
-  border-radius: 4px;
-}
-.playlist-page .playlist-header .playlist-info .actions .add-btn {
-  background: #007bff;
-}
-.playlist-page .playlist-header .playlist-info .play-count {
-  color: #999;
-  font-size: 14px;
-}
-.playlist-page .song-list-container {
-  width: 100%;
-  border-collapse: collapse;
-  margin-top: 20px;
-}
-.playlist-page .song-list-container .song-list-header {
-  display: grid;
-  grid-template-columns: 60px 1fr 80px 120px 150px;
-  background-color: #f5f5f5;
-  border-bottom: 2px solid #d90000;
-  font-weight: bold;
-  color: #333;
-  padding: 10px 0;
-  text-align: center;
-}
-.playlist-page .song-list-container .song-list {
-  list-style: none;
-  padding: 0;
-  margin: 0;
-}
-.playlist-page .song-list-container .song-list .song-item {
-  display: grid;
-  grid-template-columns: 60px 1fr 80px 120px 150px;
-  padding: 8px 0;
-  border-bottom: 1px solid #eee;
-  transition: background-color 0.2s ease;
-}
-.playlist-page .song-list-container .song-list .song-item:hover {
-  background-color: #f9f9f9;
-}
-.playlist-page .song-list-container .song-list .song-number,
-.playlist-page .song-list-container .song-list .song-title,
-.playlist-page .song-list-container .song-list .song-duration,
-.playlist-page .song-list-container .song-list .song-artist,
-.playlist-page .song-list-container .song-list .song-album {
-  padding: 4px 0;
-  text-align: center;
-}
-.playlist-page .song-list-container .song-list .song-title {
-  text-align: left;
-  padding-left: 10px;
-}
-.playlist-page .song-list-container .song-list .play-icon {
-  margin-right: 8px;
-  cursor: pointer;
-  color: #666;
-}
-.playlist-page .song-list-container .song-list .album-tag {
-  font-size: 12px;
-  color: #999;
-  margin-left: 4px;
-}

+ 0 - 125
src/pages/layout/pages/mine/playlist/index.less

@@ -1,125 +0,0 @@
-.playlist-page {
-  padding: 20px;
-  font-family: 'Microsoft YaHei', sans-serif;
-
-  .playlist-header {
-    display: flex;
-    align-items: center;
-    gap: 20px;
-    margin-bottom: 30px;
-
-    .cover-img {
-      width: 200px;
-      height: 200px;
-      border-radius: 8px;
-      object-fit: cover;
-    }
-
-    .playlist-info {
-      flex: 1;
-
-      h1 {
-        margin: 0;
-        font-size: 24px;
-        color: #333;
-      }
-
-      .meta {
-        color: #666;
-        font-size: 14px;
-        margin: 8px 0;
-      }
-
-      .actions {
-        display: flex;
-        gap: 10px;
-        margin: 10px 0;
-
-        button {
-          padding: 6px 12px;
-          border: 1px solid #ccc;
-          background: #f5f5f5;
-          cursor: pointer;
-          font-size: 12px;
-        }
-
-        .play-btn,
-        .add-btn {
-          background: linear-gradient(to right, #007bff, #0056b3);
-          color: white;
-          border: none;
-          border-radius: 4px;
-        }
-
-        .add-btn {
-          background: #007bff;
-        }
-      }
-
-      .play-count {
-        color: #999;
-        font-size: 14px;
-      }
-    }
-  }
-.song-list-container {
-  width: 100%;
-  border-collapse: collapse;
-  margin-top: 20px;
-
-  .song-list-header {
-    display: grid;
-    grid-template-columns: 60px 1fr 80px 120px 150px;
-    background-color: #f5f5f5;
-    border-bottom: 2px solid #d90000;
-    font-weight: bold;
-    color: #333;
-    padding: 10px 0;
-    text-align: center;
-  }
-
-  .song-list {
-    list-style: none;
-    padding: 0;
-    margin: 0;
-
-    .song-item {
-      display: grid;
-      grid-template-columns: 60px 1fr 80px 120px 150px;
-      padding: 8px 0;
-      border-bottom: 1px solid #eee;
-      transition: background-color 0.2s ease;
-
-      &:hover {
-        background-color: #f9f9f9;
-      }
-    }
-
-    .song-number,
-    .song-title,
-    .song-duration,
-    .song-artist,
-    .song-album {
-      padding: 4px 0;
-      text-align: center;
-    }
-
-    .song-title {
-      text-align: left;
-      padding-left: 10px;
-    }
-
-    .play-icon {
-      margin-right: 8px;
-      cursor: pointer;
-      color: #666;
-    }
-
-    .album-tag {
-      font-size: 12px;
-      color: #999;
-      margin-left: 4px;
-    }
-  }
-}
-}

+ 5 - 193
src/pages/layout/pages/mine/playlist/index.tsx

@@ -1,205 +1,17 @@
-import React, { useState, useEffect } from 'react';
 import { useParams } from 'react-router-dom';
 import './index.css';
-import { getPayListDetail } from '@/apis/payList';
-import { Image, Spin } from 'antd';
-import { LoadingOutlined } from '@ant-design/icons';
-
-// 定义API返回的数据结构
-interface Playlist {
-  id: number;
-  userId: number;
-  playlistName: string;
-  coverUrl: string | null;
-  description: string | null;
-  tag: string | null;
-  songCount: number;
-  playCount: number;
-  createTime: string;
-  updateTime: string;
-  status: boolean;
-}
-
-interface Song {
-  id: number;
-  songName: string;
-  artistId: number;
-  albumId: number | null;
-  duration: number;
-  fileUrl: string;
-  coverUrl: string;
-  releaseTime: string;
-  lyrics: string;
-  playCount: number;
-  isPaid: number;
-  price: number;
-  createTime: string;
-  updateTime: string;
-  status: number;
-  auditReason: string | null;
-  auditTime: string | null;
-  publishTime: string | null;
-  shelfTime: string | null;
-  offShelfTime: string | null;
-  deleteFlag: number;
-  deleteTime: string | null;
-  songType: string | null;
-  version: string | null;
-  workType: string | null;
-  genre: string | null;
-  language: string | null;
-  lyricist: string | null;
-  composer: string | null;
-  arranger: string | null;
-  singerName: string | null;
-}
-
-interface PlaylistData {
-  playlist: Playlist;
-  songs: Song[];
-}
+import PlaylistDetail from '../components/PlaylistDetail';
 
 const Mine_Playlist = () => {
   const { id } = useParams<{ id: string }>();
-  const [playlistData, setPlaylistData] = useState<PlaylistData | null>(null);
-  const [loading, setLoading] = useState(true);
-  const [error, setError] = useState<string | null>(null);
-
-  // 格式化时间为 YYYY-MM-DD 格式
-  const formatDate = (dateString: string) => {
-    const date = new Date(dateString);
-    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
-  };
-
-  // 将秒数转换为 mm:ss 格式
-  const formatDuration = (seconds: number) => {
-    const mins = Math.floor(seconds / 60);
-    const secs = seconds % 60;
-    return `${mins}:${secs.toString().padStart(2, '0')}`;
-  };
-
-  // 请求歌单详情
-  useEffect(() => {
-    const fetchPlaylistDetails = async () => {
-      try {
-        setLoading(true);
-        setError(null);
-
-        if (!id) {
-          throw new Error('缺少歌单ID');
-        }
-
-        // 调用API获取真实数据
-        const response = await getPayListDetail(Number(id));
-
-        if (response.code === 200) {
-          // 直接使用返回的数据,不再需要解析JSON字符串
-          setPlaylistData(response.data);
-        } else {
-          throw new Error(response.message || '获取歌单失败');
-        }
-      } catch (err: any) {
-        console.error('获取歌单失败:', err);
-        setError(err.message || '获取歌单失败');
-      } finally {
-        setLoading(false);
-      }
-    };
 
-    if (id) {
-      fetchPlaylistDetails();
-    }
-  }, [id]);
-
-  if (loading) {
-    return (
-      <div className="loading" style={{
-        display: 'flex',
-        justifyContent: 'center',
-        alignItems: 'center',
-        height: '100vh',
-        width: '100vw',
-        position: 'fixed',
-        top: 0,
-        left: 0,
-        backgroundColor: 'rgba(255, 255, 255, 0.8)',
-        zIndex: 9999
-      }}>
-        <Spin indicator={<LoadingOutlined style={{ fontSize: 48 }} spin />} />
-      </div>
-    );
-  }
-
-  if (error) {
-    return <div className="error">错误: {error}</div>;
-  }
-
-  if (!playlistData || !playlistData.playlist) {
-    return <div className="not-found">歌单不存在</div>;
+  if (!id) {
+    return <div className="not-found">缺少歌单ID</div>;
   }
 
-  const { playlist, songs } = playlistData;
-
-  // 确保 songs 是数组,如果不是则默认为空数组
-  const validSongs = Array.isArray(songs) ? songs : [];
-
   return (
-    <div className="playlist-page">
-      {/* 歌单头部 */}
-      <div className="playlist-header">
-        <img
-          src={playlist.coverUrl || 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'}
-          alt={playlist.playlistName}
-          className="cover-img"
-        />
-        <div className="playlist-info">
-          <h1>{playlist.playlistName}</h1>
-          <div className="meta">
-            <span className="create-time"> {formatDate(playlist.createTime)} 创建</span>
-          </div>
-          <div className="actions">
-            <button className="play-btn">播放</button>
-            <button className="add-btn">+</button>
-            <button className="collect-btn">收藏</button>
-            <button className="share-btn">分享</button>
-            <button className="download-btn">下载</button>
-            <button className="comment-btn">评论</button>
-          </div>
-          <div className="play-count">
-            播放:<strong>{playlist.playCount}</strong>次
-          </div>
-        </div>
-      </div>
-
-      {/* 歌曲列表 */}
-      {validSongs.length > 0 ? (
-        <div className="song-list-container">
-          <div className="song-list-header">
-            <div className="song-number">序号</div>
-            <div className="song-title">歌曲标题</div>
-            <div className="song-duration">时长</div>
-            <div className="song-artist">歌手</div>
-          </div>
-
-          <ul className="song-list">
-            {validSongs.map((song, index) => (
-              <li key={song.id} className="song-item">
-                <div className="song-number">{index + 1}</div>
-                <div className="song-title">
-                  <span className="play-icon">▶</span>
-                  {song.songName}
-                </div>
-                <div className="song-duration">{formatDuration(song.duration)}</div>
-                <div className="song-artist">{song.singerName || '未知歌手'}</div>
-              </li>
-            ))}
-          </ul>
-        </div>
-      ) : (
-        <div>
-          <Image preview={false} src="http://117.72.120.45:9000/public/musician/playlist/songNull.png" />
-        </div>
-      )}
+    <div>
+      <PlaylistDetail id={Number(id)} showCollect={false} />
     </div>
   );
 };

+ 5 - 4
src/pages/layout/pages/musician/MusicianDashboardPage/works/songup/index.tsx

@@ -34,7 +34,7 @@ const Songup = () => {
 
   // 专辑相关状态
   const [workType, setWorkType] = useState<'single' | 'album'>('single')
-  const [albums, setAlbums] = useState<{ id: number, name: string }[]>([])
+  const [albums, setAlbums] = useState<{ id: number, albumName: string }[]>([])
   const [selectedAlbum, setSelectedAlbum] = useState<number | null>(null)
   const [isCreateAlbumModalVisible, setIsCreateAlbumModalVisible] = useState(false)
 
@@ -108,12 +108,13 @@ const Songup = () => {
           coverUrl: songCoverUrl || coverUrl || '',
           duration: duration,
           releaseTime: new Date().toISOString().split('T')[0],
-          ...(workType === 'album' && selectedAlbum && { albumId: selectedAlbum }),
+          ...(workType === 'album' && selectedAlbum && {
+            albumId: selectedAlbum,
+            albumName: albums.find(album => album.id === selectedAlbum)?.albumName
+          }),
           isPaid: values.isPaid !== undefined ? values.isPaid : 0,
           price: values.isPaid === 1 && values.price ? Number(values.price) : 0
         };
-
-        console.log('提交数据:', submitData);
         // 调用歌曲上传API
         const res = await songAddapi(submitData);
         if (res.code === 200) {

+ 78 - 26
src/pages/layout/pages/testPage/index.tsx

@@ -1,10 +1,12 @@
-import React, { useState, useRef } from 'react';
+// src/pages/layout/pages/testPage/index.tsx
+import React, { useState, useRef, useEffect } from 'react';
 import AudioPlayer from 'react-h5-audio-player';
 import 'react-h5-audio-player/lib/styles.css';
 import './index.css';
 import { UnorderedListOutlined } from '@ant-design/icons';
+import { useMusicPlayer } from '@/context/MusicPlayerContext';
 
-interface Song {
+export interface Song {
   id: number;
   title: string;
   artist: string;
@@ -18,7 +20,7 @@ interface Song {
   description?: string;     // 歌曲描述
 }
 
-const playlist: Song[] = [
+const defaultPlaylist: Song[] = [
   {
     id: 1,
     title: '奇妙能力歌',
@@ -82,63 +84,113 @@ const playlist: Song[] = [
 ];
 
 const MusicPlayer: React.FC = () => {
-  const [currentTrackIndex, setCurrentTrackIndex] = useState<number>(0);
-  const [isPlaying, setIsPlaying] = useState<boolean>(false);
-  const [isPlaylistOpen, setIsPlaylistOpen] = useState<boolean>(false);
+  const {
+    currentTrackIndex,
+    isPlaying,
+    playlist,
+    isPlaylistOpen,
+    setIsPlaying,
+    setCurrentTrackIndex,
+    setIsPlaylistOpen,
+    setPlaylist
+  } = useMusicPlayer();
+
   const audioPlayerRef = useRef<AudioPlayer>(null);
-  const currentTrack = playlist[currentTrackIndex];
+
+  // 初始化播放列表
+  useEffect(() => {
+    if (playlist.length === 0) {
+      setPlaylist(defaultPlaylist);
+    }
+  }, [playlist.length, setPlaylist]);
+
+  // 确保当前索引有效
+  const validCurrentTrackIndex = playlist.length > 0
+    ? Math.max(0, Math.min(currentTrackIndex, playlist.length - 1))
+    : 0;
+
+  const currentTrack = playlist.length > 0
+    ? playlist[validCurrentTrackIndex]
+    : defaultPlaylist[0];
 
   // 修改 handlePrev 函数
   const handlePrev = () => {
-    setCurrentTrackIndex((prev) => (prev - 1 + playlist.length) % playlist.length);
-    setIsPlaying(true); // 确保切换后自动播放
+    if (playlist.length > 0) {
+      setCurrentTrackIndex((prev) => (prev - 1 + playlist.length) % playlist.length);
+      setIsPlaying(true);
+    }
   };
 
   // 修改 handleNext 函数
   const handleNext = () => {
-    setCurrentTrackIndex((prev) => (prev + 1) % playlist.length);
-    setIsPlaying(true); // 确保切换后自动播放
+    if (playlist.length > 0) {
+      setCurrentTrackIndex((prev) => (prev + 1) % playlist.length);
+      setIsPlaying(true);
+    }
   };
+
   // 播放列表项点击事件
   const handleTrackSelect = (index: number) => {
-    setCurrentTrackIndex(index);
-    setIsPlaying(true);
-    setIsPlaylistOpen(false);
+    if (playlist.length > 0) {
+      setCurrentTrackIndex(index);
+      setIsPlaying(true);
+      setIsPlaylistOpen(false);
+    }
   };
 
+  // 当播放状态改变时,确保音频元素正确响应
+  useEffect(() => {
+    if (audioPlayerRef.current) {
+      const audioElement = audioPlayerRef.current.audio.current;
+      if (audioElement) {
+        if (isPlaying) {
+          // 确保在播放前音频源已设置
+          setTimeout(() => {
+            audioElement.play().catch(e => console.log('播放失败:', e));
+          }, 0);
+        } else {
+          audioElement.pause();
+        }
+      }
+    }
+  }, [isPlaying, validCurrentTrackIndex]);
 
   return (
     <div className="music-player-wrapper">
       <div className="player-container">
         {/* 左侧:歌曲封面 + 信息 */}
         <div className="track-info">
-          <img src={currentTrack.cover} alt={currentTrack.title} className="track-cover" />
+          <img
+            src={currentTrack?.cover || defaultPlaylist[0]?.cover}
+            alt={currentTrack?.title || '未知歌曲'}
+            className="track-cover"
+          />
           <div className="track-meta">
-            <div className="track-title">{currentTrack.title}</div>
-            <div className="track-artist">{currentTrack.artist}</div>
+            <div className="track-title">{currentTrack?.title || '未知歌曲'}</div>
+            <div className="track-artist">{currentTrack?.artist || '未知艺术家'}</div>
           </div>
         </div>
 
-        {/* 中间:播放器核心(使用原生上一首/下一首按钮) */}
+        {/* 中间:播放器核心 */}
         <div className="player-ui-container">
-
           <AudioPlayer
             ref={audioPlayerRef}
-            key={currentTrackIndex}
-            src={currentTrack.url}
-            autoPlay={isPlaying}
+            key={validCurrentTrackIndex}
+            src={currentTrack?.url || defaultPlaylist[0]?.url}
+            autoPlay={false} // 关键:不使用autoPlay,而是手动控制
             onPlay={() => setIsPlaying(true)}
             onPause={() => setIsPlaying(false)}
             onEnded={handleNext}
-            showSkipControls
+            showSkipControls={playlist.length > 0}
             className="player-ui"
             listenInterval={1000}
             onClickPrevious={handlePrev}
             onClickNext={handleNext}
+            disabled={playlist.length === 0}
           />
         </div>
 
-        {/* 右侧:播放列表按钮(AntD 图标) */}
+        {/* 右侧:播放列表按钮 */}
         <button
           className="playlist-toggle"
           onClick={() => setIsPlaylistOpen(!isPlaylistOpen)}
@@ -161,10 +213,10 @@ const MusicPlayer: React.FC = () => {
             </button>
           </div>
           <ul className="track-list">
-            {playlist.map((track, index) => (
+            {(playlist.length > 0 ? playlist : defaultPlaylist).map((track, index) => (
               <li
                 key={track.id}
-                className={index === currentTrackIndex ? 'track-item active' : 'track-item'}
+                className={index === validCurrentTrackIndex ? 'track-item active' : 'track-item'}
                 onClick={() => handleTrackSelect(index)}
               >
                 <img src={track.cover} alt={track.title} className="item-cover" />

+ 78 - 0
src/store/usePlaylistStore.ts

@@ -0,0 +1,78 @@
+// src/store/usePlaylistStore.ts
+import { create } from 'zustand';
+
+interface Song {
+  id: number;
+  songName: string;
+  artistId: number;
+  albumId: number | null;
+  albumName: string | null;
+  duration: number;
+  fileUrl: string;
+  coverUrl: string;
+  releaseTime: string;
+  lyrics: string;
+  playCount: number;
+  isPaid: number;
+  price: number;
+  createTime: string;
+  updateTime: string;
+  status: number;
+  auditReason: string | null;
+  auditTime: string | null;
+  publishTime: string | null;
+  shelfTime: string | null;
+  offShelfTime: string | null;
+  deleteFlag: number;
+  deleteTime: string | null;
+  songType: string | null;
+  version: string | null;
+  workType: string | null;
+  genre: string | null;
+  language: string | null;
+  lyricist: string | null;
+  composer: string | null;
+  arranger: string | null;
+  singerName: string | null;
+}
+
+interface PlaylistState {
+  playlist: Song[];
+  currentIndex: number;
+  isPlaying: boolean;
+  setPlaylist: (songs: Song[]) => void;
+  setCurrentIndex: (index: number) => void;
+  setIsPlaying: (playing: boolean) => void;
+  addToPlaylist: (song: Song) => void;
+  clearPlaylist: () => void;
+  next: () => void;
+  prev: () => void;
+}
+
+export const usePlaylistStore = create<PlaylistState>((set, get) => ({
+  playlist: [],
+  currentIndex: 0,
+  isPlaying: false,
+  setPlaylist: (songs) => set({ playlist: songs, currentIndex: 0 }),
+  setCurrentIndex: (index) => set({ currentIndex: index }),
+  setIsPlaying: (playing) => set({ isPlaying: playing }),
+  addToPlaylist: (song) => set((state) => {
+    // 检查歌曲是否已经在播放列表中
+    const exists = state.playlist.some(s => s.id === song.id);
+    if (!exists) {
+      return { playlist: [...state.playlist, song] };
+    }
+    return state;
+  }),
+  clearPlaylist: () => set({ playlist: [], currentIndex: 0 }),
+  next: () => set((state) => {
+    if (state.playlist.length === 0) return state;
+    const newIndex = (state.currentIndex + 1) % state.playlist.length;
+    return { currentIndex: newIndex };
+  }),
+  prev: () => set((state) => {
+    if (state.playlist.length === 0) return state;
+    const newIndex = (state.currentIndex - 1 + state.playlist.length) % state.playlist.length;
+    return { currentIndex: newIndex };
+  }),
+}));

+ 35 - 0
src/type/Song.ts

@@ -0,0 +1,35 @@
+
+export  interface Song {
+  id: number;
+  songName: string;
+  artistId: number;
+  albumId: number | null;
+  albumName: string | null;
+  duration: number;
+  fileUrl: string;
+  coverUrl: string;
+  releaseTime: string;
+  lyrics: string;
+  playCount: number;
+  isPaid: number;
+  price: number;
+  createTime: string;
+  updateTime: string;
+  status: number;
+  auditReason: string | null;
+  auditTime: string | null;
+  publishTime: string | null;
+  shelfTime: string | null;
+  offShelfTime: string | null;
+  deleteFlag: number;
+  deleteTime: string | null;
+  songType: string | null;
+  version: string | null;
+  workType: string | null;
+  genre: string | null;
+  language: string | null;
+  lyricist: string | null;
+  composer: string | null;
+  arranger: string | null;
+  singerName: string | null;
+}

+ 1 - 1
src/utils/request.ts

@@ -33,7 +33,7 @@ request.interceptors.response.use((response) => {
     message.error('登录已过期,请重新登录')
     removeToken()
     removeUserinfo()
-    router.navigate('/find/recommend')
+    router.navigate('/login')
     window.location.reload()
   }
   if (error.response.status === 500) {