浏览代码

feat: 歌曲加载
1. 下拉加载歌曲功能
2. 歌曲展示页面调整
3. 拷贝曲名功能

kindring 2 月之前
父节点
当前提交
55dcde050f

+ 22 - 1
src/assets/public.css

@@ -66,24 +66,37 @@
     height: calc(100% - 51px);
     overflow-x: hidden;
     overflow-y: auto;
+    padding-bottom: 15px;
 }
 .music-list-item {
     border-bottom: 1px solid #ccc; /* 可选:增加底部边框,分隔各行 */
     position: relative;
     display: grid; /* 使用 Grid 布局 */
-    grid-template-columns: auto 1fr 1fr 1fr 1fr 1fr; /* 设置列的布局 */
+    grid-template-columns: 60px 1fr 60px 80px 80px;
     gap: 10px; /* 列之间的间距 */
     width: 100%;
     height: 60px;
     padding: 5px 10px;
     box-sizing: border-box;
 }
+.music-list-item .item-info{
+    overflow: hidden;
+}
 
 .music-list-item .name{
     font-weight: bold;
     font-size: 1.1rem;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
 }
 
+.music-list-item .name:hover{
+    text-overflow: ellipsis;
+}
+
+
+
 .cover {
     width: 50px; /* 封面图片固定宽度 */
     height: 50px; /* 和高度保持一致 */
@@ -103,6 +116,12 @@
     display: flex;
     align-items: center;
 }
+.music-list-head .music-list-item > * {
+    text-align: left; /* 文本左对齐 */
+    height: 100%; /* 所有列的最小高度一致 */
+    display: flex;
+    align-items: center;
+}
 .more{
     color: var(--color-text-pirmary);
     position: absolute;
@@ -118,3 +137,5 @@
 .music-list-con .music-list-item:hover{
     background-color: var(--color-background-soft);
 }
+
+

+ 1 - 0
src/assets/svg/more.svg

@@ -0,0 +1 @@
+<svg t="1736326749732" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4258" width="200" height="200"><path d="M288 456.864A63.264 63.264 0 0 0 256 448a64 64 0 1 0 0 128c11.712 0 22.56-3.392 32-8.896 19.04-11.072 32-31.488 32-55.104 0-23.648-12.96-44.064-32-55.136M544 456.864A63.264 63.264 0 0 0 512 448c-11.712 0-22.56 3.36-32 8.864-19.04 11.072-32 31.488-32 55.136 0 23.616 12.96 44.032 32 55.104 9.44 5.504 20.288 8.896 32 8.896s22.56-3.392 32-8.896c19.04-11.072 32-31.488 32-55.104 0-23.648-12.96-44.064-32-55.136M768 448c-11.712 0-22.56 3.392-32 8.864-19.04 11.104-32 31.52-32 55.136 0 23.616 12.96 44.032 32 55.136 9.44 5.472 20.288 8.864 32 8.864a64 64 0 1 0 0-128" p-id="4259"></path></svg>

+ 29 - 8
src/components/music/common/playListInfo.vue

@@ -6,6 +6,8 @@ import IconSvg from "@/components/public/icon/iconSvg.vue";
 import message from "@/components/public/kui/message";
 import {api_fetchMusic, api_likeMusic} from "@/apis/musicControl.ts";
 import {ErrorCode} from "@/types/apiTypes.ts";
+import {secondToTimeStr} from "../../../util/time.ts";
+import HideText from "@/components/public/hideText.vue";
 
 defineComponent({name: "play-list-info"});
 
@@ -100,9 +102,25 @@ async function likeMusic(item: MusicInfo) {
 function showMore(item: MusicInfo) {
   console.log(item);
   message.info(`show ${item.name}`);
-
 }
 
+let load_more_repeat = 0;
+async function loadMore()
+{
+  if (musicList.value.length >= scanCount.value)
+  {
+    if (load_more_repeat > 3)
+    {
+      message.info("没有更多数据了");
+      load_more_repeat = 0;
+      return;
+    }
+    return;
+  }
+  search_page.value++;
+  message.info("加载剩余数据");
+  await loadPlayListMusic(props.playList, search_page.value, search_key.value);
+}
 
 </script>
 
@@ -124,14 +142,13 @@ function showMore(item: MusicInfo) {
           <div class="cover">
 
           </div>
-          <div class="name">名称</div>
-          <div class="artists">艺术家</div>
+          <div class="item-info">名称</div>
           <div class="origin">来源</div>
           <div class="duration">时长</div>
           <div class="isLike">喜欢</div>
         </div>
       </div>
-      <div class="music-list-con scroll">
+      <div class="music-list-con scroll" @scrollend="loadMore()">
         <div v-for="item in musicList"
              class="music-list-item"
              @click="playMusic(item)"
@@ -139,14 +156,18 @@ function showMore(item: MusicInfo) {
           <div class="cover">
             <img :src="item.cover" alt=""/>
           </div>
-          <div class="name">{{item.name}}</div>
-          <div class="artists">{{item.artists}}</div>
+          <hide-text class="item-info" :text="item.name"
+                     :enable-copy="true"
+                     :copy-success="()=>{message.success('复制歌曲名称成功')}">
+            <div class="name">{{item.name}}</div>
+            <div class="artists">{{item.artists}}</div>
+          </hide-text>
           <div class="origin">{{item.origin}}</div>
-          <div class="duration">{{item.duration}}</div>
+          <div class="duration">{{ secondToTimeStr(item.duration, "m分s秒" )}}</div>
           <lick-icon class="isLike" :like="item.isLike"
                      @click.stop.capture="likeMusic(item)"/>
           <div class="more">
-            <icon-svg icon-name="add" @click.stop.capture="showMore(item)"></icon-svg>
+            <icon-svg icon-name="more" @click.stop.capture="showMore(item)"></icon-svg>
           </div>
         </div>
       </div>

+ 31 - 9
src/components/music/common/scanListInfo.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import {MusicInfo, MusicScanSetting, param_music_like} from "@/types/musicType.ts";
-import {onBeforeMount, PropType, ref, watch} from "vue";
+import {defineEmits, onBeforeMount, PropType, ref, watch} from "vue";
 import LickIcon from "@/components/music/common/lickIcon.vue";
 import IconSvg from "@/components/public/icon/iconSvg.vue";
 import message from "@/components/public/kui/message";
@@ -8,6 +8,7 @@ import {api_likeMusic, fetchScanMusic} from "@/apis/musicControl.ts";
 import {ErrorCode} from "@/types/apiTypes.ts";
 import {secondToTimeStr} from "@/util/time.ts";
 import {music_action_emits, Music_Action_events} from "@/components/music/music_emits.ts";
+import HideText from "@/components/public/hideText.vue";
 
 const props = defineProps({
   scanSetting: {
@@ -63,15 +64,31 @@ watch(()=>props.scanSetting, ()=>{
   }
 })
 
+let load_more_repeat = 0;
 async function loadMore()
 {
+  if (musicList.value.length >= scanCount.value)
+  {
+    load_more_repeat++;
+    if (load_more_repeat > 3)
+    {
+      message.info("没有更多数据了");
+      load_more_repeat = 0;
+      return;
+    }
+    return;
+  }
   search_page.value++;
+  message.info("加载剩余数据");
   await loadMusic(props.scanSetting, search_page.value, search_key.value);
 }
 
 onBeforeMount(()=>{
   loadMusic(props.scanSetting, search_page.value, search_key.value);
 })
+const music_action_emits = defineEmits<{
+  (e: Music_Action_events.play_music , music: MusicInfo): void,
+}>()
 
 function playMusic(item: MusicInfo) {
   console.log(item);
@@ -101,7 +118,7 @@ function showMore(item: MusicInfo) {
   message.info(`show ${item.name}`);
 }
 
-// todo 下拉继续加载歌单功能开发
+// todo 下拉菜单
 
 </script>
 
@@ -123,15 +140,14 @@ function showMore(item: MusicInfo) {
         <div class="cover">
 
         </div>
-        <div class="name">名称</div>
-        <div class="artists">艺术家</div>
+        <div class="item-info">曲名</div>
         <div class="origin">来源</div>
         <div class="duration">时长</div>
-        <div class="isLike">喜欢</div>
+        <div class="isLike">操作</div>
       </div>
     </div>
 <!--    让下面框在滑动到底部时自动加载下一级数据 -->
-    <div class="music-list-con scroll">
+    <div class="music-list-con scroll" @scrollend="loadMore()">
       <div v-for="item in musicList"
            class="music-list-item"
            @click="playMusic(item)"
@@ -139,14 +155,20 @@ function showMore(item: MusicInfo) {
         <div class="cover">
           <img :src="item.cover" alt=""/>
         </div>
-        <div class="name">{{item.name}}</div>
-        <div class="artists">{{item.artists}}</div>
+        <hide-text class="item-info"
+                   :text="item.name"
+                   :enable-copy="true"
+                   :copy-success="()=>{message.success('复制歌曲名称成功')}"
+        >
+          <div class="name">{{item.name}}</div>
+          <div class="artists">{{item.artists}}</div>
+        </hide-text>
         <div class="origin">{{item.origin}}</div>
         <div class="duration">{{ secondToTimeStr(item.duration, "m分s秒" )}}</div>
         <lick-icon class="isLike" :like="item.isLike"
                    @click.stop.capture="likeMusic(item)"/>
         <div class="more">
-          <icon-svg icon-name="add" @click.stop.capture="showMore(item)"></icon-svg>
+          <icon-svg icon-name="more" @click.stop.capture="showMore(item)"></icon-svg>
         </div>
       </div>
     </div>

+ 2 - 5
src/components/music/music_emits.ts

@@ -1,5 +1,4 @@
-import {defineEmits} from "vue";
-import {MusicInfo} from "@/types/musicType.ts";
+
 
 export enum Music_Action_events {
     like_music = 'like_music',
@@ -9,6 +8,4 @@ export enum Music_Action_events {
     play_pause = 'play_pause',
     play_stop = 'play_stop',
 }
-export const music_action_emits = defineEmits<{
-    (e: Music_Action_events.play_music , music: MusicInfo): void,
-}>()
+

+ 60 - 0
src/components/public/hideText.vue

@@ -0,0 +1,60 @@
+<script setup lang="ts">
+import {copyText} from "@/util/copy.ts";
+
+const props = defineProps(
+    {
+      text: {
+        type: String,
+        default: ""
+      },
+      enableCopy: {
+        type: Boolean,
+        default: false
+      },
+      copySuccess: {
+        type: Function,
+        default: () => {}
+      }
+    }
+);
+
+async function copyClickHandle() {
+  if (props.enableCopy) {
+     let bool = await copyText(props.text);
+     if (bool && props.copySuccess) {
+       props.copySuccess();
+     }
+  }
+}
+</script>
+
+<template>
+<div class="text-view showCenterTip" @click="copyClickHandle">
+  <div class="showTip">{{text}}</div>
+  <div class="text-con">
+    <slot>{{text}}</slot>
+  </div>
+</div>
+</template>
+
+<style scoped>
+.text-view {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.text-con{
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+.showTip{
+  position: absolute;
+  top: -60px;
+  left: 0;
+  right: unset;
+  width: auto;
+  padding: 0 15px;
+}
+
+</style>

+ 33 - 0
src/util/copy.ts

@@ -0,0 +1,33 @@
+export function copyText (content: string): Promise<boolean>{
+    return new Promise((resolve,reject)=>{
+        // 判断是否支持 clipboard api 复制
+        try {
+            if (navigator.clipboard) {
+                navigator.clipboard.writeText(content).then(() => {
+                    resolve(true);
+                })
+            } else {
+                let textarea = document.createElement('textarea');
+                // 隐藏此输入框
+                textarea.style.position = 'fixed';
+                textarea.style.opacity = '0';
+                textarea.style.top = '20px';
+                document.body.appendChild(textarea);
+                // 赋值
+                textarea.value = content;
+                // 选中
+                textarea.select();
+                // 复制
+                document.execCommand('copy', true);
+                // 移除输入框
+                document.body.removeChild(textarea);
+            }
+            resolve(true);
+        } catch (error) {
+            resolve(false);
+            reject(error);
+        }
+    })
+}
+
+