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