一、介绍 🍑 🍑 🍑
一个音乐播放器应该具备播放、暂停、上一首、下一首、播放模式(单曲循环、列表循环、顺序播放……)。除了这些比如还可以扩展进度条的展示、拖拽、音量大小的调节,如果资源允许的话可以进行一些歌词的展示等等。由于博主爬虫的资源没有合适的歌词,所以歌词这块功能暂时没有进行开发,感兴趣的小伙伴可以自行去完善。
本章节的话我们只讲解如何开发一个音乐播放器并且通过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--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。如果文章对你有所帮助,能否帮我点个免费的赞和收藏😍。
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇