361 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-form inline :model="search">
<el-form-item label="名称">
<el-input v-model="search.name" />
</el-form-item>
<el-form-item label="所属歌单">
<el-select v-model="search.lib_id" multiple collapse-tags>
<el-option v-for="musicLib in musicLibs" :key="musicLib._id" :value="musicLib._id" :label="musicLib.name" />
</el-select>
</el-form-item>
<el-form-item label="文件类型">
<el-select v-model="search.ext" multiple >
<el-option v-for="ext in exts" :key="ext" :value="ext" :label="ext" />
</el-select>
</el-form-item>
<el-form-item label="标题">
<el-input v-model="search.title" />
</el-form-item>
<el-form-item label="唱片集">
<el-input v-model="search.album" />
</el-form-item>
<el-form-item label="艺术家">
<el-input v-model="search.artist" />
</el-form-item>
</el-form>
<div class="btn-container">
<el-button type="success" plain icon="VideoPlay" @click="playMusic">播放</el-button>
<el-button type="primary" icon="Upload" @click="openUploadModal">上传音乐</el-button>
<div class="search-btn">
<el-button type="primary" @click="loadDataBase(true)" icon="Search">搜索</el-button>
<el-button @click="reset" icon="RefreshLeft">重置</el-button>
</div>
</div>
<div class="table-container">
<el-table :data="musicData" v-loading="loading" stripe @selection-change="dataSelect">
<el-table-column type="selection" width="55" />
<el-table-column prop="name" label="名称" show-overflow-tooltip role=""/>
<el-table-column prop="ext" label="类型" width="100" />
<el-table-column prop="size" label="文件大小" width="150">
<template #default="scope">
{{ prettyBytes(scope.row.size) }}
</template>
</el-table-column>
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="album" label="唱片集" />
<el-table-column prop="artist" label="艺术家" />
<el-table-column prop="lib_id" label="所属歌单" >
<template #default="scope">
<template v-if="scope.row.isEditing && currentRow">
<el-select v-model="currentRow.lib_id">
<el-option v-for="musicLib in musicLibs" :key="musicLib._id" :value="musicLib._id" :label="musicLib.name" />
</el-select>
<el-button link icon="Check" type="primary" @click="saveMusicLib(scope.row)"></el-button>
<el-button link icon="Close" type="primary" @click="scope.row.isEditing = false"></el-button>
</template>
<template v-else>
{{ findMusicLib(scope.row.lib_id) }}
<el-button link icon="Edit" type="primary" @click="updateLib(scope.row)"></el-button>
</template>
</template>
</el-table-column>
<el-table-column prop="lyric_id" label="歌词" width="120" >
<template #default="scope">
<div style="width: 18px">
<Check v-if="scope.row.lyric_id" />
<Close v-else/>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="230" >
<template #default="scope">
<el-button link icon="Document" @click="updateLyric(scope.row)" title="歌词"></el-button>
<el-button link type="danger" icon="Delete" @click="remove(scope.row)" title="删除"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:layout="$store.state.pageLayout"
:current-page="search.pageNum"
:total="total"
@size-change="pageSizeChange"
@current-change="pageChange">
</el-pagination>
</div>
<el-dialog v-model="modifyLyricModal" title="编辑歌词" :width="600" >
<el-form ref="lyricForm" :model="lyricFormData" :rules="lyricRuleValidate" :label-width="120">
<el-form-item label="网易云ID" prop="cloud_id">
<el-input v-model="lyricFormData.cloud_id" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="lyricFormData.name" />
</el-form-item>
<el-form-item label="歌词" prop="lyric">
<el-input v-model="lyricFormData.lyric" type="textarea" :rows="4"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="modifyLyricModal = false">取消</el-button>
<el-button type="primary" @click="saveLyric" :loading="modalLoading">确定</el-button>
</span>
</template>
</el-dialog>
<el-dialog v-model="uploadModal" title="上传音乐" :width="550" @closed="uploadModalClosed" >
<el-form :label-width="80">
<el-form-item label="歌单">
<el-select v-model="libIdSelected">
<el-option v-for="musicLib in musicLibs" :key="musicLib._id" :value="musicLib._id" :label="musicLib.name" />
</el-select>
</el-form-item>
<el-upload
ref="musicUpload"
action="/api/v2/music/upload"
name="file"
accept=".mp3,.flac"
:headers="{token: $store.state.loginInfo.token}"
:on-success="uploadSuccess"
:on-error="uploadError"
:auto-upload="false"
multiple
drag
:data="{libId: libIdSelected}">
<div class="el-upload__text">
拖拽到此处或<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
可选择 mp3/flac 文件
</div>
</template>
</el-upload>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="uploadModal = false">取消</el-button>
<el-button type="primary" @click="uploadMusic" >开始上传</el-button>
</span>
</template>
</el-dialog>
<el-drawer v-model="musicPlaying" :close-on-click-modal="false" size="40%" title="播放音乐">
<a-player autoplay showLrc :list="musicList" :music="musicList[0]"/>
</el-drawer>
</div>
</template>
<script lang="ts">
import { Options } from 'vue-class-component'
import BaseList from '../../model/baselist'
import { MsgResult, Page } from '../../model/common.dto'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, ElMessage, ElMessageBox } from 'element-plus'
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '../../model/api/music'
import APlayer from './aplayer/vue-aplayer.vue'
import prettyBytes from 'pretty-bytes'
import { VForm } from '../../types'
let selectedIds: string[] = []
@Options({
name: 'Music',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElDrawer, ElUpload, APlayer }
})
export default class Music extends BaseList<MusicPage> {
search = new MusicPage()
currentRow: MusicModel | null = null
libIdSelected: string | null = null
exts: string[] = []
musicLibs: MusicLibModel[] = []
musicData: MusicModel[] = []
uploadModal: boolean = false
modifyLyricModal: boolean = false
lyricRuleValidate = {
cloud_id: [
{ required: true, message: '请输入网易云ID', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
lyric: [
{ required: true, message: '请输入歌词正文', trigger: 'blur' }
],
}
prettyBytes = prettyBytes
lyricFormData: MusicLyricModel = {}
// 是否正在播放音乐
musicPlaying: boolean = false
musicList: MusicPlayerItem[] = []
created() {
this.$http.get<never, any>('/api/v1/music/listLibs').then(data => {
this.musicLibs = data
this.loadData()
})
this.$http.get<never, any>('/api/v1/music/listExts').then(data => {
this.exts = data
})
}
async loadData() {
this.loading = true
const data = await this.$http.get<MusicPage, any>('/api/v1/music/list', {params: this.search})
selectedIds = []
this.loading = false
this.total = data.total
this.musicData = data.data
}
dataSelect(selection: MusicModel[]) {
selectedIds = selection.map(item => item._id)
}
findMusicLib(value: string): string | null {
const musicLib = this.musicLibs.find(item => item._id === value)
return musicLib ? musicLib.name : null
}
// 根据当前搜索条件播放音乐
async playMusic() {
try {
const data = await this.$http.get<any, any>('/api/v1/music/list/all', {params: selectedIds.length ? {ids: selectedIds} : this.search})
this.musicList = data.map((item: MusicModel) => {
const musicItem: MusicPlayerItem = {
title: item.title || item.name,
artist: item.artist,
src: `/api/v2/common/music/load/${item._id}`,
pic: `/api/v2/common/music/album/${item._id}`,
}
if(item.lyric_id) {
musicItem.lrc = `${location.origin}/api/v1/common/music/lyric/${item.lyric_id}`
}
return musicItem
})
// 删除原本可能存在的播放器配置(包含播放地址 时间进度等信息)
this.musicPlaying = true
} catch (err) {
console.error(err)
ElMessage.error('获取播放列表失败')
}
}
updateLib(row: MusicModel) {
this.currentRow = { ...row }
row.isEditing = true
}
remove(row: MusicModel) {
ElMessageBox.confirm(`是否确认删除 ${row.name} `, '确认删除', {type: 'warning'}).then(async () => {
const data = await this.$http.delete<{params: {id: string}}, any>('/api/v2/music/delete', {params: {id: row._id}})
ElMessage.success(data.message)
this.loadData()
}).catch(() => {})
}
async updateLyric(row: MusicModel) {
this.currentRow = { ...row }
this.modifyLyricModal = true
if (row.lyric_id) {
const data = (await this.$http.get<any, any>('/api/v1/music/lyric/get', {params: {lyricId: row.lyric_id}}))
data.cloud_id = data.cloud_id ? data.cloud_id.toString() : null
this.lyricFormData = data
} else {
this.lyricFormData = {}
}
}
async saveLyric() {
(this.$refs.lyricForm as VForm).validate(async (valid: boolean) => {
if (!valid) return
this.modalLoading = true
const data = await this.$http.post<MusicLyricModel, any>(`/api/v1/music/lyric/save?musicId=${this.currentRow ? this.currentRow._id : ''}`, this.lyricFormData)
this.modalLoading = false
this.modifyLyricModal = false
ElMessage.success(data.message)
this.loadData()
// 清空表单
this.lyricFormData = {}
})
}
async saveMusicLib(row: MusicModel) {
if (!this.currentRow) return
const data = await this.$http.post<{id: string, libId: string}, any>('/api/v2/music/updateLib', {id: this.currentRow._id, libId: this.currentRow.lib_id})
ElMessage.success(data.message)
row.lib_id = this.currentRow.lib_id
row.isEditing = false
}
openUploadModal() {
this.uploadModal = true
this.libIdSelected = null
}
async uploadMusic() {
if (!this.libIdSelected) {
ElMessage.warning('请选择歌单')
return
}
// 执行上传
(this.$refs.musicUpload as typeof ElUpload).submit()
}
uploadSuccess(response: MsgResult) {
if(response.code === 0) {
ElMessage.success(response.message)
this.loadData()
this.uploadModal = false
} else {
ElMessage.warning(response.message)
}
}
uploadError(error: Error) {
ElMessage.error(error.message)
}
uploadModalClosed() {
(this.$refs.musicUpload as typeof ElUpload).clearFiles()
}
/**
* 创建媒体信息
*/
// musicPlay() {
// if(!('mediaSession' in window.navigator)) return;
// const currentMusic = this.player.currentMusic
// const mediaSession: any = navigator.mediaSession
// mediaSession.metadata = new MediaMetadata({
// title: currentMusic.name,
// artist: currentMusic.artist,
// album: currentMusic.album,
// artwork: [{src: currentMusic.cover}]
// });
// mediaSession.setActionHandler('play', () => { // 播放
// this.player.play()
// })
// mediaSession.setActionHandler('pause', () => { // 暂停
// this.player.pause()
// })
// const currentIndex = this.musicList.findIndex(item => item.id === currentMusic.id)
// mediaSession.setActionHandler('previoustrack', () => { // 上一首
// if (currentIndex === 0) { // 已经是第一首
// this.player.switch(this.musicList.length - 1)
// } else {
// this.player.switch(currentIndex - 1)
// }
// })
// mediaSession.setActionHandler('nexttrack', () => { // 下一首
// if (currentIndex === this.musicList.length - 1) { // 已经是最后一首
// this.player.switch(0)
// } else {
// this.player.switch(currentIndex + 1)
// }
// })
// }
}
class MusicPage extends Page {
name?: string
ext: string[] = []
title?: string
album?: string
artist?: string
lib_id?: string[] = []
reset() {
super.reset()
this.name = undefined
this.ext = []
this.title = undefined
this.album = undefined
this.artist = undefined
this.lib_id = []
}
}
</script>