|
@@ -1,222 +1,241 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <a-modal
|
|
|
|
|
- class="audio-preview-wrapper"
|
|
|
|
|
- v-model:visible="visible"
|
|
|
|
|
- :footer="null"
|
|
|
|
|
- :closable="false"
|
|
|
|
|
- :maskClosable="false"
|
|
|
|
|
- :width="'100%'"
|
|
|
|
|
- :bodyStyle="{ padding: 0 }"
|
|
|
|
|
- :centered="true"
|
|
|
|
|
- >
|
|
|
|
|
- <img class="audio-background" :src="musicImgUrl" alt="背景图" />
|
|
|
|
|
-
|
|
|
|
|
- <!-- 右上角操作 -->
|
|
|
|
|
- <div class="operate-box">
|
|
|
|
|
- <a-tooltip placement="bottom">
|
|
|
|
|
- <template #title>
|
|
|
|
|
- <div style="line-height: 2">
|
|
|
|
|
- 操作提示: <br />
|
|
|
|
|
- 1. 按 Esc 键可退出查看;<br />
|
|
|
|
|
- 2. 支持键盘控制:<br />
|
|
|
|
|
- 空格 - 暂停/播放<br />
|
|
|
|
|
- 左方向键 - 播放上一个<br />
|
|
|
|
|
- 右方向键 - 播放下一个<br />
|
|
|
|
|
- 上方向键 - 音量调大<br />
|
|
|
|
|
- 下方向键 - 音量减小<br />
|
|
|
|
|
|
|
+ <transition name="zoom-in-top">
|
|
|
|
|
+ <div class="audio-preview-wrapper" v-show="visible">
|
|
|
|
|
+ <img class="audio-background" :src="musicImgUrl" alt="背景图" />
|
|
|
|
|
+ <!-- 右上角操作 -->
|
|
|
|
|
+ <div class="operate-box">
|
|
|
|
|
+ <a-tooltip placement="bottom">
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ <div style="line-height: 2">
|
|
|
|
|
+ 操作提示: <br />
|
|
|
|
|
+ 1. 按 Esc 键可退出查看;<br />
|
|
|
|
|
+ 2. 支持键盘控制:<br />
|
|
|
|
|
+ 空格 - 暂停/播放<br />
|
|
|
|
|
+ 左方向键 - 播放上一个<br />
|
|
|
|
|
+ 右方向键 - 播放下一个<br />
|
|
|
|
|
+ 上方向键 - 音量调大<br />
|
|
|
|
|
+ 下方向键 - 音量减小<br />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <i class="tip-icon anticon anticon-bulb"></i>
|
|
|
|
|
+ </a-tooltip>
|
|
|
|
|
+ <i class="close-icon anticon anticon-close" title="关闭(Escape)" @click="handleClosePreview"></i>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <audio
|
|
|
|
|
+ ref="audioRef"
|
|
|
|
|
+ :src="activeFileObj.fileUrl"
|
|
|
|
|
+ controls
|
|
|
|
|
+ style="position: fixed; top: 0; left: 0; display: none"
|
|
|
|
|
+ @loadedmetadata="handleLoadedmetadata"
|
|
|
|
|
+ @timeupdate="handleTimeUpdate"
|
|
|
|
|
+ @ended="handleChangeAudioIndex('next')"
|
|
|
|
|
+ ></audio>
|
|
|
|
|
+ <div class="audio-list-wrapper">
|
|
|
|
|
+ <!-- 音频列表 -->
|
|
|
|
|
+ <ul class="audio-list">
|
|
|
|
|
+ <li class="audio-list-header">
|
|
|
|
|
+ <span class="name">音频名称</span>
|
|
|
|
|
+ <span class="audio-size">大小</span>
|
|
|
|
|
+ <span class="path">路径</span>
|
|
|
|
|
+ </li>
|
|
|
|
|
+ <div class="audio-list-body">
|
|
|
|
|
+ <li
|
|
|
|
|
+ class="audio-item"
|
|
|
|
|
+ v-for="(item, index) in audioList"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ :class="[activeIndex === index ? 'active' : '']"
|
|
|
|
|
+ :title="isPlay ? '暂停' : '播放'"
|
|
|
|
|
+ @click="handleChangeAudioIndex('manual', index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="name">
|
|
|
|
|
+ <span class="sequence" v-show="activeIndex !== index">
|
|
|
|
|
+ {{ index + 1 }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <img class="wave" :src="activePlayIcon" alt="波浪动图" v-show="activeIndex === index && isPlay" />
|
|
|
|
|
+ <i class="no-wave anticon anticon-bar-chart" v-show="activeIndex === index && !isPlay"></i>
|
|
|
|
|
+ <span class="text">{{ item.fileName }}.{{ item.extendName }}</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <i class="play-icon iconfont icon-icon-7" v-show="activeIndex === index && !isPlay"></i>
|
|
|
|
|
+ <i class="pause-icon iconfont icon-icon-3" v-show="activeIndex === index && isPlay"></i>
|
|
|
|
|
+ <a class="download" :href="$file.getDownloadFilePath(item)" target="_blank" title="下载">
|
|
|
|
|
+ <i class="download-icon anticon anticon-download"></i>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="share-icon anticon anticon-share-alt"
|
|
|
|
|
+ title="分享"
|
|
|
|
|
+ @click.stop="
|
|
|
|
|
+ $openDialog.shareFile({
|
|
|
|
|
+ fileInfo: [
|
|
|
|
|
+ {
|
|
|
|
|
+ userFileId: item.userFileId
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ "
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <span class="audio-size">{{ $file.calculateFileSize(item.fileSize) }}</span>
|
|
|
|
|
+ <span class="path">{{ item.filePath }}</span>
|
|
|
|
|
+ </li>
|
|
|
</div>
|
|
</div>
|
|
|
- </template>
|
|
|
|
|
- <question-circle-outlined class="tip-icon" />
|
|
|
|
|
- </a-tooltip>
|
|
|
|
|
- <close-outlined class="close-icon" title="关闭(Escape)" @click="handleClosePreview" />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <audio
|
|
|
|
|
- ref="audioRef"
|
|
|
|
|
- :src="activeFileObj.fileUrl"
|
|
|
|
|
- controls
|
|
|
|
|
- style="position: fixed; top: 0; left: 0; display: none"
|
|
|
|
|
- @loadedmetadata="handleLoadedmetadata"
|
|
|
|
|
- @timeupdate="handleTimeUpdate"
|
|
|
|
|
- @ended="handleChangeAudioIndex('next')"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <div class="audio-list-wrapper">
|
|
|
|
|
- <!-- 音频列表 -->
|
|
|
|
|
- <ul class="audio-list">
|
|
|
|
|
- <li class="audio-list-header">
|
|
|
|
|
- <span class="name">音频名称</span>
|
|
|
|
|
- <span class="audio-size">大小</span>
|
|
|
|
|
- <span class="path">路径</span>
|
|
|
|
|
- </li>
|
|
|
|
|
- <div class="audio-list-body">
|
|
|
|
|
- <li
|
|
|
|
|
- v-for="(item, index) in audioList"
|
|
|
|
|
- :key="index"
|
|
|
|
|
- class="audio-item"
|
|
|
|
|
- :class="{ active: activeIndex === index }"
|
|
|
|
|
- :title="isPlay ? '暂停' : '播放'"
|
|
|
|
|
- @click="handleChangeAudioIndex('manual', index)"
|
|
|
|
|
|
|
+ </ul>
|
|
|
|
|
+ <!-- 歌曲图片和歌词 -->
|
|
|
|
|
+ <div class="img-and-lyrics">
|
|
|
|
|
+ <img class="audio-img" :src="musicImgUrl" alt="歌曲图片" />
|
|
|
|
|
+ <div class="audio-name">{{ activeFileObj.fileName }}.{{ activeFileObj.extendName }}</div>
|
|
|
|
|
+ <div class="album-artist" v-show="audioInfo.artist">歌手:{{ audioInfo.artist }}</div>
|
|
|
|
|
+ <div class="album-name" v-if="audioInfo.album">专辑:{{ audioInfo.album }}</div>
|
|
|
|
|
+ <ul
|
|
|
|
|
+ class="lyrics-list"
|
|
|
|
|
+ ref="lyricsListRef"
|
|
|
|
|
+ :class="{ one: lyricsList.length === 1 }"
|
|
|
|
|
+ v-if="lyricsList.length"
|
|
|
>
|
|
>
|
|
|
- <span class="name">
|
|
|
|
|
- <span class="sequence" v-show="activeIndex !== index">
|
|
|
|
|
- {{ index + 1 }}
|
|
|
|
|
- </span>
|
|
|
|
|
- <img class="wave" :src="activePlayIcon" alt="波浪动图" v-show="activeIndex === index && isPlay" />
|
|
|
|
|
- <i class="no-wave el-icon-s-data" v-show="activeIndex === index && !isPlay"></i>
|
|
|
|
|
- <span class="text">{{ item.fileName }}.{{ item.extendName }}</span>
|
|
|
|
|
- </span>
|
|
|
|
|
- <i class="play-icon iconfont icon-icon-7" v-show="activeIndex === index && !isPlay"></i>
|
|
|
|
|
- <i class="pause-icon iconfont icon-icon-3" v-show="activeIndex === index && isPlay"></i>
|
|
|
|
|
- <a class="download" :href="$file.getDownloadFilePath(item)" target="_blank" title="下载">
|
|
|
|
|
- <i class="download-icon el-icon-download"></i>
|
|
|
|
|
- </a>
|
|
|
|
|
- <i
|
|
|
|
|
- class="share-icon el-icon-share"
|
|
|
|
|
- title="分享"
|
|
|
|
|
- @click.stop="
|
|
|
|
|
- $openDialog.shareFile({
|
|
|
|
|
- fileInfo: [
|
|
|
|
|
- {
|
|
|
|
|
- userFileId: item.userFileId
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- })
|
|
|
|
|
- "
|
|
|
|
|
- ></i>
|
|
|
|
|
- <span class="audio-size">{{ $file.calculateFileSize(item.fileSize) }}</span>
|
|
|
|
|
- <span class="path">{{ item.filePath }}</span>
|
|
|
|
|
- </li>
|
|
|
|
|
|
|
+ <li
|
|
|
|
|
+ class="lyrics-item"
|
|
|
|
|
+ ref="lyricsLineRef"
|
|
|
|
|
+ v-for="(item, index) in lyricsList"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ active: currentLyricsLineIndex === index
|
|
|
|
|
+ }"
|
|
|
|
|
+ @click="handleChangeProgress(transferTimeToSeconds(item.time))"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ item.text }}
|
|
|
|
|
+ </li>
|
|
|
|
|
+ </ul>
|
|
|
</div>
|
|
</div>
|
|
|
- </ul>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 歌曲图片和歌词 -->
|
|
|
|
|
- <div class="img-and-lyrics">
|
|
|
|
|
- <img class="audio-img" :src="musicImgUrl" alt="歌曲图片" />
|
|
|
|
|
- <div class="audio-name">{{ activeFileObj.fileName }}.{{ activeFileObj.extendName }}</div>
|
|
|
|
|
- <div class="album-artist" v-show="audioInfo.artist">歌手:{{ audioInfo.artist }}</div>
|
|
|
|
|
- <div class="album-name" v-if="audioInfo.album">专辑:{{ audioInfo.album }}</div>
|
|
|
|
|
- <ul class="lyrics-list" ref="lyricsListRef" :class="{ one: lyricsList.length === 1 }" v-if="lyricsList.length">
|
|
|
|
|
- <li
|
|
|
|
|
- class="lyrics-item"
|
|
|
|
|
- ref="lyricsLineRef"
|
|
|
|
|
- v-for="(item, index) in lyricsList"
|
|
|
|
|
- :key="index"
|
|
|
|
|
- :class="{
|
|
|
|
|
- active: currentLyricsLineIndex === index
|
|
|
|
|
- }"
|
|
|
|
|
- @click="handleChangeProgress(transferTimeToSeconds(item.time))"
|
|
|
|
|
- >
|
|
|
|
|
- {{ item.text }}
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 底部音乐控件 -->
|
|
|
|
|
- <div class="control-wrapper">
|
|
|
|
|
- <div class="control-left">
|
|
|
|
|
- <step-backward-outlined
|
|
|
|
|
- class="operate-icon"
|
|
|
|
|
- title="上一个(按左方向键)"
|
|
|
|
|
- @click="handleChangeAudioIndex('pre')"
|
|
|
|
|
- />
|
|
|
|
|
- <play-circle-outlined
|
|
|
|
|
- v-if="!isPlay"
|
|
|
|
|
- class="operate-icon"
|
|
|
|
|
- title="播放(按空格键)"
|
|
|
|
|
- @click="handleClickPlayIcon"
|
|
|
|
|
- />
|
|
|
|
|
- <pause-circle-outlined v-else class="operate-icon" title="暂停(按空格键)" @click="handleClickPauseIcon" />
|
|
|
|
|
- <step-forward-outlined
|
|
|
|
|
- class="operate-icon"
|
|
|
|
|
- title="下一个(按右方向键)"
|
|
|
|
|
- @click="handleChangeAudioIndex('next')"
|
|
|
|
|
- />
|
|
|
|
|
- <a-slider
|
|
|
|
|
- class="progress-bar control-item"
|
|
|
|
|
- v-model:value="currentTime"
|
|
|
|
|
- :step="progressStep"
|
|
|
|
|
- :max="audioInfo.duration"
|
|
|
|
|
- :tooltip-visible="true"
|
|
|
|
|
- :tip-formatter="(val) => transferSecondsToTime(val)"
|
|
|
|
|
- @mousedown="isDrop = true"
|
|
|
|
|
- @mouseup="isDrop = false"
|
|
|
|
|
- @change="handleChangeProgress"
|
|
|
|
|
- />
|
|
|
|
|
- <span class="time control-item">
|
|
|
|
|
- {{ transferSecondsToTime(currentTime) }} / {{ transferSecondsToTime(audioInfo.duration) }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="control-right">
|
|
|
|
|
- <i
|
|
|
|
|
- class="operate-icon cycle-type iconfont"
|
|
|
|
|
- :class="cycleTypeMap[String(cycleType)].icon"
|
|
|
|
|
- :title="cycleTypeMap[String(cycleType)].text"
|
|
|
|
|
- @click="handleChangeCycleType"
|
|
|
|
|
- ></i>
|
|
|
|
|
- <a
|
|
|
|
|
- class="operate-icon download-link"
|
|
|
|
|
- :href="$file.getDownloadFilePath(activeFileObj)"
|
|
|
|
|
- target="_blank"
|
|
|
|
|
- title="下载"
|
|
|
|
|
- >
|
|
|
|
|
- <i class="download-icon el-icon-download"></i>
|
|
|
|
|
- </a>
|
|
|
|
|
- <i
|
|
|
|
|
- class="operate-icon share-icon el-icon-share"
|
|
|
|
|
- title="分享"
|
|
|
|
|
- @click.stop="
|
|
|
|
|
- $openDialog.shareFile({
|
|
|
|
|
- fileInfo: [
|
|
|
|
|
- {
|
|
|
|
|
- userFileId: item.userFileId
|
|
|
|
|
- }
|
|
|
|
|
- ]
|
|
|
|
|
- })
|
|
|
|
|
- "
|
|
|
|
|
- ></i>
|
|
|
|
|
- <i
|
|
|
|
|
- class="operate-icon volume-icon control-item iconfont"
|
|
|
|
|
- :class="volume === 0 ? 'icon-jingyin01' : 'icon-yinliang101'"
|
|
|
|
|
- @click="handleClickVolumeIcon"
|
|
|
|
|
- ></i>
|
|
|
|
|
- <el-slider
|
|
|
|
|
- class="volume-bar control-item"
|
|
|
|
|
- v-model="volume"
|
|
|
|
|
- :step="0.01"
|
|
|
|
|
- :max="1"
|
|
|
|
|
- :format-tooltip="(val) => Math.floor(val * 100)"
|
|
|
|
|
- height="100px"
|
|
|
|
|
- title="可按上下方向键调节音量"
|
|
|
|
|
- @input="handleChangeVolumeBar"
|
|
|
|
|
- ></el-slider>
|
|
|
|
|
|
|
+ <!-- 底部音乐控件 -->
|
|
|
|
|
+ <div class="control-wrapper">
|
|
|
|
|
+ <div class="control-left">
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon iconfont icon-shangyishou"
|
|
|
|
|
+ title="上一个(按左方向键)"
|
|
|
|
|
+ @click="handleChangeAudioIndex('pre')"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon play-icon iconfont icon-icon-7"
|
|
|
|
|
+ v-show="!isPlay"
|
|
|
|
|
+ title="播放(按空格键)"
|
|
|
|
|
+ @click="handleClickPlayIcon"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon pause-icon iconfont icon-icon-3"
|
|
|
|
|
+ v-show="isPlay"
|
|
|
|
|
+ title="暂停(按空格键)"
|
|
|
|
|
+ @click="handleClickPauseIcon"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon iconfont icon-xiayishou"
|
|
|
|
|
+ title="下一个(按右方向键)"
|
|
|
|
|
+ @click="handleChangeAudioIndex('next')"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <a-slider
|
|
|
|
|
+ class="progress-bar control-item"
|
|
|
|
|
+ v-model:value="currentTime"
|
|
|
|
|
+ :step="progressStep"
|
|
|
|
|
+ :max="audioInfo.duration"
|
|
|
|
|
+ :tip-formatter="(val) => transferSecondsToTime(val)"
|
|
|
|
|
+ @mousedown="isDrop = true"
|
|
|
|
|
+ @mouseup="isDrop = false"
|
|
|
|
|
+ @change="handleChangeProgress"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="time control-item"
|
|
|
|
|
+ >{{ transferSecondsToTime(currentTime) }} / {{ transferSecondsToTime(audioInfo.duration) }}</span
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="control-right">
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon cycle-type iconfont"
|
|
|
|
|
+ :class="cycleTypeMap[String(cycleType)].icon"
|
|
|
|
|
+ :title="cycleTypeMap[String(cycleType)].text"
|
|
|
|
|
+ @click="handleChangeCycleType"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <a
|
|
|
|
|
+ class="operate-icon download-link"
|
|
|
|
|
+ :href="$file.getDownloadFilePath(activeFileObj)"
|
|
|
|
|
+ target="_blank"
|
|
|
|
|
+ title="下载"
|
|
|
|
|
+ >
|
|
|
|
|
+ <i class="download-icon anticon anticon-download"></i>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon share-icon anticon anticon-share-alt"
|
|
|
|
|
+ title="分享"
|
|
|
|
|
+ @click.stop="
|
|
|
|
|
+ $openDialog.shareFile({
|
|
|
|
|
+ fileInfo: [
|
|
|
|
|
+ {
|
|
|
|
|
+ userFileId: activeFileObj.userFileId
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ })
|
|
|
|
|
+ "
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <i
|
|
|
|
|
+ class="operate-icon volume-icon control-item iconfont"
|
|
|
|
|
+ :class="volume === 0 ? 'icon-jingyin01' : 'icon-yinliang101'"
|
|
|
|
|
+ @click="handleClickVolumeIcon"
|
|
|
|
|
+ ></i>
|
|
|
|
|
+ <a-slider
|
|
|
|
|
+ class="volume-bar control-item"
|
|
|
|
|
+ v-model:value="volume"
|
|
|
|
|
+ :step="0.01"
|
|
|
|
|
+ :max="1"
|
|
|
|
|
+ :tip-formatter="(val) => Math.floor(val * 100)"
|
|
|
|
|
+ title="可按上下方向键调节音量"
|
|
|
|
|
+ @change="handleChangeVolumeBar"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- </a-modal>
|
|
|
|
|
|
|
+ </transition>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
- import musicPng from '@/assets/images/myResource/file/file_music.png'
|
|
|
|
|
- import { message } from 'ant-design-vue'
|
|
|
|
|
- import {
|
|
|
|
|
- QuestionCircleOutlined,
|
|
|
|
|
- CloseOutlined,
|
|
|
|
|
- StepBackwardOutlined,
|
|
|
|
|
- StepForwardOutlined,
|
|
|
|
|
- PlayCircleOutlined,
|
|
|
|
|
- PauseCircleOutlined
|
|
|
|
|
- } from '@ant-design/icons-vue'
|
|
|
|
|
- import { getFileDetail } from '@/api/myResource/file'
|
|
|
|
|
|
|
+ import { ref, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
|
|
|
|
|
+ import { getFileDetail } from '@/api/myResource/file.js'
|
|
|
import { Base64 } from 'js-base64'
|
|
import { Base64 } from 'js-base64'
|
|
|
-
|
|
|
|
|
- const props = defineProps({
|
|
|
|
|
- visible: {
|
|
|
|
|
- type: Boolean,
|
|
|
|
|
- default: false
|
|
|
|
|
|
|
+ import waveGif from '@/assets/images/myResource/audio/wave.gif'
|
|
|
|
|
+ import musicImg from '@/assets/images/myResource/file/file_music.png'
|
|
|
|
|
+
|
|
|
|
|
+ const { proxy } = getCurrentInstance()
|
|
|
|
|
+
|
|
|
|
|
+ // 定义响应式数据
|
|
|
|
|
+ const visible = ref(false) // 音频预览组件是否可见
|
|
|
|
|
+ const activeIndex = ref(0) // 当前打开的音频索引
|
|
|
|
|
+ const activePlayIcon = ref(waveGif)
|
|
|
|
|
+ const cycleType = ref(1) // 音频播放的循环模式
|
|
|
|
|
+ const isPlay = ref(false) // 是否正在播放
|
|
|
|
|
+ const currentTime = ref(0) // 当前播放的秒
|
|
|
|
|
+ const isDrop = ref(false) // 是否正在拖拽播放进度滑块
|
|
|
|
|
+ const volume = ref(0) // 音量
|
|
|
|
|
+ const audioInfo = ref({}) // 音频信息
|
|
|
|
|
+ const lyricsList = ref([]) // 歌词列表
|
|
|
|
|
+ const currentLyricsLineIndex = ref(0) // 当前高亮的歌词行索引,从 0 开始
|
|
|
|
|
+ const loading = ref(false) // 加载状态
|
|
|
|
|
+ const audioRef = ref(null) // 音频元素引用
|
|
|
|
|
+ const lyricsListRef = ref(null) // 歌词列表引用
|
|
|
|
|
+ const lyricsLineRef = ref([]) // 歌词行引用
|
|
|
|
|
+ const defaultIndex = ref(0) // 默认索引
|
|
|
|
|
+
|
|
|
|
|
+ // 音频循环模式和图标对应的 Map
|
|
|
|
|
+ const cycleTypeMap = {
|
|
|
|
|
+ 1: {
|
|
|
|
|
+ icon: 'icon-xunhuanbofang',
|
|
|
|
|
+ text: '列表循环'
|
|
|
|
|
+ },
|
|
|
|
|
+ 2: {
|
|
|
|
|
+ icon: 'icon-danquxunhuan1',
|
|
|
|
|
+ text: '单曲循环'
|
|
|
},
|
|
},
|
|
|
|
|
+ 3: {
|
|
|
|
|
+ icon: 'icon-suijibofang1',
|
|
|
|
|
+ text: '随机播放'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 定义 props 接收父组件传递的参数
|
|
|
|
|
+ const props = defineProps({
|
|
|
audioList: {
|
|
audioList: {
|
|
|
type: Array,
|
|
type: Array,
|
|
|
default: () => []
|
|
default: () => []
|
|
@@ -231,53 +250,55 @@
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const emit = defineEmits(['update:visible'])
|
|
|
|
|
-
|
|
|
|
|
- const visible = ref(props.visible)
|
|
|
|
|
- const activeIndex = ref(0)
|
|
|
|
|
- const isPlay = ref(false)
|
|
|
|
|
- const currentTime = ref(0)
|
|
|
|
|
- const isDrop = ref(false)
|
|
|
|
|
- const volume = ref(0)
|
|
|
|
|
- const audioInfo = ref({})
|
|
|
|
|
- const lyricsList = ref([])
|
|
|
|
|
- const currentLyricsLineIndex = ref(0)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 计算属性
|
|
|
|
|
+ // 当前显示的文件信息
|
|
|
const activeFileObj = computed(() => {
|
|
const activeFileObj = computed(() => {
|
|
|
const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
|
|
const res = props.audioList.length ? props.audioList[activeIndex.value] : {}
|
|
|
return res
|
|
return res
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // 隐藏的 audio 标签
|
|
|
const audioElement = computed(() => {
|
|
const audioElement = computed(() => {
|
|
|
- return refs.audioRef
|
|
|
|
|
|
|
+ return audioRef.value
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // 歌曲封面
|
|
|
const musicImgUrl = computed(() => {
|
|
const musicImgUrl = computed(() => {
|
|
|
- return audioInfo.value.albumImage ? `data:image/jpeg;base64,${audioInfo.value.albumImage}` : musicPng
|
|
|
|
|
|
|
+ return audioInfo.value.albumImage ? `data:image/jpeg;base64,${audioInfo.value.albumImage}` : musicImg
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // 播放进度条步长
|
|
|
const progressStep = computed(() => {
|
|
const progressStep = computed(() => {
|
|
|
return audioInfo.value.duration / 100
|
|
return audioInfo.value.duration / 100
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- watch(
|
|
|
|
|
- () => props.visible,
|
|
|
|
|
- (newValue) => {
|
|
|
|
|
- visible.value = newValue
|
|
|
|
|
- if (newValue) {
|
|
|
|
|
- activeIndex.value = props.defaultIndex
|
|
|
|
|
- getFileDetailData()
|
|
|
|
|
- document.addEventListener('keyup', handleAddKeyupEvent)
|
|
|
|
|
- } else {
|
|
|
|
|
- document.removeEventListener('keyup', handleAddKeyupEvent)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 监听音频预览组件状态
|
|
|
|
|
+ watch(visible, (newValue) => {
|
|
|
|
|
+ if (newValue) {
|
|
|
|
|
+ activeIndex.value = defaultIndex.value
|
|
|
|
|
+ getFileDetailData()
|
|
|
|
|
+ // 添加键盘相关事件
|
|
|
|
|
+ document.addEventListener('keyup', handleAddKeyupEvent)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 移除键盘相关事件
|
|
|
|
|
+ document.removeEventListener('keyup', handleAddKeyupEvent)
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
|
|
+ // 监听当前索引变化
|
|
|
watch(activeIndex, () => {
|
|
watch(activeIndex, () => {
|
|
|
getFileDetailData()
|
|
getFileDetailData()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // 组件卸载时移除事件监听
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ document.removeEventListener('keyup', handleAddKeyupEvent)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * DOM 绑定 Esc 键、左方向键、右方向键的键盘按下事件
|
|
|
|
|
+ * @param {event} event 事件
|
|
|
|
|
+ */
|
|
|
function handleAddKeyupEvent(event) {
|
|
function handleAddKeyupEvent(event) {
|
|
|
switch (event.code) {
|
|
switch (event.code) {
|
|
|
// 关闭预览
|
|
// 关闭预览
|
|
@@ -317,6 +338,9 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取文件信息
|
|
|
|
|
+ */
|
|
|
function getFileDetailData() {
|
|
function getFileDetailData() {
|
|
|
handleClickPauseIcon()
|
|
handleClickPauseIcon()
|
|
|
loading.value = true
|
|
loading.value = true
|
|
@@ -357,7 +381,7 @@
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
// 当切换完歌曲时,歌词重新滚动到顶部
|
|
// 当切换完歌曲时,歌词重新滚动到顶部
|
|
|
- refs.lyricsListRef.scrollTo({
|
|
|
|
|
|
|
+ lyricsListRef.value.scrollTo({
|
|
|
top: 0,
|
|
top: 0,
|
|
|
behavior: 'smooth'
|
|
behavior: 'smooth'
|
|
|
})
|
|
})
|
|
@@ -369,6 +393,9 @@
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取播放器参数
|
|
|
|
|
+ */
|
|
|
function handleLoadedmetadata(event) {
|
|
function handleLoadedmetadata(event) {
|
|
|
const audioDom = event.target
|
|
const audioDom = event.target
|
|
|
volume.value = audioDom.volume || 0.5
|
|
volume.value = audioDom.volume || 0.5
|
|
@@ -376,6 +403,10 @@
|
|
|
handleClickPlayIcon()
|
|
handleClickPlayIcon()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将秒转化为时分秒
|
|
|
|
|
+ * @param {number} duration 总秒数
|
|
|
|
|
+ */
|
|
|
function transferSecondsToTime(duration) {
|
|
function transferSecondsToTime(duration) {
|
|
|
const hour = Math.floor(duration / 3600)
|
|
const hour = Math.floor(duration / 3600)
|
|
|
const minutes = Math.floor(duration / 60)
|
|
const minutes = Math.floor(duration / 60)
|
|
@@ -385,11 +416,18 @@
|
|
|
}`
|
|
}`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将分秒转化为秒
|
|
|
|
|
+ * @param {string} time 分秒,格式 00:00
|
|
|
|
|
+ */
|
|
|
function transferTimeToSeconds(time) {
|
|
function transferTimeToSeconds(time) {
|
|
|
const timeList = time.split('.')[0].split(':')
|
|
const timeList = time.split('.')[0].split(':')
|
|
|
return Number(timeList[1]) + Number(timeList[0]) * 60
|
|
return Number(timeList[1]) + Number(timeList[0]) * 60
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 当前播放时间改变时触发
|
|
|
|
|
+ */
|
|
|
function handleTimeUpdate(event) {
|
|
function handleTimeUpdate(event) {
|
|
|
// 如果正在拖拽进度滑块,函数结束,不计算当前时间
|
|
// 如果正在拖拽进度滑块,函数结束,不计算当前时间
|
|
|
if (isDrop.value) return
|
|
if (isDrop.value) return
|
|
@@ -407,8 +445,8 @@
|
|
|
// 使高亮歌词行永远保持在第二行
|
|
// 使高亮歌词行永远保持在第二行
|
|
|
if (currentLyricsLineIndex.value > 2) {
|
|
if (currentLyricsLineIndex.value > 2) {
|
|
|
// 平滑滚动
|
|
// 平滑滚动
|
|
|
- refs.lyricsListRef.scrollTo({
|
|
|
|
|
- top: refs.lyricsLineRef[index].clientHeight * (index - 2),
|
|
|
|
|
|
|
+ lyricsListRef.value.scrollTo({
|
|
|
|
|
+ top: lyricsLineRef.value[index].clientHeight * (index - 2),
|
|
|
behavior: 'smooth'
|
|
behavior: 'smooth'
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -417,11 +455,17 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 拖动播放进度滑块触发
|
|
|
|
|
+ */
|
|
|
function handleChangeProgress(progress) {
|
|
function handleChangeProgress(progress) {
|
|
|
audioElement.value.currentTime = progress
|
|
audioElement.value.currentTime = progress
|
|
|
isDrop.value = false
|
|
isDrop.value = false
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切换循环播放类型
|
|
|
|
|
+ */
|
|
|
function handleChangeCycleType() {
|
|
function handleChangeCycleType() {
|
|
|
if (cycleType.value === 3) {
|
|
if (cycleType.value === 3) {
|
|
|
cycleType.value = 1
|
|
cycleType.value = 1
|
|
@@ -430,16 +474,29 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点击播放图标触发
|
|
|
|
|
+ * @description 开始播放音频
|
|
|
|
|
+ */
|
|
|
function handleClickPlayIcon() {
|
|
function handleClickPlayIcon() {
|
|
|
isPlay.value = true
|
|
isPlay.value = true
|
|
|
audioElement.value.play()
|
|
audioElement.value.play()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点击暂停图标触发
|
|
|
|
|
+ * @description 暂停音频
|
|
|
|
|
+ */
|
|
|
function handleClickPauseIcon() {
|
|
function handleClickPauseIcon() {
|
|
|
isPlay.value = false
|
|
isPlay.value = false
|
|
|
audioElement.value.pause()
|
|
audioElement.value.pause()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 切换、暂停或播放歌曲
|
|
|
|
|
+ * @param {string} type pre - 上一首 | next - 下一首 | manual 手动切换
|
|
|
|
|
+ * @param {number} index 手动切换的音频索引,从 0 开始
|
|
|
|
|
+ */
|
|
|
function handleChangeAudioIndex(type, index) {
|
|
function handleChangeAudioIndex(type, index) {
|
|
|
// 如果当前手动切换
|
|
// 如果当前手动切换
|
|
|
if (type === 'manual') {
|
|
if (type === 'manual') {
|
|
@@ -457,22 +514,22 @@
|
|
|
// 判断当前循环播放类型
|
|
// 判断当前循环播放类型
|
|
|
switch (cycleType.value) {
|
|
switch (cycleType.value) {
|
|
|
case 3: {
|
|
case 3: {
|
|
|
- let activeIndex = 0
|
|
|
|
|
|
|
+ let newActiveIndex = 0
|
|
|
do {
|
|
do {
|
|
|
- activeIndex = Math.floor(Math.random() * (props.audioList.length - 1)) + 1
|
|
|
|
|
- } while (activeIndex === activeIndex.value)
|
|
|
|
|
- activeIndex.value = activeIndex
|
|
|
|
|
|
|
+ newActiveIndex = Math.floor(Math.random() * (props.audioList.value.length - 1)) + 1
|
|
|
|
|
+ } while (activeIndex.value === newActiveIndex)
|
|
|
|
|
+ activeIndex.value = newActiveIndex
|
|
|
break
|
|
break
|
|
|
}
|
|
}
|
|
|
default: {
|
|
default: {
|
|
|
if (type === 'pre') {
|
|
if (type === 'pre') {
|
|
|
if (activeIndex.value === 0) {
|
|
if (activeIndex.value === 0) {
|
|
|
- activeIndex.value = props.audioList.length - 1
|
|
|
|
|
|
|
+ activeIndex.value = props.audioList.value.length - 1
|
|
|
} else {
|
|
} else {
|
|
|
activeIndex.value--
|
|
activeIndex.value--
|
|
|
}
|
|
}
|
|
|
} else if (type === 'next') {
|
|
} else if (type === 'next') {
|
|
|
- if (activeIndex.value === props.audioList.length - 1) {
|
|
|
|
|
|
|
+ if (activeIndex.value === props.audioList.value.length - 1) {
|
|
|
activeIndex.value = 0
|
|
activeIndex.value = 0
|
|
|
} else {
|
|
} else {
|
|
|
activeIndex.value++
|
|
activeIndex.value++
|
|
@@ -484,30 +541,30 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 点击音量图标
|
|
|
|
|
+ */
|
|
|
function handleClickVolumeIcon() {
|
|
function handleClickVolumeIcon() {
|
|
|
volume.value = volume.value === 0 ? 0.5 : 0
|
|
volume.value = volume.value === 0 ? 0.5 : 0
|
|
|
handleChangeVolumeBar(volume.value)
|
|
handleChangeVolumeBar(volume.value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function handleChangeVolumeBar(volume) {
|
|
|
|
|
- audioElement.value.volume = Number(volume.toFixed(1))
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 音量滑块改变时触发
|
|
|
|
|
+ */
|
|
|
|
|
+ function handleChangeVolumeBar(value) {
|
|
|
|
|
+ audioElement.value.volume = Number(value.toFixed(1))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 关闭音频预览
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 关闭音频预览
|
|
|
|
|
+ */
|
|
|
function handleClosePreview() {
|
|
function handleClosePreview() {
|
|
|
visible.value = false
|
|
visible.value = false
|
|
|
- props.callback('cancel')
|
|
|
|
|
|
|
+ callback('cancel')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- onMounted(() => {
|
|
|
|
|
- visible.value = props.visible
|
|
|
|
|
- activeIndex.value = props.defaultIndex
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- onUnmounted(() => {
|
|
|
|
|
- document.removeEventListener('keyup', handleAddKeyupEvent)
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
|
|
+ // 暴露给父组件的方法和属性
|
|
|
defineExpose({
|
|
defineExpose({
|
|
|
visible
|
|
visible
|
|
|
})
|
|
})
|
|
@@ -756,19 +813,6 @@
|
|
|
.progress-bar {
|
|
.progress-bar {
|
|
|
margin-right: 16px;
|
|
margin-right: 16px;
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
- // >>> .el-slider__runway {
|
|
|
|
|
- // height: 2px;
|
|
|
|
|
- // .el-slider__button-wrapper {
|
|
|
|
|
- // top: -17px;
|
|
|
|
|
- // .el-slider__button {
|
|
|
|
|
- // border: none;
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
- // .el-slider__bar {
|
|
|
|
|
- // height: 100%;
|
|
|
|
|
- // background: @Warning;
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -810,19 +854,6 @@
|
|
|
|
|
|
|
|
.volume-bar {
|
|
.volume-bar {
|
|
|
width: 100px;
|
|
width: 100px;
|
|
|
- // >>> .el-slider__runway {
|
|
|
|
|
- // height: 2px;
|
|
|
|
|
- // .el-slider__button-wrapper {
|
|
|
|
|
- // top: -19px;
|
|
|
|
|
- // .el-slider__button {
|
|
|
|
|
- // border: none;
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
- // .el-slider__bar {
|
|
|
|
|
- // height: 100%;
|
|
|
|
|
- // background: @Warning;
|
|
|
|
|
- // }
|
|
|
|
|
- // }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|