第七章 Electron Vue3实现音乐播放器

一、介绍 🍑 🍑 🍑

一个音乐播放器应该具备播放、暂停、上一首、下一首、播放模式(单曲循环、列表循环、顺序播放……)。除了这些比如还可以扩展进度条的展示、拖拽、音量大小的调节,如果资源允许的话可以进行一些歌词的展示等等。由于博主爬虫的资源没有合适的歌词,所以歌词这块功能暂时没有进行开发,感兴趣的小伙伴可以自行去完善。

本章节的话我们只讲解如何开发一个音乐播放器并且通过nodejs去检索本地下载的歌曲,并展示在列表中,文章中的检索路径是写死的,你可以去更改为自动配置,在页面上弹出文件夹选择框,然后程序根据选择的文件夹去检索,这样更加灵活。本文就不讲解爬虫,先制作好音乐播放器,下一章节我们再详细爬取音乐资源。同时进行下载,收藏等等

二、安装依赖 🍈 🍈 🍈

这里主要是自动按需引入的一些配置和sass支持。感兴趣可以去看Vite4+Pinia2+vue-router4+ElmentPlus搭建Vue3项目(组件、图标等按需引入)[保姆级]_Etc.End的博客-CSDN博客

yarn add @iconify-json/carbon @iconify-json/ep @iconify-json/noto unplugin-auto-import unplugin-vue-components unplugin-icons sass sass-loader @types/node -D

修改vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import * as path from "path";
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

const pathSrc = path.resolve(__dirname, 'src')
export default defineConfig({
  base: './',
  plugins: [
    vue(),
    AutoImport({
      resolvers: [
        ElementPlusResolver(),
        IconsResolver({
          enabledCollections: ['ep', 'carbon', 'noto']
        })
      ],
      dts: path.resolve(pathSrc + '/autoImport', 'auto-imports.d.ts')
    }),
    Components({
      resolvers: [
        ElementPlusResolver(),
        IconsResolver({
          enabledCollections: ['ep', 'carbon', 'noto']
        })
      ],
      dts: path.resolve(pathSrc + '/autoImport', 'components.d.ts')
    }),
    Icons({
      autoInstall: true,
      compiler: 'vue3'
    })
  ],
  resolve: {
    alias: {
      '@': pathSrc, // 文件系统路径别名
    }
  },
  server: {
    port: 5173
  }
})

三、音乐播放状态管理器 🍌 🍌 🍌

安装jsmediatags插件,获取下载的音乐信息。比如歌手名称、歌曲名、专辑名称等等。这里可能会因为资源的差异,导致信息也会一定的差异,如果你复制代码没有获取到相对应的歌曲信息的话可以在控制台打印出来观察一下jsmediatags解析出来的相关信息。

yarn add jsmediatags

创建src/pinia/modules/music.modules.ts文件

import { defineStore } from 'pinia';
import {durationConversionTime} from "@/utils/DateTime";
// @ts-ignore
import {ElMessage} from "element-plus";

/**
 * @Description: 歌曲信息类型
 * @CreationDate 2023-05-08 11:50:36
 */
export interface MusicType {
    name?: string
    album?: string
    url?: string
    author?: string
    pic?: string
    percentage?: number
}

/**
 * @Description: 搜索歌曲结果
 * @CreationDate 2023-05-08 17:02:57
 */
export interface QueryMusicResultType {
    music_page_count: number // 搜索出来的歌曲总页数
    music_current_page: number // 当前搜索匹配的页数
    music_loading: boolean // 搜索歌曲时的状态
    music_noMore: boolean // 没有更多了
    music_name: string // 当前搜索歌曲的名称
    music_current_list: MusicType[] // 搜索出来的歌曲列表,叠加
}

/**
 * @Description: 当前播放音乐信息类型
 * @CreationDate 2023-05-08 11:51:00
 */
export interface CurrentPlayMusicType extends MusicType{
    current_play_audio_ready?: boolean //
}

/**
 * @Description: 歌单类型
 * @CreationDate 2023-05-11 11:00:37
 */
export interface PlaylistsListType {
    id?: number
    name: string // 歌单名称
    describe: string // 描述
}

/**
 * @Description: 我喜欢的音乐
 * @CreationDate 2023-05-13 20:06:33
 */
export interface LikeMusicType extends MusicType {
    id: number // 主键
}

/**
 * @Description: 播放器设置
 * @Author: Etc.End(710962805@qq.com)
 * @Copyright: TigerSong
 * @CreationDate 2023-05-13 00:04:43
 */
interface PlayerSettingType {
    playing: string //
    volume: number // 音量
    total_duration?: number // 当前播放歌曲时长
    total_duration_char?: string // 当前播放歌曲时长字符
    current_duration?: number // 当前播放歌曲时段
    current_duration_char?: string // 当前播放歌曲时段
    current_list_name: 'likeMusicList' | 'queryMusicResult' | 'playlistsList' | 'collectList' | 'downloadMusicList' // 当前播放列表 喜欢、查询、歌单、收藏
    play_mode: 'singleCirculation' | 'tableCirculation' | 'sequentialPlay' | 'randomPlay' // 播放模式 单曲循环 列表循环 顺序播放 随机播放
    previous_is_click: boolean // 可以点击上一首
    next_is_click: boolean // 可以点下一首
    play_is_click: boolean // 可以点播放
    play_index: number // 当前播放歌曲在列表中的索引
    play_name: string // 当前播放歌曲的名称
    play_state: boolean // 当前播放状态
}

/**
 * @Description: musicModule类型
 * @CreationDate 2023-05-08 11:51:15
 */
interface IAppState {
    player?: HTMLAudioElement // 播放器
    queryMusicResult: QueryMusicResultType // 搜索歌曲的结果
    currentPlayMusic: CurrentPlayMusicType // 当前播放音乐的相关信息
    playlistsList: PlaylistsListType[] // 歌单列表
    collectList: MusicType[] // 收藏列表
    likeMusicList: LikeMusicType[] // 喜欢的音乐
    playerSetting: PlayerSettingType // 播放器设置
    downloadMusicList: MusicType[]
}

/**
 * @Description: 音乐播放器状态管理器
 * @Author: Etc.End(710962805@qq.com)
 * @Copyright: TigerSong
 * @CreationDate 2023-05-08 11:51:33
 */
export const musicModule = defineStore({
    id: 'music',
    state(): IAppState {
        return {
            currentPlayMusic: {
                current_play_audio_ready: true,
                name: '',
                url: '',
                author: '',
                pic: '',
            },
            queryMusicResult: {
                music_name: '',
                music_page_count: 0,
                music_current_page: 1,
                music_loading: false,
                music_noMore: false,
                music_current_list: []
            },
            playlistsList: [],
            collectList: [],
            likeMusicList: [],
            playerSetting: {
                playing: '',
                volume: 100,
                current_list_name: 'downloadMusicList',
                play_mode: 'tableCirculation',
                previous_is_click: true,
                next_is_click: true,
                play_is_click: true,
                play_index: -1,
                play_name: '',
                play_state: false
            },
            downloadMusicList: []
        };
    },
    actions: {
        /**
         * @Description: 初始化音乐播放器
         * @CreationDate 2023-05-08 11:50:15
         */
        initMusic(container: HTMLAudioElement) {
            this.player = container
            this.setPreviousAndNextClick()
            this.playerSetting.play_index = -1
        },
        /**
         * @Description: 播放
         * @CreationDate 2023-05-08 11:50:03
         */
        play(index?: number):void {
            if (index || index === 0) {
                this.playerSetting.play_index = index;
                (this.player && this.currentPlayMusic.current_play_audio_ready) && this.player.play();
                this.currentPlayMusic.current_play_audio_ready = false
                this.playerSetting.play_state = true
            } else {
                (this.player) && this.player.play();
                this.playerSetting.play_state = true
            }
            this.setPreviousAndNextClick()
        },
        /**
         * @Description: 更新当前歌曲信息
         * @CreationDate 2023-05-08 11:49:06
         */
        setCurrentMusicInfo(info: MusicType):void {
            this.currentPlayMusic = Object.assign(this.currentPlayMusic, info)
            if (this.player && this.currentPlayMusic.url) {
                this.player.src = this.currentPlayMusic.url
                this.playerSetting.play_name = this.currentPlayMusic.name!
            }
        },
        /**
         * @Description: 获取已经下载的所有文件名
         * @CreationDate 2023-05-16 17:24:08
         */
        getDownloadMusicList() {
            const that = this
            const musicList :MusicType[] = [];
            const fs = require('fs')
            const pathList = fs.readdirSync(`${process.cwd()}/download/music`);

            !(async () => {
                for (let i = 0; i < pathList.length; i++) {
                    const data: any = await that.getDownloadMusicInfo(`${process.cwd()}/download/music/${pathList[i]}`)
                    if (data.tags) {
                        musicList.push({
                            name: data.tags.title || pathList[i],
                            album: data.tags.album,
                            author: data.tags.artist,
                            pic: '',
                            url: `file:${process.cwd()}/download/music/${pathList[i]}`
                        });
                    }
                }
                this.downloadMusicList = musicList
            })();
        },
        /**
         * @Description: 根据jsmediatags插件读取下载的歌曲的信息
         * @CreationDate 2023-05-22 09:33:44
         */
        getDownloadMusicInfo(path: string) {
            return new Promise((resolve, reject) => {
                const jsmediatags = require('jsmediatags')
                new jsmediatags.Reader(path).setTagsToRead(["title", "track", "artist", "album", "year"])
                    .read({
                        onSuccess: (tag: any) => {
                            resolve(tag);
                        },
                        onError: (error: any) => {
                            reject(error);
                        }
                    });
            })
        },
        // -----------------------------------------------------------------播放器相关设置-----------------------------------------------------------------
        ready ():void{
            this.currentPlayMusic.current_play_audio_ready = true
            this.playerSetting.total_duration = ~~this.player!.duration;
            this.playerSetting.total_duration && (this.playerSetting.total_duration_char = durationConversionTime(this.playerSetting.total_duration))
        },
        /**
         * @Description: 报错回调
         * @CreationDate 2023-05-13 17:39:59
         */
        error(code: any): void {
            console.log(code)
        },
        /**
         * @Description: 获取音乐当前播放的时段
         * @CreationDate 2023-05-13 20:46:53
         */
        updateTime(): void {
            if (this.player) {
                this.playerSetting.current_duration = ~~this.player.currentTime
                this.playerSetting.current_duration && (this.playerSetting.current_duration_char = durationConversionTime(this.playerSetting.current_duration))
            }
        },
        /**
         * @Description: 设置音乐当前播放的时段
         * @CreationDate 2023-05-13 20:47:17
         */
        settingDuration(val: number): void {
            if (this.player) {
                this.player.currentTime = val
            }
        },
        /**
         * @Description: 设置音量
         * @CreationDate 2023-05-12 23:44:52
         */
        settingVolume(volume: number):void {
            this.player && (this.player.volume = volume)
        },
        /**
         * @Description: 监听音乐播放结束
         * @CreationDate 2023-05-12 22:24:36
         */
        endPlayback ():void {
            this.playerSetting.current_duration = 0
            // 单曲循环
            if (this.playerSetting.play_mode === 'singleCirculation') {
                this.play(this.playerSetting.play_index)
            } else if (this.playerSetting.play_mode === 'sequentialPlay') { // 顺序播放
                let listLength:number = 0
                if (this.playerSetting.current_list_name === 'queryMusicResult') {
                    listLength = this.queryMusicResult.music_current_list.length
                } else {
                    listLength = this[this.playerSetting.current_list_name].length
                }
                if ((this.playerSetting.play_index + 1) === listLength) {
                    this.playerSetting.current_duration_char = '0:00'
                    this.suspend()
                } else {
                    this.play(this.playerSetting.play_index + 1)
                }
            }
            else { // 播放器默认就是列表循环 列表循环
                this.next()
            }
        },
        /**
         * @Description: 切换播放模式
         * @CreationDate 2023-05-15 09:43:34
         */
        changePlayMode (mode: 'singleCirculation' | 'tableCirculation' | 'sequentialPlay' | 'randomPlay'):void {
            this.playerSetting.play_mode = mode
        },
        /**
         * @Description: 上一首
         * @CreationDate 2023-05-08 11:49:31
         */
        previous():void {
            if (this.playerSetting.play_index > 1) {
                this.playerSetting.play_index -= 1
                this.setCurrentMusicInfo(this.queryMusicResult.music_current_list[this.playerSetting.play_index])
                this.play()
            }
        },
        /**
         * @Description: 下一首
         * @CreationDate 2023-05-08 11:49:24
         */
        next():void {
            if (this.playerSetting.current_list_name === 'queryMusicResult') {
                if (this.playerSetting.play_index < this.queryMusicResult.music_current_list.length) {
                    this.playerSetting.play_index += 1
                } else {
                    this.playerSetting.play_index = 0
                }
                this.setCurrentMusicInfo(this.queryMusicResult.music_current_list[this.playerSetting.play_index])
            } else {
                if (this.playerSetting.play_index < this[this.playerSetting.current_list_name].length) {
                    this.playerSetting.play_index += 1
                } else {
                    this.playerSetting.play_index = 0
                }
                this.setCurrentMusicInfo(this[this.playerSetting.current_list_name][this.playerSetting.play_index])
            }
            this.play()
        },
        /**
         * @Description: 单曲循环
         * @CreationDate 2023-05-15 10:47:50
         */
        singleCirculation() :void {
            if (this.player) {
                this.player.currentTime = 0
                this.play()
            }
        },
        /**
         * @Description: 暂停
         * @CreationDate 2023-05-08 11:49:42
         */
        suspend():void {
            this.player && this.player.pause();
            this.playerSetting.play_state = false
        },
        /**
         * @Description: 设置上一首下一首按钮可以点击
         * @CreationDate 2023-05-09 10:15:10
         */
        setPreviousAndNextClick():void {
            this.playerSetting.next_is_click = this.playerSetting.play_index !== -1;
            this.playerSetting.previous_is_click = this.playerSetting.play_index !== -1;
        },
    },
    getters: {
    },
});

修改src/pinia/index.ts

import { userModule } from './modules/user.modules';
import { musicModule } from './modules/music.modules';

export interface IAppStore {
    userModule: ReturnType<typeof userModule>;
    musicModule: ReturnType<typeof musicModule>;
}

const appStore: IAppStore = {} as IAppStore;

export const registerStore = () => {
    appStore.userModule = userModule();
    appStore.musicModule = musicModule();
};

export default appStore;

四、编写播放器 🍍 🍍 🍍

创建src/components/music/player.vue文件

<template>
  <!--    <div class="ts-music&#45;&#45;player" v-if="currentMusicInfo.url">-->
    <div style="padding: 0 20px">
        <div class="ts-music--player">
            <div class="ts-music--player__title">
                <el-image :class="{ 'is-move': playerSetting.play_state}" :src="currentPlayMusic.pic"/>
                <span :title="currentPlayMusic.name">{{ currentPlayMusic.name }}</span>
            </div>
            <div class="ts-music--player__state">
                <div>
                    <el-button size="small" v-if="playerSetting.play_mode === 'tableCirculation'" @click="changePlayMode('sequentialPlay')" circle title="列表循环">
                        <template #icon>
                            <i-noto-counterclockwise-arrows-button />
                        </template>
                    </el-button>
                    <el-button size="small" v-else-if="playerSetting.play_mode === 'sequentialPlay'" @click="changePlayMode('randomPlay')" circle title="顺序播放">
                        <template #icon>
                            <i-noto-clockwise-vertical-arrows />
                        </template>
                    </el-button>
                    <el-button size="small" v-else-if="playerSetting.play_mode === 'singleCirculation'" @click="changePlayMode('tableCirculation')" circle title="单曲循环">
                        <template #icon>
                            <i-noto-repeat-single-button />
                        </template>
                    </el-button>
                    <el-button size="small" v-else circle title="随机播放" @click="changePlayMode('singleCirculation')">
                        <template #icon>
                            <i-noto-shuffle-tracks-button />
                        </template>
                    </el-button>
                    <el-button size="small" circle @click="previous" :disabled="!playerSetting.previous_is_click" title="上一首">
                        <template #icon>
                            <i-noto-fast-reverse-button />
                        </template>
                    </el-button>
                    <el-button size="small" circle v-if="!playerSetting.play_state" :disabled="playerSetting.play_index === -1" @click="play" title="播放">
                        <template #icon>
                            <i-noto-play-button />
                        </template>
                    </el-button>
                    <el-button size="small" circle v-else @click="suspend" title="暂停">
                        <template #icon>
                            <i-noto-pause-button />
                        </template>
                    </el-button>
                    <el-button size="small" circle @click="next" :disabled="!playerSetting.previous_is_click" title="下一首">
                        <template #icon>
                            <i-noto-fast-forward-button />
                        </template>
                    </el-button>
                    <el-button size="small" circle color="#ddd">词</el-button>
                </div>
                <div class="music-monitor">
                    <span>{{ playerSetting.current_duration_char }}</span>
                    <el-slider :show-tooltip="false" @change="changeDuration" style="width: 280px!important;margin: 0 16px;" v-model="playerSetting.current_duration" :min="0" :max="playerSetting.total_duration" size="small" />
                    <span>{{ playerSetting.total_duration_char }}</span>
                </div>
            </div>
            <div class="ts-music--player__use">
                <div style="display: flex;align-items: center;justify-content: center;">
                    <i-noto-speaker-high-volume />
                    <el-slider style="width: 100px!important;margin: 0 0 0 6px;" color="#03a9a9" v-model="playerSetting.volume" @input="changeVolume" size="small" />
                </div>
            </div>
        </div>
    </div>
</template>

<script lang="ts">
import {defineComponent} from "vue";
import appStore from "@/pinia";
import {storeToRefs} from "pinia";
export default defineComponent({
    name: 'Player',
    // setup(props: Readonly<{ info: MusicType }>) {
    setup() {

        const { currentPlayMusic, playerSetting } = storeToRefs(appStore.musicModule)

        const play = () => {
            appStore.musicModule.play()
        }

        const suspend = () => {
            appStore.musicModule.suspend()
        }

        const previous = () => {
            appStore.musicModule.previous()
        }

        const next = () => {
            appStore.musicModule.next()
        }

        const changeVolume = (val: number) => {
            appStore.musicModule.settingVolume(val / 100)
        }

        const changeDuration = (val: number) => {
            appStore.musicModule.settingDuration(val)
        }

        const changePlayMode = (mode: 'singleCirculation' | 'tableCirculation' | 'sequentialPlay' | 'randomPlay') => {
            appStore.musicModule.changePlayMode(mode)
        }

        return {
            currentPlayMusic,
            play,
            suspend,
            previous,
            next,
            playerSetting,
            changeVolume,
            changeDuration,
            changePlayMode,
        }
    }
})
</script>

<style scoped lang="scss">
::v-deep(.el-slider__bar) {
  background: #03a9a9!important;
}

.ts-music--player {
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    padding: 0 20px;
    background: #101010;
    border: 1px solid #4e4e4f;
    font-size: 14px;
    color: white;
    height: 70px;
    border-radius: 8px;

    .ts-music--player__title {
        width: 300px;
        display: flex;
        height: 100%;
        align-items: center;

        .el-image {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            margin-right: 5px;
        }

        span {
            width: 220px;
            overflow: hidden;
            text-overflow:ellipsis;
            white-space: nowrap;
            margin-left: 6px;
        }
        
        .is-move {
            animation: rotation 10s linear infinite;
            -webkit-transform: rotate(360deg);
            -webkit-animation: rotation 10s linear infinite ;
            -o-animation: rotation 10s linear infinite;
        }

        @-webkit-keyframes rotation {
            from {
                -webkit-transform: rotate(0deg);
            }
            to {
                -webkit-transform: rotate(360deg);
            }
        }
    }

    .ts-music--player__state {
        height: 100%;
        width: calc(100% - 600px);
        display: flex;
        flex-direction: column;
        justify-content: space-evenly;
        align-items: center;

        .music-monitor {
            display: flex;
            font-size: 11px;
            align-items: center;
        }
    }

    .ts-music--player__use {
        height: 100%;
        width: 300px;
        display: flex;
        flex-direction: column;
        justify-content: space-evenly;
        align-items: center;
    }
}
</style>

修改src/App.vue

我们都知道,在我们切换路由的时候,上一个页面的内容会被销毁,但是我们音乐播放器,希望做到一个切换路由也不会被影响,只要程序在正常运行。它也可以做到一个持久性播放,背景音乐的效果。所有放在App.vue中的目的就是解决这个问题。

<template>
    <Header />
    <router-view />
    <audio
        v-show="false"
        ref="audioRef"
        preload
        @canplay="ready"
        @ended="endPlayback"
        @error="error"
        @timeupdate="updateTime"
    />
</template>

<script lang="ts">
import {defineComponent, nextTick, onMounted, ref} from "vue";
import Header from '@/components/header/index.vue'
import appStore from "@/pinia";
export default defineComponent({
    components: {
        Header
    },
    setup() {

        const isShow = ref<boolean>(false)
        const audioRef = ref()
        const ready = appStore.musicModule.ready
        const endPlayback = appStore.musicModule.endPlayback
        const error = appStore.musicModule.error
        const updateTime = appStore.musicModule.updateTime

        onMounted(() => {
            nextTick(async () => {
                const isActivate = await appStore.userModule.sendActivate('')
                if (isActivate) {
                    isShow.value = true
                    appStore.musicModule.initMusic(audioRef.value)
                    appStore.musicModule.getDownloadMusicList()
                }
            })
        })

        return {
            isShow,
            audioRef,
            ready,
            endPlayback,
            error,
            updateTime,
        }
    }
})
</script>


<style>
html, body, #app {
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
}
</style>

五、下载列表 🍇 🍇 🍇

修改src/views/home/index.vue并且引用播放器

<template>
    <div class="ts-music" v-if="downloadMusicList.length > 0">
        <div class="ts-music--search">
            <el-input v-model="musicName" style="width: 400px" placeholder="关键词" class="input-with-select">
                <template #append>
                    <el-button @click="searchMusic">
                        <i-carbon-search style="color: #03a9a9;" />
                    </el-button>
                </template>
            </el-input>
        </div>
        <div style="display: flex;align-items: center;padding: 0;font-size: 12px;color: #a55151;justify-content: center;">注: 双击歌曲进行音乐播放</div>
        <div class="ts-music--content">
            <ul
                    class="ts-music--content__ul"
            >
                <li class="ts-music--content__li" style="font-weight: bold;">
                    <div style="width: 40px;display: flex;justify-content: space-around;align-items: center;height: 60px;font-size: 18px;">
                        序号
                    </div>
                    <div style="text-align: center;width: calc(100% - 380px)">标题</div>
                    <div style="width: 160px;text-align: center;">歌手</div>
                    <div style="text-align: center;width: 240px">专辑</div>
                </li>
                <li v-for="(item, index) in list" :key="index" class="ts-music--content__li" @dblclick="dblclick(item, index)">
                    <div style="width: 40px;display: flex;justify-content: space-around;align-items: center;height: 60px;font-size: 18px;">
                        <i-noto-speaker-high-volume v-if="playerSetting.play_state && playerSetting.play_name === item.name" />
                        <i-noto-speaker-low-volume v-else-if="playerSetting.play_name === item.name && !playerSetting.play_state" />
                        <span v-else>{{ `${index > 8 ? '' : 0 }${index + 1}` }}</span>
                    </div>
                    <div style="text-align: center;width: calc(100% - 380px)" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.name}}</div>
                    <div style="width: 160px;text-align: center;" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.author}}</div>
                    <div style="text-align: center;width: 240px" :style="{color: playerSetting.play_name === item.name ? '#03a9a9' : ''}">{{item.album ? item.album : '-'}}</div>
                </li>
            </ul>
        </div>
        <Player />
    </div>
    <template v-else>
        <div style="width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;">
            <el-empty :image-size="200" description="你还没有下载任何音乐,赶紧去搜索下载吧!" />
        </div>
    </template>
</template>

<script lang="ts">
import  {defineComponent, onMounted, reactive, toRefs} from "vue";
import appStore from "@/pinia";
import {storeToRefs} from "pinia";
import {MusicType} from "@/pinia/modules/music.modules";

export default defineComponent({
    setup() {
        const { downloadMusicList, playerSetting } = storeToRefs(appStore.musicModule)

        const state = reactive({
            list: [] as MusicType[],
            musicName: '' ,
        })

        /**
         * @Description: 搜索功能
         * @CreationDate 2023-05-05 10:15:13
         */
        const searchMusic = async () => {
            const list = JSON.parse(JSON.stringify(downloadMusicList.value))
            state.list = list.filter(item => item?.name?.includes(state.musicName))
        }

        /**
         * @Description: 监听回车事件
         * @CreationDate 2023-05-22 10:38:06
         */
        const keydownEvent = () => {
            document.onkeydown = (e: any) => {
                if (e.defaultPrevented) {
                    return;
                }
                if (e.keyCode === 13) {
                    searchMusic()
                }
            }
        }

        /**
         * @Description: 双击播放音乐
         * @CreationDate 2023-05-22 10:38:14
         */
        const dblclick = (item: MusicType, index: number) => {
            appStore.musicModule.setCurrentMusicInfo({
                author: item.author,
                pic: item.pic,
                name: item.name,
                url: item.url
            })
            appStore.musicModule.play(index)
            playerSetting.value.current_list_name = 'downloadMusicList'
        }

        onMounted(() => {
            appStore.musicModule.getDownloadMusicList()
            searchMusic()
            keydownEvent()
        })

        return {
            ...toRefs(state),
            searchMusic,
            dblclick,
            downloadMusicList,
            playerSetting,
        }
    }
})
</script>

<style scoped lang="scss">
::v-deep(.el-table__fixed-body-wrapper) {
  z-index: auto !important;
}

.ts-music {
    height: calc(100% - 60px);
    width: 100%;
    background: #171717;
    font-size: 14px;
    display: flex;
    color: white;
    flex-direction: column;
    overflow: hidden;
    -webkit-touch-callout:none; /*系统默认菜单被禁用*/
    -webkit-user-select:none; /*webkit浏览器*/
    -khtml-user-select:none; /*早期浏览器*/
    -moz-user-select:none;/*火狐*/
    -ms-user-select:none; /*IE10*/
    user-select:none;

    .ts-music--search {
        display:flex;
        justify-content: center;
        padding: 10px 0;
    }

    .ts-music--content {
        height: calc(100% - 130px);
        overflow-y: hidden;
        padding: 0 20px 0 20px;

        .ts-music--content__ul {
            height: 94%;
            list-style: none;
            padding: 0;
            overflow-y: auto;
            border: 1px solid #4e4e4f;
            border-radius: 8px;

            .ts-music--content__li {
                display: flex;
                flex-direction: row;
                align-items: center;
                justify-content: space-between;
                height: 80px;
                padding: 0 20px;
                border-bottom: 1px solid #4e4e4f;

                &:last-child {
                    border-bottom: none;
                }
            }
        }
    }
}
</style>

下载的歌曲存放目录

我是Etc.End。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。

 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/27135.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业工程项目管理系统源码-全面的工程项目管理

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 如今建筑行业竞争激烈&#xff0c;内卷严重&#xff0c…

chatgpt赋能python:Python循环间隔-了解如何在循环中增加延时

Python循环间隔 - 了解如何在循环中增加延时 在Python编程中&#xff0c;循环是非常常见且重要的控制语句。 它使我们可以多次执行代码块。 但是&#xff0c;在有些情况下&#xff0c;您可能需要在循环之间增加一定的延时时间。 这就是Python循环间隔的概念。 在本文中&#x…

Linux系统下SQLite创建数据库, 建表, 插入数据保姆级教程

1,创建数据库: sqlite test.db 我这边是sqlite2版本, 直接使用命令sqlite test.db创建一个名称为test的数据库; test是你自定义是数据库名, 创建好数据库后, 接下来开始创建表格 2.创建表格, 就是常规的sql建表语句 CREATE TABLE ids_logs ( english_details TEXT, chines…

嵌入式软件工程师培训:提升技能、实现卓越

如果您对嵌入式培训感兴趣&#xff0c;以下是一些建议和关键点&#xff0c;可以帮助您进行嵌入式培训&#xff1a; 培训目标&#xff1a;明确确定您的嵌入式培训目标。是为了提升员工的技能水平&#xff0c;使他们能够承担更高级别的嵌入式开发工作&#xff0c;还是为了向非嵌入…

iOS App的打包和上架流程

转载&#xff1a;iOS App的打包和上架流程 - 掘金 1. 创建账号 苹果开发者账号几种开发者账号类型 个人开发者账号 费用&#xff1a;99 美元/年&#xff08;688.00元&#xff09;协作人数&#xff1a;仅限开发者自己不需要填写公司的邓百氏编码&#xff08; D-U-N-S Number…

网络安全:信息收集专总结【社会工程学】

前言 俗话说“渗透的本质也就是信息收集”&#xff0c;信息收集的深度&#xff0c;直接关系到渗透测试的成败&#xff0c;打好信息收集这一基础可以让测试者选择合适和准确的渗透测试攻击方式&#xff0c;缩短渗透测试的时间。 一、思维导图 二、GoogleHacking 1、介绍 利用…

大数据需要学习哪些内容?

大数据技术的体系庞大且复杂&#xff0c;每年都会涌现出大量新的技术&#xff0c;目前大数据行业所涉及到的核心技术主要就是&#xff1a;数据采集、数据存储、数据清洗、数据查询分析和数据可视化。 Python 已成利器 在大数据领域中大放异彩 Python&#xff0c;成为职场人追求…

甘孜州文化旅游产品市场营销策略研究_kaic

甘孜州文化旅游产品市场营销策略研究 摘要&#xff1a; 近年来&#xff0c;随着文化旅游的兴起&#xff0c;越来越多的旅游者渴望精神层面的满足&#xff0c;获得新奇的文化体验&#xff0c;而我国文化旅游仍处于单层次的观赏旅游。本文研究背景包括对旅游行业的背景介绍&#…

【第三章:链路层】

目录 知识框架No.0 引言No.1 功能零、基本功能概念一、封装成帧1、字符计数法2、字符填充法3、零比特填充法4、违规编码法 二、透明传输三、差错控制1、位错1.1、奇偶校验码1.2、循环冗余码CRC2、帧错2.1、海明码 四、流量控制1、停止-等待协议2、滑动窗口协议2.1、后退N帧协议…

【新星计划回顾】第五篇学习计划-数据库开启定时任务知识点

&#x1f3c6;&#x1f3c6;时间过的真快&#xff0c;这是导师回顾新星计划学习的第五篇文章&#xff01;本篇文章主要是承接上一篇学习计划&#xff0c;通过开启定时任务进行模拟生成数据&#xff0c;实际开发项目中&#xff0c;可能会用到其他方式&#xff01; 最近这段时间非…

Dockerfile应用的容器化

文章目录 Dockerfile应用的容器化应用的容器化——简介应用的容器化——详解单体应用容器化获取应用代码分析Dockerfile容器化当前应用/构建具体的镜像推送镜像到仓库运行应用程序测试总结 最佳实践利用构建缓存合并镜像 命令总结 Dockerfile应用的容器化 Docker 的核心思想是…

软件测试之路已不再是坦途

去年下半年才跳了槽&#xff0c;过程非常顺利&#xff0c;没有经历大家所说的工作荒的境地&#xff0c;所以一直没有直观地感受到软件测试就业形势到底有多严峻。 近来看到一些机构频频发出某某测试员在糟糕的就业形势下逆袭拿下XXW的某厂offer&#xff0c;然后推荐测试进阶课…

【PCIE体系结构十三】LTSSM

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a;《PCI.EXPRESS系统体系结构标准教材 Mindshare》 目录 概览…

【数据恢复、安全和备份解决方案】上海道宁与LSoft为企业提供先进的技术来处理现代数据安全和保存问题

需要备份和恢复磁盘、 恢复已删除的文档和照片、 安全擦除磁盘、 监控硬盘健康状况、 甚至在Windows 无法正常启动时修复 PC&#xff1f; Active Data Studio是 一组桌面应用程序 和可引导CD/DVD或USB磁盘 用于将任何系统引导至 Windows恢复环境 开发商介绍 LSoft Te…

Anaconda教程,Python版本控制

Anaconda教程,Python版本控制 文章目录 Anaconda教程,Python版本控制1&#xff1a;Anaconda安装1.1&#xff1a;Windows1.2&#xff1a;Linux1.3&#xff1a;MacOS 2&#xff1a;Anaconda使用2.1&#xff1a;创建一个新的环境2.2&#xff1a;安装 Python 包2.3&#xff1a;激活…

HNU-操作系统OS-作业2(15-22章)

OS_homework_2 这份文件是OS_homework_2 by计科210X wolf 202108010XXX 文档设置了目录,可以通过目录快速跳转至答案部分。 第15章 运行程序OS-homework/vm-mechanism/relocation.py 15.1 用种子 1、2 和 3 运行,并计算进程生成的每个虚拟地址是处于界限内还是界限外? 如…

信创办公–基于WPS的EXCEL最佳实践系列 (创建表格)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;创建表格&#xff09; 目录 应用背景操作步骤1、新建空白工作簿并命名为“奖牌榜”2、使用模板新建工作簿3、新增一张工作表&#xff0c;并将工作簿的标签更改为红色4、复制与隐藏工作表5、添加工作簿属性值6、更改工作簿主题…

有了这套方案,企业降本增效不再是纸上谈兵 (2)

一、生存压力逼近&#xff0c;企业如何应对经济下行残酷挑战&#xff1f; 当前市场经济下滑&#xff0c;客户预算紧缩和投资削减可能导致IT项目推迟或取消&#xff0c;从而直接影响公司收入和盈利能力。各大厂商都在陆续裁员或调整业务&#xff0c;以人力等成本为主的IT公司也必…

QT调用linux外部命令或者shell script

目录 原文链接&#xff1a;https://blog.csdn.net/YMGogre/article/details/128973098 0、本文使用的环境配置&#xff1a; 1、新建一个简单的Shell脚本&#xff1a; 2、在Qt中启动外部Shell脚本&#xff1a; 2.1、使用标准库中提供的方法 —— system() 2.2、使用Qt提供…

占据80%中国企业出海市场,亚马逊云科技如何为出海客户提供更多资源和附加值

亚马逊云科技就可以做到&#xff0c;作为占据80%中国企业出海市场的亚马逊云科技&#xff0c;其覆盖全球的业务体系&#xff0c;从亚马逊海外购、亚马逊全球开店、亚马逊智能硬件与服务&#xff0c;Amazon Alexa&#xff0c;Amazon Music都是属于亚马逊云科技“梦之队”的一员。…