10 changed files with 914 additions and 31 deletions
@ -0,0 +1,107 @@ |
|||
<template> |
|||
<div></div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'AudioPlayer', |
|||
props: ['src'], |
|||
data() { |
|||
return { |
|||
backgroundAudioManager: null, |
|||
currentTime: 0, |
|||
duration: 0, |
|||
timeUpdateTimer: null |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.backgroundAudioManager = wx.getBackgroundAudioManager() |
|||
|
|||
// 设置音频信息 |
|||
this.backgroundAudioManager.title = '有声电子书' |
|||
this.backgroundAudioManager.epname = '章节播放' |
|||
this.backgroundAudioManager.singer = 'EpicSoul' |
|||
this.backgroundAudioManager.coverImgUrl = '' |
|||
|
|||
// 监听播放事件 |
|||
this.backgroundAudioManager.onPlay(() => { |
|||
this.startTimeUpdate() |
|||
this.$emit('play') |
|||
}) |
|||
|
|||
// 监听暂停事件 |
|||
this.backgroundAudioManager.onPause(() => { |
|||
this.stopTimeUpdate() |
|||
this.$emit('pause') |
|||
}) |
|||
|
|||
// 监听停止事件 |
|||
this.backgroundAudioManager.onStop(() => { |
|||
this.stopTimeUpdate() |
|||
this.$emit('pause') |
|||
}) |
|||
|
|||
// 监听播放结束事件 |
|||
this.backgroundAudioManager.onEnded(() => { |
|||
this.stopTimeUpdate() |
|||
this.$emit('ended') |
|||
}) |
|||
|
|||
// 监听错误事件 |
|||
this.backgroundAudioManager.onError((err) => { |
|||
console.error('背景音频播放错误:', err) |
|||
this.stopTimeUpdate() |
|||
}) |
|||
|
|||
// 设置音频源 |
|||
if (this.src) { |
|||
this.backgroundAudioManager.src = this.src |
|||
} |
|||
}, |
|||
methods: { |
|||
play() { |
|||
if (this.backgroundAudioManager.src) { |
|||
this.backgroundAudioManager.play() |
|||
} |
|||
}, |
|||
pause() { |
|||
this.backgroundAudioManager.pause() |
|||
}, |
|||
seek(time) { |
|||
this.backgroundAudioManager.seek(time) |
|||
}, |
|||
startTimeUpdate() { |
|||
this.stopTimeUpdate() |
|||
this.timeUpdateTimer = setInterval(() => { |
|||
this.currentTime = this.backgroundAudioManager.currentTime || 0 |
|||
this.duration = this.backgroundAudioManager.duration || 0 |
|||
this.$emit('timeupdate', { |
|||
currentTime: this.currentTime, |
|||
duration: this.duration |
|||
}) |
|||
}, 100) |
|||
}, |
|||
stopTimeUpdate() { |
|||
if (this.timeUpdateTimer) { |
|||
clearInterval(this.timeUpdateTimer) |
|||
this.timeUpdateTimer = null |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
src(newSrc) { |
|||
if (newSrc) { |
|||
this.backgroundAudioManager.src = newSrc |
|||
} |
|||
} |
|||
}, |
|||
beforeDestroy() { |
|||
this.stopTimeUpdate() |
|||
// 注意:不要销毁backgroundAudioManager,因为它是全局的 |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
/* AudioPlayer组件样式 */ |
|||
</style> |
@ -0,0 +1,720 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<!-- 顶部标题栏 --> |
|||
<!-- 主要内容区域 --> |
|||
<!-- 文本显示区域 --> |
|||
<scroll-view |
|||
class="content" |
|||
:show-scrollbar="false" |
|||
enhanced |
|||
scroll-y |
|||
:scroll-top="scrollTop" |
|||
scroll-with-animation |
|||
:style="{ height: scrollViewHeight + 'px' }" |
|||
> |
|||
<view class="content-wrapper"> |
|||
<view class="article-content"> |
|||
<text |
|||
v-for="(line, index) in currentChapter.lines" |
|||
:key="index" |
|||
class="sentence" |
|||
:class="{ active: index === activeLineIndex }" |
|||
> |
|||
{{ line.text }} |
|||
</text> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
<!-- 底部控制栏 --> |
|||
<view class="controls"> |
|||
<!-- 章节信息栏 --> |
|||
<view class="chapter-info-bar"> |
|||
<text class="chapter-title">{{ currentChapter.title }}</text> |
|||
<text class="chapter-count"> |
|||
{{ currentChapterIndex + 1 }}/{{ chapters.length }} |
|||
</text> |
|||
</view> |
|||
|
|||
<!-- 进度条区域 --> |
|||
<view class="progress-section"> |
|||
<text class="current-time">{{ formatTime(currentTime) }}</text> |
|||
<view class="progress-container"> |
|||
<view class="progress-bar" @click="seek"> |
|||
<view class="progress" :style="{ width: progress + '%' }"></view> |
|||
<view |
|||
class="progress-thumb" |
|||
:style="{ left: progress + '%' }" |
|||
></view> |
|||
</view> |
|||
</view> |
|||
<text class="duration">{{ formatTime(duration) }}</text> |
|||
</view> |
|||
|
|||
<!-- 控制按钮区域 --> |
|||
<view class="control-buttons"> |
|||
<!-- 上一章按钮 --> |
|||
<button |
|||
class="control-btn prev-btn" |
|||
@click="prevChapter" |
|||
:disabled="currentChapterIndex === 0" |
|||
> |
|||
<text class="control-icon">⏮</text> |
|||
</button> |
|||
|
|||
<!-- 播放/暂停按钮 --> |
|||
<button class="play-btn" @click="togglePlay"> |
|||
<text class="play-icon">{{ playing ? "⏸️" : "▶️" }}</text> |
|||
</button> |
|||
|
|||
<!-- 下一章按钮 --> |
|||
<button |
|||
class="control-btn next-btn" |
|||
@click="nextChapter" |
|||
:disabled="currentChapterIndex === chapters.length - 1" |
|||
> |
|||
<text class="control-icon">⏭</text> |
|||
</button> |
|||
</view> |
|||
</view> |
|||
|
|||
<AudioPlayer |
|||
ref="audioPlayer" |
|||
:src="currentChapter.audioUrl" |
|||
@timeupdate="onTimeUpdate" |
|||
@ended="onEnded" |
|||
></AudioPlayer> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 由于uniapp不支持直接使用audio元素,我们使用innerAudioContext |
|||
import AudioPlayer from "../components/AudioPlayer.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
AudioPlayer, |
|||
}, |
|||
data() { |
|||
return { |
|||
chapters: [ |
|||
{ |
|||
title: "第一章:人工智能的崛起", |
|||
audioUrl: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3", |
|||
content: `[00:00.00] 人工智能的发展历程 |
|||
[00:05.50] 从1950年代开始,人工智能作为一门学科正式诞生 |
|||
[00:12.30] 阿兰·图灵提出了著名的图灵测试 |
|||
[00:18.80] 这个测试成为了判断机器是否具有智能的标准 |
|||
[00:25.40] 早期的人工智能研究主要集中在符号推理 |
|||
[00:32.20] 科学家们试图用逻辑和规则来模拟人类思维 |
|||
[00:39.10] 然而,这种方法在处理复杂问题时遇到了困难 |
|||
[00:46.70] 直到神经网络和机器学习的出现 |
|||
[00:53.50] 人工智能才迎来了新的发展机遇 |
|||
[01:00.30] 深度学习技术的突破更是推动了AI的快速发展 |
|||
[01:07.80] 如今,人工智能已经渗透到我们生活的方方面面 |
|||
[01:14.60] 从智能手机到自动驾驶汽车 |
|||
[01:21.40] 从医疗诊断到金融分析 |
|||
[01:28.20] AI正在改变着整个世界的运作方式`, |
|||
}, |
|||
{ |
|||
title: "第二章:科技与人文的融合", |
|||
audioUrl: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3", |
|||
content: `[00:00.00] 科技发展与人文关怀的平衡 |
|||
[00:06.50] 在快速发展的数字时代 |
|||
[00:12.20] 我们常常被各种新技术所包围 |
|||
[00:18.00] 智能手机、物联网、云计算 |
|||
[00:24.40] 这些技术极大地便利了我们的生活 |
|||
[00:30.10] 但同时也带来了新的挑战 |
|||
[00:35.80] 如何在享受科技便利的同时 |
|||
[00:41.50] 保持人与人之间的真实连接 |
|||
[00:47.20] 这成为了现代社会的重要课题 |
|||
[00:53.90] 教育领域正在探索新的教学模式 |
|||
[01:00.60] 将传统的人文教育与现代科技相结合 |
|||
[01:07.30] 培养既有技术能力又有人文素养的人才 |
|||
[01:14.00] 艺术创作也在科技的推动下焕发新生 |
|||
[01:20.70] 数字艺术、虚拟现实、增强现实 |
|||
[01:27.40] 为艺术家提供了全新的创作工具和表达方式`, |
|||
}, |
|||
{ |
|||
title: "第三章:未来社会的展望", |
|||
audioUrl: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3", |
|||
content: `[00:00.00] 展望未来社会的发展趋势 |
|||
[00:06.50] 可持续发展将成为全球共识 |
|||
[00:12.20] 绿色能源技术不断突破 |
|||
[00:18.00] 太阳能、风能、氢能源的应用日益广泛 |
|||
[00:24.40] 智慧城市的建设正在加速推进 |
|||
[00:30.10] 通过大数据和人工智能 |
|||
[00:35.80] 城市管理将变得更加高效和智能 |
|||
[00:41.50] 交通拥堵、环境污染等问题将得到有效解决 |
|||
[00:47.20] 教育模式也将发生根本性变革 |
|||
[00:53.90] 个性化学习将成为主流 |
|||
[01:00.60] 每个学生都能获得量身定制的教育方案 |
|||
[01:07.30] 远程教育和虚拟课堂将普及全球 |
|||
[01:14.00] 医疗健康领域将迎来精准医疗时代 |
|||
[01:20.70] 基因治疗、纳米医学、再生医学 |
|||
[01:27.40] 这些前沿技术将为人类健康带来革命性改变 |
|||
[01:34.10] 未来的社会将更加包容、智能和可持续`, |
|||
}, |
|||
], |
|||
currentChapterIndex: 0, |
|||
playing: false, |
|||
currentTime: 0, |
|||
duration: 0, |
|||
activeLineIndex: -1, |
|||
scrollTop: 0, |
|||
scrollViewHeight: 0, |
|||
}; |
|||
}, |
|||
mounted() { |
|||
this.syncAudioState(); |
|||
this.calculateScrollViewHeight(); |
|||
}, |
|||
onShow() { |
|||
// 页面显示时同步音频状态 |
|||
this.syncAudioState(); |
|||
}, |
|||
onHide() { |
|||
// 页面隐藏时保持音频播放状态 |
|||
// 背景音频会自动继续播放 |
|||
}, |
|||
computed: { |
|||
currentChapter() { |
|||
const chapter = this.chapters[this.currentChapterIndex]; |
|||
// 解析章节内容 |
|||
const lines = chapter.content |
|||
.split("\n") |
|||
.filter((line) => line.trim() !== ""); |
|||
const parsedLines = lines.map((line) => { |
|||
const match = line.match(/\[(\d+):(\d+)\.(\d+)\](.*)/); |
|||
if (match) { |
|||
return { |
|||
time: match[0].slice(0, match[0].indexOf("]") + 1), |
|||
text: match[4], |
|||
timeInSeconds: |
|||
parseInt(match[1]) * 60 + |
|||
parseInt(match[2]) + |
|||
parseInt(match[3]) / 100, |
|||
}; |
|||
} |
|||
return { time: "", text: line, timeInSeconds: 0 }; |
|||
}); |
|||
return { |
|||
...chapter, |
|||
lines: parsedLines, |
|||
}; |
|||
}, |
|||
progress() { |
|||
return (this.currentTime / this.duration) * 100 || 0; |
|||
}, |
|||
currentTimeFormatted() { |
|||
return this.formatTime(this.currentTime); |
|||
}, |
|||
durationFormatted() { |
|||
return this.formatTime(this.duration); |
|||
}, |
|||
}, |
|||
methods: { |
|||
togglePlay() { |
|||
if (this.playing) { |
|||
this.$refs.audioPlayer.pause(); |
|||
this.playing = false; |
|||
} else { |
|||
this.$refs.audioPlayer.play(); |
|||
this.playing = true; |
|||
} |
|||
}, |
|||
prevChapter() { |
|||
if (this.currentChapterIndex > 0) { |
|||
this.currentChapterIndex--; |
|||
this.resetPlayback(); |
|||
} |
|||
}, |
|||
nextChapter() { |
|||
if (this.currentChapterIndex < this.chapters.length - 1) { |
|||
this.currentChapterIndex++; |
|||
this.resetPlayback(); |
|||
} |
|||
}, |
|||
resetPlayback() { |
|||
this.playing = false; |
|||
this.currentTime = 0; |
|||
this.duration = 0; |
|||
this.activeLineIndex = -1; |
|||
this.scrollTop = 0; |
|||
|
|||
// 更新背景音频信息 |
|||
const currentChapter = this.chapters[this.currentChapterIndex]; |
|||
if ( |
|||
this.$refs.audioPlayer && |
|||
this.$refs.audioPlayer.backgroundAudioManager |
|||
) { |
|||
const bgAudio = this.$refs.audioPlayer.backgroundAudioManager; |
|||
bgAudio.title = currentChapter.title; |
|||
bgAudio.epname = `第${this.currentChapterIndex + 1}章`; |
|||
bgAudio.src = currentChapter.audioUrl; |
|||
// 确保音频从头开始播放 |
|||
bgAudio.currentTime = 0; |
|||
// 如果音频播放器有seek方法,也调用它来确保重置 |
|||
if (this.$refs.audioPlayer.seek) { |
|||
this.$refs.audioPlayer.seek(0); |
|||
} |
|||
} |
|||
}, |
|||
onTimeUpdate(e) { |
|||
this.currentTime = e.currentTime; |
|||
this.duration = e.duration; |
|||
|
|||
// 同步播放状态 |
|||
this.playing = !e.paused; |
|||
|
|||
// 找到当前应该高亮的行 |
|||
const lines = this.currentChapter.lines; |
|||
for (let i = lines.length - 1; i >= 0; i--) { |
|||
if (this.currentTime >= lines[i].timeInSeconds) { |
|||
if (this.activeLineIndex !== i) { |
|||
this.activeLineIndex = i; |
|||
// 滚动到当前行,这里简单实现:每行高度约100rpx,滚动到当前行 |
|||
this.scrollTop = i * 100; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
}, |
|||
onEnded() { |
|||
this.playing = false; |
|||
// 自动播放下一章节 |
|||
if (this.currentChapterIndex < this.chapters.length - 1) { |
|||
this.nextChapter(); |
|||
setTimeout(() => { |
|||
this.$refs.audioPlayer.play(); |
|||
this.playing = true; |
|||
}, 500); |
|||
} |
|||
}, |
|||
seek(e) { |
|||
// 使用uni.createSelectorQuery获取进度条信息 |
|||
const query = uni.createSelectorQuery().in(this); |
|||
query |
|||
.select(".progress-bar") |
|||
.boundingClientRect((rect) => { |
|||
if (rect) { |
|||
const clickX = e.detail.x - rect.left; |
|||
const progressPercent = clickX / rect.width; |
|||
const seekTime = progressPercent * this.duration; |
|||
|
|||
if (seekTime >= 0 && seekTime <= this.duration) { |
|||
this.$refs.audioPlayer.seek(seekTime); |
|||
} |
|||
} |
|||
}) |
|||
.exec(); |
|||
}, |
|||
formatTime(seconds) { |
|||
const mins = Math.floor(seconds / 60); |
|||
const secs = Math.floor(seconds % 60); |
|||
return `${mins.toString().padStart(2, "0")}:${secs |
|||
.toString() |
|||
.padStart(2, "0")}`; |
|||
}, |
|||
syncAudioState() { |
|||
// 同步背景音频状态到界面 |
|||
if ( |
|||
this.$refs.audioPlayer && |
|||
this.$refs.audioPlayer.backgroundAudioManager |
|||
) { |
|||
const bgAudio = this.$refs.audioPlayer.backgroundAudioManager; |
|||
|
|||
// 检查当前是否有音频在播放 |
|||
if (bgAudio.src) { |
|||
// 同步播放状态 - 注意:paused为true表示暂停,false表示播放 |
|||
this.playing = !bgAudio.paused; |
|||
|
|||
// 同步时间信息 |
|||
this.currentTime = bgAudio.currentTime || 0; |
|||
this.duration = bgAudio.duration || 0; |
|||
|
|||
// 如果有音频在播放,启动时间更新 |
|||
if (this.playing) { |
|||
this.$refs.audioPlayer.startTimeUpdate(); |
|||
} |
|||
} else { |
|||
// 没有音频源时重置状态 |
|||
this.playing = false; |
|||
this.currentTime = 0; |
|||
this.duration = 0; |
|||
} |
|||
} |
|||
}, |
|||
calculateScrollViewHeight() { |
|||
// 获取系统信息 |
|||
const systemInfo = uni.getSystemInfoSync(); |
|||
const windowHeight = systemInfo.windowHeight; |
|||
|
|||
// 计算控制栏高度(大约300rpx转换为px) |
|||
const controlsHeight = 300 * (systemInfo.windowWidth / 750); |
|||
|
|||
// 计算内容区域可用高度 |
|||
this.scrollViewHeight = windowHeight - controlsHeight - 60; // 60px为额外边距 |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
.container { |
|||
height: 100vh; |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
|||
padding: 30rpx; |
|||
position: relative; |
|||
} |
|||
|
|||
/* 顶部标题栏样式 */ |
|||
.header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 40rpx 48rpx; |
|||
background: rgba(255, 255, 255, 0.95); |
|||
backdrop-filter: blur(20rpx); |
|||
border-bottom: 2rpx solid rgba(255, 255, 255, 0.2); |
|||
box-shadow: 0 4rpx 40rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.header-content { |
|||
flex: 1; |
|||
} |
|||
|
|||
.title { |
|||
font-size: 48rpx; |
|||
font-weight: 700; |
|||
color: #2d3748; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.subtitle { |
|||
font-size: 32rpx; |
|||
color: #718096; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.chapter-info { |
|||
background: linear-gradient(135deg, #667eea, #764ba2); |
|||
padding: 16rpx 32rpx; |
|||
border-radius: 40rpx; |
|||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3); |
|||
} |
|||
|
|||
.chapter-count { |
|||
color: white; |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.content { |
|||
width: 690rpx; |
|||
background: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(20rpx); |
|||
border-radius: 32rpx; |
|||
margin: 0 auto; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.content-wrapper { |
|||
padding: 48rpx; |
|||
} |
|||
|
|||
.article-content { |
|||
line-height: 1.8; |
|||
font-size: 32rpx; |
|||
color: #2d3748; |
|||
text-align: justify; |
|||
} |
|||
|
|||
.sentence { |
|||
transition: all 0.3s ease; |
|||
padding: 4rpx 8rpx; |
|||
border-radius: 8rpx; |
|||
display: inline; |
|||
} |
|||
|
|||
.sentence.active { |
|||
background: linear-gradient( |
|||
135deg, |
|||
rgba(102, 126, 234, 0.15), |
|||
rgba(118, 75, 162, 0.15) |
|||
); |
|||
color: #667eea; |
|||
font-weight: 600; |
|||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.2); |
|||
} |
|||
|
|||
/* 底部控制栏样式 */ |
|||
.controls { |
|||
background: rgba(255, 255, 255, 0.98); |
|||
backdrop-filter: blur(40rpx); |
|||
border-top: 2rpx solid rgba(255, 255, 255, 0.3); |
|||
box-shadow: 0 -16rpx 64rpx rgba(0, 0, 0, 0.12), |
|||
0 -4rpx 16rpx rgba(0, 0, 0, 0.08); |
|||
border-radius: 48rpx 48rpx 0 0; |
|||
padding: 32rpx 40rpx 40rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 24rpx; |
|||
position: fixed; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 1000; |
|||
} |
|||
|
|||
/* 章节信息栏 */ |
|||
.chapter-info-bar { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 0 8rpx; |
|||
} |
|||
|
|||
.chapter-title { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #2d3748; |
|||
flex: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
margin-right: 16rpx; |
|||
} |
|||
|
|||
.chapter-count { |
|||
font-size: 24rpx; |
|||
color: #718096; |
|||
font-weight: 500; |
|||
background: rgba(139, 92, 246, 0.1); |
|||
padding: 8rpx 16rpx; |
|||
border-radius: 20rpx; |
|||
backdrop-filter: blur(10rpx); |
|||
} |
|||
|
|||
/* 进度条区域 */ |
|||
.progress-section { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.current-time, |
|||
.duration { |
|||
font-size: 24rpx; |
|||
color: #718096; |
|||
font-weight: 500; |
|||
font-family: "Courier New", monospace; |
|||
min-width: 80rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 控制按钮区域 */ |
|||
.control-buttons { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 32rpx; |
|||
} |
|||
|
|||
/* 播放按钮 */ |
|||
.play-btn { |
|||
width: 96rpx; |
|||
height: 96rpx; |
|||
border-radius: 50%; |
|||
border: none; |
|||
background: linear-gradient(135deg, #8b5cf6, #a855f7); |
|||
color: white; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|||
box-shadow: 0 12rpx 40rpx rgba(139, 92, 246, 0.4), |
|||
0 6rpx 20rpx rgba(0, 0, 0, 0.15); |
|||
position: relative; |
|||
overflow: hidden; |
|||
z-index: 2; |
|||
} |
|||
|
|||
/* 控制按钮(上一章/下一章) */ |
|||
.control-btn { |
|||
width: 88rpx; |
|||
height: 88rpx; |
|||
border-radius: 50%; |
|||
border: none; |
|||
background: rgba(139, 92, 246, 0.08); |
|||
color: #8b5cf6; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|||
box-shadow: 0 4rpx 16rpx rgba(139, 92, 246, 0.15); |
|||
backdrop-filter: blur(20rpx); |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.play-btn::before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: linear-gradient( |
|||
135deg, |
|||
rgba(255, 255, 255, 0.2), |
|||
rgba(255, 255, 255, 0.05) |
|||
); |
|||
border-radius: 50%; |
|||
opacity: 0; |
|||
transition: opacity 0.3s ease; |
|||
} |
|||
|
|||
.play-btn:hover { |
|||
transform: scale(1.08); |
|||
box-shadow: 0 18rpx 56rpx rgba(139, 92, 246, 0.5), |
|||
0 9rpx 28rpx rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
.play-btn:hover::before { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.play-btn:active { |
|||
transform: scale(0.96); |
|||
transition: transform 0.1s ease; |
|||
} |
|||
|
|||
/* 进度条容器 */ |
|||
.progress-container { |
|||
flex: 1; |
|||
position: relative; |
|||
} |
|||
|
|||
.progress-bar { |
|||
height: 12rpx; |
|||
background: rgba(139, 92, 246, 0.12); |
|||
border-radius: 6rpx; |
|||
position: relative; |
|||
cursor: pointer; |
|||
overflow: hidden; |
|||
backdrop-filter: blur(20rpx); |
|||
box-shadow: inset 0 2rpx 6rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.progress { |
|||
height: 100%; |
|||
background: linear-gradient(90deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%); |
|||
border-radius: 6rpx; |
|||
transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
|||
box-shadow: 0 4rpx 16rpx rgba(139, 92, 246, 0.3); |
|||
position: relative; |
|||
} |
|||
|
|||
.progress::after { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: linear-gradient( |
|||
90deg, |
|||
rgba(255, 255, 255, 0.3) 0%, |
|||
rgba(255, 255, 255, 0.1) 100% |
|||
); |
|||
border-radius: 6rpx; |
|||
} |
|||
|
|||
.progress-thumb { |
|||
position: absolute; |
|||
top: 50%; |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
background: white; |
|||
border: 4rpx solid #8b5cf6; |
|||
border-radius: 50%; |
|||
transform: translate(-50%, -50%); |
|||
box-shadow: 0 6rpx 20rpx rgba(139, 92, 246, 0.4), |
|||
0 3rpx 6rpx rgba(0, 0, 0, 0.1); |
|||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.progress-thumb:hover { |
|||
transform: translate(-50%, -50%) scale(1.2); |
|||
box-shadow: 0 8rpx 24rpx rgba(139, 92, 246, 0.5), |
|||
0 4rpx 8rpx rgba(0, 0, 0, 0.15); |
|||
} |
|||
|
|||
/* 控制按钮交互效果 */ |
|||
.control-btn::before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: linear-gradient( |
|||
135deg, |
|||
rgba(139, 92, 246, 0.1), |
|||
rgba(168, 85, 247, 0.05) |
|||
); |
|||
border-radius: 50%; |
|||
opacity: 0; |
|||
transition: opacity 0.3s ease; |
|||
} |
|||
|
|||
.control-btn:hover { |
|||
background: rgba(139, 92, 246, 0.15); |
|||
transform: scale(1.1); |
|||
box-shadow: 0 8rpx 32rpx rgba(139, 92, 246, 0.25); |
|||
} |
|||
|
|||
.control-btn:hover::before { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.control-btn:active { |
|||
transform: scale(0.95); |
|||
transition: transform 0.1s ease; |
|||
} |
|||
|
|||
.control-btn:disabled { |
|||
opacity: 0.3; |
|||
cursor: not-allowed; |
|||
transform: none; |
|||
box-shadow: 0 2rpx 6rpx rgba(139, 92, 246, 0.1); |
|||
} |
|||
|
|||
.control-btn:disabled:hover { |
|||
background: rgba(139, 92, 246, 0.08); |
|||
transform: none; |
|||
box-shadow: 0 2rpx 6rpx rgba(139, 92, 246, 0.1); |
|||
} |
|||
|
|||
.control-btn:disabled::before { |
|||
opacity: 0; |
|||
} |
|||
|
|||
/* 控制图标样式 */ |
|||
.control-icon { |
|||
font-size: 32rpx; |
|||
line-height: 1; |
|||
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.1)); |
|||
} |
|||
|
|||
.play-icon { |
|||
font-size: 42rpx; |
|||
line-height: 1; |
|||
margin-left: 3rpx; |
|||
filter: drop-shadow(0 3rpx 6rpx rgba(0, 0, 0, 0.2)); |
|||
} |
|||
</style> |
Loading…
Reference in new issue