页面效果如图
组件代码html
<template>
<!--音频播放组件 -->
<div class="my-video-group" :style="{width:videoWidth + 'px'}">
<div class="box u-flex-between">
<!-- 音频暂停/播放 -->
<div class="isPlay">
<el-button type="info" text size="small" @click="togglePlay">
<el-icon size="20">
<VideoPlay v-if="!isPlaying" />
<VideoPause v-else />
</el-icon>
</el-button>
</div>
<!-- 音频时长 -->
<div class="isPlay-time">
<el-text>{{ formatTime(currentTime) }}</el-text>
<el-text class="pr5 pl5">/</el-text>
<el-text>{{ formatTime(duration) }}</el-text>
</div>
<!-- 音频进度条 -->
<div class="isPlay-progess">
<el-slider
v-model="progress"
:min="0"
:max="100"
:step="0.1"
:show-tooltip="false"
@change="handleProgressChange"
style="width: 420px;"
></el-slider>
</div>
<!-- 音频音量 -->
<div class="isPlay-voice u-flex">
<el-dropdown placement="bottom" max-height="220px">
<el-button type='info' text='' size="small">
<el-icon size="20"><BellFilled /></el-icon>
</el-button>
<template #dropdown>
<el-slider
class="mt20 mb20"
v-model="volume"
size="small"
:min="0"
:max="100"
:step="1"
@change="handleVolumeChange"
height="160px"
vertical
></el-slider>
</template>
</el-dropdown>
</div>
</div>
<!-- 音频播放器 -->
<audio ref="audioRef" :src="props.audioSrc" preload="metadata" @canplay="onCanplay"></audio>
</div>
</template>
js
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const audioRef = ref(null)
const props = defineProps({
videoWidth: {
type: Number,
default: 740
},
audioSrc: {
type: String,
default: 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3'
}
})
const isPlaying = ref(false)
const currentTime = ref(0)
const duration = ref(0)
const progress = ref(0)
const volume = ref(100)
// 定义命名事件处理函数用于正确移除监听
const handleTimeUpdate = () => {
const audio = audioRef.value
if (!audio) return
currentTime.value = audio.currentTime
if (audio.duration > 0) { // 防止duration未加载完成时计算错误
progress.value = (audio.currentTime / audio.duration) * 100
}
}
const handleLoadedMetadata = () => {
const audio = audioRef.value
if (audio) duration.value = audio.duration
}
const handleEnded = () => {
isPlaying.value = false
}
const handleError = (e) => {
console.error('音频加载失败:', e)
// 可根据业务需求添加提示(如使用ElMessage)
// ElMessage.error('音频加载失败,请检查地址')
}
onMounted(() => {
const audio = audioRef.value
if (!audio) return
audio.addEventListener('timeupdate', handleTimeUpdate)
audio.addEventListener('loadedmetadata', handleLoadedMetadata)
audio.addEventListener('ended', handleEnded)
audio.addEventListener('error', handleError)
})
onBeforeUnmount(() => {
const audio = audioRef.value
if (!audio) return
audio.removeEventListener('timeupdate', handleTimeUpdate)
audio.removeEventListener('loadedmetadata', handleLoadedMetadata)
audio.removeEventListener('ended', handleEnded)
audio.removeEventListener('error', handleError)
audio.pause() // 卸载时暂停播放
})
const togglePlay = () => {
const audio = audioRef.value
if (!audio) return
if (isPlaying.value) {
audio.pause()
} else {
audio.play().catch(err => {
console.error('播放失败:', err)
isPlaying.value = false // 播放失败时恢复状态
})
}
isPlaying.value = !isPlaying.value
}
const handleProgressChange = (value) => {
const audio = audioRef.value
if (!audio || audio.duration === 0) return // 防止未加载完成时操作
const time = (value / 100) * audio.duration
audio.currentTime = time
currentTime.value = time
}
const handleVolumeChange = (value) => {
const audio = audioRef.value
if (audio) audio.volume = value / 100
}
const formatTime = (time) => {
const minutes = Math.floor(time / 60)
const seconds = Math.floor(time % 60)
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
}
const onCanplay = () => {
if (audioRef.value) {
duration.value = audioRef.value.duration
}
}
// 关闭后重置并关闭播放
const reset = () => {
isPlaying.value = false
currentTime.value = 0
progress.value = 0
if (audioRef.value) {
audioRef.value.pause()
audioRef.value.currentTime = 0
audioRef.value.src = props.audioSrc
audioRef.value.load()
}
}
defineExpose({ reset })
</script>
css
<style lang="scss" scoped>
.my-video-group{
border-radius: 50px;
padding: 4px;
background: var(--appmain-default-bg);
}
</style>