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