Browse Source

feat: 歌单加载
1. 歌单加载功能完善
2. 取消喜欢功能

kindring 2 months ago
parent
commit
7f3590a6d8

+ 1 - 0
src/apis/ApiAction.ts

@@ -15,4 +15,5 @@ export enum Music_Actions {
   scan_music_delete = 'scan_music_delete',
   scan_music_fetch = 'scan_music_fetch',
   like_music = 'like_music',
+  playlist_music_fetch = 'playlist_music_fetch'
 }

+ 13 - 3
src/apis/musicControl.ts

@@ -1,7 +1,7 @@
 import api from "./baseApi.ts"
 import {Order, Page, ResponseData} from "@/types/apiTypes.ts";
 import {Music_Actions} from "@/apis/ApiAction.ts";
-import {MusicInfo, MusicScanSetting, PlayList} from "@/types/musicType.ts";
+import {MusicInfo, MusicScanSetting, param_music_like, PlayList} from "@/types/musicType.ts";
 import {promises} from "fs-extra";
 
 export async function musicAppStart()
@@ -59,9 +59,19 @@ export async function fetchScanMusic(scanId: number, page: number = 1, size: num
     return await promise as ResponseData<Page<MusicInfo[]>>;
 }
 
-export async function api_likeMusic(musicId: number): Promise<ResponseData<boolean>>
+export async function api_fetchMusic(playlist_id: number, page: number = 1, size: number = 10,
+                                     key: string = '',
+                                     sort: string = 'id',
+                                     order: Order = Order.desc): Promise<ResponseData<Page<MusicInfo[]>>>
+{
+    let [_callId, promise] = api.sendQuery(Music_Actions.playlist_music_fetch,
+        {data: playlist_id, page, size, sort, order, key} as Page<number>);
+    return await promise as ResponseData<Page<MusicInfo[]>>;
+}
+
+export async function api_likeMusic(param: param_music_like): Promise<ResponseData<boolean>>
 {
-    let [_callId, promise] = api.sendQuery(Music_Actions.like_music, musicId);
+    let [_callId, promise] = api.sendQuery(Music_Actions.like_music, param);
     return await promise;
 }
 

+ 117 - 5
src/common/db/db_music.ts

@@ -121,7 +121,7 @@ async function _initSongsTable(db : Knex): PromiseResult<boolean>
             //     // table.string('key')
             //     table.integer('scanId')
             // })
-            await removeMusicByScanId(4);
+            // await removeMusicByScanId(4);
         }
         return [null, true];
     }
@@ -164,6 +164,10 @@ async function _initPlaylistSongs(db : Knex): PromiseResult<boolean>
         logger.error(`[歌单歌曲表初始化] ${err.message}`)
         return [new Error('歌单歌曲表初始化'), false]
     }
+    // 删除表
+    // if (hasTable) {
+    //     await db.schema.dropTable(MusicTableName.music_play_list_songs)
+    // }
     if (hasTable) {
         return [null, true];
     }
@@ -450,7 +454,22 @@ export async function addPlayListMusic(playlist_id: number, music_id: number) :
         logger.error(`${__func__} 数据库初始化失败`)
         return [new Error(`${__key__} 音乐数据库初始化失败`), false]
     }
+    // 如果播放列表中已存在, 则返回 true
     let [err, _res] = await handle(
+        db.select('id')
+            .from(MusicTableName.music_play_list_songs)
+            .where('playListId', playlist_id)
+            .andWhere('musicId', music_id)
+    )
+    if (err) {
+        err = err as Error;
+        logger.error(`${__func__} ${__key__} 获取音频是否存在失败 ${err.message}`)
+        return [err, false];
+    }
+    if (_res && _res.length > 0) {
+        return [null, true];
+    }
+    [err, _res] = await handle(
         db.insert({
             playListId: playlist_id,
             musicId: music_id,
@@ -459,13 +478,36 @@ export async function addPlayListMusic(playlist_id: number, music_id: number) :
     )
     if (err) {
         err = err as Error;
-        logger.error(`${__func__} ${__key__} 失败 ${err.message}`)
+        logger.error(`${__func__} ${__key__} 添加失败 ${err.message}`)
         return [err, false];
     }
     return [null, true];
     
 }
 
+export async function removePlayListMusic(playlist_id: number, music_id: number) : PromiseResult<boolean>
+{
+    const __func__ = 'addPlayListMusic()'
+    const __key__ = '列表移除歌曲'
+    let db = loadDb(AppDbName.music_db)
+    if(!db){
+        logger.error(`${__func__} 数据库初始化失败`)
+        return [new Error(`${__key__} 音乐数据库初始化失败`), false]
+    }
+    let [err, _res] = await handle(
+        db.delete()
+            .from(MusicTableName.music_play_list_songs)
+            .where('playListId', playlist_id)
+            .andWhere('musicId', music_id)
+    )
+    if (err) {
+        err = err as Error;
+        logger.error(`${__func__} ${__key__} 移除失败 ${err.message}`)
+        return [err, false];
+    }
+    return [null, true];
+}
+
 export async function getMusicByKey(musicKey: string) : PromiseResult<MusicInfo>
 {
     let db = loadDb(AppDbName.music_db)
@@ -532,7 +574,7 @@ export async function likeMusic(id: number, isLike: boolean) : PromiseResult<boo
         return [new Error('音乐数据库初始化失败'), false]
     }
     let [err, _res] = await handle(
-        db.update({isLike: isLike}).where('id', id)
+        db.update({isLike: isLike}).from(MusicTableName.music_songs).where('id', id)
     )
     if (err) {
         err = err as Error;
@@ -553,7 +595,7 @@ export async function likeMusic(id: number, isLike: boolean) : PromiseResult<boo
  * @param order
  */
 export async function getMusicsByScanId(scanId: number, key: string = '', page: number = 1, size: number = 10,
-                                        sort: string = 'id', order: 'asc' | 'desc' = 'asc')
+                                        sort: string = 'id', order: Order = Order.asc)
     : PromiseResult<Page<MusicInfo[]>>
 {
     let db = loadDb(AppDbName.music_db)
@@ -567,7 +609,7 @@ export async function getMusicsByScanId(scanId: number, key: string = '', page:
         data: [],
         page: page,
         size: size,
-        order: order as Order,
+        order: order,
         sort: sort,
         key: key,
     }
@@ -630,3 +672,73 @@ export async function removeMusicByScanId(scanId: number) : PromiseResult<boolea
     }
     return [err, true];
 }
+
+
+/**
+ * 根据歌单ID获取歌曲
+ * @param playListId
+ * @param key
+ * @param page
+ * @param size
+ * @param sort
+ * @param order
+ */
+export async function getMusicsByPlayListId(playListId: number, key: string = '', page: number = 1, size: number = 10,
+                                        sort: string = 'id', order: Order = Order.asc): PromiseResult<Page<MusicInfo[]>>
+{
+    const __func__ = `getMusicsByPlayListId`
+    let db = loadDb(AppDbName.music_db)
+    if(!db){
+        logger.error(`${__func__} 数据库初始化失败`)
+        return [new Error('音乐数据库初始化失败'), null]
+    }
+    let countPromise;
+    let resData: Page<MusicInfo[]> = {
+        total: 0,
+        data: [],
+        page: page,
+        size: size,
+        order: order as Order,
+        sort: sort,
+        key: key,
+    }
+    // 第一页尝试获取总数量
+    if (page === 1)
+    {
+        countPromise = db.count(`${MusicTableName.music_play_list_songs}.id as count`)
+            .from(MusicTableName.music_play_list_songs)
+            .join(MusicTableName.music_songs, `${MusicTableName.music_play_list_songs}.musicId`, '=', 'music_songs.id')
+            .where(`${MusicTableName.music_play_list_songs}.playListId`, playListId)
+            if (key) {
+                countPromise.andWhere('key', 'like', `%${key}%`)
+            }
+    } else
+    {
+        countPromise = Promise.resolve([{count: 0}]);
+    }
+    let listPromise = db.select(...Music_field.map(_field=>`${MusicTableName.music_songs}.${_field}`))
+        .from(MusicTableName.music_play_list_songs)
+        .join(MusicTableName.music_songs, `${MusicTableName.music_play_list_songs}.musicId`, '=', 'music_songs.id')
+        .where(`${MusicTableName.music_play_list_songs}.playListId`, playListId)
+        if (key) {
+            listPromise.andWhere(`${MusicTableName.music_songs}.name`, 'like', `%${key}%`)
+        }
+        listPromise.limit(size)
+        .offset((page - 1) * size)
+        .orderBy(`${MusicTableName.music_songs}.${sort}`, order )
+    let [err, res] = await handle<[[{ count: number}], MusicInfo[]]>(
+        Promise.all([countPromise, listPromise]) as Promise<[[{ count: number}], MusicInfo[]]>)
+    if (err) {
+        err = err as Error;
+        logger.error(`${__func__} [获取歌单歌曲失败] ${err.message}`)
+        return [err, resData];
+    }
+    if (!res) {
+        logger.error(`${__func__} [获取歌单歌曲失败] 无法获取指定歌单数据`)
+        return [err, resData];
+    }
+    resData.total = res[0][0].count as number;
+    resData.data = res[1] as MusicInfo[];
+
+    return [err, resData];
+}

+ 76 - 24
src/components/music/common/playListInfo.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts">
-import {defineComponent, PropType, ref} from "vue";
-import {MusicInfo, PlayList} from "@/types/musicType.ts";
+import {defineComponent, onBeforeMount, PropType, ref, watch} from "vue";
+import {MusicInfo, param_music_like, PlayList} from "@/types/musicType.ts";
 import LickIcon from "@/components/music/common/lickIcon.vue";
 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";
 
 defineComponent({name: "play-list-info"});
 
@@ -14,37 +16,87 @@ const props = defineProps({
   }
 })
 
-const musicList = ref<MusicInfo[]>([
+const musicList = ref<MusicInfo[]>([])
+
+let playlist_id = ref(0)
+const lock_loading = ref(false);
+const scanCount = ref(0)
+const search_key = ref("");
+const search_page = ref(1);
+
+const page_limit = 10;
+
+async function loadPlayListMusic(playList: PlayList, page: number, key: string){
+  if (lock_loading.value)
+  {
+    message.info("正在加载中,请稍后");
+    return;
+  }
+  lock_loading.value = true;
+  let res = await api_fetchMusic(playList.id, page, page_limit, key)
+  lock_loading.value = false;
+  if (res.code === ErrorCode.success)
+  {
+    if (page === 1)
+    {
+      scanCount.value = res.data.total?? 0;
+    }
+    let pageData = res.data.data;
+    for ( let i = 0; i < pageData.length; i++)
+    {
+      pageData[i].isLike = !!pageData[i].isLike;
+      pageData[i].isLocal = !!pageData[i].isLocal;
+      musicList.value.push(pageData[i]);
+    }
+  } else {
+    message.error(res.msg);
+  }
+
+}
+
+onBeforeMount(()=>{
+  if (props.playList && props.playList.id)
   {
-    name: "霜雪千年",
-    artists: ["1"],
-    cover: "1",
-    duration: 1,
-    filePath: "1",
-    id: 1,
-    key: "",
-    isLike: true,
-    isLocal: true,
-    lyricPath: "1",
-    origin: "1",
-    playCount: 1,
-    tags: ["1"],
-    type: 1,
-    album: "1",
-    scanId: 1,
-  },
-])
+    loadPlayListMusic(props.playList, search_page.value, search_key.value);
+  }
+})
 
+watch(()=>props.playList, ()=>{
+  if (playlist_id.value !== props.playList.id)
+  {
+    musicList.value = [];
+    loadPlayListMusic(props.playList, 1, search_key.value);
+  }
+})
 
 function playMusic(item: MusicInfo) {
   console.log(item);
   message.info(`play ${item.name}`);
 }
 
-function likeMusic(item: MusicInfo) {
- console.log(item);
- message.info(`like ${item.name}`);
+async function likeMusic(item: MusicInfo) {
+  // console.log(item);
+  // message.info(`like ${item.name}`);
+  let nextLike = !item.isLike;
+  let param: param_music_like = {
+    musicId: item.id,
+    isLike: nextLike
+  }
+  let res = await api_likeMusic(param);
+  if (res.code === ErrorCode.success)
+  {
+    item.isLike = nextLike;
+    if ( props.playList.isLike )
+    {
+      // 移除该项
+      musicList.value = musicList.value.filter(item => item.id !== param.musicId);
+    }
+  }
+  else {
+    message.error(res.msg);
+  }
 }
+
 function showMore(item: MusicInfo) {
   console.log(item);
   message.info(`show ${item.name}`);

+ 20 - 6
src/components/music/common/scanListInfo.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import {MusicInfo, MusicScanSetting} from "@/types/musicType.ts";
+import {MusicInfo, MusicScanSetting, param_music_like} from "@/types/musicType.ts";
 import {onBeforeMount, PropType, ref, watch} from "vue";
 import LickIcon from "@/components/music/common/lickIcon.vue";
 import IconSvg from "@/components/public/icon/iconSvg.vue";
@@ -79,16 +79,30 @@ function playMusic(item: MusicInfo) {
   music_action_emits(Music_Action_events.play_music, item);
 }
 
-function likeMusic(item: MusicInfo) {
-  console.log(item);
-  message.info(`like ${item.name}`);
-  api_likeMusic(item.id);
+async function likeMusic(item: MusicInfo) {
+  // console.log(item);
+  // message.info(`like ${item.name}`);
+  let nextLike = !item.isLike;
+  let param: param_music_like = {
+    musicId: item.id,
+    isLike: nextLike
+  }
+  let res = await api_likeMusic(param);
+  if (res.code === ErrorCode.success)
+  {
+    item.isLike = nextLike;
+  }
+  else {
+    message.error(res.msg);
+  }
 }
 function showMore(item: MusicInfo) {
   console.log(item);
   message.info(`show ${item.name}`);
-
 }
+
+// todo 下拉继续加载歌单功能开发
+
 </script>
 
 <template>

+ 1 - 0
src/components/music/music_emits.ts

@@ -1,3 +1,4 @@
+import {defineEmits} from "vue";
 import {MusicInfo} from "@/types/musicType.ts";
 
 export enum Music_Action_events {

+ 4 - 1
src/main/control/api_router.ts

@@ -2,7 +2,7 @@ import {ApiType, ErrorCode, RequestData, ResponseData} from "@/types/apiTypes.ts
 import {Magnet_Actions, Music_Actions} from "@/apis/ApiAction.ts";
 import {c_fetchMagnetList, c_magnet_batch_update, c_magnet_delete} from "@/main/control/magnet/magnet.ts";
 import {
-    c_fetchPlayList, c_like_music, c_load_scan_music, c_music_appStart,
+    c_fetchPlayList, c_fetchPlayList_music, c_like_music, c_load_scan_music, c_music_appStart,
     c_scanMusicAdd, c_scanMusicDelete,
     c_scanMusicSelect,
     c_scanMusicUpdate,
@@ -50,6 +50,9 @@ export async function apiRouter(requestData: RequestData<any>){
         case Music_Actions.like_music:
             responseData = await c_like_music(requestData);
             break;
+        case Music_Actions.playlist_music_fetch:
+            responseData = await c_fetchPlayList_music(requestData);
+            break;
         default:
             responseData = {
                 type: ApiType.res,

+ 46 - 9
src/main/control/magnet/music.ts

@@ -7,9 +7,9 @@ import {MusicInfo, MusicScanSetting, MusicType, param_music_like, PlayList} from
 import {
     addMusic, addPlayListMusic,
     addScanConfig,
-    deleteScanConfig, get_like_playlist, getMusicByKey, getMusicsByScanId, getPlayList,
+    deleteScanConfig, get_like_playlist, getMusicByKey, getMusicsByPlayListId, getMusicsByScanId, getPlayList,
     getScanConfig,
-    getScanConfigByPath, initDefaultPlayList, likeMusic,
+    getScanConfigByPath, initDefaultPlayList, likeMusic, removePlayListMusic,
     updateScanConfig
 } from "@/common/db/db_music.ts";
 import {handle, PromiseResult, ResType} from "@/util/promiseHandle.ts";
@@ -187,15 +187,18 @@ export async function c_load_scan_music(requestData: RequestData<Page<number>>)
 }
 
 
+
 /**
  * 喜欢音频
  * @param requestData
  */
 export async function c_like_music(requestData: RequestData<param_music_like>) : Promise<ResponseData<boolean>>
 {
-    logger.info(`[喜欢音频] ${requestData.data}`)
+    const __func__ = 'c_like_music'
     let likeData = requestData.data;
     logger.info(`[喜欢音频] ${likeData.musicId} ${likeData.isLike}`)
+
+
     let [err, res] = await get_like_playlist();
     if (err) {
         logger.error(`[获取喜欢列表] ${err.message}`)
@@ -209,20 +212,54 @@ export async function c_like_music(requestData: RequestData<param_music_like>) :
     }
     let bool: ResType<boolean> = false;
     // 歌单中添加歌曲
-    [err, bool] = await addPlayListMusic(playList.id, likeData.musicId);
-    if (err) {
-        logger.error(`[添加喜欢列表] ${err.message}`)
-        return t_gen_res(requestData, ErrorCode.db, '添加喜欢列表失败', false)
+    if (likeData.isLike)
+    {
+        // 判断歌单中是否存在该歌曲
+        [err, bool] = await addPlayListMusic(playList.id, likeData.musicId);
+        if (err) {
+            logger.error(`${__func__} [添加喜欢列表] ${err.message}`)
+            return t_gen_res(requestData, ErrorCode.db, '添加喜欢列表失败', false)
+        }
+    } else
+    {
+        // 取消喜欢
+        [err, bool] = await removePlayListMusic(playList.id, likeData.musicId);
+        if (err) {
+            logger.error(`${__func__} [取消喜欢] ${err.message}`)
+            return t_gen_res(requestData, ErrorCode.db, '取消喜欢失败', false)
+        }
     }
+
     [err, bool] = await likeMusic(likeData.musicId, likeData.isLike);
     if (err) {
-        logger.error(`[喜欢音频失败] ${err.message}`)
+        logger.error(`${__func__} 更改数据失败 ${err.message}`)
         return t_gen_res(requestData, ErrorCode.db, '喜欢音频失败', false)
     }
     bool = bool as boolean;
     return t_res_ok(requestData,  bool)
 }
 
+
+export async function c_fetchPlayList_music(requestData: RequestData<Page<number>>): Promise<ResponseData<Page<MusicInfo>>>
+{
+    const __func__ = 'c_fetchPlayList_music()'
+    let queryParam = requestData.data as Page<number>;
+    logger.info(`[获取歌单音频] ${queryParam}`)
+    logger.info(`[获取歌单音频] ${queryParam.data}`)
+    let [err, sons] = await getMusicsByPlayListId(queryParam.data,
+        queryParam.key,
+        queryParam.page,
+        queryParam.size,
+        queryParam.sort,
+        queryParam.order)
+    if (err) {
+        logger.error(`${__func__} [获取歌单音频] ${err.message}`)
+        return t_gen_res(requestData, ErrorCode.db, '获取歌单音频失败', false)
+    }
+    sons = sons as Page<MusicInfo[]>;
+    return t_res_ok(requestData,  sons)
+}
+
 async function _read_music_info(filePath: string) : PromiseResult<IAudioMetadata>
 {
     let [err, metadata] = await handle(parseFile(filePath));
@@ -288,7 +325,7 @@ async function _scan_dir(scanSetting: MusicScanSetting, basePath: string = "") :
 }
 // https://www.npmjs.com/package/music-metadata
 
-async function _next_id(scanSetting_id: number):Promise<string>
+export async function _next_id(scanSetting_id: number):Promise<string>
 {
     let id = randomAzStr(randomNumber(16));
     id = `${scanSetting_id}_${id}`