瀏覽代碼

success前

xiang 3 天之前
父節點
當前提交
387091fbb7
共有 53 個文件被更改,包括 2600 次插入401 次删除
  1. 1 0
      package.json
  2. 19 0
      pnpm-lock.yaml
  3. 6 0
      src/apis/artist.ts
  4. 29 0
      src/apis/order.ts
  5. 6 0
      src/apis/payList.ts
  6. 4 0
      src/apis/playRecord.ts
  7. 8 0
      src/apis/recentPlayStats.ts
  8. 50 0
      src/apis/share.ts
  9. 2 1
      src/apis/song.ts
  10. 0 1
      src/apis/upload.ts
  11. 4 0
      src/apis/user-level.ts
  12. 9 0
      src/apis/user-sign-in.ts
  13. 4 0
      src/apis/userBalance.ts
  14. 10 0
      src/apis/userFavoriteSong.ts
  15. 162 0
      src/pages/layout/pages/RechargePage/index.css
  16. 207 0
      src/pages/layout/pages/RechargePage/index.less
  17. 189 0
      src/pages/layout/pages/RechargePage/index.tsx
  18. 36 5
      src/pages/layout/pages/SongDetail/components/SongDetailPage.tsx
  19. 2 2
      src/pages/layout/pages/SongDetail/index.tsx
  20. 1 1
      src/pages/layout/pages/find/rank/compomemts/Rank_Recommend_body_list_Comment/index.tsx
  21. 1 0
      src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content/index.css
  22. 1 0
      src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content/index.less
  23. 77 54
      src/pages/layout/pages/find/recommend/components/Recommend_body_left/index.tsx
  24. 5 4
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.css
  25. 22 14
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.less
  26. 59 7
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.tsx
  27. 1 0
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.css
  28. 1 0
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.less
  29. 52 87
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.tsx
  30. 13 3
      src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/UserStats/index.tsx
  31. 1 0
      src/pages/layout/pages/friend/Mine_Friend_recommend/index.css
  32. 2 2
      src/pages/layout/pages/friend/Mine_Friend_recommend/index.less
  33. 118 32
      src/pages/layout/pages/friend/Mine_Friend_recommend/index.tsx
  34. 34 3
      src/pages/layout/pages/friend/index.css
  35. 46 12
      src/pages/layout/pages/friend/index.less
  36. 516 47
      src/pages/layout/pages/friend/index.tsx
  37. 35 8
      src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx
  38. 0 0
      src/pages/layout/pages/payError/index.css
  39. 0 0
      src/pages/layout/pages/payError/index.less
  40. 41 0
      src/pages/layout/pages/payError/index.tsx
  41. 0 0
      src/pages/layout/pages/paySuccess/index.css
  42. 0 0
      src/pages/layout/pages/paySuccess/index.less
  43. 18 0
      src/pages/layout/pages/paySuccess/index.tsx
  44. 187 0
      src/pages/layout/pages/pushSong/index.css
  45. 234 0
      src/pages/layout/pages/pushSong/index.less
  46. 190 0
      src/pages/layout/pages/pushSong/index.tsx
  47. 1 1
      src/pages/layout/pages/song/index.css
  48. 1 1
      src/pages/layout/pages/song/index.less
  49. 34 22
      src/pages/layout/pages/song/index.tsx
  50. 11 1
      src/pages/layout/pages/testPage/index.css
  51. 25 7
      src/pages/layout/pages/testPage/index.less
  52. 123 85
      src/pages/layout/pages/testPage/index.tsx
  53. 2 1
      src/router/index.tsx

+ 1 - 0
package.json

@@ -24,6 +24,7 @@
     "react-audio-player": "^0.17.0",
     "react-dom": "^19.1.0",
     "react-h5-audio-player": "^3.10.1",
+    "react-infinite-scroll-component": "^6.1.1",
     "react-router-dom": "^7.6.2",
     "spark-md5": "^3.0.2",
     "zustand": "^5.0.5"

+ 19 - 0
pnpm-lock.yaml

@@ -50,6 +50,9 @@ importers:
       react-h5-audio-player:
         specifier: ^3.10.1
         version: 3.10.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+      react-infinite-scroll-component:
+        specifier: ^6.1.1
+        version: 6.1.1(react@19.1.0)
       react-router-dom:
         specifier: ^7.6.2
         version: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -1536,6 +1539,11 @@ packages:
       react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
       react-dom: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
 
+  react-infinite-scroll-component@6.1.1:
+    resolution: {integrity: sha512-R8YoOyiNDynSWmfVme5LHslsKrP+/xcRUWR2ies8UgUab9dtyw5ECnMCVPPmnmjjF4MWQmfVdRwRWcWaDgeyMA==}
+    peerDependencies:
+      react: '>=16.0.0'
+
   react-is@16.13.1:
     resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
 
@@ -1633,6 +1641,10 @@ packages:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
 
+  throttle-debounce@2.3.0:
+    resolution: {integrity: sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==}
+    engines: {node: '>=8'}
+
   throttle-debounce@5.0.2:
     resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
     engines: {node: '>=12.22'}
@@ -3323,6 +3335,11 @@ snapshots:
       react: 19.1.0
       react-dom: 19.1.0(react@19.1.0)
 
+  react-infinite-scroll-component@6.1.1(react@19.1.0):
+    dependencies:
+      react: 19.1.0
+      throttle-debounce: 2.3.0
+
   react-is@16.13.1: {}
 
   react-is@18.3.1: {}
@@ -3413,6 +3430,8 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
+  throttle-debounce@2.3.0: {}
+
   throttle-debounce@5.0.2: {}
 
   tinyglobby@0.2.14:

+ 6 - 0
src/apis/artist.ts

@@ -14,4 +14,10 @@ export const ChangeArtistIsCollectByid = (id) => {
 }
 export const queryArtistIsCollectByUserid = (id) => {
   return request.get('/user-favorite-artist/queryById', { params: { id } })
+}
+export const ArtisstRecommend = () => {
+  return request.get('/artist/recommend')
+}
+export const ArtisstRecommendCount6 = () => {
+  return request.get('/artist/recommendArtist')
 }

+ 29 - 0
src/apis/order.ts

@@ -0,0 +1,29 @@
+import { request } from "@/utils";
+// 定义 AddOrderDto 对应的 TypeScript 接口
+export interface AddOrderDto {
+  /** 商品id */
+  productId: number;
+  /** 订单总金额 */
+  totalAmount: number;
+  /** 实付金额 */
+  payAmount: number;
+  /** 优惠金额 */
+  discountAmount: number;
+  /** 订单类型 */
+  orderType: number;
+  /** 收货人姓名 */
+  receiveName: string|null;
+  /** 收货人电话 */
+  receivePhone: string|null;
+  /** 收货地址 */
+  receiveAddress: string|null;
+  /** 订单备注 */
+  note: string|null;
+  /** 运费 */
+  shippingFee: number;
+}
+
+// 更新 API 函数
+export const addOrder = (data: AddOrderDto) => {
+  return request.post('/order/add', data);
+};

+ 6 - 0
src/apis/payList.ts

@@ -40,4 +40,10 @@ export const UnCollectionSongApi = (playlistId: number) => {
 }
 export const IsCollectionSongApi = (playlistId: number) => {
   return request.get('/user-playlist-favorite/queryIsCollection', { params: { playlistId } })
+}
+
+
+
+export const getEightPlaylistApi = () => {
+  return request.get('/playlist/getEightPlaylist')
 }

+ 4 - 0
src/apis/playRecord.ts

@@ -0,0 +1,4 @@
+import { request } from "@/utils"
+export const updatePlayCount = (data: { resourceId: number, resourceType: number }) => {
+  return request.post('/play-record/updatePlayCount', { ...data });
+};

+ 8 - 0
src/apis/recentPlayStats.ts

@@ -0,0 +1,8 @@
+import { request } from "@/utils"
+export const updateRecentPlay = (data: { resourceId: number, resourceType: number }) => {
+  return request.post('/recent-play-stats/add', { ...data });
+};
+
+export const getrecentPlayStats = (contentType: number) => {
+  return request.get('/recent-play-stats/list', { params: { contentType } });
+};

+ 50 - 0
src/apis/share.ts

@@ -0,0 +1,50 @@
+import { request } from '@/utils'
+
+// 添加分享
+export const addShare = ({ content, files, jumpUrls }: { content?: string; files?: string; jumpUrls?: string }) => {
+  return request.post('/share/add', { content, files, jumpUrls })
+}
+
+// 获取分享列表
+export const shareList = (pageNo: number = 1, pageSize: number = 5) => {
+  return request.post('/share/list', { pageNo, pageSize })
+}
+
+// 点赞分享
+export const likeShare = (id: number) => {
+  return request.post('/share/like', {}, { params: { id } })
+}
+
+// 取消点赞分享
+export const cancelLikeShare = (id: number) => {
+  return request.delete('/share/like', { params: { id } })
+}
+
+// 点踩分享
+export const dislikeShare = (id: number) => {
+  return request.post('/share/dislike', {}, { params: { id } })
+}
+
+// 取消点踩分享
+export const cancelDislikeShare = (id: number) => {
+  return request.delete('/share/dislike', { params: { id } })
+}
+
+// 转发分享
+export const forwardShare = (id: number) => {
+  return request.post('/share/forward', {}, { params: { id } })
+}
+
+// 取消转发分享
+export const cancelForwardShare = (id: number) => {
+  return request.delete('/share/forward', { params: { id } })
+}
+// 删除分享
+export const deleteShare = (id: number) => {
+  return request.delete(`/share/delete/${id}`)
+}
+
+
+export const getFollowApi = () => {
+  return request.get(`/share/followCount`)
+}

+ 2 - 1
src/apis/song.ts

@@ -25,4 +25,5 @@ export const geSongById = (id: number) => {
 
 export const CollectSong = (data: any) => {
   return request.post(`/playlist/collectSong`, data)
-}
+}
+

+ 0 - 1
src/apis/upload.ts

@@ -1,4 +1,3 @@
-// src/apis/upload.ts
 import { request } from '../utils/request';
 
 export const uploadFile = (bucket: string, file: File, md5: string, onProgress?: (percent: number) => void) => {

+ 4 - 0
src/apis/user-level.ts

@@ -0,0 +1,4 @@
+import { request } from '../utils/request';
+export const getuserlevel = () => {
+  return request.get('/user-level/queryLevelIn')
+}

+ 9 - 0
src/apis/user-sign-in.ts

@@ -0,0 +1,9 @@
+import { request } from "@/utils";
+
+export const userSignInApi = () => {
+  return request.get('/user-sign-in/signIn');
+};
+
+export const signStatusApi = () => {
+  return request.get('/user-sign-in/status');
+};

+ 4 - 0
src/apis/userBalance.ts

@@ -0,0 +1,4 @@
+import { request } from "@/utils";
+export const getUserBalance = () => {
+  return request.get('/user-balance/get');
+};

+ 10 - 0
src/apis/userFavoriteSong.ts

@@ -0,0 +1,10 @@
+import { request } from "@/utils";
+export const changeFavoriteStatusApi = (id: number) => {
+  return request.get('/user-favorite-song/changeStatus', { params: { id } })
+}
+export const getStatusApi = (id: number) => {
+  return request.get('/user-favorite-song/status', { params: { id } })
+}
+export const getfavoritePlaylistApi = () => {
+  return request.get('/user-favorite-song/list')
+}

+ 162 - 0
src/pages/layout/pages/RechargePage/index.css

@@ -0,0 +1,162 @@
+.recharge-container {
+  max-width: 600px;
+  margin: 40px auto;
+  padding: 20px;
+  background-color: white;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+.recharge-container .user-info {
+  display: flex;
+  align-items: center;
+  margin-bottom: 30px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #eee;
+}
+.recharge-container .user-info .avatar {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  margin-right: 12px;
+}
+.recharge-container .user-info .user-details {
+  flex: 1;
+}
+.recharge-container .user-info .user-details .username {
+  font-size: 16px;
+  margin: 0;
+  color: #333;
+}
+.recharge-container .user-info .user-details .balance {
+  font-size: 14px;
+  color: #666;
+  margin: 4px 0 0;
+}
+.recharge-container .user-info .user-details .balance .balance-value {
+  color: #f5222d;
+  font-weight: bold;
+}
+.recharge-container .recharge-section {
+  padding: 20px;
+}
+.recharge-container .recharge-section .section-title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 8px;
+}
+.recharge-container .recharge-section .agreement-text {
+  font-size: 12px;
+  color: #999;
+  margin-bottom: 20px;
+}
+.recharge-container .recharge-section .agreement-text .agreement-link {
+  color: #1890ff;
+  cursor: pointer;
+}
+.recharge-container .recharge-section .amount-options {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 16px;
+  margin-bottom: 20px;
+}
+.recharge-container .recharge-section .amount-options .amount-btn {
+  padding: 16px;
+  border: 1px solid #d9d9d9;
+  border-radius: 8px;
+  background-color: white;
+  color: #f5222d;
+  font-size: 18px;
+  font-weight: 600;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  text-align: center;
+}
+.recharge-container .recharge-section .amount-options .amount-btn:hover {
+  border-color: #1890ff;
+  background-color: #f5f5f5;
+}
+.recharge-container .recharge-section .amount-options .amount-btn.selected {
+  border-color: #1890ff;
+  background-color: #e6f7ff;
+  color: #1890ff;
+}
+.recharge-container .recharge-section .error-message {
+  color: #f5222d;
+  font-size: 12px;
+  margin-top: 4px;
+}
+.recharge-container .payment-section {
+  margin-bottom: 30px;
+}
+.recharge-container .payment-section .payment-title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 12px;
+}
+.recharge-container .payment-section .payment-method {
+  display: flex;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+  padding: 16px;
+  border: 1px solid #e9e9e9;
+}
+.recharge-container .payment-section .payment-method .qr-code-container {
+  flex-shrink: 0;
+  margin-right: 16px;
+}
+.recharge-container .payment-section .payment-method .qr-code {
+  width: 120px;
+  height: 120px;
+  border: 1px solid #ddd;
+}
+.recharge-container .payment-section .payment-method .payment-info {
+  flex: 1;
+}
+.recharge-container .payment-section .payment-method .payment-info .payment-description {
+  font-size: 14px;
+  color: #333;
+  margin-bottom: 8px;
+}
+.recharge-container .payment-section .payment-method .payment-info .payment-icons {
+  display: flex;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+.recharge-container .payment-section .payment-method .payment-info .payment-icon {
+  width: 20px;
+  height: 20px;
+}
+.recharge-container .payment-section .payment-method .payment-info .amount-display {
+  font-size: 24px;
+  font-weight: 600;
+  color: #333;
+}
+.recharge-container .payment-section .payment-method .payment-info .amount-display .amount {
+  color: #f5222d;
+}
+.recharge-container .payment-section .payment-method .payment-info .amount-display .currency {
+  color: #666;
+  font-size: 16px;
+  margin-left: 4px;
+}
+.recharge-container .submit-btn {
+  width: 100%;
+  padding: 12px;
+  background-color: #1890ff;
+  color: white;
+  border: none;
+  border-radius: 8px;
+  font-size: 16px;
+  font-weight: 500;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+}
+.recharge-container .submit-btn:hover:not(:disabled) {
+  background-color: #096dd9;
+}
+.recharge-container .submit-btn:disabled {
+  background-color: #ccc;
+  cursor: not-allowed;
+}

+ 207 - 0
src/pages/layout/pages/RechargePage/index.less

@@ -0,0 +1,207 @@
+@primary-color: #1890ff;
+@primary-color-hover: #096dd9;
+@danger-color: #f5222d;
+@border-color: #d9d9d9;
+@border-color-light: #e9e9e9;
+@bg-color: #f8f9fa;
+@bg-light: #f5f5f5;
+@text-color: #333;
+@text-color-secondary: #666;
+@text-color-tertiary: #999;
+@shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+@border-radius: 12px;
+@border-radius-small: 8px;
+
+.recharge-container {
+  max-width: 600px;
+  margin: 40px auto;
+  padding: 20px;
+  background-color: white;
+  border-radius: @border-radius;
+  box-shadow: @shadow;
+
+  .user-info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 30px;
+    padding-bottom: 20px;
+    border-bottom: 1px solid #eee;
+
+    .avatar {
+      width: 40px;
+      height: 40px;
+      border-radius: 50%;
+      margin-right: 12px;
+    }
+
+    .user-details {
+      flex: 1;
+
+      .username {
+        font-size: 16px;
+        margin: 0;
+        color: @text-color;
+      }
+
+      .balance {
+        font-size: 14px;
+        color: @text-color-secondary;
+        margin: 4px 0 0;
+
+        .balance-value {
+          color: @danger-color;
+          font-weight: bold;
+        }
+      }
+    }
+  }
+
+  .recharge-section {
+    padding: 20px;
+
+    .section-title {
+      font-size: 18px;
+      font-weight: 600;
+      color: @text-color;
+      margin-bottom: 8px;
+    }
+
+    .agreement-text {
+      font-size: 12px;
+      color: @text-color-tertiary;
+      margin-bottom: 20px;
+
+      .agreement-link {
+        color: @primary-color;
+        cursor: pointer;
+      }
+    }
+
+    .amount-options {
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 16px;
+      margin-bottom: 20px;
+
+      .amount-btn {
+        padding: 16px;
+        border: 1px solid @border-color;
+        border-radius: @border-radius-small;
+        background-color: white;
+        color: @danger-color;
+        font-size: 18px;
+        font-weight: 600;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        text-align: center;
+
+
+        &:hover {
+          border-color: @primary-color;
+          background-color: @bg-light;
+        }
+
+        &.selected {
+          border-color: @primary-color;
+          background-color: #e6f7ff;
+          color: @primary-color;
+        }
+      }
+    }
+
+    .error-message {
+      color: @danger-color;
+      font-size: 12px;
+      margin-top: 4px;
+    }
+  }
+
+  .payment-section {
+    margin-bottom: 30px;
+
+    .payment-title {
+      font-size: 16px;
+      font-weight: 500;
+      color: @text-color;
+      margin-bottom: 12px;
+    }
+
+    .payment-method {
+      display: flex;
+      background-color: @bg-color;
+      border-radius: @border-radius-small;
+      padding: 16px;
+      border: 1px solid @border-color-light;
+
+      .qr-code-container {
+        flex-shrink: 0;
+        margin-right: 16px;
+      }
+
+      .qr-code {
+        width: 120px;
+        height: 120px;
+        border: 1px solid #ddd;
+      }
+
+      .payment-info {
+        flex: 1;
+
+        .payment-description {
+          font-size: 14px;
+          color: @text-color;
+          margin-bottom: 8px;
+        }
+
+        .payment-icons {
+          display: flex;
+          gap: 8px;
+          margin-bottom: 12px;
+        }
+
+        .payment-icon {
+          width: 20px;
+          height: 20px;
+        }
+
+        .amount-display {
+          font-size: 24px;
+          font-weight: 600;
+          color: @text-color;
+
+          .amount {
+            color: @danger-color;
+          }
+
+          .currency {
+            color: @text-color-secondary;
+            font-size: 16px;
+            margin-left: 4px;
+          }
+        }
+      }
+    }
+  }
+
+  .submit-btn {
+    width: 100%;
+    padding: 12px;
+    background-color: @primary-color;
+    color: white;
+    border: none;
+    border-radius: @border-radius-small;
+    font-size: 16px;
+    font-weight: 500;
+    cursor: pointer;
+    transition: background-color 0.2s ease;
+
+    &:hover:not(:disabled) {
+      background-color: @primary-color-hover;
+    }
+
+    &:disabled {
+      background-color: #ccc;
+      cursor: not-allowed;
+    }
+  }
+}

+ 189 - 0
src/pages/layout/pages/RechargePage/index.tsx

@@ -0,0 +1,189 @@
+import { useEffect, useState } from 'react';
+import './index.css';
+import { Image, Input } from 'antd';
+import { getUserBalance } from '@/apis/userBalance';
+import { getUserinfo } from '@/utils';
+
+const RechargePage = () => {
+  const [selectedAmount, setSelectedAmount] = useState<number | null>(50);
+  const [customAmount, setCustomAmount] = useState<string>('');
+  const [isCustomAmountValid, setIsCustomAmountValid] = useState<boolean>(true);
+  const [isCustomInputFocused, setIsCustomInputFocused] = useState<boolean>(false);
+  const [blanceinfo, setBlanceinfo] = useState<any>({ availableBalance: 0 });
+  const userinfo = getUserinfo();
+
+  // 预设充值金额选项
+  const presetAmounts = [
+    { value: 50, label: '¥50' },
+    { value: 98, label: '¥98' },
+    { value: 298, label: '¥298' },
+    { value: 998, label: '¥998' },
+    { value: 1998, label: '¥1998' }
+  ];
+
+  // 处理预设金额选择
+  const handlePresetAmountSelect = (amount: number) => {
+    setSelectedAmount(amount);
+    setCustomAmount('');
+    setIsCustomInputFocused(false);
+    setIsCustomAmountValid(true);
+  };
+
+  // 处理自定义金额输入
+  const handleCustomAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const value = e.target.value;
+    setCustomAmount(value);
+
+    // 验证自定义金额是否有效(30元起)
+    if (value && !isNaN(parseFloat(value)) && parseFloat(value) >= 30) {
+      setIsCustomAmountValid(true);
+    } else {
+      setIsCustomAmountValid(false);
+    }
+  };
+
+  // 处理输入框获得焦点
+  const handleCustomInputFocus = () => {
+    setIsCustomInputFocused(true);
+    setSelectedAmount(null);
+  };
+
+  // 处理输入框失去焦点
+  const handleCustomInputBlur = () => {
+    // 如果输入框没有值,则取消输入框激活状态
+    if (!customAmount) {
+      setIsCustomInputFocused(false);
+    }
+  };
+
+  // 获取最终充值金额
+  const getFinalAmount = (): number => {
+    // 如果输入框处于激活状态且有有效值,则使用输入框的值
+    if (isCustomInputFocused && customAmount && !isNaN(parseFloat(customAmount)) && parseFloat(customAmount) >= 30) {
+      return parseFloat(customAmount);
+    }
+    // 否则使用预设金额
+    if (selectedAmount !== null) {
+      return selectedAmount;
+    }
+    return 50; // 默认值
+  };
+
+  // 提交充值
+  const handleSubmit = () => {
+    const amount = getFinalAmount();
+    if (amount < 30) {
+      alert('充值金额不能低于30元');
+      return;
+    }
+
+    // 这里可以调用API提交充值请求
+    console.log('提交充值:', amount);
+    alert(`正在为您充值 ¥${amount}...`);
+  };
+
+  const getBlance = async () => {
+    const res = await getUserBalance();
+    setBlanceinfo(res.data);
+  };
+
+  useEffect(() => {
+    getBlance();
+  }, []);
+
+  return (
+    <div className="recharge-container">
+      <div className="user-info">
+        <Image src={userinfo.url} alt="头像" className="avatar" preview={false} />
+        <div className="user-details">
+          <p className="username">你好,{userinfo.name}</p>
+          <p className="balance">当前账户余额:<span className="balance-value">{blanceinfo.availableBalance}</span></p>
+        </div>
+      </div>
+
+      <div className="recharge-section">
+        <h3 className="section-title">请选择充值金额</h3>
+        <p className="agreement-text">充值即代表同意<span className="agreement-link">服务协议</span></p>
+
+        <div className="amount-options">
+          {/* 预设金额按钮 */}
+          {presetAmounts.map((option) => (
+            <button
+              key={option.value}
+              className={`amount-btn ${selectedAmount === option.value && !isCustomInputFocused ? 'selected' : ''}`}
+              onClick={() => handlePresetAmountSelect(option.value)}
+            >
+              {option.label}
+            </button>
+          ))}
+
+          {/* 自定义金额输入框 */}
+          <div className="amount-btn">
+            <Input
+              placeholder="自定义金额"
+              value={customAmount}
+              onChange={handleCustomAmountChange}
+              prefix="¥"
+              type="number"
+              min="30"
+              step="1"
+              onFocus={handleCustomInputFocus}
+              onBlur={handleCustomInputBlur}
+              style={{
+                border: 'none',
+                outline: 'none',
+                boxShadow: 'none',
+                background: 'transparent',
+                color: '#333',
+                fontSize: '15px',
+                fontWeight: '600',
+                textAlign: 'center'
+              }}
+            />
+          </div>
+        </div>
+
+        {/* 自定义金额验证消息 */}
+        {!isCustomAmountValid && customAmount && (
+          <div className="error-message">金额需大于等于30元</div>
+        )}
+
+        {/* 支付方式 */}
+        <div className="payment-section">
+          <h4 className="payment-title">支付方式</h4>
+          <div className="payment-method">
+            <div className="qr-code-container">
+              <img
+                src="https://via.placeholder.com/120x120"
+                alt="二维码"
+                className="qr-code"
+              />
+            </div>
+            <div className="payment-info">
+              <div className="payment-description">使用支付宝、微信扫码支付</div>
+              <div className="payment-icons">
+                <img src="https://via.placeholder.com/20x20" alt="支付宝" className="payment-icon" />
+                <img src="https://via.placeholder.com/20x20" alt="微信" className="payment-icon" />
+              </div>
+              <div className="amount-display">
+                <span className="amount">{getFinalAmount()}</span>
+                <span className="currency">元</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        {/* 充值按钮 */}
+        <button
+          className="submit-btn"
+          onClick={handleSubmit}
+          disabled={!isCustomAmountValid && selectedAmount === null}
+        >
+          确认充值
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default RechargePage;

+ 36 - 5
src/pages/layout/pages/SongDetail/components/SongDetailPage.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import { HeartOutlined, LoadingOutlined, PlusCircleOutlined } from '@ant-design/icons';
 import { useMusicPlayer } from '@/context/MusicPlayerContext';
 import './SongDetailPage.css';
@@ -6,6 +6,7 @@ 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';
+import { changeFavoriteStatusApi, getStatusApi } from '@/apis/userFavoriteSong';
 interface Song {
   id: number;
   songName: string;
@@ -45,7 +46,7 @@ const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
   const [isModalOpen, setIsModalOpen] = useState(false);
   const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); // 新增状态
   const [messageApi, contextHolder] = message.useMessage();
-
+  const [isCollection, setIsCollection] = useState(false)
   const {
     addToPlaylist,
     setCurrentTrackIndex,
@@ -174,6 +175,24 @@ const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
     setIsCreateModalOpen(false);
     setIsModalOpen(true);
   };
+  const addFavorite = async () => {
+    const res = await changeFavoriteStatusApi(song.id)
+    if (res.code === 200) {
+      messageApi.open({
+        type: 'success',
+        content: res.data
+      })
+    }
+    getStatus()
+    setIsModalOpen(false);
+  }
+  const getStatus = async () => {
+    const res = await getStatusApi(song.id)
+    setIsCollection(res.data)
+  }
+  useEffect(() => {
+    getStatus()
+  }, [])
   return (
     <div className="song-detail-container">
       {contextHolder}
@@ -208,9 +227,15 @@ const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
           <Button className="btn-play" onClick={handlePlay}>
             <span>▶</span> 播放
           </Button>
-          <Button onClick={() => collection()} className="btn-favorite">
-            <HeartOutlined />
-            收藏
+          <Button
+            onClick={() => collection()}
+            className="btn-favorite"
+            style={{ color: isCollection ? '#f5222d' : 'inherit' }}
+          >
+            <HeartOutlined
+              style={{ color: isCollection ? '#f5222d' : 'inherit' }}
+            />
+            {isCollection ? '已收藏我的喜欢' : '收藏'}
           </Button>
           <Button className="btn-share">分享</Button>
           <Button className="btn-download">下载</Button>
@@ -243,6 +268,12 @@ const SongDetailPage: React.FC<{ song: Song }> = ({ song }) => {
                 新歌单
               </div>
             </div>
+            <div className="selplaylistItem" style={{ backgroundColor: '#e9e7e7ff' }} onClick={addFavorite}>
+              <HeartOutlined 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

+ 2 - 2
src/pages/layout/pages/SongDetail/index.tsx

@@ -94,10 +94,10 @@ const SongDetail = () => {
       <Rank_Recommend_body_list_Comment 
         contentId={song.id} 
         type={0} 
-        onCommentSubmit={handleCommentSubmit} // 传递回调函数
+        onCommentSubmit={handleCommentSubmit}
       />
       <Rank_Recommend_body_list_Comment_list 
-        key={`comment-list-${refreshTrigger}`} // 使用key强制重新渲染
+        key={`comment-list-${refreshTrigger}`}
         contentId={song.id} 
         type={0} 
       />

+ 1 - 1
src/pages/layout/pages/find/rank/compomemts/Rank_Recommend_body_list_Comment/index.tsx

@@ -4,7 +4,7 @@ import './index.css';
 interface CommentProps {
   contentId?: number;
   type: number;
-  onCommentSubmit?: () => void; // 添加回调函数属性
+  onCommentSubmit?: () => void;
 }
 
 const Rank_Recommend_body_list_Comment = ({ contentId, type, onCommentSubmit }: CommentProps) => {

+ 1 - 0
src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content/index.css

@@ -6,6 +6,7 @@
   gap: 30px;
 }
 .recommend_content .recommend_content_item {
+  cursor: pointer;
   flex: 0 0 calc((100% - 3 * 30px) / 4);
   max-width: calc((100% - 3 * 30px) / 4);
   display: flex;

+ 1 - 0
src/pages/layout/pages/find/recommend/components/Recommend_body_left/components/Recommend_content/index.less

@@ -5,6 +5,7 @@
   flex-wrap: wrap; // 允许换行
   gap: 30px; // 统一设置元素之间的间距(可选)
   .recommend_content_item {
+    cursor: pointer;
     flex: 0 0 calc((100% - 3 * 30px) / 4); // 精确计算每个 item 的宽度,考虑 gap
     max-width: calc((100% - 3 * 30px) / 4);
     display: flex;

+ 77 - 54
src/pages/layout/pages/find/recommend/components/Recommend_body_left/index.tsx

@@ -1,64 +1,87 @@
+import { getEightPlaylistApi } from '@/apis/payList';
 import Recommend_content_bill from './components/Recommend_content_bill'
 import Recommend_index from './components/Recommend_index'
 import './index.css'
+import { useEffect, useState } from 'react';
 const Recommend_body_left = () => {
-  const playlistConfig = {
-    title: '推荐歌单',
-    children: [
-      { id: 1, content: '华语' },
-      { id: 2, content: '流行' },
-      { id: 3, content: '摇滚' },
-      { id: 4, content: '民谣' },
-      { id: 5, content: '电子' },
-    ],
-    morePath: '/find/recommend/playlists',
-    basePath: '/playlist',
-    list: [
-      {
-        id: 10,
-        title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
-        src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-      },
-      {
-        id: 1,
-        title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
-        src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
-      },
-      {
-        id: 2,
-        title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
-        src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-      },
-      {
-        id: 3,
-        title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
-        src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
-      }, {
-        id: 4,
-        title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
-        src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-      },
-      {
-        id: 5,
-        title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
-        src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
-      },
-      {
-        id: 6,
-        title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
-        src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-      },
-      {
-        id: 7,
-        title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
-        src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
-      },
-    ]
+  const [playlistConfig, setplaylistConfig] = useState<any>()
+  const getplaylistFun = async () => {
+    const res = await getEightPlaylistApi()
+    setplaylistConfig({
+      title: '推荐歌单',
+      children: [
+        { id: 1, content: '华语' },
+        { id: 2, content: '流行' },
+        { id: 3, content: '摇滚' },
+        { id: 4, content: '民谣' },
+        { id: 5, content: '电子' },
+      ],
+      morePath: '/find/songList',
+      basePath: '/playlist',
+      list: [
+        ...res.data
+      ]
+    })
   };
+  // const playlistConfig = {
+  //   title: '推荐歌单',
+  //   children: [
+  //     { id: 1, content: '华语' },
+  //     { id: 2, content: '流行' },
+  //     { id: 3, content: '摇滚' },
+  //     { id: 4, content: '民谣' },
+  //     { id: 5, content: '电子' },
+  //   ],
+  //   morePath: '/find/recommend/playlists',
+  //   basePath: '/playlist',
+  //   list: [
+  //     {
+  //       id: 10,
+  //       title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
+  //       src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  //     },
+  //     {
+  //       id: 1,
+  //       title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
+  //       src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
+  //     },
+  //     {
+  //       id: 2,
+  //       title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
+  //       src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  //     },
+  //     {
+  //       id: 3,
+  //       title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
+  //       src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
+  //     }, {
+  //       id: 4,
+  //       title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
+  //       src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  //     },
+  //     {
+  //       id: 5,
+  //       title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
+  //       src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
+  //     },
+  //     {
+  //       id: 6,
+  //       title: '华语速爆新歌 | G.E.M.邓紫棋:全新诠释经典曲目,回溯过往蜕变重生',
+  //       src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
+  //     },
+  //     {
+  //       id: 7,
+  //       title: '【怀疑耳机坏了系列】耳机你对耳朵做了什么',
+  //       src: 'https://p2.music.126.net/lE22tb7rZh-FsfpNf1lEeA==/109951169008502767.jpg?param=140y140',
+  //     },
+  //   ]
+  // };
+  useEffect(() => {
+    getplaylistFun()
+  }, [])
   return (
     <div className="Recommend_left">
-      <Recommend_index config={playlistConfig} />
-
+      {playlistConfig && <Recommend_index config={playlistConfig} />}
       <Recommend_content_bill />
     </div>
   )

+ 5 - 4
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.css

@@ -37,7 +37,6 @@
 }
 .Myinfo .Myinfo_first .Myinfo_first_right .Myinfo_first_top .Myinfo_first_top_bottom {
   display: flex;
-  margin-top: 5px;
 }
 .Myinfo .Myinfo_first .Myinfo_first_right .Myinfo_first_top .Myinfo_first_top_bottom .Myinfo_first_top_bottom_lv {
   width: 40px;
@@ -51,6 +50,7 @@
 }
 .Myinfo .Myinfo_first .Myinfo_first_right .Myinfo_first_bottom {
   height: 32px;
+  margin-top: 10px;
 }
 .Myinfo .Myinfo_first .Myinfo_first_right .Myinfo_first_bottom .Myinfo_first_bottom_button_Isok {
   width: 100px;
@@ -69,7 +69,8 @@
   border-radius: 3px;
   text-align: center;
   line-height: 31px;
-  background: linear-gradient(to bottom, #fafafa, #dcdcdc);
-  border: #999999;
-  color: #999999;
+  cursor: pointer;
+  background: #e0dddd;
+  border: #e0dddd;
+  color: #777575;
 }

+ 22 - 14
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.less

@@ -1,26 +1,33 @@
 .Myinfo {
   margin-top: 20px;
   display: flex;
+
   .Myinfo_first {
     display: flex;
     margin: auto 20px;
+
     .Myinfo_first_left {
       background-color: #ffffff;
       padding: 3px;
       border: 2px solid rgb(203, 201, 201);
     }
+
     .Myinfo_first_right {
       margin-left: 20px;
       width: 100px;
+
       .Myinfo_first_top {
         height: 48px;
+
         .Myinfo_first_top_top {
           display: flex;
+
           .Myinfo_first_top_top_left {
             color: #000000;
             font-weight: bolder;
             width: 60px;
           }
+
           .Myinfo_first_top_top_right {
             width: 45px;
             height: 16px;
@@ -31,9 +38,11 @@
             cursor: pointer;
           }
         }
+
         .Myinfo_first_top_bottom {
           display: flex;
-          margin-top: 5px;
+
+
           .Myinfo_first_top_bottom_lv {
             width: 40px;
             text-align: center;
@@ -46,8 +55,11 @@
           }
         }
       }
+
       .Myinfo_first_bottom {
         height: 32px;
+        margin-top: 10px;
+
         .Myinfo_first_bottom_button_Isok {
           width: 100px;
           height: 31px;
@@ -55,29 +67,25 @@
           text-align: center;
           line-height: 31px;
           cursor: pointer;
-          background: linear-gradient(
-            to bottom,
-            rgb(55, 143, 211),
-            rgb(66, 141, 214)
-          );
+          background: linear-gradient(to bottom,
+              rgb(55, 143, 211),
+              rgb(66, 141, 214));
           border: rgb(34, 115, 196);
           color: #ffffff;
         }
+
         .Myinfo_first_bottom_button_isfalse {
           width: 100px;
           height: 31px;
           border-radius: 3px;
           text-align: center;
           line-height: 31px;
-          background: linear-gradient(
-            to bottom,
-            rgb(250, 250, 250),
-            rgb(220, 220, 220)
-          );
-          border: rgb(153, 153, 153);
-          color: rgb(153, 153, 153);
+          cursor: pointer;
+          background: rgb(224, 221, 221);
+          border: rgb(224, 221, 221);
+          color: #777575;
         }
       }
     }
   }
-}
+}

+ 59 - 7
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Myinfo/index.tsx

@@ -1,34 +1,86 @@
-import { Image } from 'antd'
+import { Image, Button, message } from 'antd'
 import './index.css'
 import { getUserinfo } from '@/utils/userinfo'
+import { useEffect, useState } from 'react'
+import { getuserlevel } from '@/apis/user-level'
+import { signStatusApi, userSignInApi } from '@/apis/user-sign-in'
+
 const Myinfo = () => {
-    const userinfo = getUserinfo()
+  const userinfo = getUserinfo()
+  const [levelinfo, setlevelinfo] = useState({})
+  const [signStatus, setsignStatus] = useState(false) // 签到状态
+  const [messageApi, contextHolder] = message.useMessage()
+
+  const getuserlevelFun = async () => {
+    const res = await getuserlevel()
+    setlevelinfo(res.data)
+  }
+  
+  const getSignStatus = async () => {
+    try {
+      const res = await signStatusApi()
+      setsignStatus(res.data.isSigned || res.data === true || res.data === '今天已经签到过了');
+    } catch (error) {
+      console.error('获取签到状态失败:', error);
+    }
+  }
+
+  const handleSignIn = async () => {
+    try {
+      const res = await userSignInApi();
+      messageApi.success(res.data); // 显示签到结果,如"签到成功"或"今天已经签到过了"
+      setsignStatus(true); // 更新签到状态
+      getSignStatus(); // 重新获取签到状态
+    } catch (error) {
+      messageApi.error('签到失败,请重试');
+      console.error('签到失败:', error);
+    }
+  }
+  
+  const infoInit = () => {
+    getSignStatus()
+    getuserlevelFun()
+  }
+  
+  useEffect(() => {
+    infoInit()
+  }, [])
+  
   return (
     <div className="Myinfo">
+      {contextHolder}
       <div className="Myinfo_first">
         <div className="Myinfo_first_left">
           <Image
             preview={false}
             width={80}
-            src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
+            src={userinfo?.url}
           />
         </div>
         <div className="Myinfo_first_right">
           <div className="Myinfo_first_top">
             <div className="Myinfo_first_top_top">
-              <div className="Myinfo_first_top_top_left">{userinfo?.Name}</div>
+              <div className="Myinfo_first_top_top_left">{userinfo?.name}</div>
               <div className="Myinfo_first_top_top_right"></div>
             </div>
             <div className="Myinfo_first_top_bottom">
-              <div className="Myinfo_first_top_bottom_lv">Lv.9</div>
+              <div className="Myinfo_first_top_bottom_lv">Lv.{levelinfo?.level}</div>
             </div>
           </div>
           <div className="Myinfo_first_bottom">
-            <div className="Myinfo_first_bottom_button_Isok">签到</div>
+            <Button 
+              type="primary"
+              onClick={handleSignIn}
+              disabled={signStatus}
+              className={signStatus ? "Myinfo_first_bottom_button_isfalse" : "Myinfo_first_bottom_button_Isok"}
+            >
+              {signStatus ? '已签到' : '签到'}
+            </Button>
           </div>
         </div>
       </div>
     </div>
   )
 }
-export default Myinfo
+
+export default Myinfo

+ 1 - 0
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.css

@@ -12,6 +12,7 @@
 }
 .singer .title .more {
   color: #5e6060;
+  cursor: pointer;
 }
 .singer .content {
   margin-top: 20px;

+ 1 - 0
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.less

@@ -10,6 +10,7 @@
     height: 25px;
     .more {
       color: rgb(94, 96, 96);
+      cursor: pointer;
     }
   }
   .content {

+ 52 - 87
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/Singer/index.tsx

@@ -1,109 +1,74 @@
 import { Image } from 'antd'
 import './index.css'
-const Singerinfo = [
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 1,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 2,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 3,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 4,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 5,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '张惠妹aMEI',
-    sinInfo: '台湾歌手张惠妹',
-    id: 6,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-]
-const zhubo = [
-  {
-    name: '陈立',
-    sinInfo: '心理学家、美食家陈立教授',
-    id: 1,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '陈立',
-    sinInfo: '心理学家、美食家陈立教授',
-    id: 2,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '陈立',
-    sinInfo: '心理学家、美食家陈立教授',
-    id: 3,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  {
-    name: '陈立',
-    sinInfo: '心理学家、美食家陈立教授',
-    id: 4,
-    img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
-  },
-  
-]
+import { ArtisstRecommendCount6 } from '@/apis/artist'
+import { useEffect, useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+
+// 接口定义
+interface Artist {
+  id: number;
+  userId: number | null;
+  artistName: string;
+  avatar: string;
+  headerImage: string | null;
+  gender: string | null;
+  birthday: string | null;
+  region: string | null;
+  genre: string | null;
+  company: string | null;
+  introduction: string | null;
+  invitationCode: string | null;
+  wechat: string | null;
+  status: number | null;
+  createTime: string | null;
+  updateTime: string | null;
+}
+
 const Singer = () => {
+  const [Singerinfo, setArtistList] = useState<Artist[]>([])
+  const navigate = useNavigate()
+
+  const getartistFun = async () => {
+    const res = await ArtisstRecommendCount6()
+    setArtistList(res.data)
+  }
+
+  useEffect(() => {
+    getartistFun()
+  }, [])
+
+  const handleArtistClick = (id: number) => {
+    navigate(`/find/artistinfo/${id}`)
+  }
+
   return (
     <div className="singer">
       <div>
         <div className="title">
           <div>入驻歌手</div>
-          <div className="more">查看全部{'>'}</div>
+          {/* <div className="more" onClick={()=>{navigate('')}}>查看全部{'>'}</div> */}
         </div>
         {Singerinfo.map((item) => (
-          <div className="content" key={item.id}>
+          <div 
+            className="content" 
+            key={item.id}
+            onClick={() => handleArtistClick(item.id)}
+            style={{ cursor: 'pointer' }}
+          >
             <div className="content_left">
-              <Image preview={false} width={62} src={item.img} />
+              <Image preview={false} width={62} height={62} src={item.avatar} />
             </div>
             <div className="content_right">
-              <div className="content_right_top">{item.name}</div>
-              <div className="content_right_bottom">{item.sinInfo}</div>
+              <div className="content_right_top">{item.artistName}</div>
+              <div className="content_right_bottom">{item.genre || '暂无信息'}</div>
             </div>
           </div>
         ))}
 
         <div className="blank">申请成为网易音乐人</div>
       </div>
-      <div>
-        <div className="title">
-          <div>热门主播</div>
-        </div>
-        {zhubo.map((item) => (
-          <div className="container" key={item.id}>
-            <div className="container_left">
-              <Image preview={false} width={40} src={item.img} />
-            </div>
-            <div className="container_right">
-              <div className="container_right_top">{item.name}</div>
-              <div className="container_right_bottom">{item.sinInfo}</div>
-            </div>
-          </div>
-        ))}
-      </div>
     </div>
   )
 }
-export default Singer
+
+export default Singer

+ 13 - 3
src/pages/layout/pages/find/recommend/components/Recommend_body_right/components/UserStats/index.tsx

@@ -1,17 +1,27 @@
+import { getFollowApi } from '@/apis/share'
 import './index.css'
+import { useEffect, useState } from 'react'
 const UserStats = () => {
+  const [followlist, Setfollowlist] = useState<any>({})
+  const getFollowFUn = async () => {
+    const res = await getFollowApi()
+    Setfollowlist(res.data)
+  }
+  useEffect(() => {
+    getFollowFUn()
+  }, [])
   return (
     <div className="UserStats">
       <div className="UserStats_content">
-        <div className="UserStats_content_top">3</div>
+        <div className="UserStats_content_top">{followlist.shareCount}</div>
         <div className="UserStats_content_bottom">动态</div>
       </div>
       <div className="UserStats_content">
-        <div className="UserStats_content_top">3</div>
+        <div className="UserStats_content_top">{followlist.followCount}</div>
         <div className="UserStats_content_bottom">关注</div>
       </div>
       <div className="UserStats_content">
-        <div className="UserStats_content_top">3</div>
+        <div className="UserStats_content_top">{followlist.favoriteCount}</div>
         <div className="UserStats_content_bottom">粉丝</div>
       </div>
     </div>

+ 1 - 0
src/pages/layout/pages/friend/Mine_Friend_recommend/index.css

@@ -18,6 +18,7 @@
   padding: 0 30px;
   display: flex;
   justify-content: space-between;
+  cursor: pointer;
 }
 .Mine_Friend_recommend .Mine_Friend_recommend_content .Mine_Friend_recommend_content_art .Mine_Friend_recommend_content_art_name {
   color: #959698;

+ 2 - 2
src/pages/layout/pages/friend/Mine_Friend_recommend/index.less

@@ -25,7 +25,7 @@
     padding: 0 30px;
     display: flex;
     justify-content: space-between;
-
+    cursor: pointer;
     .Mine_Friend_recommend_content_art {
 
       .Mine_Friend_recommend_content_art_name {
@@ -34,7 +34,7 @@
       }
 
       .Mine_Friend_recommend_content_art_team {
-          color: rgb(195, 198, 203);
+        color: rgb(195, 198, 203);
       }
     }
 

+ 118 - 32
src/pages/layout/pages/friend/Mine_Friend_recommend/index.tsx

@@ -1,47 +1,133 @@
-import { Button, Image } from 'antd'
+import { Image, Button, message, Modal } from 'antd'
 import './index.css'
+import { useEffect, useState } from 'react';
+import { ArtisstRecommend, ChangeArtistIsCollectByid, queryArtistIsCollectByid } from '@/apis/artist';
+import { useNavigate } from 'react-router-dom';
+import { shareList } from '@/apis/share';
+
 const Mine_Friend_recommend = () => {
+  const [artistRecommend, setArtistRecommend] = useState([])
+  const [followStatus, setFollowStatus] = useState({});
+  const [messageApi, contextHolder] = message.useMessage();
+  const useroute = useNavigate()
+
+  const getArtistRecommend = async () => {
+    try {
+      const res = await ArtisstRecommend()
+      setArtistRecommend(res.data)
+      const statusMap = {}; // 修正:用对象存储更合理,原数组会有索引问题
+      for (const item of res.data) {
+        try {
+          const statusRes = await queryArtistIsCollectByid(item.id);
+          statusMap[item.id] = statusRes.data || false;
+        } catch (error) {
+          statusMap[item.id] = false;
+        }
+      }
+      setFollowStatus(statusMap);
+    } catch (error) {
+      messageApi.error('获取推荐歌手失败');
+      console.error('获取推荐歌手失败:', error);
+    }
+  }
+
+  // 关键修改:添加e参数,阻止事件冒泡
+  const toggleFollow = async (e, artistId, artistIndex) => {
+    // 阻止事件向上冒泡到外层div,避免触发跳转
+    e.stopPropagation();
+    try {
+      const currentStatus = followStatus[artistId];
+      await ChangeArtistIsCollectByid(artistId);
+
+      // 更新本地状态
+      setFollowStatus(prev => ({
+        ...prev,
+        [artistId]: !prev[artistId]
+      }));
+
+      // 显示成功消息
+      if (currentStatus) {
+        messageApi.success('已取消关注');
+      } else {
+        messageApi.success('关注成功');
+      }
+    } catch (error) {
+      messageApi.error('操作失败,请重试');
+      console.error('切换关注状态失败:', error);
+    }
+  };
+
+  const handleRefresh = async () => {
+    try {
+      messageApi.loading('正在获取新推荐...', 1);
+      await getArtistRecommend();
+      messageApi.success('换一批成功');
+    } catch (error) {
+      messageApi.error('换一批失败');
+      console.error('换一批失败:', error);
+    }
+  };
+
+  const getShareListFun = async () => {
+    const res = await shareList()
+  }
+
+  useEffect(() => {
+    getArtistRecommend()
+    getShareListFun()
+  }, []);
+
   return (
-    <div className="Mine_Friend_recommend" >
+    <div className="Mine_Friend_recommend">
+      {contextHolder}
       <div className="Mine_Friend_recommend_title">
-
         <div className="Mine_Friend_recommend_title_left">
           明星用户
         </div>
-        <div className="Mine_Friend_recommend_title_right">
+        <div
+          className="Mine_Friend_recommend_title_right"
+          onClick={handleRefresh}
+          style={{ cursor: 'pointer' }}
+        >
           换一换
         </div>
       </div>
       <div className="Mine_Friend_recommend_hr"></div>
-      <div className="Mine_Friend_recommend_content">
-        <div style={{width:45}}>       
-           <Image
-          width={45}
-          src="https://p1.music.126.net/OVosmVr7h0kgRX04QxuZEw==/109951170941969309.jpg?param=45y45"
-        /></div>
-        <div className='Mine_Friend_recommend_content_art'>
-          <div className='Mine_Friend_recommend_content_art_name'>周子琰</div>
-          <div className='Mine_Friend_recommend_content_art_team'>周子琰</div>
-        </div>
-        <div className='Mine_Friend_recommend_content_button'>
-          <Button type='default'><p>+</p><i>关注</i></Button>
-        </div>
-      </div>
-            <div className="Mine_Friend_recommend_content">
-        <div style={{width:45}}>       
-           <Image
-          width={45}
-          src="https://p1.music.126.net/OVosmVr7h0kgRX04QxuZEw==/109951170941969309.jpg?param=45y45"
-        /></div>
-        <div className='Mine_Friend_recommend_content_art'>
-          <div className='Mine_Friend_recommend_content_art_name'>周子琰</div>
-          <div className='Mine_Friend_recommend_content_art_team'>周子琰</div>
-        </div>
-        <div className='Mine_Friend_recommend_content_button'>
-          <Button type='default'><p>+</p><i>关注</i></Button>
+      {artistRecommend.map((item, index) => (
+        <div 
+          className="Mine_Friend_recommend_content" 
+          key={item.id} 
+          onClick={() => useroute(`/find/artistinfo/${item.id}`)}
+        >
+          <div style={{ width: 45 }}>
+            <Image
+              width={45}
+              preview={false}
+              height={45}
+              src={item.avatar}
+            />
+          </div>
+          <div className='Mine_Friend_recommend_content_art'>
+            <div className='Mine_Friend_recommend_content_art_name'>{item.artistName}</div>
+            <div className='Mine_Friend_recommend_content_art_team'>{item.company}</div>
+          </div>
+          <div className='Mine_Friend_recommend_content_button'>
+            <Button
+              type='default'
+              // 关键修改:传递事件对象e给toggleFollow
+              onClick={(e) => toggleFollow(e, item.id, index)}
+              style={{
+                backgroundColor: followStatus[item.id] ? '#f0f0f0' : '#1890ff',
+                color: followStatus[item.id] ? '#000' : '#fff'
+              }}
+            >
+              <i>{followStatus[item.id] ? '已关注' : '关注'}</i>
+            </Button>
+          </div>
         </div>
-      </div>
-    </div >
+      ))}
+    </div>
   )
 }
+
 export default Mine_Friend_recommend

+ 34 - 3
src/pages/layout/pages/friend/index.css

@@ -94,8 +94,22 @@
   line-height: 20px;
 }
 .Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_image {
-  padding-top: 20px;
-  padding-left: 50px;
+  padding: 20px 0 20px 50px;
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_image .image-wrapper {
+  display: inline-block;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_image .image-wrapper a {
+  display: block;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_image .image-wrapper a .ant-image-img {
+  cursor: pointer;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_image .image-wrapper a .ant-image-img:hover {
+  opacity: 0.8;
 }
 .Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom {
   display: flex;
@@ -105,8 +119,25 @@
   /* 元素之间的间隙 */
   color: #235D9D;
 }
-.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom:hover {
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom .action-item {
+  display: inline-flex;
+  align-items: center;
+  margin-right: 16px;
   cursor: pointer;
+  transition: color 0.3s;
+  color: #235D9D;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom .action-item:hover {
+  opacity: 0.8;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom .action-item.liked {
+  color: #1890ff;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom .action-item.disliked {
+  color: #ff4d4f;
+}
+.Mine_Friend .Mine_Friend_left .Mine_Friend_content .Mine_Friend_content_bottom .action-item.forwarded {
+  color: #52c41a;
 }
 .Mine_Friend .Mine_Friend_right {
   border-right: 1px solid #c6c6c6;

+ 46 - 12
src/pages/layout/pages/friend/index.less

@@ -11,7 +11,6 @@
 
     .Mine_Friend_left_title {
       display: flex;
-
       justify-content: space-between;
 
       .Mine_Friend_left_title_p {
@@ -41,7 +40,7 @@
           width: 90px;
           border-radius: 15px;
           border: 1px solid rgb(198, 198, 198);
-          padding: 0 10px
+          padding: 0 10px;
         }
       }
     }
@@ -69,8 +68,7 @@
       }
 
       .notify_right {
-
-        //悬浮变手
+        // 悬浮变手
         &:hover {
           cursor: pointer;
         }
@@ -107,36 +105,72 @@
             color: #787676;
           }
         }
-
       }
 
       .Mine_Friend_content_text {
         padding-left: 50px;
-        //行间距
+        // 行间距
         line-height: 20px;
       }
 
       .Mine_Friend_content_image {
-        padding-top: 20px;
-        padding-left: 50px;
+        padding: 20px 0 20px 50px;
+        display: flex;
+        gap: 10px; // 图片之间的间距
+        flex-wrap: wrap; // 允许换行
+
+        .image-wrapper {
+          display: inline-block;
+
+          a {
+            display: block;
+
+            .ant-image-img {
+              cursor: pointer;
+
+              &:hover {
+                opacity: 0.8;
+              }
+            }
+          }
+        }
       }
 
       .Mine_Friend_content_bottom {
         display: flex;
         align-items: center;
         justify-content: right;
-        gap: 12px;
-        /* 元素之间的间隙 */
+        gap: 12px; /* 元素之间的间隙 */
         color: #235D9D;
 
-        &:hover {
+        .action-item {
+          display: inline-flex;
+          align-items: center;
+          margin-right: 16px;
           cursor: pointer;
+          transition: color 0.3s;
+          color: #235D9D;
+
+          &:hover {
+            opacity: 0.8;
+          }
+
+          &.liked {
+            color: #1890ff;
+          }
+
+          &.disliked {
+            color: #ff4d4f;
+          }
+
+          &.forwarded {
+            color: #52c41a;
+          }
         }
       }
     }
   }
 
-
   .Mine_Friend_right {
     border-right: 1px solid rgb(198, 198, 198);
     width: 250px;

+ 516 - 47
src/pages/layout/pages/friend/index.tsx

@@ -1,79 +1,548 @@
-import { DislikeOutlined, EditOutlined, LikeOutlined, VideoCameraOutlined, WeiboSquareOutlined } from '@ant-design/icons'
+import { DislikeOutlined, EditOutlined, LikeOutlined, PictureOutlined, VideoCameraOutlined, WeiboSquareOutlined, RetweetOutlined, MessageOutlined } from '@ant-design/icons'
+import { Dropdown, Menu } from 'antd'
 import Myinfo from '../find/recommend/components/Recommend_body_right/components/Myinfo'
 import UserStats from '../find/recommend/components/Recommend_body_right/components/UserStats'
 import './index.css'
-import { Button, Image } from 'antd'
+import { Button, Form, Image, Input, message, Modal, Upload } from 'antd'
 import Mine_Friend_recommend from './Mine_Friend_recommend'
+import { useEffect, useState, useRef } from 'react'
+import TextArea from 'antd/es/input/TextArea'
+import { useNavigate } from 'react-router-dom'
+import type { UploadFile, UploadProps } from 'antd'
+import { uploadFile } from '@/apis/upload'
+import { addShare, shareList, likeShare, cancelLikeShare, dislikeShare, cancelDislikeShare, forwardShare, cancelForwardShare, deleteShare } from '@/apis/share'
+import InfiniteScroll from 'react-infinite-scroll-component'
+
+// 定义分享项的类型
+interface ShareItem {
+  id: number;
+  userId: string;
+  artistName: string;
+  avatar: string;
+  content: string;
+  likeCount: number;
+  unlikeCount: number;
+  isForward: boolean;
+  forwardCount: number;
+  commentCount: number;
+  jumpUrls: string | null;
+  files: string; // JSON字符串
+  createdAt: string;
+  isLiked: boolean; // 用户是否已点赞
+  isDisliked: boolean; // 用户是否已点踩
+  isForwarded: boolean; // 用户是否已转发
+}
+
 const Mine_Friend = () => {
-  return (
-    <div className='Mine_Friend'>
-      <div className="Mine_Friend_left">
-        <div className="Mine_Friend_left_title">
-          <div className="Mine_Friend_left_title_p">
-            笔记
-          </div>
-          <div className="Mine_Friend_left_title_Button">
-            <div className="Mine_Friend_left_title_Button_content">
-              <EditOutlined />发笔记
-            </div>
-            <div className="Mine_Friend_left_title_Button_content">
-              <VideoCameraOutlined />  发视频
-            </div>
-          </div>
-        </div>
-        <div className="Mine_Friend_left_hr" />
-        <div className="notify">
-          <div className="notify_left">你关注的杭盖乐队等2位歌手发布了新作品</div>
-          <div className="notify_right">快去看看{'>'}</div>
-        </div>
-        <div className="Mine_Friend_content">
+  const [isModalOpen, setIsModalOpen] = useState(false)
+  const useroute = useNavigate()
+  const [messageApi, contextHolder] = message.useMessage()
+  const [form] = Form.useForm()
+  const [fileList, setFileList] = useState<UploadFile[]>([])
+  const [shareData, setShareData] = useState<ShareItem[]>([])
+  const [hasMore, setHasMore] = useState(true)
+  const [page, setPage] = useState(1)
+  const [loading, setLoading] = useState(false)
+  const [totalPages, setTotalPages] = useState(0)
+
+  const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
+    console.log(newFileList);
+    setFileList(newFileList)
+
+    const fileInfoArray = newFileList
+      .filter(file => file.status === 'done' && (file.response || file.url))
+      .map(file => ({
+        url: file.response
+      }))
+
+    form.setFieldsValue({
+      files: fileInfoArray
+    })
+  }
+  const uploadButton = <PictureOutlined />
+
+  const onFinish = async (values: any) => {
+    console.log(values);
+    
+    const processedValues = {
+      ...values,
+      files: values.files ? JSON.stringify(values.files) : ""
+    }
+
+    console.log('表单值:', processedValues)
+    await addShare(processedValues)
+    messageApi.success('发布成功')
+    setIsModalOpen(false)
+    form.resetFields()
+    setFileList([])
+    // 重新加载第一页数据
+    setPage(1)
+    setShareData([])
+    await fetchShareList(1)
+  }
+
+  const onFinishFailed = (errorInfo: any) => {
+    console.log('表单验证失败:', errorInfo)
+  }
+
+  const handleOk = () => {
+    form.submit()
+  }
+
+  const handleCancel = () => {
+    setIsModalOpen(false)
+    form.resetFields()
+  }
+
+  const onopenModal = () => {
+    setIsModalOpen(true)
+  }
+
+  const customRequest = async (options: any) => {
+    const { file, onSuccess, onError, onProgress } = options
+    try {
+      const result = await uploadFile('Video_Temp', file, '', onProgress)
+      messageApi.success('封面上传成功')
+      console.log(result);
+      
+      onSuccess(result.data)
+    } catch (error) {
+      console.error('封面上传失败:', error)
+      const errorMessage = error.response?.data?.error || '封面上传失败'
+      messageApi.error(errorMessage)
+      onError(error)
+    }
+  }
+
+  const fetchShareList = async (pageNum: number) => {
+    try {
+      setLoading(true)
+      const res = await shareList(pageNum, 5) // 每页5条数据
+      if (pageNum === 1) {
+        setShareData(res.data.records)
+      } else {
+        setShareData(prev => [...prev, ...res.data.records])
+      }
+      setTotalPages(res.data.pages)
+      setHasMore(pageNum < res.data.pages)
+    } catch (error) {
+      console.error('获取分享列表失败:', error)
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  useEffect(() => {
+    fetchShareList(1)
+  }, [])
+
+  // 加载更多数据
+  const loadMoreData = async () => {
+    if (loading || !hasMore) return
+    const nextPage = page + 1
+    await fetchShareList(nextPage)
+    setPage(nextPage)
+  }
+
+  // 解析图片文件
+  const parseFiles = (filesStr: string) => {
+    try {
+      return JSON.parse(filesStr) || []
+    } catch {
+      return []
+    }
+  }
+
+  // 处理点赞
+  const handleLike = async (id: number, index: number) => {
+    try {
+      const item = shareData[index]
+      if (item.isLiked) {
+        // 如果已点赞,取消点赞
+        await cancelLikeShare(id)
+        messageApi.success('已取消点赞')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              likeCount: Math.max(0, item.likeCount - 1),
+              isLiked: false,
+              isDisliked: false // 取消点赞时也取消点踩状态
+            }
+            : item
+        ))
+      } else {
+        // 如果未点赞,点赞
+        await likeShare(id)
+        messageApi.success('已点赞')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              likeCount: item.likeCount + 1,
+              isLiked: true,
+              isDisliked: false // 点赞时取消点踩状态
+            }
+            : item
+        ))
+      }
+    } catch (error) {
+      console.error('操作失败:', error)
+      messageApi.error('操作失败')
+    }
+  }
+
+  // 处理点踩
+  const handleDislike = async (id: number, index: number) => {
+    try {
+      const item = shareData[index]
+      if (item.isDisliked) {
+        // 如果已点踩,取消点踩
+        await cancelDislikeShare(id)
+        messageApi.success('已取消点踩')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              unlikeCount: Math.max(0, item.unlikeCount - 1),
+              isDisliked: false,
+              isLiked: false // 取消点踩时也取消点赞状态
+            }
+            : item
+        ))
+      } else {
+        // 如果未点踩,点踩
+        await dislikeShare(id)
+        messageApi.success('已点踩')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              unlikeCount: item.unlikeCount + 1,
+              isDisliked: true,
+              isLiked: false // 点踩时取消点赞状态
+            }
+            : item
+        ))
+      }
+    } catch (error) {
+      console.error('操作失败:', error)
+      messageApi.error('操作失败')
+    }
+  }
+
+  // 处理转发
+  const handleForward = async (id: number, index: number) => {
+    try {
+      const item = shareData[index]
+      if (item.isForwarded) {
+        // 如果已转发,取消转发
+        await cancelForwardShare(id)
+        messageApi.success('已取消转发')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              forwardCount: Math.max(0, item.forwardCount - 1),
+              isForwarded: false
+            }
+            : item
+        ))
+      } else {
+        // 如果未转发,转发
+        await forwardShare(id)
+        messageApi.success('已转发')
+        // 更新本地状态
+        setShareData(prev => prev.map((item, i) =>
+          i === index
+            ? {
+              ...item,
+              forwardCount: item.forwardCount + 1,
+              isForwarded: true
+            }
+            : item
+        ))
+      }
+    } catch (error) {
+      console.error('转发失败:', error)
+      messageApi.error('转发失败')
+    }
+  }
+
+  // 删除分享
+  const handleDeleteShare = async (id: number) => {
+    try {
+      await deleteShare(id)
+      messageApi.success('分享已删除')
+      // 从本地状态中移除该分享
+      setShareData(prev => prev.filter(item => item.id !== id))
+    } catch (error) {
+      console.error('删除分享失败:', error)
+      messageApi.error('删除分享失败')
+    }
+  }
+
+  // 处理评论(暂时跳转到详情页)
+  const handleComment = (id: number) => {
+    // 这里可以跳转到详情页或者打开评论弹窗
+    console.log('评论分享:', id)
+    messageApi.info('评论功能开发中...')
+  }
+
+  // 右键菜单
+  const getContextMenu = (item: ShareItem) => {
+    const menu = (
+      <Menu>
+        <Menu.Item
+          key="delete"
+          danger
+          onClick={() => handleDeleteShare(item.id)}
+        >
+          删除分享
+        </Menu.Item>
+      </Menu>
+    )
+
+    return menu
+  }
+
+  // 渲染分享内容
+  const renderShareContent = (item: ShareItem, index: number) => {
+    const files = parseFiles(item.files)
+
+    return (
+      <Dropdown
+        overlay={getContextMenu(item)}
+        trigger={['contextMenu']}
+      >
+        <div
+          className="Mine_Friend_content"
+          key={item.id}
+          onContextMenu={(e) => e.preventDefault()} // 阻止默认右键菜单
+        >
           <div className="Mine_Friend_content_title">
             <Image
               width={50}
-              src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
+              height={50}
+              src={item.avatar}
+              preview={false}
             />
             <div className="Mine_Friend_content_title_right">
               <div className="Mine_Friend_content_title_right_top">
-                <div className="Mine_Friend_content_title_username">
-                  云音乐VIP
+                <div className="Mine_Friend_content_title_username">{item.artistName}</div>
+                <div className="Mine_Friend_content_title_source">
+                  {item.isForward ? '分享网页' : '个人发布'}
                 </div>
-                <div className="Mine_Friend_content_title_source">分享网页</div>
               </div>
               <div className="Mine_Friend_content_title_right_bottom">
-                12:00
+                {new Date(item.createdAt).toLocaleString()}
               </div>
             </div>
           </div>
           <div className="Mine_Friend_content_text">
-            🧀奶酪派对时间到!欢喜冤家组合已就位,只等你啦~
-            「猫和老鼠7件套」奇趣装扮礼包上线🥳!
-            头像挂件/图标/启动图/红心动效/点赞动效/DIY播放器背景/皮肤
-            “猫”力全开😆萌“鼠”我了
-            戳⬇️链接⬇️直达猫鼠派对现场😍限时解锁主题装扮!
-          </div>
-          <div className="Mine_Friend_content_image">
-            <Image
-              width={200}
-              src="https://p1.music.126.net/mPpP_sr3mhGdyD0Qb2g8xg==/109951172248792284.jpg?param=0x338&quality=100"
-            />
+            {item.content}
           </div>
+          {files.length > 0 && (
+            <div className="Mine_Friend_content_image">
+              {files.map((file: any, fileIndex: number) => (
+                <div key={fileIndex} className="image-wrapper">
+                  {item.jumpUrls ? (
+                    <a href={item.jumpUrls} target="_blank" rel="noopener noreferrer">
+                      <Image
+                        width={180}
+                        src={file.url}
+                        preview={false} // 禁用预览
+                      />
+                    </a>
+                  ) : (
+                    <Image
+                      width={180}
+                      src={file.url}
+                      preview={{
+                        visible: false, // 不显示预览
+                        onVisibleChange: (vis) => {
+                          if (vis) {
+                            // 如果需要自定义预览行为,可以在这里处理
+                          }
+                        }
+                      }}
+                    />
+                  )}
+                </div>
+              ))}
+            </div>
+          )}
           <div className="Mine_Friend_content_bottom">
-            <LikeOutlined />
-            <DislikeOutlined />
-            <span>转发 (1)</span>
-            <span>评论 (22)</span>
+            <span
+              className={`action-item ${item.isLiked ? 'liked' : ''}`}
+              onClick={(e) => {
+                e.stopPropagation(); // 阻止事件冒泡到右键菜单
+                handleLike(item.id, index)
+              }}
+            >
+              {item.isLiked ? <LikeOutlined style={{ color: '#1890ff' }} /> : <LikeOutlined />}
+              <span style={{ marginLeft: '4px' }}>{item.likeCount}</span>
+            </span>
+
+            <span
+              className={`action-item ${item.isDisliked ? 'disliked' : ''}`}
+              onClick={(e) => {
+                e.stopPropagation(); // 阻止事件冒泡到右键菜单
+                handleDislike(item.id, index)
+              }}
+            >
+              {item.isDisliked ? <DislikeOutlined style={{ color: '#ff4d4f' }} /> : <DislikeOutlined />}
+              <span style={{ marginLeft: '4px' }}>{item.unlikeCount}</span>
+            </span>
+
+            <span
+              className={`action-item ${item.isForwarded ? 'forwarded' : ''}`}
+              onClick={(e) => {
+                e.stopPropagation(); // 阻止事件冒泡到右键菜单
+                handleForward(item.id, index)
+              }}
+            >
+              {item.isForwarded ? <RetweetOutlined style={{ color: '#52c41a' }} /> : <RetweetOutlined />}
+              <span style={{ marginLeft: '4px' }}>转发 ({item.forwardCount})</span>
+            </span>
+
+            <span
+              className="action-item"
+              onClick={(e) => {
+                e.stopPropagation(); // 阻止事件冒泡到右键菜单
+                handleComment(item.id)
+              }}
+            >
+              <MessageOutlined />
+              <span style={{ marginLeft: '4px' }}>评论 ({item.commentCount})</span>
+            </span>
+          </div>
+        </div>
+      </Dropdown>
+    )
+  }
+
+  return (
+    <div className="Mine_Friend">
+      {contextHolder}
+      <div className="Mine_Friend_left">
+        <div className="Mine_Friend_left_title">
+          <div className="Mine_Friend_left_title_p">笔记</div>
+          <div className="Mine_Friend_left_title_Button">
+            <div className="Mine_Friend_left_title_Button_content" onClick={onopenModal}>
+              <EditOutlined />
+              发笔记
+            </div>
+            <div
+              className="Mine_Friend_left_title_Button_content"
+              onClick={() => useroute(`/musician/musicianHome`)}
+            >
+              <VideoCameraOutlined /> 发视频
+            </div>
           </div>
         </div>
+        <div className="Mine_Friend_left_hr" />
+        <div className="notify">
+          <div className="notify_left">你关注的杭盖乐队等2位歌手发布了新作品</div>
+          <div className="notify_right">快去看看{'>'}</div>
+        </div>
+
+        {/* 使用 InfiniteScroll 组件实现触底加载 */}
+        <InfiniteScroll
+          dataLength={shareData.length}
+          next={loadMoreData}
+          hasMore={hasMore}
+          loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
+          endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>没有更多数据了</div>}
+          style={{ overflow: 'none' }} // 移除默认滚动
+        >
+          {/* 渲染分享列表 */}
+          {shareData.map((item, index) => renderShareContent(item, index))}
+        </InfiniteScroll>
       </div>
       <div className="Mine_Friend_right">
         <Myinfo></Myinfo>
         <UserStats></UserStats>
         <Mine_Friend_recommend></Mine_Friend_recommend>
-        <div className='Mine_Friend_right_bottom_p'>添加微博好友,和他们分享音乐</div>
-        <div className='Mine_Friend_right_bottom_button'>    <Button ><WeiboSquareOutlined /> 绑定新浪微博</Button></div>
-
+        <div className="Mine_Friend_right_bottom_p">添加微博好友,和他们分享音乐</div>
+        <div className="Mine_Friend_right_bottom_button">
+          <Button>
+            <WeiboSquareOutlined /> 绑定新浪微博
+          </Button>
+        </div>
       </div>
+
+      <Form
+        form={form}
+        name="note_form"
+        onFinish={onFinish}
+        onFinishFailed={onFinishFailed}
+        autoComplete="off"
+        initialValues={{
+          content: null,
+          files: [],
+          jumpUrls: null
+        }}
+      >
+        <Modal
+          title="分享"
+          closable={{ 'aria-label': 'Custom Close Button' }}
+          open={isModalOpen}
+          onOk={handleOk}
+          onCancel={handleCancel}
+        >
+          <Form.Item
+            name="content"
+            rules={[{ required: true, message: '请输入笔记内容!' }]}
+          >
+            <TextArea
+              showCount
+              maxLength={100}
+              placeholder="disable resize"
+              style={{ height: 120, resize: 'none' }}
+            />
+          </Form.Item>
+          <Form.Item name="jumpUrls">
+            <Input placeholder="请输入点击图片跳转的链接没有则跳过" />
+          </Form.Item>
+          <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
+            <Form.Item name="files" noStyle>
+              <Upload
+                listType="picture-card"
+                fileList={fileList}
+                onChange={handleChange}
+                customRequest={customRequest}
+              >
+                {fileList.length >= 8 ? null : uploadButton}
+              </Upload>
+            </Form.Item>
+
+            <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '8px' }}>
+              {fileList.map(file => (
+                <div
+                  key={file.uid}
+                  style={{
+                    display: 'flex',
+                    alignItems: 'center',
+                    gap: '8px',
+                    padding: '4px 0'
+                  }}
+                >
+                  <Image width={24} src={file.url} preview={false} />
+                  <span style={{ fontSize: '12px', color: '#666' }}>{file.name}</span>
+                </div>
+              ))}
+            </div>
+          </div>
+        </Modal>
+      </Form>
     </div>
   )
 }
+
 export default Mine_Friend

+ 35 - 8
src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx

@@ -1,10 +1,11 @@
-// src/pages/layout/pages/mine/components/PlaylistDetail/PlaylistHeader/index.tsx
 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 字段
+import { updatePlayCount } from '@/apis/playRecord';
+import { updateRecentPlay } from '@/apis/recentPlayStats';
+
 interface Album {
   id: number;
   albumName: string;
@@ -25,10 +26,9 @@ interface Album {
   offShelfTime: string | null;
   deleteFlag: number;
   deleteTime: string | null;
-  playCount?: number; // 添加播放次数字段,用于显示播放次数
+  playCount?: number;
 }
 
-// 歌单类型定义
 interface Playlist {
   id: number;
   userId: number;
@@ -95,11 +95,8 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
   const coverUrl = playlist.coverUrl;
   const createTime = playlist.createTime;
 
-  // 修复:专辑没有 playCount 字段,使用 songsCount 或其他相关字段,或者默认为 0
-  // 如果专辑需要显示播放次数,可能需要后端API返回这个字段
   let playCount = 0;
   if (isAlbum) {
-    // 如果专辑有 playCount 字段(即使为 undefined),使用它,否则默认为 0
     playCount = playlist.playCount !== undefined ? playlist.playCount : 0;
   } else if (isPlaylist) {
     playCount = playlist.playCount;
@@ -136,7 +133,37 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
       setLoading(false);
     }
   }
+  // 在 PlaylistHeader 组件内部添加播放类型判断
+  const handlePlayPlaylist = async () => {
+    // 判断资源类型:1-歌曲,2-专辑,3-歌单等
+    let resourceType = 3; // 默认为歌单
+    if (isAlbum) {
+      resourceType = 2; // 专辑
+    } else if (isPlaylist) {
+      resourceType = 3; // 歌单
+    }
 
+    const resourceId = playlist.id;
+
+    try {
+      // 同时更新播放记录和最近播放统计
+      await Promise.all([
+        updatePlayCount({
+          resourceId: resourceId,
+          resourceType: resourceType
+        }),
+        updateRecentPlay({
+          resourceId: resourceId,
+          resourceType: resourceType
+        })
+      ]);
+
+      // 执行原有的播放逻辑
+      onPlayPlaylist();
+    } catch (error) {
+      console.error('更新播放记录失败:', error);
+    }
+  };
   return (
     <div className="playlist-header">
       {contextHolder}
@@ -159,7 +186,7 @@ const PlaylistHeader: React.FC<PlaylistHeaderProps> = ({
         <div className="actions">
           <Button
             className="play-btn"
-            onClick={onPlayPlaylist}
+            onClick={handlePlayPlaylist}
           >
             播放
           </Button>

+ 0 - 0
src/pages/layout/pages/payError/index.css


+ 0 - 0
src/pages/layout/pages/payError/index.less


+ 41 - 0
src/pages/layout/pages/payError/index.tsx

@@ -0,0 +1,41 @@
+import { Button, Result, Typography } from 'antd';
+const { Paragraph, Text } = Typography;
+import './index.css'
+import { CloseCircleOutlined } from '@ant-design/icons'
+const payError = () => {
+  return <div>
+    <Result
+      status="error"
+      title="Submission Failed"
+      subTitle="Please check and modify the following information before resubmitting."
+      extra={[
+        <Button type="primary" key="console">
+          Go Console
+        </Button>,
+        <Button key="buy">Buy Again</Button>,
+      ]}
+    >
+      <div className="desc">
+        <Paragraph>
+          <Text
+            strong
+            style={{
+              fontSize: 16,
+            }}
+          >
+            The content you submitted has the following error:
+          </Text>
+        </Paragraph>
+        <Paragraph>
+          <CloseCircleOutlined className="site-result-demo-error-icon" /> Your account has been
+          frozen. <a>Thaw immediately &gt;</a>
+        </Paragraph>
+        <Paragraph>
+          <CloseCircleOutlined className="site-result-demo-error-icon" /> Your account is not yet
+          eligible to apply. <a>Apply Unlock &gt;</a>
+        </Paragraph>
+      </div>
+    </Result>
+  </div>
+}
+export default payError

+ 0 - 0
src/pages/layout/pages/paySuccess/index.css


+ 0 - 0
src/pages/layout/pages/paySuccess/index.less


+ 18 - 0
src/pages/layout/pages/paySuccess/index.tsx

@@ -0,0 +1,18 @@
+import { Button, Result } from 'antd'
+import './index.css'
+const paySuccess = () => {
+  return <div>
+    <Result
+      status="success"
+      title="Successfully Purchased Cloud Server ECS!"
+      subTitle="Order number: 2017182818828182881 Cloud server configuration takes 1-5 minutes, please wait."
+      extra={[
+        <Button type="primary" key="console">
+          Go Console
+        </Button>,
+        <Button key="buy">Buy Again</Button>,
+      ]}
+    />
+  </div>
+}
+export default paySuccess

+ 187 - 0
src/pages/layout/pages/pushSong/index.css

@@ -0,0 +1,187 @@
+/* index.less */
+.push-song-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+  background-color: #f5f5f5;
+}
+.push-song-container .push-song-header {
+  margin-bottom: 30px;
+}
+.push-song-container .push-song-header h2 {
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+}
+.push-song-container .push-song-content {
+  background-color: white;
+  border-radius: 8px;
+  padding: 30px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+.push-song-container .push-song-content .song-detail {
+  margin-bottom: 30px;
+}
+.push-song-container .push-song-content .song-detail .song-info {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+}
+.push-song-container .push-song-content .song-detail .song-info .album-cover {
+  width: 80px;
+  height: 80px;
+  border-radius: 4px;
+  object-fit: cover;
+}
+.push-song-container .push-song-content .song-detail .song-info .song-text h3 {
+  font-size: 20px;
+  margin: 0;
+  color: #333;
+}
+.push-song-container .push-song-content .song-detail .song-info .song-text p {
+  margin: 5px 0;
+  color: #666;
+  font-size: 14px;
+}
+.push-song-container .push-song-content .custom-push-section {
+  margin-bottom: 30px;
+}
+.push-song-container .push-song-content .custom-push-section .section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 20px;
+  font-size: 16px;
+  color: #333;
+}
+.push-song-container .push-song-content .custom-push-section .section-title .info-icon {
+  font-size: 12px;
+  color: #999;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 20px;
+  flex-wrap: wrap;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options .amount-btn {
+  padding: 12px 20px;
+  border: 1px solid #d9d9d9;
+  border-radius: 8px;
+  background-color: white;
+  color: #f5222d;
+  font-size: 16px;
+  font-weight: bold;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options .amount-btn:hover {
+  background-color: #f5f5f5;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options .amount-btn.selected {
+  border-color: #f5222d;
+  background-color: #fff7f7;
+  color: #f5222d;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options .amount-range-btn {
+  padding: 12px 20px;
+  border: 1px solid #d9d9d9;
+  border-radius: 8px;
+  background-color: #f5f5f5;
+  color: #999;
+  font-size: 16px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+.push-song-container .push-song-content .custom-push-section .amount-options .amount-range-btn:hover {
+  background-color: #dcdcdc;
+}
+.push-song-container .push-song-content .custom-push-section .predicted-traffic {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+.push-song-container .push-song-content .custom-push-section .predicted-traffic .label {
+  font-size: 14px;
+  color: #666;
+}
+.push-song-container .push-song-content .custom-push-section .predicted-traffic .value {
+  font-size: 16px;
+  font-weight: bold;
+  color: #f5222d;
+}
+.push-song-container .push-song-content .custom-push-section .start-date {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+.push-song-container .push-song-content .custom-push-section .start-date .label {
+  font-size: 14px;
+  color: #666;
+}
+.push-song-container .push-song-content .custom-push-section .start-date .date-input {
+  padding: 8px 12px;
+  border: 1px solid #d9d9d9;
+  border-radius: 4px;
+  font-size: 14px;
+}
+.push-song-container .push-song-content .footer-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 20px;
+  border-top: 1px solid #eee;
+}
+.push-song-container .push-song-content .footer-section .agreement {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #666;
+}
+.push-song-container .push-song-content .footer-section .agreement .agree-checkbox {
+  width: 14px;
+  height: 14px;
+}
+.push-song-container .push-song-content .footer-section .agreement .agree-text {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.push-song-container .push-song-content .footer-section .payment-info {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+.push-song-container .push-song-content .footer-section .payment-info .actual-amount {
+  font-size: 14px;
+  color: #666;
+}
+.push-song-container .push-song-content .footer-section .payment-info .amount-value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #f5222d;
+}
+.push-song-container .push-song-content .footer-section .payment-info .pay-btn {
+  padding: 10px 24px;
+  background-color: #f5222d;
+  color: white;
+  border: none;
+  border-radius: 20px;
+  font-size: 14px;
+  font-weight: bold;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+.push-song-container .push-song-content .footer-section .payment-info .pay-btn:hover {
+  background-color: #da0a15;
+}
+.push-song-container .link {
+  color: #1890ff;
+  text-decoration: none;
+}
+.push-song-container .link:hover {
+  text-decoration: underline;
+}

+ 234 - 0
src/pages/layout/pages/pushSong/index.less

@@ -0,0 +1,234 @@
+/* index.less */
+@primary-color: #f5222d;
+@secondary-color: #1890ff;
+@border-color: #d9d9d9;
+@text-color: #333;
+@text-color-secondary: #666;
+@text-color-tertiary: #999;
+@bg-color: #f5f5f5;
+@bg-light: #fff7f7;
+@shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+@border-radius: 8px;
+
+.push-song-container {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 20px;
+  background-color: @bg-color;
+
+  .push-song-header {
+    margin-bottom: 30px;
+
+    h2 {
+      font-size: 24px;
+      font-weight: bold;
+      color: @text-color;
+    }
+  }
+
+  .push-song-content {
+    background-color: white;
+    border-radius: @border-radius;
+    padding: 30px;
+    box-shadow: @shadow;
+
+    .song-detail {
+      margin-bottom: 30px;
+
+      .song-info {
+        display: flex;
+        align-items: center;
+        gap: 20px;
+
+        .album-cover {
+          width: 80px;
+          height: 80px;
+          border-radius: 4px;
+          object-fit: cover;
+        }
+
+        .song-text {
+          h3 {
+            font-size: 20px;
+            margin: 0;
+            color: @text-color;
+          }
+
+          p {
+            margin: 5px 0;
+            color: @text-color-secondary;
+            font-size: 14px;
+          }
+        }
+      }
+    }
+
+    .custom-push-section {
+      margin-bottom: 30px;
+
+      .section-title {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        margin-bottom: 20px;
+        font-size: 16px;
+        color: @text-color;
+
+        .info-icon {
+          font-size: 12px;
+          color: @text-color-tertiary;
+        }
+      }
+
+      .amount-options {
+        display: flex;
+        gap: 10px;
+        margin-bottom: 20px;
+        flex-wrap: wrap;
+
+        .amount-btn {
+          padding: 12px 20px;
+          border: 1px solid @border-color;
+          border-radius: @border-radius;
+          background-color: white;
+          color: @primary-color;
+          font-size: 16px;
+          font-weight: bold;
+          cursor: pointer;
+          transition: all 0.2s ease;
+
+          &:hover {
+            background-color: @bg-color;
+          }
+
+          &.selected {
+            border-color: @primary-color;
+            background-color: @bg-light;
+            color: @primary-color;
+          }
+        }
+
+        .amount-range-btn {
+          padding: 12px 20px;
+          border: 1px solid @border-color;
+          border-radius: @border-radius;
+          background-color: @bg-color;
+          color: @text-color-tertiary;
+          font-size: 16px;
+          cursor: pointer;
+          transition: all 0.2s ease;
+
+          &:hover {
+            background-color: darken(@bg-color, 10%);
+          }
+        }
+      }
+
+      .predicted-traffic {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        margin-bottom: 20px;
+
+        .label {
+          font-size: 14px;
+          color: @text-color-secondary;
+        }
+
+        .value {
+          font-size: 16px;
+          font-weight: bold;
+          color: @primary-color;
+        }
+      }
+
+      .start-date {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        margin-bottom: 20px;
+
+        .label {
+          font-size: 14px;
+          color: @text-color-secondary;
+        }
+
+        .date-input {
+          padding: 8px 12px;
+          border: 1px solid @border-color;
+          border-radius: 4px;
+          font-size: 14px;
+        }
+      }
+    }
+
+    .footer-section {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding-top: 20px;
+      border-top: 1px solid #eee;
+
+      .agreement {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        font-size: 12px;
+        color: @text-color-secondary;
+
+        .agree-checkbox {
+          width: 14px;
+          height: 14px;
+        }
+
+        .agree-text {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+        }
+      }
+
+      .payment-info {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+
+        .actual-amount {
+          font-size: 14px;
+          color: @text-color-secondary;
+        }
+
+        .amount-value {
+          font-size: 20px;
+          font-weight: bold;
+          color: @primary-color;
+        }
+
+        .pay-btn {
+          padding: 10px 24px;
+          background-color: @primary-color;
+          color: white;
+          border: none;
+          border-radius: 20px;
+          font-size: 14px;
+          font-weight: bold;
+          cursor: pointer;
+          transition: all 0.2s ease;
+
+          &:hover {
+            background-color: darken(@primary-color, 10%);
+          }
+        }
+      }
+    }
+  }
+
+  .link {
+    color: @secondary-color;
+    text-decoration: none;
+
+    &:hover {
+      text-decoration: underline;
+    }
+  }
+}

+ 190 - 0
src/pages/layout/pages/pushSong/index.tsx

@@ -0,0 +1,190 @@
+import { useParams } from 'react-router-dom';
+import './index.css';
+import { useEffect, useState } from 'react';
+import { Button, Input, Space, DatePicker, Checkbox, message, Image, Tooltip } from 'antd';
+import { InfoCircleOutlined } from '@ant-design/icons';
+import type { DatePickerProps } from 'antd';
+import { geSongById } from '@/apis/song';
+import { addOrder, type AddOrderDto } from '@/apis/order';
+
+const PushSong = () => {
+  const { id } = useParams();
+  const [selectedAmount, setSelectedAmount] = useState(30);
+  const [customAmount, setCustomAmount] = useState<number | null>(null);
+  const [startDate, setStartDate] = useState<string>('');
+  const [agreed, setAgreed] = useState(false);
+  const [songInfo, setSongInfo] = useState<any>(null);
+  const handleAmountSelect = (amount: number) => {
+    setSelectedAmount(amount);
+    setCustomAmount(null); // 清空自定义金额
+  };
+
+  const handleCustomAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    const value = e.target.value;
+    const numValue = value ? parseFloat(value) : null;
+    setCustomAmount(numValue);
+    if (numValue !== null && !isNaN(numValue) && numValue > 0) {
+      setSelectedAmount(numValue);
+    }
+  };
+
+
+
+  const handleAgreeChange = (e: any) => {
+    setAgreed(e.target.checked);
+  };
+
+  const handleDateChange: DatePickerProps['onChange'] = (date, dateString) => {
+    setStartDate(dateString);
+  };
+
+  const handleSubmit = async () => {
+    if (!agreed) {
+      message.warning('请先阅读并同意服务协议');
+      return;
+    }
+
+    // 获取最终金额
+    const finalAmount = customAmount !== null ? customAmount : selectedAmount;
+
+    // 提交数据
+    const submitData: AddOrderDto = {
+      productId: parseInt(id),
+      totalAmount: finalAmount,
+      payAmount: finalAmount,
+      discountAmount: 0,
+      orderType: 3,
+      // date: startDate,
+      receiveName: null,
+      receivePhone: null,
+      receiveAddress: null,
+      note: null,
+      shippingFee: 0
+    };
+
+    const res = await addOrder(submitData)
+    console.log(res);
+  };
+
+  useEffect(() => {
+    if (id) {
+      fetchSongInfo(id);
+    }
+  }, [id]);
+
+  const fetchSongInfo = async (songId: string) => {
+    const res = await geSongById(parseInt(songId));
+    setSongInfo(res.data);
+  };
+
+  // 计算预计播放量
+  const predictedPlays = (customAmount !== null ? customAmount : selectedAmount) * 20;
+
+  return (
+    <div className="push-song-container">
+      <div className="push-song-header">
+        <h2>智能推荐</h2>
+      </div>
+
+      <div className="push-song-content">
+        <div className="song-detail">
+          <div className="song-info">
+            <Image
+              src={songInfo?.coverUrl}
+              alt="专辑封面"
+              className="album-cover"
+              preview={false}
+            />
+            <div className="song-text">
+              <h3>{songInfo?.songName}</h3>
+              <p>{songInfo?.singerName}</p>
+            </div>
+          </div>
+        </div>
+
+        <div className="custom-push-section">
+          <div className="section-title">
+            <span>自定义推歌</span>
+            <Tooltip title="投放金额为此次投放的最大消耗值,系统会尽可能100%完成投放。但投放过程中若遇到歌曲删除/人工举报/投放效率不佳,系统会自动结束投放。剩余未消耗的金额将在2个工作日内退回到云音乐账户">
+              <InfoCircleOutlined style={{ marginLeft: 8, color: '#1890ff', cursor: 'pointer' }} />
+            </Tooltip>
+          </div>
+
+          <div className="amount-options">
+            <Space wrap>
+              {[30, 100, 300, 500, 1000].map((amount) => (
+                <Button
+                  key={amount}
+                  type={selectedAmount === amount ? 'primary' : 'default'}
+                  onClick={() => handleAmountSelect(amount)}
+                  style={{ marginBottom: 8 }}
+                >
+                  ¥{amount}
+                </Button>
+              ))}
+              <Input
+                placeholder="自定义金额"
+                value={customAmount !== null ? customAmount : ''}
+                onChange={handleCustomAmountChange}
+                style={{ width: 180, marginBottom: 8 }}
+                addonBefore="¥"
+                type="number"
+              />
+            </Space>
+          </div>
+
+          <div className="predicted-traffic" style={{ marginBottom: 20 }}>
+            <span className="label">预计提升播放量:</span>
+            <span className="value" style={{ color: '#f5222d', fontWeight: 'bold' }}>
+              {predictedPlays}
+            </span>
+          </div>
+
+          <div className="start-date" style={{ marginBottom: 20 }}>
+            <span className="label" style={{ marginRight: 16 }}>投放开始时间:</span>
+            <DatePicker
+              onChange={handleDateChange}
+              style={{ width: 200 }}
+            />
+          </div>
+        </div>
+
+        <div className="footer-section">
+          <div className="agreement">
+            <Checkbox
+              checked={agreed}
+              onChange={handleAgreeChange}
+              style={{ marginRight: 8 }}
+            >
+              已阅读并同意
+            </Checkbox>
+            <a href="#" className="link" style={{ marginRight: 8 }}>《服务协议》</a>
+            <a href="#" className="link">《个人信息保护指引》</a>
+          </div>
+
+          <div className="payment-info">
+            <span className="actual-amount" style={{ marginRight: 8 }}>实付金额:</span>
+            <span className="amount-value" style={{
+              fontSize: 20,
+              fontWeight: 'bold',
+              color: '#f5222d',
+              marginRight: 16
+            }}>
+              ¥{customAmount !== null ? customAmount : selectedAmount}
+            </span>
+            <Button
+              type="primary"
+              size="large"
+              onClick={handleSubmit}
+              disabled={!agreed}
+            >
+              去支付
+            </Button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default PushSong;

+ 1 - 1
src/pages/layout/pages/song/index.css

@@ -61,7 +61,7 @@
   display: flex;
   flex-wrap: wrap;
   gap: 16px;
-  justify-content: space-between;
+  justify-content: left;
 }
 .Song1 .song_body .PromotionBanner {
   background-color: #000;

+ 1 - 1
src/pages/layout/pages/song/index.less

@@ -62,7 +62,7 @@
         display: flex;
         flex-wrap: wrap;
         gap: 16px;
-        justify-content: space-between;
+        justify-content: left;
       }
     }
 

+ 34 - 22
src/pages/layout/pages/song/index.tsx

@@ -2,18 +2,40 @@
 import { Button, Image, Input, Card } from 'antd';
 import './index.css';
 import { SearchOutlined } from '@ant-design/icons';
+import { useEffect, useState } from 'react';
+import { getrecentPlayStats } from '@/apis/recentPlayStats';
+import { getfavoritePlaylistApi } from '@/apis/userFavoriteSong';
+import { useNavigate } from 'react-router-dom';
 const SongTab = () => {
+  const [recentPlayStatsList, setRecentPlayStatsList] = useState([])
+  const [favoritePlaylist, setFavoritePlaylist] = useState([])
+  const navigator = useNavigate();
+
+  const getRecentPlayStatsFun = async () => {
+    const res = await getrecentPlayStats(1)
+    setRecentPlayStatsList(res.data)
+  }
+  const getfavoritePlaylistFun = async () => {
+    const res = await getfavoritePlaylistApi()
+    setFavoritePlaylist(res.data)
+  }
+  useEffect(() => {
+    getRecentPlayStatsFun()
+    getfavoritePlaylistFun()
+  }, []);
+  //路由跳转
+  const gotoPush = (id) => { 
+    navigator(`/pushSong/${id}`)
+  }
   return (
     <div className='Song1'>
-      {/* 图片容器 - 作为定位参考 */}
       <div className="Song_Image">
         <Image
           width={980}
           src="https://web-asd-asd.oss-cn-beijing.aliyuncs.com/song.webp"
-          preview={false} // 关闭antd图片预览功能,避免干扰
+          preview={false}
         />
       </div>
-      {/* 搜索框 - 绝对定位实现覆盖 */}
       <div className="Song_Search_container">
         <div className="Song_Search_title">
           <p>好歌上热门,就用云推歌</p>
@@ -31,18 +53,13 @@ const SongTab = () => {
         />
       </div>
       <div className="song_body">      {/* 推荐歌单 */}
+        {/* 推荐歌单 */}
         <div className="RecommendSection">
           <h3>我的喜欢</h3>
           <div className="RecommendList">
-            {[
-              { title: '找个 / 有我来治愈你', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '丢星星', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '路边的野花不要采', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: 'Gimme! Gimme!', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '为你我受冷风吹', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-            ].map((item, index) => (
-              <Card key={index} hoverable cover={<Image src={item.cover} alt={item.title} />}>
-                <Card.Meta title={item.title} description="已推 | 24小时" />
+            {favoritePlaylist.map((song, index) => (
+              <Card key={song.id} hoverable cover={<Image preview={false} src={song.coverUrl} alt={song.songName} />} onClick={()=>{gotoPush(song.id)}}>
+                <Card.Meta title={song.songName} description={`歌手: ${song.singerName || '未知歌手'}`} />
                 <div className="ActionButtons">
                   <Button type="primary" size="small">推歌</Button>
                   <Button type="link" size="small">详情</Button>
@@ -56,15 +73,9 @@ const SongTab = () => {
         <div className="RecentSection">
           <h3>最近播放</h3>
           <div className="RecentList">
-            {[
-              { title: '轻轻地说后会无期', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: 'I Will Survive - Si...', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '风雨彩虹 铿锵玫瑰', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '天不下雨天不晴', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-              { title: '滕王阁序', cover: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png' },
-            ].map((item, index) => (
-              <Card key={index} hoverable cover={<Image src={item.cover} alt={item.title} />}>
-                <Card.Meta title={item.title} description="已推 | 24小时" />
+            {recentPlayStatsList.map((song, index) => (
+              <Card key={song.id} hoverable cover={<Image preview={false}  src={song.coverUrl} alt={song.songName} />} onClick={()=>{gotoPush(song.id)}}>
+                <Card.Meta title={song.songName} description={`歌手: ${song.singerName || '未知歌手'}`} />
                 <div className="ActionButtons">
                   <Button type="primary" size="small">推歌</Button>
                   <Button type="link" size="small">详情</Button>
@@ -104,7 +115,8 @@ const SongTab = () => {
               <p>可联系客服或通过平台提交需求,我们将为您匹配最优推广方案。</p>
             </div>
           </div>
-        </div></div>
+        </div>
+      </div>
 
 
       {/* 底部版权信息 */}

+ 11 - 1
src/pages/layout/pages/testPage/index.css

@@ -169,7 +169,6 @@
 .playlist-panel::-webkit-scrollbar-thumb:hover {
   background: #666;
 }
-/* 保留你原有样式,补充以下优化 */
 /* 确保播放器原生按钮样式适配深色主题 */
 .rhap-skip-button,
 .rhap-volume-button,
@@ -207,3 +206,14 @@
 .remove-track-btn {
   margin-left: 20px;
 }
+/* 在 index.css 中添加 */
+.track-play-count {
+  font-size: 12px;
+  color: #666;
+  margin-top: 4px;
+}
+.item-play-count {
+  font-size: 12px;
+  color: #666;
+  margin: 0 8px;
+}

+ 25 - 7
src/pages/layout/pages/testPage/index.less

@@ -61,16 +61,18 @@
 }
 
 .player-ui {
-  color:  #fff;
-  width: 100%; /* 让播放器控件充满容器 */
-  background-color:#282828;
+  color: #fff;
+  width: 100%;
+  /* 让播放器控件充满容器 */
+  background-color: #282828;
   height: 76px;
   display: flex;
   justify-content: center;
 }
 
 /* 修复按钮交互 */
-.rhap-skip-button, .rhap-volume-button {
+.rhap-skip-button,
+.rhap-volume-button {
   cursor: pointer;
 }
 
@@ -195,7 +197,8 @@
 .playlist-panel::-webkit-scrollbar-thumb:hover {
   background: #666;
 }
-/* 保留你原有样式,补充以下优化 */
+
+
 
 /* 确保播放器原生按钮样式适配深色主题 */
 .rhap-skip-button,
@@ -208,7 +211,8 @@
 .rhap-skip-button:hover,
 .rhap-volume-button:hover,
 .rhap-play-pause-button:hover {
-  color: #1db954 !important; /* 网易云绿色高亮 */
+  color: #1db954 !important;
+  /* 网易云绿色高亮 */
 }
 
 /* 进度条样式优化(适配深色主题) */
@@ -237,6 +241,20 @@
 .rhap_volume-indicator {
   background-color: #fff !important;
 }
-.remove-track-btn{
+
+.remove-track-btn {
   margin-left: 20px;
+}
+
+/* 在 index.css 中添加 */
+.track-play-count {
+  font-size: 12px;
+  color: #666;
+  margin-top: 4px;
+}
+
+.item-play-count {
+  font-size: 12px;
+  color: #666;
+  margin: 0 8px;
 }

+ 123 - 85
src/pages/layout/pages/testPage/index.tsx

@@ -1,88 +1,30 @@
 // src/pages/layout/pages/testPage/index.tsx
-import React, { useRef, useEffect } from 'react';
+import React, { useRef, useEffect, useState } from 'react';
 import AudioPlayer from 'react-h5-audio-player';
 import 'react-h5-audio-player/lib/styles.css';
 import './index.css';
 import { UnorderedListOutlined, CloseOutlined } from '@ant-design/icons';
 import { useMusicPlayer } from '@/context/MusicPlayerContext';
-import { useNavigate } from 'react-router-dom'; // 导入 useNavigate
-
-export interface Song {
-  id: number;
-  title: string;
-  artist: string;
-  url: string;
-  cover: string;
-  duration: string;
-  isFavorite?: boolean;
-  album?: string;
-  composer?: string;
-  arranger?: string;
-  description?: string;
-}
+import { useNavigate } from 'react-router-dom';
+import type { Song } from '@/type/Song';
+import { updateRecentPlay } from '@/apis/recentPlayStats';
+import { updatePlayCount } from '@/apis/playRecord';
 
 const defaultPlaylist: Song[] = [
   {
-    id: 1,
-    title: '奇妙能力歌',
+    id: 131,
+    title: 'G.E.M.邓紫棋 - 唯一',
     artist: '陈粒',
-    url: 'http://117.72.120.45:9000/song-audio/1765437295748_123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '03:18',
-    album: '如也',
-    composer: '陈粒',
-    arranger: '陈粒',
-    description: '独立音乐人陈粒代表作,独特的嗓音和诗意的歌词'
-  },
-  {
-    id: 2,
-    title: '南山南',
-    artist: '马頔',
-    url: 'http://117.72.120.45:9000/song-audio/1765437295748_123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '04:32',
-    album: '孤岛',
-    composer: '马頔',
-    arranger: '马頔',
-    description: '民谣歌手马頔经典作品,深情的旋律打动人心'
-  },
-  {
-    id: 3,
-    title: '董小姐',
-    artist: '宋冬野',
-    url: 'http://117.72.120.45:9000/song-audio/1765437295748_123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '04:15',
-    album: '摩登天空7',
-    composer: '宋冬野',
-    arranger: '宋冬野',
-    description: '民谣音乐人宋冬野代表作,简单却直击心灵'
-  },
-  {
-    id: 4,
-    title: '玫瑰',
-    artist: '贰佰',
-    url: 'http://117.72.120.45:9000/song-audio/1765437295748_123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '04:38',
-    album: '玫瑰',
-    composer: '贰佰',
-    arranger: '贰佰',
-    description: '独立摇滚音乐人贰佰经典作品,充满力量的嗓音'
-  },
-  {
-    id: 5,
-    title: '理想三旬',
-    artist: '陈鸿宇',
-    url: 'http://117.72.120.45:9000/song-audio/1765437295748_123.mp3',
-    cover: 'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
-    duration: '04:26',
-    album: '浓烟下的诗歌电台',
-    composer: '陈鸿宇',
-    arranger: '陈鸿宇',
-    description: '独立音乐人陈鸿宇代表作,低沉磁性的嗓音'
+    url: 'http://117.72.120.45:9000/song-audio/1767769846676_G.E.M.邓紫棋 - 唯一.mp3',
+    cover: 'http://117.72.120.45:9000/music-covers/1767769925575_c47d220f-fceb-4632-8c48-ede3c1b7a1ac.png',
+    duration: '04:13',
+    album: '无',
+    composer: 'xxl122121',
+    arranger: 'xxl122121',
+    description: 'xxl122121',
+    playCount: 0 // 添加初始播放次数
   }
-];
+]
 
 const MusicPlayer: React.FC = () => {
   const {
@@ -98,7 +40,10 @@ const MusicPlayer: React.FC = () => {
   } = useMusicPlayer();
 
   const audioPlayerRef = useRef<AudioPlayer>(null);
-  const navigate = useNavigate(); // 初始化 navigate
+  const navigate = useNavigate();
+  const [playProgress, setPlayProgress] = useState<number>(0);
+  const progressRef = useRef<number>(0);
+  const hasRecordedPlay = useRef<{ [key: number]: boolean }>({}); // 记录每首歌是否已统计
 
   // 初始化播放列表
   useEffect(() => {
@@ -138,40 +83,93 @@ const MusicPlayer: React.FC = () => {
       setCurrentTrackIndex(index);
       setIsPlaying(true);
       setIsPlaylistOpen(false);
+
+      // 重置新选择歌曲的播放记录状态
+      if (playlist[index]) {
+        hasRecordedPlay.current[playlist[index].id] = false;
+      }
     }
   };
 
   // 删除播放列表中的歌曲
   const handleRemoveTrack = (e: React.MouseEvent, trackId: number) => {
-    e.stopPropagation(); // 阻止事件冒泡,避免触发播放列表项点击事件
+    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}`);
     }
   };
 
+  // 播放开始时记录最近播放
+  const handlePlayStart = () => {
+    if (currentTrack) {
+      // 调用API记录最近播放
+      updateRecentPlay({ resourceId: currentTrack.id, resourceType: 1 });
+      setIsPlaying(true);
+    }
+  };
+
+  // 播放进度处理
+  const handleProgress = (e: any) => {
+    const currentTime = e.currentTarget.currentTime || 0;
+    const duration = e.currentTarget.duration || 1;
+    const progress = (currentTime / duration) * 100;
+
+    setPlayProgress(progress);
+    progressRef.current = progress;
+
+    // 检查是否达到80%播放进度且尚未记录
+    if (progress >= 80 && currentTrack && !hasRecordedPlay.current[currentTrack.id]) {
+      hasRecordedPlay.current[currentTrack.id] = true;
+      recordPlayStatistics();
+    }
+  };
+
+  // 记录播放统计的函数
+  const recordPlayStatistics = async () => {
+    if (currentTrack) {
+      try {
+        // 调用后端API记录播放统计
+        await updatePlayCount({
+          resourceId: currentTrack.id,
+          resourceType: 1 // 1表示歌曲
+        });
+
+        console.log('Play record updated successfully');
+      } catch (error) {
+        console.error('Failed to update play record:', error);
+      }
+    }
+  };
+
+  // 当切换歌曲时重置记录状态
+  useEffect(() => {
+    if (currentTrack) {
+      // 如果当前歌曲之前没有播放过,重置记录状态
+      if (!hasRecordedPlay.current[currentTrack.id]) {
+        hasRecordedPlay.current[currentTrack.id] = false;
+      }
+    }
+  }, [validCurrentTrackIndex, currentTrack]);
+
   // 当播放状态改变时,确保音频元素正确响应
   useEffect(() => {
     if (audioPlayerRef.current) {
       const audioElement = audioPlayerRef.current.audio.current;
       if (audioElement) {
         if (isPlaying) {
-          // 确保在播放前音频源已设置
           setTimeout(() => {
             audioElement.play().catch(e => console.log('播放失败:', e));
           }, 0);
@@ -195,6 +193,8 @@ const MusicPlayer: React.FC = () => {
           <div className="track-meta">
             <div className="track-title">{currentTrack?.title || '未知歌曲'}</div>
             <div className="track-artist">{currentTrack?.artist || '未知艺术家'}</div>
+            {/* 显示播放次数 */}
+            <div className="track-play-count">播放次数: {currentTrack?.playCount || 0}</div>
           </div>
         </div>
 
@@ -204,17 +204,51 @@ const MusicPlayer: React.FC = () => {
             ref={audioPlayerRef}
             key={validCurrentTrackIndex}
             src={currentTrack?.url || defaultPlaylist[0]?.url}
-            autoPlay={false} // 关键:不使用autoPlay,而是手动控制
-            onPlay={() => setIsPlaying(true)}
+            autoPlay={false}
+            onPlay={handlePlayStart}
             onPause={() => setIsPlaying(false)}
-            onEnded={handleNext}
+            onEnded={() => {
+              handleNext();
+              // 歌曲结束时也检查是否需要记录统计
+              if (progressRef.current >= 80 && currentTrack && !hasRecordedPlay.current[currentTrack.id]) {
+                hasRecordedPlay.current[currentTrack.id] = true;
+                recordPlayStatistics();
+              }
+            }}
             showSkipControls={playlist.length > 0}
             className="player-ui"
             listenInterval={1000}
             onClickPrevious={handlePrev}
             onClickNext={handleNext}
+            onListen={handleProgress} // 使用onListen监听播放进度
             disabled={playlist.length === 0}
           />
+
+          {/* 进度显示区域 */}
+          <div className="progress-display">
+            <div className="progress-bar-container">
+              <div
+                className="progress-bar"
+                style={{
+                  width: `${playProgress}%`,
+                  backgroundColor: playProgress >= 80 ? '#52c41a' : '#1890ff' // 达到80%时改变颜色
+                }}
+              >
+                {playProgress >= 80 && (
+                  <span className="progress-label">已达到统计阈值</span>
+                )}
+              </div>
+            </div>
+            {/* <div className="progress-text">
+              播放进度: {playProgress.toFixed(2)}% | 阈值: 80%
+              {playProgress >= 80 && currentTrack && !hasRecordedPlay.current[currentTrack.id] && (
+                <span className="threshold-reached"> (即将记录播放统计...)</span>
+              )}
+              {hasRecordedPlay.current[currentTrack?.id || 0] && (
+                <span className="recorded"> (已记录)</span>
+              )}
+            </div> */}
+          </div>
         </div>
 
         {/* 右侧:播放列表按钮 */}
@@ -244,7 +278,9 @@ const MusicPlayer: React.FC = () => {
               <li
                 key={track.id}
                 className={index === validCurrentTrackIndex ? 'track-item active' : 'track-item'}
-                onClick={() => handleTrackSelect(index)}
+                onClick={() => {
+                  handleTrackSelect(index);
+                }}
               >
                 <img src={track.cover} alt={track.title} className="item-cover" />
                 <div className="item-info">
@@ -252,8 +288,10 @@ const MusicPlayer: React.FC = () => {
                   <div className="item-artist">{track.artist}</div>
                 </div>
                 <div className="item-duration">{track.duration}</div>
+                {/* 显示播放次数 */}
+                <div className="item-play-count">播放: {track.playCount || 0}</div>
                 {/* 删除按钮 */}
-                <button 
+                <button
                   className="remove-track-btn"
                   onClick={(e) => handleRemoveTrack(e, track.id)}
                   aria-label={`删除 ${track.title}`}

+ 2 - 1
src/router/index.tsx

@@ -37,6 +37,7 @@ 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 />
@@ -201,7 +202,7 @@ const router = createBrowserRouter([
       {
         path: '/album/:id',
         element: <AlbumDetail />,
-      },
+      }
     ],
   },
 ])