Browse Source

分块前

xiang 2 weeks ago
parent
commit
06f428faa2
43 changed files with 2222 additions and 454 deletions
  1. 3 0
      src/apis/album.ts
  2. 12 2
      src/apis/artist.ts
  3. 31 0
      src/apis/payList.ts
  4. 8 0
      src/apis/song.ts
  5. 263 0
      src/pages/layout/components/CreatePlaylistModal/index.tsx
  6. 1 1
      src/pages/layout/pages/AlbumDetail/index.tsx
  7. 25 0
      src/pages/layout/pages/SongDetail/components/SongDetailPage.css
  8. 30 0
      src/pages/layout/pages/SongDetail/components/SongDetailPage.less
  9. 216 39
      src/pages/layout/pages/SongDetail/components/SongDetailPage.tsx
  10. 81 12
      src/pages/layout/pages/SongDetail/index.tsx
  11. 36 0
      src/pages/layout/pages/artistinfo/index.css
  12. 45 0
      src/pages/layout/pages/artistinfo/index.less
  13. 78 7
      src/pages/layout/pages/artistinfo/index.tsx
  14. 18 19
      src/pages/layout/pages/find/album/index.tsx
  15. 2 1
      src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content_bill/index.tsx
  16. 1 2
      src/pages/layout/pages/find/recommend/components/Recommend_body_left/index.tsx
  17. 325 183
      src/pages/layout/pages/find/songList/index.tsx
  18. 1 0
      src/pages/layout/pages/mine/artist/index.css
  19. 1 1
      src/pages/layout/pages/mine/artist/index.less
  20. 19 33
      src/pages/layout/pages/mine/artist/index.tsx
  21. 0 0
      src/pages/layout/pages/mine/components/MineTitle/index.css
  22. 0 0
      src/pages/layout/pages/mine/components/MineTitle/index.less
  23. 0 0
      src/pages/layout/pages/mine/components/MineTitle/index.tsx
  24. 1 0
      src/pages/layout/pages/mine/components/PlaylistDetail/MusicList/index.css
  25. 1 1
      src/pages/layout/pages/mine/components/PlaylistDetail/MusicList/index.less
  26. 24 9
      src/pages/layout/pages/mine/components/PlaylistDetail/MusicList/index.tsx
  27. 82 13
      src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx
  28. 5 22
      src/pages/layout/pages/mine/components/PlaylistDetail/index.tsx
  29. 34 98
      src/pages/layout/pages/mine/index.tsx
  30. 1 0
      src/pages/layout/pages/mine/mv/index.tsx
  31. 2 1
      src/pages/layout/pages/musician/MusicianDashboardPage/index.tsx
  32. 96 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.css
  33. 124 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.less
  34. 135 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.tsx
  35. 93 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.css
  36. 112 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.less
  37. 243 0
      src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.tsx
  38. 3 0
      src/pages/layout/pages/testPage/index.css
  39. 3 0
      src/pages/layout/pages/testPage/index.less
  40. 44 9
      src/pages/layout/pages/testPage/index.tsx
  41. 11 1
      src/router/index.tsx
  42. 6 0
      src/type/PageDto.ts
  43. 6 0
      src/type/PageQuery.ts

+ 3 - 0
src/apis/album.ts

@@ -16,3 +16,6 @@ export const deleteAlbumApi = (id, IsDeleteAll) => {
 export const selSongByALbumIdApi = (id) => {
   return request.get('/album/selSongByALbumId', { params: { id } })
 }
+export const listAllAlbumApi = () => {
+  return request.get('/album/listAllAlbum')
+}

+ 12 - 2
src/apis/artist.ts

@@ -1,7 +1,17 @@
 import { request } from "@/utils"
 export const SingerAPi = (region, gender) => {
-  return request.post('/artist/querySinger',{region, gender})
+  return request.post('/artist/querySinger', { region, gender })
 }
 export const SingerByIdApi = (id) => {
-  return request.get('/artist/querySingerById',{params:{id}})
+  return request.get('/artist/querySingerById', { params: { id } })
 }
+export const queryArtistIsCollectByid = (id) => {
+  return request.get('/user-favorite-artist/queryByid', { params: { id } })
+}
+
+export const ChangeArtistIsCollectByid = (id) => {
+  return request.get('/user-favorite-artist/ChangeFavoriteArtist', { params: { id } })
+}
+export const queryArtistIsCollectByUserid = (id) => {
+  return request.get('/user-favorite-artist/queryById', { params: { id } })
+}

+ 31 - 0
src/apis/payList.ts

@@ -1,4 +1,6 @@
 import { request } from "@/utils"
+import type { PageQuery } from "@/type/PageQuery"
+import type { PageDto } from "@/type/PageDto"
 export const addplayList = (data: any) => {
   return request.post('/playlist/add', data)
 }
@@ -6,7 +8,36 @@ export const addplayList = (data: any) => {
 export const getPaylistApi = () => {
   return request.get('/playlist/list')
 }
+export const getCollectionListApi = () => {
+  return request.get('/user-playlist-favorite/queryAll')
+}
 
 export const getPayListDetail = (id: number) => {
   return request.get('/playlist/payListDetail', { params: { id } })
+}
+
+
+
+export interface Playlist {
+  id: number;
+  src: string;
+  title: string;
+  content: string;
+  tag?: string;
+}
+
+
+export const getPlaylistPage = (data: PageQuery) => {
+  return request.post<PageDto<Playlist>>('/playlist/PageQuery', data)
+}
+
+
+export const CollectionSongApi = (playlistId: number) => {
+  return request.get('/user-playlist-favorite/add', { params: { playlistId } })
+}
+export const UnCollectionSongApi = (playlistId: number) => {
+  return request.get('/user-playlist-favorite/delete', { params: { playlistId } })
+}
+export const IsCollectionSongApi = (playlistId: number) => {
+  return request.get('/user-playlist-favorite/queryIsCollection', { params: { playlistId } })
 }

+ 8 - 0
src/apis/song.ts

@@ -17,4 +17,12 @@ export const getSongBySonger = (id: number) => {
 }
 export const songUpdateApi = (id, status) => {
   return request.put('/song/updateSongStatus', { id, status })
+}
+
+export const geSongById = (id: number) => {
+  return request.get('/song/getById', { params: { id } })
+}
+
+export const CollectSong = (data: any) => {
+  return request.post(`/playlist/collectSong`, data)
 }

+ 263 - 0
src/pages/layout/components/CreatePlaylistModal/index.tsx

@@ -0,0 +1,263 @@
+// src/components/CreatePlaylistModal.tsx
+import { useState } from 'react';
+import { Modal, message, Input, Checkbox, Select, Collapse } from 'antd';
+import { addplayList } from '@/apis/payList';
+
+interface CreatePlaylistModalProps {
+  open: boolean;
+  onCancel: () => void;
+  onOk: () => void;
+}
+
+// 定义下拉选项
+const languageOptions = [
+  { label: '华语', value: '华语' },
+  { label: '欧美', value: '欧美' },
+  { label: '日韩', value: '日韩' },
+  { label: '其他', value: '其他' },
+];
+
+const styleOptions = [
+  { label: '流行', value: '流行' },
+  { label: '摇滚', value: '摇滚' },
+  { label: '民谣', value: '民谣' },
+  { label: '电子', value: '电子' },
+  { label: '说唱', value: '说唱' },
+  { label: '古典', value: '古典' },
+  { label: '轻音乐', value: '轻音乐' },
+  { label: '其他', value: '其他' },
+];
+
+const sceneOptions = [
+  { label: '学习', value: '学习' },
+  { label: '工作', value: '工作' },
+  { label: '运动', value: '运动' },
+  { label: '旅行', value: '旅行' },
+  { label: '聚会', value: '聚会' },
+  { label: '其他', value: '其他' },
+];
+
+const emotionOptions = [
+  { label: '治愈', value: '治愈' },
+  { label: '放松', value: '放松' },
+  { label: '欢快', value: '欢快' },
+  { label: '抒情', value: '抒情' },
+  { label: '伤感', value: '伤感' },
+  { label: '其他', value: '其他' },
+];
+
+const CreatePlaylistModal: React.FC<CreatePlaylistModalProps> = ({ 
+  open, 
+  onCancel, 
+  onOk 
+}) => {
+  const [payName, setPayName] = useState('');
+  const [checked, setChecked] = useState(false);
+  const [language, setLanguage] = useState<string | undefined>(undefined);
+  const [style, setStyle] = useState<string | undefined>(undefined);
+  const [scene, setScene] = useState<string | undefined>(undefined);
+  const [emotion, setEmotion] = useState<string | undefined>(undefined);
+  const [submitLoading, setSubmitLoading] = useState(false);
+  
+  // 使用 contextHolder
+  const [messageApi, contextHolder] = message.useMessage();
+
+  const handleOk = async () => {
+    if (payName === '') {
+      messageApi.open({
+        type: 'error',
+        content: '请输入歌单名',
+      });
+      return;
+    }
+
+    setSubmitLoading(true);
+
+    try {
+      const params: any = {
+        playlistName: payName,
+        status: checked
+      };
+
+      if (language) params.language = language;
+      if (style) params.style = style;
+      if (scene) params.scene = scene;
+      if (emotion) params.emotion = emotion;
+
+      const res = await addplayList(params);
+      
+      if (res.code !== 200) {
+        messageApi.open({
+          type: 'error',
+          content: res.message,
+        });
+        return;
+      }
+
+      messageApi.open({
+        type: 'success',
+        content: '歌单创建成功',
+      });
+
+      // 重置状态
+      setPayName('');
+      setChecked(false);
+      setLanguage(undefined);
+      setStyle(undefined);
+      setScene(undefined);
+      setEmotion(undefined);
+      
+      onOk(); // 通知父组件刷新数据
+    } catch (error) {
+      console.error('创建歌单失败:', error);
+      messageApi.open({
+        type: 'error',
+        content: '创建歌单失败,请重试',
+      });
+    } finally {
+      setSubmitLoading(false);
+    }
+  };
+
+  const handleModalCancel = () => {
+    if (!submitLoading) {
+      // 重置状态
+      setPayName('');
+      setChecked(false);
+      setLanguage(undefined);
+      setStyle(undefined);
+      setScene(undefined);
+      setEmotion(undefined);
+      onCancel();
+    }
+  };
+
+  return (
+    <>
+      {contextHolder} {/* 使用 contextHolder 替代 messageApi */}
+      <Modal
+        title="新建歌单"
+        open={open}
+        onOk={handleOk}
+        onCancel={handleModalCancel}
+        width={500}
+        centered
+        destroyOnHidden
+        cancelText="取消"
+        okText={submitLoading ? "提交中..." : "确定"}
+        okButtonProps={{ 
+          loading: submitLoading,
+          disabled: submitLoading
+        }}
+      >
+        <div style={{ padding: '20px 0' }}>
+          <div style={{ marginBottom: '24px' }}>
+            <label style={{ display: 'inline-block', width: '60px', verticalAlign: 'middle', marginRight: '10px' }}>
+              歌单名:
+            </label>
+            <Input 
+              onChange={(e) => setPayName(e.target.value)} 
+              value={payName} 
+              placeholder="请输入歌单名称" 
+              style={{ width: '300px' }} 
+              disabled={submitLoading}
+            />
+            <Checkbox 
+              style={{ paddingLeft: 10 }} 
+              onChange={(e) => setChecked(e.target.checked)}
+              disabled={submitLoading}
+            >
+              公开
+            </Checkbox>
+          </div>
+          
+          <Collapse 
+            ghost 
+            style={{ marginBottom: '24px' }}
+            disabled={submitLoading}
+            items={[
+              {
+                key: 'advanced',
+                label: '高级选项',
+                children: (
+                  <div style={{ marginTop: '16px' }}>
+                    <div style={{ marginBottom: '16px' }}>
+                      <label style={{ display: 'inline-block', width: '80px', marginRight: '10px' }}>
+                        语种:
+                      </label>
+                      <Select
+                        style={{ width: '200px' }}
+                        placeholder="请选择语种"
+                        allowClear
+                        value={language}
+                        onChange={setLanguage}
+                        options={languageOptions}
+                        disabled={submitLoading}
+                      />
+                    </div>
+                    
+                    <div style={{ marginBottom: '16px' }}>
+                      <label style={{ display: 'inline-block', width: '80px', marginRight: '10px' }}>
+                        风格:
+                      </label>
+                      <Select
+                        style={{ width: '200px' }}
+                        placeholder="请选择风格"
+                        allowClear
+                        value={style}
+                        onChange={setStyle}
+                        options={styleOptions}
+                        disabled={submitLoading}
+                      />
+                    </div>
+                    
+                    <div style={{ marginBottom: '16px' }}>
+                      <label style={{ display: 'inline-block', width: '80px', marginRight: '10px' }}>
+                        场景:
+                      </label>
+                      <Select
+                        style={{ width: '200px' }}
+                        placeholder="请选择场景"
+                        allowClear
+                        value={scene}
+                        onChange={setScene}
+                        options={sceneOptions}
+                        disabled={submitLoading}
+                      />
+                    </div>
+                    
+                    <div style={{ marginBottom: '16px' }}>
+                      <label style={{ display: 'inline-block', width: '80px', marginRight: '10px' }}>
+                        情感:
+                      </label>
+                      <Select
+                        style={{ width: '200px' }}
+                        placeholder="请选择情感"
+                        allowClear
+                        value={emotion}
+                        onChange={setEmotion}
+                        options={emotionOptions}
+                        disabled={submitLoading}
+                      />
+                    </div>
+                  </div>
+                ),
+              },
+            ]}
+          />
+          
+          <p style={{
+            color: '#999',
+            fontSize: '12px',
+            textAlign: 'center',
+            marginBottom: '0'
+          }}>
+            可通过"收藏"将音乐添加到新歌单中
+          </p>
+        </div>
+      </Modal>
+    </>
+  );
+};
+
+export default CreatePlaylistModal;

+ 1 - 1
src/pages/layout/pages/AlbumDetail/index.tsx

@@ -139,7 +139,7 @@ const AlbumDetail = () => {
         onPlayPlaylist={handlePlayPlaylist}
         onAddPlaylist={handleAddPlaylist}
       />
-      <MusicList songs={songs} />
+      <div style={{ padding:20 }}><MusicList songs={songs} /></div>
     </div>
   );
 };

+ 25 - 0
src/pages/layout/pages/SongDetail/components/SongDetailPage.css

@@ -104,3 +104,28 @@
   color: #666;
   line-height: 1.6;
 }
+.selplaylist {
+  height: 100%;
+  min-height: 300px;
+  max-height: 500px;
+  overflow-y: auto;
+}
+.selplaylist .loaing {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+.selplaylist .selplaylistItem {
+  cursor: pointer;
+  padding: 10px 20px;
+  height: 55px;
+  border-bottom: 1px solid #e0e0e0;
+  display: flex;
+}
+.selplaylist .selplaylistItem .selplaylistItem_txt {
+  padding-left: 20px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}

+ 30 - 0
src/pages/layout/pages/SongDetail/components/SongDetailPage.less

@@ -122,4 +122,34 @@
       line-height: 1.6;
     }
   }
+}
+
+.selplaylist {
+  height: 100%;
+  min-height: 300px;
+  max-height: 500px;
+  overflow-y: auto;
+
+.loaing {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+  .selplaylistItem {
+    cursor: pointer;
+    padding: 10px 20px;
+    height: 55px;
+    border-bottom: 1px solid #e0e0e0;
+    display: flex;
+
+    .selplaylistItem_txt {
+      padding-left: 20px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+    }
+  }
+
 }

+ 216 - 39
src/pages/layout/pages/SongDetail/components/SongDetailPage.tsx

@@ -1,48 +1,185 @@
-// src/pages/layout/pages/testPage/SongDetailPage.tsx
 import React, { useState } from 'react';
-import { UnorderedListOutlined, HeartOutlined, HeartTwoTone } from '@ant-design/icons';
+import { HeartOutlined, LoadingOutlined, PlusCircleOutlined } from '@ant-design/icons';
+import { useMusicPlayer } from '@/context/MusicPlayerContext';
 import './SongDetailPage.css';
-
+import { Button, Image, Modal, Spin, message } from 'antd';
+import { getPaylistApi } from '@/apis/payList';
+import { CollectSong } from '@/apis/song';
+import CreatePlaylistModal from '@/pages/layout/components/CreatePlaylistModal';
 interface Song {
   id: number;
-  title: string;
-  artist: string;
-  url: string;
-  cover: string;
-  duration: string;
-  isFavorite?: boolean;
-  album?: string;           // 所属专辑
-  composer?: string;        // 作曲
-  arranger?: string;        // 编曲
-  description?: string;     // 歌曲描述
+  songName: string;
+  artistId?: number;
+  albumId?: number | null;
+  albumName?: string;
+  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;
 }
 
 const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
-  const [isPlaying, setIsPlaying] = useState<boolean>(false);
-  const [isFavorite, setIsFavorite] = useState<boolean>(false);
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); // 新增状态
+  const [messageApi, contextHolder] = message.useMessage();
+
+  const {
+    addToPlaylist,
+    setCurrentTrackIndex,
+    setIsPlaying,
+    playlist
+  } = useMusicPlayer();
 
-  // 模拟播放逻辑(可连接 AudioPlayer)
+  // 将秒数转换为 mm:ss 格式
+  const formatDuration = (seconds: number): string => {
+    const mins = Math.floor(seconds / 60);
+    const secs = Math.floor(seconds % 60);
+    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
+  };
+
+  // 播放当前歌曲
   const handlePlay = () => {
+    // 添加到播放列表(如果不存在)
+    const exists = playlist.some(existingSong => existingSong.id === song.id);
+    if (!exists) {
+      addToPlaylist({
+        id: song.id,
+        title: song.songName,
+        artist: song.singerName || '未知歌手',
+        url: song.fileUrl,
+        cover: song.coverUrl,
+        duration: formatDuration(song.duration),
+        album: song.albumName,
+        composer: song.composer || song.singerName || '未知作曲',
+        arranger: song.arranger || song.singerName || '未知编曲',
+        description: song.lyrics || '暂无歌词'
+      });
+    }
+
+    // 找到当前歌曲在播放列表中的位置
+    const songIndex = playlist.findIndex(s => s.id === song.id);
+    if (songIndex !== -1) {
+      setCurrentTrackIndex(songIndex);
+    } else {
+      // 如果不在播放列表中,添加到末尾并播放
+      const newSong = {
+        id: song.id,
+        title: song.songName,
+        artist: song.singerName || '未知歌手',
+        url: song.fileUrl,
+        cover: song.coverUrl,
+        duration: formatDuration(song.duration),
+        album: song.albumName,
+        composer: song.composer || song.singerName || '未知作曲',
+        arranger: song.arranger || song.singerName || '未知编曲',
+        description: song.lyrics || '暂无歌词'
+      };
+      addToPlaylist(newSong);
+      setCurrentTrackIndex(playlist.length);
+    }
+
     setIsPlaying(true);
-    // 这里可以触发全局播放器播放该歌曲
-    console.log('播放歌曲:', song.title);
   };
 
+  // 添加到播放列表
   const handleAddToPlaylist = () => {
-    // 添加到播放列表并播放
-    console.log('添加到播放列表并播放:', song.title);
-    setIsPlaying(true);
+    const newSong = {
+      id: song.id,
+      title: song.songName,
+      artist: song.singerName || '未知歌手',
+      url: song.fileUrl,
+      cover: song.coverUrl,
+      duration: formatDuration(song.duration),
+      album: song.albumName,
+      composer: song.composer || song.singerName || '未知作曲',
+      arranger: song.arranger || song.singerName || '未知编曲',
+      description: song.lyrics || '暂无歌词'
+    };
+
+    addToPlaylist(newSong);
   };
+  const [playList, setplayList] = useState([])
+  const [collectLoading, setcollectLoading] = useState(false)
+  const getPlayListFun = async () => {
+    const res = await getPaylistApi()
+    setplayList(res.data)
+  }
+  const collection = () => {
+    getPlayListFun().then(res => {
+      setIsModalOpen(true)
+    })
+  };
+  const onCollect = async (id) => {
+    setcollectLoading(true)
+    try {
+      const res = await CollectSong({ playlistId: id, songId: song.id })
+      if (res.code === 200) {
+        messageApi.open({
+          type: 'success',
+          content: '收藏成功'
+        })
+        getPlayListFun()
+        setIsModalOpen(false)
+
+      }
+    } catch (error) {
+      messageApi.open({
+        type: 'error',
+        content: error.response.data.message
+      })
+    } finally {
+      setcollectLoading(false)
+    }
+  }
 
-  const toggleFavorite = () => {
-    setIsFavorite(!isFavorite);
+  const handleCreatePlaylist = () => {
+    setIsModalOpen(false);
+    setIsCreateModalOpen(true);
   };
 
+
+  const handleCreatePlaylistOk = () => {
+    setIsCreateModalOpen(false);
+
+    getPlayListFun().then(() => {
+      setIsModalOpen(true);
+    });
+  };
+
+  // 处理新建歌单取消
+  const handleCreatePlaylistCancel = () => {
+    setIsCreateModalOpen(false);
+    setIsModalOpen(true);
+  };
   return (
     <div className="song-detail-container">
+      {contextHolder}
       {/* 左侧:封面 */}
       <div className="cover-section">
-        <img src={song.cover} alt={song.title} className="album-cover" />
+        <img src={song.coverUrl} alt={song.songName} className="album-cover" />
         <div className="actions">
           <button className="play-btn" onClick={handlePlay}>
             <span>▶</span> 播放
@@ -59,35 +196,75 @@ const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
       <div className="info-section">
         <div className="title-section">
           <span className="tag">单曲</span>
-          <h1>{song.title}</h1>
+          <h1>{song.songName}</h1>
         </div>
         <div className="artist-info">
-          歌手:<a href="#">{song.artist}</a>
+          歌手:<a href="#">{song.singerName || '未知歌手'}</a>
         </div>
         <div className="album-info">
-          所属专辑:<a href="#">{song.album || song.title}</a>
+          所属专辑:<a href="#">{song.albumName || '未知专辑'}</a>
         </div>
         <div className="action-buttons">
-          <button className="btn-play" onClick={handlePlay}>
+          <Button className="btn-play" onClick={handlePlay}>
             <span>▶</span> 播放
-          </button>
-          <button className="btn-favorite" onClick={toggleFavorite}>
-            {isFavorite ? <HeartTwoTone twoToneColor="#eb2f96" /> : <HeartOutlined />}
+          </Button>
+          <Button onClick={() => collection()} className="btn-favorite">
+            <HeartOutlined />
             收藏
-          </button>
-          <button className="btn-share">分享</button>
-          <button className="btn-download">下载</button>
-          <button className="btn-comment">
+          </Button>
+          <Button className="btn-share">分享</Button>
+          <Button className="btn-download">下载</Button>
+          <Button className="btn-comment">
             <span>💬</span> (6112)
-          </button>
+          </Button>
         </div>
 
         <div className="music-info">
-          <p>作曲:{song.composer || song.artist}</p>
-          <p>编曲:{song.arranger || song.artist}</p>
-          <p>纯音乐,请欣赏</p>
+          <p>作曲:{song.composer || song.singerName || '未知作曲'}</p>
+          <p>编曲:{song.arranger || song.singerName || '未知编曲'}</p>
+          <p>发行时间:{song.releaseTime ? new Date(song.releaseTime).toLocaleDateString() : '未知'}</p>
+          <p>播放次数:{song.playCount || 0}</p>
+          {song.lyrics && <p>歌词:{song.lyrics.substring(0, 100)}...</p>}
         </div>
       </div>
+      <Modal
+        title="添加到歌单"
+        closable={{ 'aria-label': 'Custom Close Button' }}
+        open={isModalOpen}
+        keyboard={true}
+        footer={null}
+        onCancel={() => setIsModalOpen(false)}
+      >
+        {collectLoading ? <div className="selplaylist"> <Spin className='loaing' indicator={<LoadingOutlined spin />} size="large" /></div> :
+          <div className="selplaylist">
+            <div className="selplaylistItem" style={{ backgroundColor: '#e9e7e7ff' }} onClick={handleCreatePlaylist}>
+              <PlusCircleOutlined style={{ lineHeight: '55px', fontSize: '55px', color: '#9e9e9eff' }} />
+              <div style={{ lineHeight: '55px', paddingLeft: '20px' }}>
+                新歌单
+              </div>
+            </div>
+            {playList.map((item, index) => (
+              <div className="selplaylistItem" key={index} onClick={() => { onCollect(item.id) }}>
+                <Image
+                  src={item.coverUrl}
+                  preview={false} width={55} height={55}></Image>
+                <div className='selplaylistItem_txt'>
+                  <div>
+                    {item.playlistName}
+                  </div>
+                  <div>{item.songCount}首</div>
+                </div>
+              </div>
+            ))}
+          </div>}
+      </Modal>
+
+      {/* 新建歌单模态框组件 */}
+      <CreatePlaylistModal
+        open={isCreateModalOpen}
+        onCancel={handleCreatePlaylistCancel}
+        onOk={handleCreatePlaylistOk}
+      />
     </div>
   );
 };

+ 81 - 12
src/pages/layout/pages/SongDetail/index.tsx

@@ -1,20 +1,88 @@
+import { useState, useEffect } from 'react';
+import { useParams } from 'react-router-dom';
 import Rank_Recommend_body_list_Comment from '../find/rank/compomemts/Rank_Recommend_body_list_Comment'
 import Rank_Recommend_body_list_Comment_list from '../find/rank/compomemts/Rank_Recommend_body_list_Comment_list'
 import SongDetailPage from './components/SongDetailPage';
 import './index.css'
+import { geSongById } from '@/apis/song';
+
+interface Song {
+  id: number;
+  songName: string;
+  artistId?: number;
+  albumId?: number | null;
+  albumName?: string;
+  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;
+}
+
 const SongDetail = () => {
-  const song = {
-    id: 4,
-    title: '忠诚 loyalty(PHONK)',
-    artist: 'chao / BOY / KKK',
-    url: 'http://117.72.120.45:9000/testbucket/123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '03:45',
-    album: '忠诚 loyalty(PHONK)',
-    composer: 'chao/BOY/KKK',
-    arranger: 'chao/BOY/KKK',
-    description: '纯音乐,请欣赏'
-  };
+  const { id } = useParams<{ id: string }>();
+  const [song, setSong] = useState<Song | null>(null);
+  const [loading, setLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+
+  useEffect(() => {
+    const fetchSongDetail = async () => {
+      try {
+        if (id) {
+          const response = await geSongById(Number(id));
+          if (response.code === 200 && response.data) {
+            setSong(response.data);
+          } else {
+            setError(response.message || '获取歌曲详情失败');
+          }
+        }
+      } catch (error) {
+        console.error('获取歌曲详情失败:', error);
+        setError('获取歌曲详情失败');
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchSongDetail();
+  }, [id]);
+
+
+  if (loading) {
+    return <div className="loading">加载中...</div>;
+  }
+
+
+  if (error) {
+    return <div className="error">错误: {error}</div>;
+  }
+
+
+  if (!song) {
+    return <div className="no-song">歌曲不存在</div>;
+  }
 
   return (
     <div className='SongDetail'>
@@ -24,4 +92,5 @@ const SongDetail = () => {
     </div>
   )
 }
+
 export default SongDetail

+ 36 - 0
src/pages/layout/pages/artistinfo/index.css

@@ -50,3 +50,39 @@
 .artistinfo-container .content {
   min-height: 400px;
 }
+/* src/pages/layout/pages/find/singer/artistinfo/index.css */
+.artist-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  position: relative;
+}
+.artist-image-container {
+  position: relative;
+  display: inline-block;
+}
+.artist-avatar {
+  width: 150px !important;
+  height: 150px !important;
+  border-radius: 50%;
+  object-fit: cover;
+}
+.favorite-btn-overlay {
+  position: absolute;
+  top: 110px;
+  right: 10px;
+  z-index: 10;
+  background-color: rgba(255, 255, 255, 0.9);
+  border: none;
+}
+/* 如果你想让按钮在图片底部中央 */
+.favorite-btn-overlay {
+  position: absolute;
+  bottom: 10px;
+  right: 50%;
+  transform: translateX(50%);
+  z-index: 10;
+  background-color: rgba(0, 0, 0, 0.6);
+  color: white;
+}

+ 45 - 0
src/pages/layout/pages/artistinfo/index.less

@@ -6,6 +6,7 @@
   margin: 0 auto;
   border-left: 1px solid rgb(153, 153, 153);
   border-right: 1px solid rgb(153, 153, 153);
+
   .artist-header {
     margin-bottom: 20px;
 
@@ -59,4 +60,48 @@
   .content {
     min-height: 400px;
   }
+}
+
+/* src/pages/layout/pages/find/singer/artistinfo/index.css */
+
+.artist-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  position: relative;
+}
+
+.artist-image-container {
+  position: relative;
+  display: inline-block;
+}
+
+.artist-avatar {
+  width: 150px !important;
+  height: 150px !important;
+  border-radius: 50%;
+  object-fit: cover;
+}
+
+.favorite-btn-overlay {
+  position: absolute;
+  top: 110px;
+  right: 10px;
+  z-index: 10;
+  background-color: rgba(255, 255, 255, 0.9);
+  border: none;
+}
+
+
+
+/* 如果你想让按钮在图片底部中央 */
+.favorite-btn-overlay {
+  position: absolute;
+  bottom: 10px;
+  right: 50%;
+  transform: translateX(50%);
+  z-index: 10;
+  background-color: rgba(0, 0, 0, 0.6);
+  color: white;
 }

+ 78 - 7
src/pages/layout/pages/artistinfo/index.tsx

@@ -6,25 +6,75 @@ import Albums from './Albums/Albums'
 import MVs from './MVs/MVs'
 import Bio from './Bio/Bio'
 import { useParams } from 'react-router-dom'
-import { Image } from 'antd'
-import { SingerByIdApi } from '@/apis/artist'
+import { Image, Button, message } from 'antd'
+import { ChangeArtistIsCollectByid, queryArtistIsCollectByid, SingerByIdApi } from '@/apis/artist'
+import { HeartOutlined, HeartFilled } from '@ant-design/icons'
+import { getArtinfo } from '@/utils/artlist'
 
 const Artistinfo = () => {
   const [activeTab, setActiveTab] = useState('hot')
   const { id } = useParams()
   const [singerInfo, setSingerInfo] = useState<any>({})
+  const [isFavorite, setIsFavorite] = useState(false) // 跟踪收藏状态
+  const [loading, setLoading] = useState(false)
+  const artinfo = getArtinfo()
+  const [messageApi, contextHolder] = message.useMessage();
+
   const getSingerInfoDetail = async () => {
     const res = await SingerByIdApi(id)
     setSingerInfo(res.data)
   }
+
   useEffect(() => {
     getSingerInfoDetail()
+    getIsCOllectionFun(id)
   }, [])
+
+  const getIsCOllectionFun = async (id) => {
+    const res = await queryArtistIsCollectByid(id)
+    setIsFavorite(res.data)
+  }
+
+
+
+  // 收藏/取消收藏歌手的处理函数
+  const handleFavorite = async () => {
+    if (!singerInfo.id) {
+      messageApi.error('歌手信息加载中,请稍后再试')
+      return
+    }
+
+    setLoading(true)
+
+    try {
+      // 直接调用API函数并获取返回值
+      const res = await ChangeArtistIsCollectByid(id)
+      if (res.code === 200) {
+        // 更新本地状态
+        setIsFavorite(res.data)
+        if (res.data) {
+          messageApi.success(`已收藏 ${singerInfo.artistName}`)
+        } else {
+          messageApi.success(`已取消收藏 ${singerInfo.artistName}`)
+        }
+      } else {
+        messageApi.error(res.message || '操作失败')
+      }
+    } catch (error) {
+      console.error('操作失败:', error)
+      messageApi.error('操作失败,请重试')
+    } finally {
+      setLoading(false)
+    }
+  }
+
   const changeTab = (value: string) => {
     setActiveTab(value)
   }
+
   return (
     <div className="artistinfo-container">
+      {contextHolder}
       {/* 头部区域 */}
       <div className="artist-header">
         <div className="artist-info">
@@ -33,11 +83,32 @@ const Artistinfo = () => {
             {singerInfo.introduction?.split(',')[0] || '华语流行乐歌手'}
           </p>
         </div>
-        <Image
-          src={singerInfo.avatar}
-          preview={false}
-          alt={`${singerInfo.artistName}头像`}
-        />
+
+        {/* 图片和收藏按钮容器 */}
+        <div className="artist-image-container">
+          <Image
+            src={singerInfo.avatar}
+            preview={false}
+            alt={`${singerInfo.artistName}头像`}
+            className="artist-avatar"
+            fallback="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
+          />
+
+          {/* 收藏按钮覆盖在图片上 */}
+          {artinfo.id !== singerInfo.id && (
+            <Button
+              type="primary"
+              shape="circle"
+              size="small"
+              icon={isFavorite ? <HeartFilled /> : <HeartOutlined />}
+              onClick={handleFavorite}
+              className="favorite-btn-overlay"
+              danger={isFavorite} // 当已收藏时显示红色
+              loading={loading}
+            >
+            </Button>
+          )}
+        </div>
       </div>
 
       {/* Tab 切换 */}

+ 18 - 19
src/pages/layout/pages/find/album/index.tsx

@@ -1,28 +1,27 @@
+import { listAllAlbumApi } from '@/apis/album';
 import Recommend_index from '../recommend/components/Recommend_body_left/components/Recommend_index'
 import './index.css'
-const album = () => {
-  // 新碟上架配置
-  const albumConfig = {
-    title: '新碟上架',
-    basePath: '/playlist',
-    list: [
-      {
-        id: 10,
-        title: '摩天动物园 - 邓紫棋',
-        src: 'https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg?param=140y140',
-      },
-      {
-        id: 21,
-        title: '范特西 - 周杰伦',
-        src: 'https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg?param=140y140',
-      },
-    ]
-  };
+import { useEffect, useState } from 'react';
+const Album = () => {
+  const [albumConfig, setAlbumConfig] = useState<any>({})
+  const getAlbumFun = async () => {
+    const res = await listAllAlbumApi()
+    // 新碟上架配置
+    setAlbumConfig({
+      title: '新碟上架',
+      basePath: '/album',
+      list: res.data
+    })
+  }
 
+
+  useEffect(() => {
+    getAlbumFun()
+  }, [])
   return (
     <div className="Album">
       <Recommend_index config={albumConfig} />
     </div>
   )
 }
-export default album
+export default Album

+ 2 - 1
src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content_bill/index.tsx

@@ -3,9 +3,10 @@ import Recommend_content_title from '../Recommend_content_title'
 import './index.css'
 
 const Recommend_content_bill = () => {
+const title='榜单'
   return (
     <div>
-      <Recommend_content_title />
+      <Recommend_content_title title={title} />
       <div className='Recommend_content_bill_item_container'>
         <Recommend_content_bill_item />
         <Recommend_content_bill_item />

+ 1 - 2
src/pages/layout/pages/find/recommend/components/Recommend_body_left/index.tsx

@@ -58,8 +58,7 @@ const Recommend_body_left = () => {
   return (
     <div className="Recommend_left">
       <Recommend_index config={playlistConfig} />
-      <Recommend_index config={playlistConfig} />
-      <Recommend_index config={playlistConfig} />
+
       <Recommend_content_bill />
     </div>
   )

+ 325 - 183
src/pages/layout/pages/find/songList/index.tsx

@@ -4,74 +4,331 @@ import {
   Image,
   Pagination,
   Popover,
-  type PaginationProps,
+  Spin,
 } from 'antd'
 import './index.css'
 import { DownOutlined } from '@ant-design/icons'
 import { useNavigate } from 'react-router-dom'
-const SongListinfo = [
-  {
-    id: 10,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 1,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 2,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 3,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 4,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 5,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 6,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 7,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 8,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-  {
-    id: 9,
-    src: 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
-    title: '深度睡眠 |重度失眠者专用歌单',
-    content: 'by 音旧- ',
-  },
-]
+import { useState, useEffect } from 'react'
+import { getPlaylistPage } from '@/apis/payList'
+import type { PageQuery } from '@/type/PageQuery'
+
+// 定义歌单数据类型
+interface PlaylistItem {
+  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;
+}
+
+const SongList = () => {
+  const navigate = useNavigate()
+  const [playlistData, setPlaylistData] = useState<PlaylistItem[]>([])
+  const [loading, setLoading] = useState(false)
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  })
+  // 使用一个对象来存储所有分类的选中状态
+  const [selectedFilters, setSelectedFilters] = useState<Record<string, string[]>>({})
+
+  const fetchPlaylistData = async (pageNo: number, pageSize: number, filters?: Record<string, string>) => {
+    setLoading(true);
+    try {
+      const params: PageQuery = {
+        pageNo,
+        pageSize,
+      };
+
+      // 添加筛选参数
+      if (filters) {
+        Object.keys(filters).forEach(key => {
+          if (filters[key]) {
+            params[key as keyof PageQuery] = filters[key];
+          }
+        });
+      }
+
+      console.log('请求参数:', params); // 调试用
+
+      const response = await getPlaylistPage(params);
+      const { records, total, current, size } = response.data;
 
+      // 转换数据格式
+      const transformedData = records.map(item => ({
+        id: item.id,
+        src: item.coverUrl || 'http://p1.music.126.net/g2_Gv0dtAicJ3ChTYu28_g==/1393081239628722.jpg?param=140y140',
+        title: item.playlistName,
+        content: `${item.songCount}首`,
+      }));
+
+      setPlaylistData(transformedData);
+
+      // 更新分页信息
+      setPagination({
+        current: current || pageNo,
+        pageSize: size || pageSize,
+        total: total || 0,
+      });
+    } catch (error) {
+      console.error('获取歌单数据失败:', error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 构建筛选参数
+  const buildFilterParams = () => {
+    const params: Record<string, string> = {};
+    
+    Object.entries(selectedFilters).forEach(([category, tags]) => {
+      if (tags.length > 0) {
+        // 根据分类映射到后端参数名
+        let paramName = '';
+        switch (category) {
+          case '语种':
+            paramName = 'language';
+            break;
+          case '风格':
+            paramName = 'style';
+            break;
+          case '场景':
+            paramName = 'scene';
+            break;
+          case '情感':
+            paramName = 'emotion';
+            break;
+          case '主题':
+            paramName = 'theme'; // 主题使用theme字段
+            break;
+          default:
+            paramName = 'tag'; // 默认使用tag字段
+        }
+        
+        // 只取第一个选中的标签,因为后端可能只支持单个值
+        if (tags.length > 0) {
+          params[paramName] = tags[0];
+        }
+      }
+    });
+    
+    return params;
+  };
+
+  // 获取选中的标签名称数组
+  const getSelectedTagNames = () => {
+    return Object.values(selectedFilters).flat();
+  };
+
+  // 当筛选条件变化时重新请求数据
+  useEffect(() => {
+    const filterParams = buildFilterParams();
+    fetchPlaylistData(pagination.current, pagination.pageSize, filterParams);
+  }, [selectedFilters]);
+
+  const handlePageChange = (page: number) => {
+    if (pagination.current === page) return;
+    setPagination(prev => ({
+      ...prev,
+      current: page,
+    }));
+    const filterParams = buildFilterParams();
+    fetchPlaylistData(page, pagination.pageSize, filterParams);
+  };
+
+  // 处理歌单点击事件
+  const handlePlaylistClick = (id: number) => {
+    navigate(`/playlist/${id}`)
+  }
+
+  // 处理分类选择
+  const handleCategorySelect = (category: string, tag: string) => {
+    setSelectedFilters(prev => {
+      const currentFilters = { ...prev };
+      const categoryFilters = currentFilters[category] || [];
+      
+      // 如果标签已存在,则移除;如果不存在,则添加
+      if (categoryFilters.includes(tag)) {
+        currentFilters[category] = categoryFilters.filter(t => t !== tag);
+      } else {
+        // 每个分类只允许选择一个标签
+        currentFilters[category] = [tag];
+      }
+      
+      // 如果该分类没有选中任何标签,则删除该分类
+      if (currentFilters[category].length === 0) {
+        delete currentFilters[category];
+      }
+      
+      return currentFilters;
+    });
+    
+    // 重置分页到第一页
+    setPagination(prev => ({ ...prev, current: 1 }));
+  };
+
+  // 检查标签是否被选中
+  const isTagSelected = (category: string, tag: string) => {
+    return selectedFilters[category]?.includes(tag) || false;
+  };
+
+  // 重置特定分类
+  const handleResetCategory = (category: string) => {
+    setSelectedFilters(prev => {
+      const newFilters = { ...prev };
+      delete newFilters[category];
+      return newFilters;
+    });
+    
+    setPagination(prev => ({ ...prev, current: 1 }));
+  };
+
+  // 重置所有筛选
+  const handleResetAll = () => {
+    setSelectedFilters({});
+    setPagination(prev => ({ ...prev, current: 1 }));
+    fetchPlaylistData(1, pagination.pageSize, {});
+  };
+
+  const content = (
+    <div className="SongList_dig">
+      {SongList_category.map((item, index) => (
+        <div className="SongList_dig_content" key={index}>
+          <div className="SongList_dig_content_left">
+            <div className="SongList_dig_content_left_image">
+              <Image src={item.icon} preview={false}></Image>
+            </div>
+            <div className="SongList_dig_content_left_title">{item.title}</div>
+          </div>
+          <div className="SongList_dig_content_right">
+            <div className="SongList_dig_content_right_item">
+              {item.data.map((subItem, subIndex) => (
+                <span
+                  className={`SongList_dig_content_right_item_item ${isTagSelected(item.title, subItem.title) ? 'active' : ''}`}
+                  key={subIndex}
+                  onClick={() => handleCategorySelect(item.title, subItem.title)}
+                  style={{ cursor: 'pointer' }}
+                >
+                  {subItem.title}
+                </span>
+              ))}
+              <Button 
+                type="link" 
+                size="small" 
+                onClick={() => handleResetCategory(item.title)}
+                style={{ marginLeft: 8 }}
+              >
+                清除
+              </Button>
+            </div>
+          </div>
+        </div>
+      ))}
+    </div>
+  )
+
+  // 获取选中的标签名称
+  const selectedTagNames = getSelectedTagNames();
+
+  return (
+    <div className="songList">
+      <div className="songList_Title">
+        <div className="songList_Title_left">
+          <div className="songList_Title_left_title">
+            {selectedTagNames.length > 0 ? selectedTagNames.join(',') : '全部'}
+            {selectedTagNames.length > 0 && (
+              <Button
+                type="link"
+                size="small"
+                onClick={handleResetAll}
+                style={{ marginLeft: 8 }}
+              >
+                清除
+              </Button>
+            )}
+          </div>
+          <div className="songList_Title_left_select">
+            <Popover
+              content={content}
+              placement="bottomRight"
+              title={<Button>全部风格</Button>}
+            >
+              <Button style={{ color: 'rgb(0, 135, 253)' }}>
+                选择分类
+                <DownOutlined />
+              </Button>
+            </Popover>
+          </div>
+        </div>
+        <div className="songList_Title_right">
+          <Button
+            type="primary"
+            danger
+            onClick={() => handleCategorySelect('风格', '热门')}
+          >
+            热门
+          </Button>
+        </div>
+      </div>
+
+      <Spin spinning={loading}>
+        <div className="songList_Content">
+          {playlistData.map((item) => (
+            <div
+              className="songList_Content_body"
+              key={item.id}
+              onClick={() => handlePlaylistClick(item.id)}
+              style={{ cursor: 'pointer' }}
+            >
+              <div className="songList_Content_top">
+                <Image preview={false} width={140} height={140} src={item.src}></Image>
+              </div>
+              <div className="songList_Content_bottom">
+                <div className="songList_Content_bottom_title">
+                  {item.title}
+                </div>
+                <div className="songList_Content_bottom_content">
+                  {item.content}
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </Spin>
+
+      <div className="songList_list_Pagination">
+        <ConfigProvider
+          theme={{
+            components: {
+              Pagination: {
+                itemActiveBg: 'rgb(255, 0, 0)',
+              },
+            },
+          }}
+        >
+          <Pagination
+            current={pagination.current}
+            pageSize={pagination.pageSize}
+            total={pagination.total}
+            onChange={handlePageChange}
+            showSizeChanger={false}
+            showTotal={(total, range) => `${range[0]}-${range[1]} 共 ${total} 条`}
+          />
+        </ConfigProvider>
+      </div>
+    </div>
+  )
+}
+
+// 歌单分类数据
 const SongList_category = [
   {
     id: 0,
@@ -89,7 +346,6 @@ const SongList_category = [
     id: 1,
     icon: 'https://web-asd-asd.oss-cn-beijing.aliyuncs.com/%E9%A3%8E%E6%A0%BC.png',
     title: '风格',
-    //流行| 摇滚| 民谣| 电子| 舞曲| 说唱| 轻音乐| 爵士| 乡村| R&B/Soul| 古典| 民族| 英伦| 金属| 朋克| 蓝调| 雷鬼| 世界音乐| 拉丁| New Age| 古风| 后摇| Bossa Nova|
     data: [
       { id: 5, title: '流行', pid: 1 },
       { id: 6, title: '摇滚', pid: 1 },
@@ -121,7 +377,6 @@ const SongList_category = [
     icon: 'https://web-asd-asd.oss-cn-beijing.aliyuncs.com/%E5%9C%BA%E6%99%AF.png',
     title: '场景',
     data: [
-      //清晨| 夜晚| 学习| 工作| 午休| 下午茶| 地铁| 驾车| 运动| 旅行| 散步| 酒吧|
       { id: 0, title: '清晨', pid: 2 },
       { id: 1, title: '夜晚', pid: 2 },
       { id: 2, title: '学习', pid: 2 },
@@ -173,126 +428,13 @@ const SongList_category = [
       { id: 10, title: 'KTV', pid: 4 },
       { id: 11, title: '经典', pid: 4 },
       { id: 12, title: '翻唱', pid: 4 },
-      { id: 13, title: '吉他', pid: 4 },
-      { id: 14, title: '钢琴', pid: 4 },
-      { id: 15, title: '器乐', pid: 4 },
-      { id: 16, title: '榜单', pid: 4 },
-      { id: 17, title: '00后', pid: 4 },
+      { id: 13, title: '吉他', pid: 13 },
+      { id: 14, title: '钢琴', pid: 14 },
+      { id: 15, title: '器乐', pid: 15 },
+      { id: 16, title: '榜单', pid: 16 },
+      { id: 17, title: '00后', pid: 17 },
     ],
   },
 ]
 
-const SongList = () => {
-  const navigate = useNavigate() // 使用 useNavigate hook
-
-  const itemRender: PaginationProps['itemRender'] = (
-    _,
-    type,
-    originalElement
-  ) => {
-    if (type === 'prev') {
-      return <a style={{ color: 'black' }}>上一页</a>
-    }
-    if (type === 'next') {
-      return <a style={{ color: 'black' }}>下一页</a>
-    }
-    return originalElement
-  }
-
-  // 处理歌单点击事件
-  const handlePlaylistClick = (id: number) => {
-    navigate(`/playlist/${id}`)
-  }
-
-  const content = (
-    <div className="SongList_dig">
-      {SongList_category.map((item, index) => (
-        <div className="SongList_dig_content" key={index}>
-          <div className="SongList_dig_content_left">
-            <div className="SongList_dig_content_left_image">
-              <Image src={item.icon} preview={false}></Image>
-            </div>
-            <div className="SongList_dig_content_left_title">{item.title}</div>
-          </div>
-          <div className="SongList_dig_content_right">
-            <div className="SongList_dig_content_right_item">
-              {item.data.map((item, index) => (
-                <span className='SongList_dig_content_right_item_item' key={index}>{item.title}</span>
-              ))}
-            </div>
-          </div>
-        </div>
-      ))}
-    </div>
-  )
-
-  return (
-    <>
-      <div className="songList">
-        <div className="songList_Title">
-          <div className="songList_Title_left">
-            <div className="songList_Title_left_title">全部</div>
-            <div className="songList_Title_left_select">
-              <Popover
-                content={content}
-                placement="bottomRight"
-                title={<Button>全部风格</Button>}
-              >
-                <Button style={{ color: 'rgb(0, 135, 253)' }}>
-                  选择分类
-                  <DownOutlined />
-                </Button>
-              </Popover>
-            </div>
-          </div>
-          <div className="songList_Title_right">
-            <Button type="primary" danger>
-              热门
-            </Button>
-          </div>
-        </div>
-        <div className="songList_Content">
-          {SongListinfo.map((item) => (
-            <div
-              className="songList_Content_body"
-              key={item.id}
-              onClick={() => handlePlaylistClick(item.id)} // 添加点击事件
-              style={{ cursor: 'pointer' }} // 添加鼠标指针样式
-            >
-              <div className="songList_Content_top">
-                <Image preview={false} width={140} src={item.src}></Image>
-              </div>
-              <div className="songList_Content_bottom">
-                <div className="songList_Content_bottom_title">
-                  {item.title}
-                </div>
-                <div className="songList_Content_bottom_content">
-                  {item.content}
-                </div>
-              </div>
-            </div>
-          ))}
-        </div>
-        <div className="songList_list_Pagination">
-          <ConfigProvider
-            theme={{
-              components: {
-                Pagination: {
-                  itemActiveBg: 'rgb(255, 0, 0)',
-                },
-              },
-            }}
-          >
-            <Pagination
-              total={500}
-              itemRender={itemRender}
-              showSizeChanger={false}
-            />
-          </ConfigProvider>
-        </div>
-      </div>
-    </>
-  )
-}
-
 export default SongList

+ 1 - 0
src/pages/layout/pages/mine/artist/index.css

@@ -1,5 +1,6 @@
 .Mine_Artist .Mine_Artist_item {
   display: flex;
+  cursor: pointer;
   padding: 20px 0;
   border-bottom: 1px solid #c6c6c6;
 }

+ 1 - 1
src/pages/layout/pages/mine/artist/index.less

@@ -1,7 +1,7 @@
 .Mine_Artist {
   .Mine_Artist_item {
     display: flex;
-
+    cursor: pointer;
     padding: 20px 0;
     border-bottom: 1px solid rgb(198, 198, 198);
 

+ 19 - 33
src/pages/layout/pages/mine/artist/index.tsx

@@ -1,37 +1,21 @@
 import { Image } from 'antd';
 import './index.css';
-import { useEffect, useState } from 'react';
-
-// 模拟接口请求获取歌手数据
-const fetchArtists = async () => {
-  // 这里应该是真实的API调用
-  return [
-    { id: '1', name: 'HOPERUI', albumCount: 26, avatar: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-    { id: '2', name: 'Taylor Swift', albumCount: 10, avatar: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-    { id: '3', name: 'Ed Sheeran', albumCount: 5, avatar: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-    { id: '4', name: 'Adele', albumCount: 8, avatar: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-  ];
-};
-
+import { use, useEffect, useState } from 'react';
+import { getUserinfo } from '@/utils';
+import { queryArtistIsCollectByUserid } from '@/apis/artist';
+import { useNavigate } from 'react-router-dom';
 const Mine_Artist = () => {
-  const [artists, setArtists] = useState<any[]>([]);
   const [loading, setLoading] = useState(true);
-
-  // 获取歌手数据
+  const userinfo = getUserinfo()
+  const [ArtistList, setArtistList] = useState([])
+  const useroute = useNavigate()
+  const getSongerFun = async () => {
+    const res = await queryArtistIsCollectByUserid(userinfo.id)
+    setArtistList(res.data)
+    setLoading(false)
+  }
   useEffect(() => {
-    const loadArtists = async () => {
-      try {
-        setLoading(true);
-        const data = await fetchArtists();
-        setArtists(data);
-      } catch (error) {
-        console.error('获取歌手失败:', error);
-      } finally {
-        setLoading(false);
-      }
-    };
-
-    loadArtists();
+    getSongerFun()
   }, []);
 
   if (loading) {
@@ -40,17 +24,19 @@ const Mine_Artist = () => {
 
   return (
     <div className='Mine_Artist'>
-      {artists.map(artist => (
-        <div key={artist.id} className="Mine_Artist_item">
+      {ArtistList.map(artist => (
+        <div key={artist.id} className="Mine_Artist_item" onClick={() => useroute(`/find/artistinfo/${artist.id}`)}>
           <div className="Mine_Artist_item_Img">
             <Image
+              preview={false}
               width={80}
+              height={80}
               src={artist.avatar}
             />
           </div>
           <div className="Mine_Artist_item_Info">
-            <div className="Mine_Artist_item_Info_title">{artist.name}</div>
-            <div className="Mine_Artist_item_Info_num">{artist.albumCount}个专辑</div>
+            <div className="Mine_Artist_item_Info_title">{artist.artistName}</div>
+            <div className="Mine_Artist_item_Info_num">{artist.num}个专辑</div>
           </div>
         </div>
       ))}

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


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


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


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

@@ -17,6 +17,7 @@
   list-style: none;
   padding: 0;
   margin: 0;
+  min-height: 35vh;
 }
 .song-list-container .song-list .song-item {
   display: grid;

+ 1 - 1
src/pages/layout/pages/mine/components/PlaylistDetail/MusicList/index.less

@@ -22,7 +22,7 @@
     list-style: none;
     padding: 0;
     margin: 0;
-
+    min-height: 35vh;
     // 单个歌曲项
     .song-item {
       display: grid;

+ 24 - 9
src/pages/layout/pages/mine/components/PlaylistDetail/MusicList/index.tsx

@@ -1,9 +1,8 @@
-// src/components/SongList.tsx
 import React from 'react';
 import { useMusicPlayer } from '@/context/MusicPlayerContext';
 import type { Song } from '@/type/Song';
+import { useNavigate } from 'react-router-dom';
 import './index.css'
-// 将秒数转换为 mm:ss 格式
 const formatDuration = (seconds: number) => {
   const mins = Math.floor(seconds / 60);
   const secs = seconds % 60;
@@ -21,7 +20,10 @@ const MusicList: React.FC<SongListProps> = ({ songs }) => {
     setIsPlaying,
     addToPlaylist
   } = useMusicPlayer();
-  // 播放指定歌曲
+
+  const navigate = useNavigate();
+
+
   const handlePlaySong = (index: number) => {
     if (songs[index]) {
       const formattedSongs = songs.map(song => ({
@@ -33,14 +35,19 @@ const MusicList: React.FC<SongListProps> = ({ songs }) => {
         duration: formatDuration(song.duration)
       }));
 
-      // 替换播放列表为当前歌单
+
       setPlaylist(formattedSongs);
       setCurrentTrackIndex(index);
       setIsPlaying(true);
     }
   };
 
-  // 添加单首歌曲到播放列表
+
+  const handleSongClick = (songId: number) => {
+    navigate(`/SongDetail/${songId}`);
+  };
+
+
   const handleAddSong = (songIndex: number) => {
     if (songs[songIndex]) {
       const song = songs[songIndex];
@@ -61,7 +68,7 @@ const MusicList: React.FC<SongListProps> = ({ songs }) => {
     <div className={`song-list-container`}>
       <div className="song-list-header">
         <div className="song-number">序号</div>
-        <div className="song-title">歌曲标题</div>
+        <div className="song-title" style={{ textAlign: 'left' }}>歌曲标题</div>
         <div className="song-duration">时长</div>
         <div className="song-artist">歌手</div>
         <div className="song-album">专辑</div>
@@ -73,9 +80,17 @@ const MusicList: React.FC<SongListProps> = ({ songs }) => {
             <div className="song-number">{index + 1}</div>
             <div
               className="song-title"
-              onClick={() => handlePlaySong(index)}
+              onClick={() => handleSongClick(song.id)}
             >
-              <span className="play-icon">▶</span>
+              <span
+                className="play-icon"
+                onClick={(e) => {
+                  e.stopPropagation();
+                  handlePlaySong(index);
+                }}
+              >
+                ▶
+              </span>
               {song.songName}
             </div>
             <div className="song-duration">{formatDuration(song.duration)}</div>
@@ -84,7 +99,7 @@ const MusicList: React.FC<SongListProps> = ({ songs }) => {
             <button
               className="add-song-btn"
               onClick={(e) => {
-                e.stopPropagation(); // 防止触发歌曲播放
+                e.stopPropagation();
                 handleAddSong(index);
               }}
             >

+ 82 - 13
src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx

@@ -1,8 +1,9 @@
 // src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx
-import { Image } from 'antd';
-import React from 'react';
+import { Button, Image, message, } from 'antd';
+import React, { useState, useEffect } from 'react';
 import './index.css'
-
+import { getArtinfo } from '@/utils/artlist';
+import { CollectionSongApi, IsCollectionSongApi, UnCollectionSongApi } from '@/apis/payList';
 // 专辑类型定义 - 添加 playCount 字段
 interface Album {
   id: number;
@@ -54,7 +55,7 @@ const formatDate = (dateString: string | null) => {
   if (!dateString) return '未知时间';
   const date = new Date(dateString);
   return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
-};
+}
 
 const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
   playlist,
@@ -62,6 +63,22 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
   onPlayPlaylist,
   onAddPlaylist
 }) => {
+  const [isCollection, setIsCollection] = useState<boolean>(false)
+  const [loading, setLoading] = useState(false);
+  const [messageApi, contextHolder] = message.useMessage();
+
+  console.log(playlist);
+  const artinfo = getArtinfo();
+  useEffect(() => {
+    const checkCollectionStatus = async () => {
+      if (playlist) {
+        const res = await IsCollectionSongApi(playlist.id)
+        setIsCollection(res.data)
+      }
+    };
+    checkCollectionStatus();
+  }, [playlist?.id]);
+
   if (!playlist) {
     return (
       <div className="playlist-header">
@@ -77,7 +94,7 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
   const name = isAlbum ? playlist.albumName : (isPlaylist ? playlist.playlistName : '未知');
   const coverUrl = playlist.coverUrl;
   const createTime = playlist.createTime;
-  
+
   // 修复:专辑没有 playCount 字段,使用 songsCount 或其他相关字段,或者默认为 0
   // 如果专辑需要显示播放次数,可能需要后端API返回这个字段
   let playCount = 0;
@@ -88,9 +105,43 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
     playCount = playlist.playCount;
   }
 
+  const CollectionSongFun = async (playlistId: number) => {
+    setLoading(true);
+    try {
+      const res = await CollectionSongApi(playlistId)
+      console.log(res);
+      if (res.code == 200) {
+        messageApi.success(res.data);
+        setIsCollection(true); // 成功收藏后更新状态
+      }
+    } catch (error) {
+      messageApi.error('收藏失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  const UnCollectionSongFun = async (playlistId: number) => {
+    setLoading(true);
+    try {
+      const res = await UnCollectionSongApi(playlistId)
+      console.log(res);
+      if (res.code == 200) {
+        messageApi.success(res.data);
+        setIsCollection(false);
+      }
+    } catch (error) {
+      messageApi.error('取消收藏失败');
+    } finally {
+      setLoading(false);
+    }
+  }
+
   return (
     <div className="playlist-header">
+      {contextHolder}
       <Image
+        preview={false}
         src={coverUrl || 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'}
         alt={name}
         className="cover-img"
@@ -106,22 +157,40 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
           <span className="create-time"> {formatDate(createTime)} 创建</span>
         </div>
         <div className="actions">
-          <button
+          <Button
             className="play-btn"
             onClick={onPlayPlaylist}
           >
             播放
-          </button>
-          <button
+          </Button>
+          <Button
             className="add-btn"
             onClick={onAddPlaylist}
           >
             +
-          </button>
-          {showCollect && <button className="collect-btn">收藏</button>}
-          <button className="share-btn">分享</button>
-          <button className="download-btn">下载</button>
-          <button className="comment-btn">评论</button>
+          </Button>
+          {showCollect && artinfo.id != playlist.artistId && (
+            isCollection ? (
+              <Button
+                loading={loading}
+                onClick={() => { UnCollectionSongFun(playlist.id) }}
+                className="collect-btn"
+              >
+                已收藏
+              </Button>
+            ) : (
+              <Button
+                loading={loading}
+                onClick={() => { CollectionSongFun(playlist.id) }}
+                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>{playCount}</strong>次

+ 5 - 22
src/pages/layout/pages/mine/components/PlaylistDetail/index.tsx

@@ -1,4 +1,3 @@
-// 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';
@@ -9,7 +8,6 @@ import type { Song } from '@/type/Song';
 import PlaylistHeader from './PlaylistHeader';
 import MusicList from './MusicList';
 
-// 歌单类型定义
 interface Playlist {
   id: number;
   userId: number;
@@ -24,25 +22,18 @@ interface Playlist {
   status: boolean;
 }
 
-// 响应数据类型
 interface PlaylistDataResponse {
-  playlist: Playlist; // 只有歌单对象
+  playlist: Playlist;
   songs: Song[];
 }
 
-// 将秒数转换为 mm:ss 格式
+
 const formatDuration = (seconds: number) => {
   const mins = Math.floor(seconds / 60);
   const secs = seconds % 60;
   return `${mins}:${secs.toString().padStart(2, '0')}`;
 };
 
-// 格式化时间为 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')}`;
-};
-
 interface PlaylistDetailProps {
   id: number;
   showCollect?: boolean;
@@ -59,7 +50,6 @@ const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true
     addToPlaylist
   } = useMusicPlayer();
 
-  // 播放整个歌单(替换当前播放列表)
   const handlePlayPlaylist = () => {
     if (playlistData && playlistData.songs.length > 0) {
       const formattedSongs = playlistData.songs.map(song => ({
@@ -69,16 +59,13 @@ const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true
         url: song.fileUrl,
         cover: song.coverUrl,
         duration: formatDuration(song.duration)
-      }));
-
-      // 替换整个播放列表
+      }))
       setPlaylist(formattedSongs);
       setCurrentTrackIndex(0);
       setIsPlaying(true);
     }
-  };
+  }
 
-  // 添加整个歌单到播放列表(不替换)
   const handleAddPlaylist = () => {
     if (playlistData && playlistData.songs.length > 0) {
       const formattedSongs = playlistData.songs.map(song => ({
@@ -90,12 +77,10 @@ const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true
         duration: formatDuration(song.duration)
       }));
 
-      // 添加到现有播放列表
       formattedSongs.forEach(song => addToPlaylist(song));
     }
   };
 
-  // 请求歌单详情
   useEffect(() => {
     const fetchData = async () => {
       try {
@@ -120,13 +105,12 @@ const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true
         setLoading(false);
       }
     };
-
     if (id) {
       fetchData();
     }
   }, [id]);
 
-  // 如果没有ID,直接返回错误信息
+
   if (!id) {
     return <div className="error">无效的ID</div>;
   }
@@ -170,7 +154,6 @@ const PlaylistDetail: React.FC<PlaylistDetailProps> = ({ id, showCollect = true
         onAddPlaylist={handleAddPlaylist}
       />
 
-      {/* 使用独立的歌曲列表组件 */}
       {validSongs.length > 0 ? (
         <MusicList songs={validSongs} />
       ) : (

+ 34 - 98
src/pages/layout/pages/mine/index.tsx

@@ -1,21 +1,15 @@
+// src/pages/layout/pages/mine/index.tsx
 import './index.css'
 import { useState, useEffect } from 'react';
-import { Button, Checkbox, Flex, Image, Input, Menu, Modal } from 'antd';
+import { Button, Image, Menu } from 'antd';
 import type { MenuProps } from 'antd';
 import { AppstoreOutlined, MailOutlined, PlusOutlined, SettingOutlined } from '@ant-design/icons';
 import { Outlet, useNavigate } from 'react-router-dom';
-import { message } from 'antd';
-import { addplayList, getPaylistApi } from '@/apis/payList';
+import { getCollectionListApi, getPaylistApi } from '@/apis/payList';
+import CreatePlaylistModal from '../../components/CreatePlaylistModal';
+
 type MenuItem = Required<MenuProps>['items'][number];
-const fetchCollectionists = async () => {
-  return [
-    { id: '6', name: '我喜欢的音乐', count: 906 },
-    { id: '7', name: '2025、8、21', count: 19 },
-    { id: '8', name: 'dj', count: 55 },
-    { id: '9', name: '奥斯卡', count: 35 },
-    { id: '10', name: '131313', count: 4 },
-  ]
-}
+
 const Mine = () => {
   const navigate = useNavigate()
   const [selectedKey, setSelectedKey] = useState('artist')
@@ -23,79 +17,30 @@ const Mine = () => {
   const [collectonList, setCollectonList] = useState<any[]>([])
   const [loading, setLoading] = useState(true)
   const [isModalOpen, setIsModalOpen] = useState(false)
-  const [payName, setPayName] = useState('')
-  const [messageApi, contextHolder] = message.useMessage()
-  const [checked, setChecked] = useState(false)
-  const showModal = () => {
-    setIsModalOpen(true);
-  };
-
-
-
-  const handleCancel = () => {
-    setIsModalOpen(false);
-  };
 
-  // 标题映射对象
-  const titleMap = {
-    artist: '我的歌手',
-    mv: '我的视频',
-    radio: '我的电台',
-    playlist: '新建创建的歌单',
-    collection: '收藏的歌单'
-  };
   const loadPlaylists = async () => {
     try {
       setLoading(true);
       const res = await getPaylistApi();
       setPlaylists(res.data);
-      const data1 = await fetchCollectionists();
-      setCollectonList(data1);
+      const data1 = await getCollectionListApi();
+      setCollectonList(data1.data);
     } catch (error) {
       console.error('获取歌单失败:', error);
     } finally {
       setLoading(false);
     }
   };
+
   // 获取歌单数据
   useEffect(() => {
-
-
     loadPlaylists();
   }, []);
-  const handleOk = async () => {
-    if (payName === '') {
-      // 提示用户输入歌单名称
-      messageApi.open({
-        type: 'error',
-        content: '请输入歌单名',
-      });
-      return;
-    }
-    const res = await addplayList({ playlistName: payName, status: checked })
-    console.log(res)
-    if (res.code != 200) {
-      messageApi.open({
-        type: 'error',
-        content: res.message,
-      });
-    }
-    setChecked(false)
-    setPayName('')
-    setIsModalOpen(false)
-    console.log(payName)
-    messageApi.open({
-      type: 'success',
-      content: '歌单创建成功',
-    });
-    loadPlaylists()
-  };
+
   // 处理菜单项点击事件
   const onClick: MenuProps['onClick'] = (e) => {
     const { key } = e;
-
     console.log(e);
-
     setSelectedKey(key);
 
     if (key.startsWith('playlist-')) {
@@ -162,7 +107,7 @@ const Mine = () => {
             icon={<PlusOutlined />}
             onClick={(e) => {
               e.stopPropagation();
-              showModal()
+              setIsModalOpen(true)
             }}
             style={{ margin: 0, width: '80px', height: '30px', marginLeft: '10px', padding: '5px' }}
             color="primary" variant="outlined"
@@ -181,10 +126,17 @@ const Mine = () => {
     },
   ];
 
+  const handleModalOk = () => {
+    setIsModalOpen(false);
+    loadPlaylists(); // 刷新歌单列表
+  };
+
+  const handleModalCancel = () => {
+    setIsModalOpen(false);
+  };
 
   return (
     <div className='Mine'>
-      {contextHolder}
       <div className='Mine_tabs'>
         <Menu
           mode="inline"
@@ -202,40 +154,24 @@ const Mine = () => {
         <div className='Mine_Content_hr' />
         <Outlet />
       </div>
-      <Modal
-        title="新建歌单"
-        closable
+      
+      {/* 使用组件 */}
+      <CreatePlaylistModal
         open={isModalOpen}
-        onOk={handleOk}
-        onCancel={handleCancel}
-        width={500}
-        centered
-        destroyOnHidden
-        cancelText={"取消"}
-        okText={"确定"}
-      >
-        <div style={{ padding: '20px 0' }}>
-          <div style={{ marginBottom: '24px' }}>
-            <label style={{ display: 'inline-block', width: '60px', verticalAlign: 'middle', marginRight: '10px' }}>
-              歌单名:
-            </label>
-            <Input onChange={(e) => setPayName(e.target.value)} value={payName} placeholder="请输入歌单名称" style={{ width: '300px' }} />
-            <Checkbox style={{ paddingLeft: 10 }} onChange={(e) => {
-              setChecked(e.target.checked); console.log(e.target.checked);
-            }}>公开</Checkbox>
-          </div>
-          <p style={{
-            color: '#999',
-            fontSize: '12px',
-            textAlign: 'center',
-            marginBottom: '0'
-          }}>
-            可通过"收藏"将音乐添加到新歌单中
-          </p>
-        </div>
-      </Modal>
+        onCancel={handleModalCancel}
+        onOk={handleModalOk}
+      />
     </div>
   );
 };
 
+// 标题映射对象移到组件外部
+const titleMap = {
+  artist: '我的歌手',
+  mv: '我的视频',
+  radio: '我的电台',
+  playlist: '新建创建的歌单',
+  collection: '收藏的歌单'
+};
+
 export default Mine;

+ 1 - 0
src/pages/layout/pages/mine/mv/index.tsx

@@ -178,6 +178,7 @@ const Mine_Mv = () => {
           <div key={mv.id} className="mv-item">
             <div className="mv-cover">
               <Image
+                preview={false}
                 width={130}
                 height={75}
                 src={mv.cover}

+ 2 - 1
src/pages/layout/pages/musician/MusicianDashboardPage/index.tsx

@@ -26,7 +26,8 @@ const items: MenuItem[] = [
     // 可以根据实际需求添加子菜单
     children: [
       { key: 'wsongs', label: '歌曲' },
-      { key: 'wlyrics', label: '歌词' }
+      { key: 'wlyrics', label: '歌词' },
+      { key: 'MV', label: 'MV' },
     ]
   },
   {

+ 96 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.css

@@ -0,0 +1,96 @@
+.upload-mv-container {
+  font-family: 'Microsoft YaHei', sans-serif;
+  padding: 20px;
+  background-color: #f9f9f9;
+  width: 724px;
+}
+.upload-mv-container h2 {
+  font-size: 24px;
+  color: #333;
+  margin-bottom: 30px;
+  text-align: center;
+}
+.upload-mv-container .ant-form {
+  max-width: 800px;
+  margin: 0 auto;
+  background-color: white;
+  padding: 30px;
+  border-radius: 8px;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+}
+.upload-mv-container .ant-form .ant-form-item {
+  margin-bottom: 24px;
+}
+.upload-mv-container .ant-form .ant-form-item .ant-form-item-label {
+  font-weight: 500;
+  color: #333;
+}
+.upload-mv-container .ant-form .ant-form-item .ant-form-item-control .ant-upload {
+  display: inline-block;
+  width: 100%;
+}
+.upload-mv-container .ant-form .ant-form-item .ant-form-item-control .ant-upload .ant-btn {
+  width: 100%;
+  padding: 8px 16px;
+  border-radius: 4px;
+  background-color: #f5f5f5;
+  border: 1px solid #d9d9d9;
+  color: #333;
+}
+.upload-mv-container .ant-form .ant-form-item .ant-form-item-control .ant-upload .ant-btn:hover {
+  background-color: #e6e6e6;
+}
+.upload-mv-container .ant-form .ant-form-item:last-child {
+  margin-bottom: 0;
+}
+.upload-mv-container .ant-form .ant-form-item-control .ant-btn-primary {
+  width: 120px;
+  height: 40px;
+  font-size: 16px;
+  border-radius: 4px;
+  background-color: #e63b3b;
+  border-color: #e63b3b;
+  color: white;
+}
+.upload-mv-container .ant-form .ant-form-item-control .ant-btn-primary:hover {
+  background-color: #d42a2a;
+}
+.upload-mv-container .ant-input {
+  border-radius: 4px;
+  border: 1px solid #d9d9d9;
+  padding: 8px 12px;
+  font-size: 14px;
+}
+.upload-mv-container .ant-input:focus {
+  border-color: #e63b3b;
+  box-shadow: 0 0 0 2px rgba(230, 59, 59, 0.1);
+}
+.upload-mv-container .ant-input-textarea {
+  border-radius: 4px;
+  border: 1px solid #d9d9d9;
+  padding: 8px 12px;
+  font-size: 14px;
+}
+.upload-mv-container .ant-input-textarea:focus {
+  border-color: #e63b3b;
+  box-shadow: 0 0 0 2px rgba(230, 59, 59, 0.1);
+}
+.upload-mv-container .ant-select {
+  width: 100%;
+  border-radius: 4px;
+}
+.upload-mv-container .ant-select .ant-select-selection {
+  border-radius: 4px;
+  border: 1px solid #d9d9d9;
+}
+.upload-mv-container .ant-select .ant-select-selection:hover {
+  border-color: #e63b3b;
+}
+.upload-mv-container .ant-select .ant-select-arrow {
+  color: #333;
+}
+.upload-mv-container .ant-form-item-explain {
+  color: #f5222d;
+  font-size: 12px;
+  margin-top: 4px;
+}

+ 124 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.less

@@ -0,0 +1,124 @@
+// src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.less
+.upload-mv-container {
+  font-family: 'Microsoft YaHei', sans-serif;
+  padding: 20px;
+  background-color: #f9f9f9;
+  width: 724px;
+
+  h2 {
+    font-size: 24px;
+    color: #333;
+    margin-bottom: 30px;
+    text-align: center;
+  }
+
+  .ant-form {
+    max-width: 800px;
+    margin: 0 auto;
+    background-color: white;
+    padding: 30px;
+    border-radius: 8px;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+
+    .ant-form-item {
+      margin-bottom: 24px;
+
+      .ant-form-item-label {
+        font-weight: 500;
+        color: #333;
+      }
+
+      .ant-form-item-control {
+        .ant-upload {
+          display: inline-block;
+          width: 100%;
+
+          .ant-btn {
+            width: 100%;
+            padding: 8px 16px;
+            border-radius: 4px;
+            background-color: #f5f5f5;
+            border: 1px solid #d9d9d9;
+            color: #333;
+
+            &:hover {
+              background-color: #e6e6e6;
+            }
+          }
+        }
+      }
+    }
+
+    .ant-form-item:last-child {
+      margin-bottom: 0;
+    }
+
+    .ant-form-item-control {
+      .ant-btn-primary {
+        width: 120px;
+        height: 40px;
+        font-size: 16px;
+        border-radius: 4px;
+        background-color: #e63b3b;
+        border-color: #e63b3b;
+        color: white;
+
+        &:hover {
+          background-color: #d42a2a;
+        }
+      }
+    }
+  }
+
+  // 输入框样式
+  .ant-input {
+    border-radius: 4px;
+    border: 1px solid #d9d9d9;
+    padding: 8px 12px;
+    font-size: 14px;
+
+    &:focus {
+      border-color: #e63b3b;
+      box-shadow: 0 0 0 2px rgba(230, 59, 59, 0.1);
+    }
+  }
+
+  // 文本域样式
+  .ant-input-textarea {
+    border-radius: 4px;
+    border: 1px solid #d9d9d9;
+    padding: 8px 12px;
+    font-size: 14px;
+
+    &:focus {
+      border-color: #e63b3b;
+      box-shadow: 0 0 0 2px rgba(230, 59, 59, 0.1);
+    }
+  }
+
+  // 下拉选择框样式
+  .ant-select {
+    width: 100%;
+    border-radius: 4px;
+
+    .ant-select-selection {
+      border-radius: 4px;
+      border: 1px solid #d9d9d9;
+
+      &:hover {
+        border-color: #e63b3b;
+      }
+    }
+
+    .ant-select-arrow {
+      color: #333;
+    }
+  }
+
+  // 错误提示样式
+  .ant-form-item-explain {
+    color: #f5222d;
+    font-size: 12px;
+    margin-top: 4px;
+  }
+}

+ 135 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.tsx

@@ -0,0 +1,135 @@
+// src/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv/index.tsx
+import { Upload, Button, Form, Input, Select, message } from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import './index.css';
+import { uploadFile } from '@/apis/upload';
+
+const { Option } = Select;
+
+const UploadMv = () => {
+  const [form] = Form.useForm();
+  const [messageApi, contextHolder] = message.useMessage();
+
+  const onFinish = (values: any) => {
+    console.log('表单数据:', values);
+    messageApi.success('MV上传成功!');
+    form.resetFields();
+  };
+
+  const onFinishFailed = (errorInfo: any) => {
+    console.log('表单错误:', errorInfo);
+    errorInfo.errorFields.forEach((field: any) => {
+      messageApi.error(field.errors[0]);
+    });
+  };
+
+  const customUploadCover = async (options: any) => {
+    const { file, onSuccess, onError, onProgress } = options;
+    try {
+      const response = await uploadFile('VIDEO_HOME', file, '', onProgress);
+      onSuccess(response);
+      messageApi.success('封面上传成功');
+    } catch (error: any) {
+      console.error('封面上传失败:', error);
+      const errorMessage = error.response?.data?.error || '封面上传失败';
+      messageApi.error(errorMessage);
+      onError(error);
+    }
+  };
+
+  const customUploadVideo = async (options: any) => {
+    const { file, onSuccess, onError, onProgress } = options;
+    try {
+      const response = await uploadFile('VIDEO_FILES', file, '', onProgress);
+      onSuccess(response);
+      messageApi.success('视频上传成功');
+    } catch (error: any) {
+      console.error('视频上传失败:', error);
+      const errorMessage = error.response?.data?.error || '视频上传失败';
+      messageApi.error(errorMessage);
+      onError(error);
+    }
+  };
+
+  return (
+    <div className="upload-mv-container">
+      {contextHolder}
+      <h2>上传MV</h2>
+      <Form
+        form={form}
+        name="uploadMv"
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 18 }}
+        initialValues={{ remember: true }}
+        onFinish={onFinish}
+        onFinishFailed={onFinishFailed}
+        autoComplete="off"
+      >
+        <Form.Item
+          label="MV名称"
+          name="mvName"
+          rules={[{ required: true, message: '请输入MV名称!' }]}
+        >
+          <Input placeholder="请输入MV名称" />
+        </Form.Item>
+
+        <Form.Item
+          label="MV封面"
+          name="cover"
+        >
+          <Upload
+            customRequest={customUploadCover}
+            showUploadList={true}
+            accept=".png,.jpg"
+          >
+            <Button icon={<UploadOutlined />}>上传封面</Button>
+          </Upload>
+        </Form.Item>
+
+        <Form.Item
+          label="MV视频"
+          name="video"
+          rules={[{ required: true, message: '请上传MV视频!' }]}
+        >
+          <Upload
+            customRequest={customUploadVideo}
+            showUploadList={true}
+            accept=".mp4,.avi,.mov,.wmv,.flv,.mkv"
+          >
+            <Button icon={<UploadOutlined />}>上传MV视频</Button>
+          </Upload>
+        </Form.Item>
+
+        <Form.Item
+          label="描述"
+          name="description"
+        >
+          <Input.TextArea rows={4} placeholder="请输入MV描述" />
+        </Form.Item>
+
+        <Form.Item
+          label="标签"
+          name="tags"
+        >
+          <Select
+            mode="tags"
+            placeholder="添加标签"
+            style={{ width: '100%' }}
+          >
+            <Option value="流行">流行</Option>
+            <Option value="摇滚">摇滚</Option>
+            <Option value="电子">电子</Option>
+          </Select>
+        </Form.Item>
+
+        <Form.Item wrapperCol={{ offset: 6, span: 18 }}>
+          <Button type="primary" htmlType="submit">
+            提交上传
+          </Button>
+        </Form.Item>
+      </Form>
+    </div>
+  );
+};
+
+export default UploadMv;

+ 93 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.css

@@ -0,0 +1,93 @@
+.mv-container {
+  font-family: 'Microsoft YaHei', sans-serif;
+  padding: 20px;
+  background-color: #f9f9f9;
+  flex: 1;
+}
+.mv-container .header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+}
+.mv-container .header h2 {
+  font-size: 24px;
+  color: #333;
+}
+.mv-container .header .author-info {
+  font-size: 14px;
+  color: #9ecadd;
+  cursor: pointer;
+}
+.mv-container .filter-bar1 {
+  display: flex;
+  justify-content: right;
+  margin-right: 30px;
+}
+.mv-container .filter-bar1 .publish-btn {
+  padding: 10px 20px;
+  background-color: #e63b3b;
+  color: white;
+  border: none;
+  border-radius: 20px;
+  cursor: pointer;
+  font-size: 16px;
+}
+.mv-container .filter-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.mv-container .search-box {
+  position: relative;
+}
+.mv-container .search-box input {
+  width: 150px;
+  padding: 8px 12px;
+  border: 1px solid #ddd;
+  border-radius: 20px;
+  outline: none;
+  text-align: center;
+}
+.mv-container .search-box .icon-search {
+  position: absolute;
+  right: 10px;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 16px;
+  color: #999;
+}
+.table-container {
+  background-color: white;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+}
+.mv-detail .detail-item {
+  display: flex;
+  margin-bottom: 16px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #f0f0f0;
+}
+.mv-detail .detail-item label {
+  font-weight: bold;
+  width: 100px;
+  margin-right: 16px;
+  color: #333;
+}
+.mv-detail .detail-item span {
+  flex: 1;
+  color: #666;
+}
+.mv-detail .mv-cover {
+  width: 200px;
+  height: 200px;
+  margin: 16px auto;
+  border-radius: 8px;
+  overflow: hidden;
+}
+.mv-detail .mv-cover img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}

+ 112 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.less

@@ -0,0 +1,112 @@
+.mv-container {
+  font-family: 'Microsoft YaHei', sans-serif;
+  padding: 20px;
+  background-color: #f9f9f9;
+  flex: 1;
+
+  .header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+
+    h2 {
+      font-size: 24px;
+      color: #333;
+    }
+
+    .author-info {
+      font-size: 14px;
+      color: #9ecadd;
+      cursor: pointer;
+    }
+  }
+
+  .filter-bar1 {
+    display: flex;
+    justify-content: right;
+    margin-right: 30px;
+
+    .publish-btn {
+      padding: 10px 20px;
+      background-color: #e63b3b;
+      color: white;
+      border: none;
+      border-radius: 20px;
+      cursor: pointer;
+      font-size: 16px;
+    }
+  }
+
+  .filter-bar {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  .search-box {
+    position: relative;
+
+    input {
+      width: 150px;
+      padding: 8px 12px;
+      border: 1px solid #ddd;
+      border-radius: 20px;
+      outline: none;
+      text-align: center;
+    }
+
+    .icon-search {
+      position: absolute;
+      right: 10px;
+      top: 50%;
+      transform: translateY(-50%);
+      font-size: 16px;
+      color: #999;
+    }
+  }
+}
+
+.table-container {
+  background-color: white;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+}
+
+// MV详情样式
+.mv-detail {
+  .detail-item {
+    display: flex;
+    margin-bottom: 16px;
+    padding-bottom: 8px;
+    border-bottom: 1px solid #f0f0f0;
+
+    label {
+      font-weight: bold;
+      width: 100px;
+      margin-right: 16px;
+      color: #333;
+    }
+
+    span {
+      flex: 1;
+      color: #666;
+    }
+  }
+
+  // 添加MV封面图片样式
+  .mv-cover {
+    width: 200px;
+    height: 200px;
+    margin: 16px auto;
+    border-radius: 8px;
+    overflow: hidden;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+}

+ 243 - 0
src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.tsx

@@ -0,0 +1,243 @@
+// src/pages/layout/pages/musician/MusicianDashboardPage/works/mv/index.tsx
+import { Button, Drawer, Image, Modal, notification, Space, Table, Tabs } from 'antd';
+import './index.css';
+import { useEffect, useState } from 'react';
+import type { NotificationPlacement } from 'antd/es/notification/interface';
+import { useNavigate } from 'react-router-dom';
+
+const Mv = () => {
+  const navigate = useNavigate();
+  const [mvStatus, setMvStatus] = useState(0);
+  const [mvs, setMvs] = useState<any[]>([]);
+  const [currentRecord, setCurrentRecord] = useState<any>(null);
+  const [openDetail, setOpenDetail] = useState(false);
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [deleteMvId, setDeleteMvId] = useState<number | null>(null);
+
+  const [api, contextHolder] = notification.useNotification();
+
+  const mvStatusContent = [
+    { key: 0, label: '审核中' },
+    { key: 1, label: '审核失败' },
+    { key: 2, label: '发布中' },
+    { key: 3, label: '已上架' },
+    { key: 4, label: '已下架' }
+  ];
+
+  const handleMvStatusChange = (key: string) => {
+    setMvStatus(Number(key));
+  };
+
+  // MV表格列定义
+  const mvColumns = [
+    {
+      title: 'MV名称',
+      dataIndex: 'mvName',
+      key: 'mvName',
+    },
+    {
+      title: '专辑名',
+      dataIndex: 'albumName',
+      key: 'albumName',
+      render: (albumName: string) => albumName || '独立MV'
+    },
+    {
+      title: '发布时间',
+      dataIndex: 'releaseTime',
+      key: 'releaseTime',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      key: 'status',
+      render: (status: number) => {
+        const statusMap: Record<number, string> = {
+          0: '审核中',
+          1: '审核失败',
+          2: '发布中',
+          3: '已上架',
+          4: '已下架'
+        };
+        return statusMap[status] || '未知';
+      }
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: any) => (
+        <Space size="middle">
+          <Button size="small" onClick={() => handleViewDetails(record)}>
+            查看详情
+          </Button>
+          <Button danger size="small" onClick={() => handleDelete(record)}>
+            删除
+          </Button>
+        </Space>
+      ),
+    }
+  ];
+
+  const handleViewDetails = (record: any) => {
+    setCurrentRecord(record);
+    setOpenDetail(true);
+  };
+
+  const handleDelete = (record: any) => {
+    setDeleteMvId(record.id);
+    setIsModalOpen(true);
+  };
+
+  const handleOk = async () => {
+    // 模拟删除操作
+    console.log('删除MV ID:', deleteMvId);
+    setIsModalOpen(false);
+    // 实际删除逻辑可以在这里添加
+  };
+
+  const handleCancel = () => {
+    setIsModalOpen(false);
+    openNotification('topRight');
+  };
+
+  const openNotification = (placement: NotificationPlacement) => {
+    api.info({
+      message: `取消操作`,
+      placement,
+    });
+  };
+
+  const onClose = () => {
+    setOpenDetail(false);
+  };
+
+  // 模拟获取数据
+  useEffect(() => {
+    // 获取MV列表
+    setMvs([
+      { id: 1, mvName: 'MV示例1', albumName: '专辑1', releaseTime: '2023-05-15', status: 3 },
+      { id: 2, mvName: 'MV示例2', albumName: null, releaseTime: '2023-06-20', status: 0 },
+      { id: 3, mvName: 'MV示例3', albumName: '专辑2', releaseTime: '2023-07-10', status: 2 }
+    ]);
+  }, [mvStatus]);
+
+  return (
+    <div className="mv-container">
+      {contextHolder}
+      <div className="header">
+        <h2>MV管理</h2>
+        <span className="author-info">作者身份/收益认领审核</span>
+      </div>
+
+      <div className="filter-bar1">
+        <button className="publish-btn" onClick={() => navigate('/musician/works/uploadMv')}>
+          发布MV
+        </button>
+      </div>
+
+      <div className="filter-bar">
+        <div className="status-filter">
+          <Tabs
+            defaultActiveKey="0"
+            items={mvStatusContent}
+            onChange={handleMvStatusChange}
+          />
+        </div>
+        <div className="search-box">
+          <input type="text" placeholder="搜索MV" />
+          <i className="icon-search"></i>
+        </div>
+      </div>
+
+      <Table
+        dataSource={mvs}
+        columns={mvColumns}
+        rowKey={(record) => record.id}
+        scroll={{ x: 'max-content' }}
+        pagination={false}
+      />
+
+      <Modal
+        title="删除MV"
+        open={isModalOpen}
+        onOk={handleOk}
+        onCancel={handleCancel}
+        cancelText="取消"
+      >
+        <p>确定要删除选中MV吗?</p>
+      </Modal>
+
+      <Drawer
+        title="MV详情"
+        placement="right"
+        closable={false}
+        onClose={onClose}
+        open={openDetail}
+        width={600}
+      >
+        {currentRecord && (
+          <div className="mv-detail">
+            <div style={{ display: 'flex', marginBottom: 20 }}>
+              <div style={{ flex: 1 }}>
+                <div className="detail-item">
+                  <label>MV名称:</label>
+                  <span>{currentRecord.mvName}</span>
+                </div>
+                <div className="detail-item">
+                  <label>ID:</label>
+                  <span>{currentRecord.id}</span>
+                </div>
+                <div className="detail-item">
+                  <label>状态:</label>
+                  <span>
+                    {(() => {
+                      const statusMap: Record<number, string> = {
+                        0: '审核中',
+                        1: '审核失败',
+                        2: '发布中',
+                        3: '已上架',
+                        4: '已下架'
+                      };
+                      return statusMap[currentRecord.status] || '未知';
+                    })()}
+                  </span>
+                </div>
+              </div>
+              {currentRecord.thumbnailUrl && (
+                <div className="mv-cover">
+                  <Image
+                    width={180}
+                    height={180}
+                    src={currentRecord.thumbnailUrl}
+                    alt="MV封面"
+                    style={{ borderRadius: 8 }}
+                  />
+                </div>
+              )}
+            </div>
+
+            <div className="detail-item">
+              <label>描述:</label>
+              <div style={{
+                maxHeight: 80,
+                overflowY: 'auto',
+                padding: 8,
+                backgroundColor: '#f5f5f5',
+                borderRadius: 4,
+                marginTop: 4
+              }}>
+                {currentRecord.description || '无描述'}
+              </div>
+            </div>
+
+            <div className="detail-item">
+              <label>发布时间:</label>
+              <span>{currentRecord.releaseTime || '未设置'}</span>
+            </div>
+          </div>
+        )}
+      </Drawer>
+    </div>
+  );
+};
+
+export default Mv;

+ 3 - 0
src/pages/layout/pages/testPage/index.css

@@ -204,3 +204,6 @@
 .rhap_volume-indicator {
   background-color: #fff !important;
 }
+.remove-track-btn {
+  margin-left: 20px;
+}

+ 3 - 0
src/pages/layout/pages/testPage/index.less

@@ -236,4 +236,7 @@
 
 .rhap_volume-indicator {
   background-color: #fff !important;
+}
+.remove-track-btn{
+  margin-left: 20px;
 }

+ 44 - 9
src/pages/layout/pages/testPage/index.tsx

@@ -1,10 +1,11 @@
 // src/pages/layout/pages/testPage/index.tsx
-import React, { useState, useRef, useEffect } from 'react';
+import React, { 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 { UnorderedListOutlined, CloseOutlined } from '@ant-design/icons';
 import { useMusicPlayer } from '@/context/MusicPlayerContext';
+import { useNavigate } from 'react-router-dom'; // 导入 useNavigate
 
 export interface Song {
   id: number;
@@ -14,10 +15,10 @@ export interface Song {
   cover: string;
   duration: string;
   isFavorite?: boolean;
-  album?: string;           // 所属专辑
-  composer?: string;        // 作曲
-  arranger?: string;        // 编曲
-  description?: string;     // 歌曲描述
+  album?: string;
+  composer?: string;
+  arranger?: string;
+  description?: string;
 }
 
 const defaultPlaylist: Song[] = [
@@ -92,10 +93,12 @@ const MusicPlayer: React.FC = () => {
     setIsPlaying,
     setCurrentTrackIndex,
     setIsPlaylistOpen,
-    setPlaylist
+    setPlaylist,
+    removeFromPlaylist
   } = useMusicPlayer();
 
   const audioPlayerRef = useRef<AudioPlayer>(null);
+  const navigate = useNavigate(); // 初始化 navigate
 
   // 初始化播放列表
   useEffect(() => {
@@ -138,6 +141,30 @@ const MusicPlayer: React.FC = () => {
     }
   };
 
+  // 删除播放列表中的歌曲
+  const handleRemoveTrack = (e: React.MouseEvent, trackId: number) => {
+    e.stopPropagation(); // 阻止事件冒泡,避免触发播放列表项点击事件
+    removeFromPlaylist(trackId);
+
+    // 如果删除的是当前播放的歌曲,调整播放索引
+    if (playlist[validCurrentTrackIndex]?.id === trackId) {
+      // 如果当前播放的是最后一首歌且被删除,则播放上一首
+      if (validCurrentTrackIndex === playlist.length - 1) {
+        setCurrentTrackIndex(Math.max(0, playlist.length - 2));
+      } else {
+        // 否则保持当前索引位置
+        setCurrentTrackIndex(validCurrentTrackIndex);
+      }
+    }
+  };
+
+  // 处理左侧点击事件,跳转到歌曲详情页
+  const handleTrackInfoClick = () => {
+    if (currentTrack) {
+      navigate(`/SongDetail/${currentTrack.id}`);
+    }
+  };
+
   // 当播放状态改变时,确保音频元素正确响应
   useEffect(() => {
     if (audioPlayerRef.current) {
@@ -158,8 +185,8 @@ const MusicPlayer: React.FC = () => {
   return (
     <div className="music-player-wrapper">
       <div className="player-container">
-        {/* 左侧:歌曲封面 + 信息 */}
-        <div className="track-info">
+        {/* 左侧:歌曲封面 + 信息 - 添加点击事件 */}
+        <div className="track-info" onClick={handleTrackInfoClick} style={{ cursor: 'pointer' }}>
           <img
             src={currentTrack?.cover || defaultPlaylist[0]?.cover}
             alt={currentTrack?.title || '未知歌曲'}
@@ -225,6 +252,14 @@ const MusicPlayer: React.FC = () => {
                   <div className="item-artist">{track.artist}</div>
                 </div>
                 <div className="item-duration">{track.duration}</div>
+                {/* 删除按钮 */}
+                <button 
+                  className="remove-track-btn"
+                  onClick={(e) => handleRemoveTrack(e, track.id)}
+                  aria-label={`删除 ${track.title}`}
+                >
+                  <CloseOutlined />
+                </button>
               </li>
             ))}
           </ul>

+ 11 - 1
src/router/index.tsx

@@ -35,6 +35,8 @@ import Uploadsel from '@/pages/layout/pages/musician/MusicianDashboardPage/works
 import Songup from '@/pages/layout/pages/musician/MusicianDashboardPage/works/songup'
 import PlaylistDetailWrapper from '@/pages/layout/pages/playDetailPage'
 import AlbumDetail from '@/pages/layout/pages/AlbumDetail'
+import Mv from '@/pages/layout/pages/musician/MusicianDashboardPage/works/mv'
+import UploadMv from '@/pages/layout/pages/musician/MusicianDashboardPage/works/UploadMv'
 const router = createBrowserRouter([
   //   <AuthRoute>
   //   <WYLayout />
@@ -144,6 +146,14 @@ const router = createBrowserRouter([
           {
             path: '/musician/works/songup',
             element: <Songup />
+          },
+          {
+            path: '/musician/works/mv',
+            element: <Mv />
+          },
+          {
+            path: '/musician/works/uploadMv',
+            element: <UploadMv />
           }
         ]
       },
@@ -173,7 +183,7 @@ const router = createBrowserRouter([
         element: <MusicPlayer />,
       },
       {
-        path: '/songdetail',
+        path: '/songdetail/:id',
         element: <SongDetail />,
       },
       {

+ 6 - 0
src/type/PageDto.ts

@@ -0,0 +1,6 @@
+export interface PageDto<T> {
+  total: number;
+  records: T[];
+  current: number;
+  size: number;
+}

+ 6 - 0
src/type/PageQuery.ts

@@ -0,0 +1,6 @@
+// src/types/page.ts
+export interface PageQuery {
+  pageNo?: number;
+  pageSize?: number;
+  tag?: string;
+}