3 changed files with 11431 additions and 101 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,739 @@ |
|||
<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="(char, index) in currentChapter.characters" |
|||
:key="index" |
|||
class="character" |
|||
:class="{ active: index === activeCharIndex }" |
|||
> |
|||
{{ char.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", |
|||
// 字符级别的时间同步数据 |
|||
characters: [ |
|||
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "ren2", "Text": "人"}, |
|||
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "gong1", "Text": "工"}, |
|||
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "zhi4", "Text": "智"}, |
|||
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "neng2", "Text": "能"}, |
|||
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2200, "Phoneme": "", "Text": "的"}, |
|||
{"BeginIndex": 5, "BeginTime": 2200, "EndIndex": 6, "EndTime": 2700, "Phoneme": "fa1", "Text": "发"}, |
|||
{"BeginIndex": 6, "BeginTime": 2700, "EndIndex": 7, "EndTime": 3200, "Phoneme": "zhan3", "Text": "展"}, |
|||
{"BeginIndex": 7, "BeginTime": 3200, "EndIndex": 8, "EndTime": 3700, "Phoneme": "li4", "Text": "历"}, |
|||
{"BeginIndex": 8, "BeginTime": 3700, "EndIndex": 9, "EndTime": 4200, "Phoneme": "cheng2", "Text": "程"}, |
|||
{"BeginIndex": 9, "BeginTime": 4200, "EndIndex": 10, "EndTime": 4400, "Phoneme": "", "Text": "。"}, |
|||
{"BeginIndex": 10, "BeginTime": 5500, "EndIndex": 11, "EndTime": 6000, "Phoneme": "cong2", "Text": "从"}, |
|||
{"BeginIndex": 11, "BeginTime": 6000, "EndIndex": 12, "EndTime": 6500, "Phoneme": "yi1", "Text": "1"}, |
|||
{"BeginIndex": 12, "BeginTime": 6500, "EndIndex": 13, "EndTime": 7000, "Phoneme": "jiu3", "Text": "9"}, |
|||
{"BeginIndex": 13, "BeginTime": 7000, "EndIndex": 14, "EndTime": 7500, "Phoneme": "wu3", "Text": "5"}, |
|||
{"BeginIndex": 14, "BeginTime": 7500, "EndIndex": 15, "EndTime": 8000, "Phoneme": "ling2", "Text": "0"}, |
|||
{"BeginIndex": 15, "BeginTime": 8000, "EndIndex": 16, "EndTime": 8500, "Phoneme": "nian2", "Text": "年"}, |
|||
{"BeginIndex": 16, "BeginTime": 8500, "EndIndex": 17, "EndTime": 9000, "Phoneme": "dai4", "Text": "代"}, |
|||
{"BeginIndex": 17, "BeginTime": 9000, "EndIndex": 18, "EndTime": 9500, "Phoneme": "kai1", "Text": "开"}, |
|||
{"BeginIndex": 18, "BeginTime": 9500, "EndIndex": 19, "EndTime": 10000, "Phoneme": "shi3", "Text": "始"}, |
|||
{"BeginIndex": 19, "BeginTime": 10000, "EndIndex": 20, "EndTime": 10200, "Phoneme": "", "Text": ","}, |
|||
{"BeginIndex": 20, "BeginTime": 10200, "EndIndex": 21, "EndTime": 10700, "Phoneme": "ren2", "Text": "人"}, |
|||
{"BeginIndex": 21, "BeginTime": 10700, "EndIndex": 22, "EndTime": 11200, "Phoneme": "gong1", "Text": "工"}, |
|||
{"BeginIndex": 22, "BeginTime": 11200, "EndIndex": 23, "EndTime": 11700, "Phoneme": "zhi4", "Text": "智"}, |
|||
{"BeginIndex": 23, "BeginTime": 11700, "EndIndex": 24, "EndTime": 12200, "Phoneme": "neng2", "Text": "能"}, |
|||
{"BeginIndex": 24, "BeginTime": 12200, "EndIndex": 25, "EndTime": 12700, "Phoneme": "zuo4", "Text": "作"}, |
|||
{"BeginIndex": 25, "BeginTime": 12700, "EndIndex": 26, "EndTime": 13200, "Phoneme": "wei2", "Text": "为"}, |
|||
{"BeginIndex": 26, "BeginTime": 13200, "EndIndex": 27, "EndTime": 13700, "Phoneme": "yi1", "Text": "一"}, |
|||
{"BeginIndex": 27, "BeginTime": 13700, "EndIndex": 28, "EndTime": 14200, "Phoneme": "men2", "Text": "门"}, |
|||
{"BeginIndex": 28, "BeginTime": 14200, "EndIndex": 29, "EndTime": 14700, "Phoneme": "xue2", "Text": "学"}, |
|||
{"BeginIndex": 29, "BeginTime": 14700, "EndIndex": 30, "EndTime": 15200, "Phoneme": "ke1", "Text": "科"}, |
|||
{"BeginIndex": 30, "BeginTime": 15200, "EndIndex": 31, "EndTime": 15700, "Phoneme": "zheng4", "Text": "正"}, |
|||
{"BeginIndex": 31, "BeginTime": 15700, "EndIndex": 32, "EndTime": 16200, "Phoneme": "shi4", "Text": "式"}, |
|||
{"BeginIndex": 32, "BeginTime": 16200, "EndIndex": 33, "EndTime": 16700, "Phoneme": "dan4", "Text": "诞"}, |
|||
{"BeginIndex": 33, "BeginTime": 16700, "EndIndex": 34, "EndTime": 17200, "Phoneme": "sheng1", "Text": "生"}, |
|||
{"BeginIndex": 34, "BeginTime": 17200, "EndIndex": 35, "EndTime": 17400, "Phoneme": "", "Text": "。"} |
|||
], |
|||
content: `人工智能的发展历程。从1950年代开始,人工智能作为一门学科正式诞生。`, |
|||
}, |
|||
{ |
|||
title: "第二章:科技与人文的融合", |
|||
audioUrl: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3", |
|||
// 字符级别的时间同步数据 |
|||
characters: [ |
|||
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "ke1", "Text": "科"}, |
|||
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "ji4", "Text": "技"}, |
|||
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "fa1", "Text": "发"}, |
|||
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "zhan3", "Text": "展"}, |
|||
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2500, "Phoneme": "yu3", "Text": "与"}, |
|||
{"BeginIndex": 5, "BeginTime": 2500, "EndIndex": 6, "EndTime": 3000, "Phoneme": "ren2", "Text": "人"}, |
|||
{"BeginIndex": 6, "BeginTime": 3000, "EndIndex": 7, "EndTime": 3500, "Phoneme": "wen2", "Text": "文"}, |
|||
{"BeginIndex": 7, "BeginTime": 3500, "EndIndex": 8, "EndTime": 4000, "Phoneme": "guan1", "Text": "关"}, |
|||
{"BeginIndex": 8, "BeginTime": 4000, "EndIndex": 9, "EndTime": 4500, "Phoneme": "huai2", "Text": "怀"}, |
|||
{"BeginIndex": 9, "BeginTime": 4500, "EndIndex": 10, "EndTime": 5000, "Phoneme": "de5", "Text": "的"}, |
|||
{"BeginIndex": 10, "BeginTime": 5000, "EndIndex": 11, "EndTime": 5500, "Phoneme": "ping2", "Text": "平"}, |
|||
{"BeginIndex": 11, "BeginTime": 5500, "EndIndex": 12, "EndTime": 6000, "Phoneme": "heng2", "Text": "衡"}, |
|||
{"BeginIndex": 12, "BeginTime": 6000, "EndIndex": 13, "EndTime": 6200, "Phoneme": "", "Text": "。"} |
|||
], |
|||
content: `科技发展与人文关怀的平衡。`, |
|||
}, |
|||
{ |
|||
title: "第三章:未来社会的展望", |
|||
audioUrl: "https://static.ticket.sz-trip.com/epicSoul/EpicSouls.mp3", |
|||
// 字符级别的时间同步数据 |
|||
characters: [ |
|||
{"BeginIndex": 0, "BeginTime": 0, "EndIndex": 1, "EndTime": 500, "Phoneme": "zhan3", "Text": "展"}, |
|||
{"BeginIndex": 1, "BeginTime": 500, "EndIndex": 2, "EndTime": 1000, "Phoneme": "wang4", "Text": "望"}, |
|||
{"BeginIndex": 2, "BeginTime": 1000, "EndIndex": 3, "EndTime": 1500, "Phoneme": "wei4", "Text": "未"}, |
|||
{"BeginIndex": 3, "BeginTime": 1500, "EndIndex": 4, "EndTime": 2000, "Phoneme": "lai2", "Text": "来"}, |
|||
{"BeginIndex": 4, "BeginTime": 2000, "EndIndex": 5, "EndTime": 2500, "Phoneme": "she4", "Text": "社"}, |
|||
{"BeginIndex": 5, "BeginTime": 2500, "EndIndex": 6, "EndTime": 3000, "Phoneme": "hui4", "Text": "会"}, |
|||
{"BeginIndex": 6, "BeginTime": 3000, "EndIndex": 7, "EndTime": 3500, "Phoneme": "de5", "Text": "的"}, |
|||
{"BeginIndex": 7, "BeginTime": 3500, "EndIndex": 8, "EndTime": 4000, "Phoneme": "fa1", "Text": "发"}, |
|||
{"BeginIndex": 8, "BeginTime": 4000, "EndIndex": 9, "EndTime": 4500, "Phoneme": "zhan3", "Text": "展"}, |
|||
{"BeginIndex": 9, "BeginTime": 4500, "EndIndex": 10, "EndTime": 5000, "Phoneme": "qu1", "Text": "趋"}, |
|||
{"BeginIndex": 10, "BeginTime": 5000, "EndIndex": 11, "EndTime": 5500, "Phoneme": "shi4", "Text": "势"}, |
|||
{"BeginIndex": 11, "BeginTime": 5500, "EndIndex": 12, "EndTime": 5700, "Phoneme": "", "Text": "。"} |
|||
], |
|||
content: `展望未来社会的发展趋势。`, |
|||
}, |
|||
], |
|||
currentChapterIndex: 0, |
|||
playing: false, |
|||
currentTime: 0, |
|||
duration: 0, |
|||
activeCharIndex: -1, |
|||
scrollTop: 0, |
|||
scrollViewHeight: 0, |
|||
}; |
|||
}, |
|||
mounted() { |
|||
this.syncAudioState(); |
|||
this.calculateScrollViewHeight(); |
|||
}, |
|||
onShow() { |
|||
// 页面显示时同步音频状态 |
|||
this.syncAudioState(); |
|||
}, |
|||
onHide() { |
|||
// 页面隐藏时保持音频播放状态 |
|||
// 背景音频会自动继续播放 |
|||
}, |
|||
computed: { |
|||
currentChapter() { |
|||
const chapter = this.chapters[this.currentChapterIndex]; |
|||
// 使用字符级别的时间同步数据 |
|||
const characters = chapter.characters.map((char) => { |
|||
return { |
|||
...char, |
|||
timeInSeconds: char.BeginTime / 1000, // 将毫秒转换为秒 |
|||
}; |
|||
}); |
|||
return { |
|||
...chapter, |
|||
characters: characters, |
|||
}; |
|||
}, |
|||
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.activeCharIndex = -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 characters = this.currentChapter.characters; |
|||
const currentTimeMs = this.currentTime * 1000; // 转换为毫秒 |
|||
for (let i = characters.length - 1; i >= 0; i--) { |
|||
if (currentTimeMs >= characters[i].BeginTime && currentTimeMs <= characters[i].EndTime) { |
|||
if (this.activeCharIndex !== i) { |
|||
this.activeCharIndex = i; |
|||
// 滚动到当前字符,这里简单实现:每个字符高度约30rpx,滚动到当前字符 |
|||
this.scrollTop = Math.floor(i / 20) * 30; // 假设每行约20个字符 |
|||
} |
|||
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; |
|||
} |
|||
|
|||
.character { |
|||
transition: all 0.3s ease; |
|||
padding: 2rpx 4rpx; |
|||
border-radius: 6rpx; |
|||
display: inline; |
|||
margin: 0 1rpx; |
|||
} |
|||
|
|||
.character.active { |
|||
background: linear-gradient( |
|||
135deg, |
|||
rgba(102, 126, 234, 0.25), |
|||
rgba(118, 75, 162, 0.25) |
|||
); |
|||
color: #667eea; |
|||
font-weight: 700; |
|||
box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.3); |
|||
transform: scale(1.1); |
|||
} |
|||
|
|||
/* 底部控制栏样式 */ |
|||
.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