- <template>
- <!-- 小说文本页面 -->
- <view class="reader-container" :class="{'dark-mode': isDarkMode}">
- <view class="top-controls" :class="{'top-controls-hidden': isFullScreen}">
- <view class="controls-inner">
- <view class="left" @click="$utils.navigateBack">
- <uv-icon name="arrow-left" :color="isDarkMode ? '#ccc' : '#333'" size="46rpx"></uv-icon>
- </view>
- <view class="center">
- <text class="title">{{ novelData.name }}</text>
- <text class="chapter">{{ currentChapter }}</text>
- </view>
- <!-- <view class="right">
- <uv-icon name="more-dot-fill" color="#333" size="46rpx"></uv-icon>
- </view> -->
- </view>
- <!-- <view class="progress-bar">
- <view class="progress-inner" :style="{width: readProgress + '%'}"></view>
- </view> -->
- </view>
-
-
-
-
- <view class="chapter-content" :class="{'full-content': isFullScreen}"
- @tap="handleContentClick">
-
- <view class="chapter-content-item">
- <view class="chapter-title">{{ currentChapter }}</view>
- <view class="paragraph-content">
- <view class="paragraph" v-for="(paragraph, index) in paragraphs" :key="index">
- {{ paragraph }}
- </view>
- </view>
-
- <!-- 加载更多提示区域 -->
- <view class="load-more-area" v-if="autoLoadNext && !isPay">
- <view class="load-more-content" v-if="!isAutoLoading && hasNextChapter && !isAtBottom">
- <view class="load-more-line"></view>
- <text class="load-more-text">滚动到底部停留2秒自动加载下一章</text>
- <view class="load-more-line"></view>
- </view>
- <view class="waiting-content" v-else-if="!isAutoLoading && hasNextChapter && isAtBottom">
- <uv-icon name="time" color="#ff6b6b" size="30rpx"></uv-icon>
- <text class="waiting-text">正在底部停留 {{ bottomStayTime.toFixed(1) }}s / 2.0s</text>
- <text class="tip-text">向上滚动可取消</text>
- </view>
- <view class="loading-content" v-else-if="isAutoLoading">
- <uv-icon name="clock" color="#4a90e2" size="30rpx"></uv-icon>
- <text class="loading-text">{{ countdown }}秒后自动跳转下一章</text>
- <text class="cancel-text" @tap="cancelAutoLoad">点击取消</text>
- </view>
- </view>
- </view>
-
- </view>
-
- <view class="bottom-bar" :class="{'bottom-bar-hidden': isFullScreen}">
- <view class="bottom-left">
- <view class="bar-item" v-if="!isBooshelf" @click="addToBookshelf">
- <view class="bar-icon"> <uv-icon name="plus"></uv-icon> </view>
- <text class="bar-label">加入书架</text>
- </view>
- <view class="bar-item" @click="toggleThemeMode">
- <view class="bar-icon">
- <uv-icon :name="isDarkMode ? 'eye' : 'eye-fill'"></uv-icon>
- </view>
- <text class="bar-label">{{ isDarkMode ? '白天' : '夜间' }}</text>
- </view>
- <view class="bar-item" @click="toggleAutoLoad">
- <view class="bar-icon">
- <uv-icon :name="autoLoadNext ? 'play-circle-fill' : 'play-circle'"></uv-icon>
- </view>
- <text class="bar-label">{{ autoLoadNext ? '自动' : '手动' }}</text>
- </view>
- <!-- <view class="bar-item" @click="resetReadingPosition">
- <view class="bar-icon">
- <uv-icon name="rewind-left"></uv-icon>
- </view>
- <text class="bar-label">重读</text>
- </view> -->
- </view>
- <view class="bottom-right">
- <button class="outline-btn"
- @click="nextChapter(-1)">
- <text class="btn-text">上一章</text>
- </button>
- <button class="outline-btn" @click="$refs.chapterPopup.open()">
- <text class="btn-text">目录</text>
- </button>
- <button class="outline-btn"
- @click="nextChapter(1)">
- <text class="btn-text">下一章</text>
- </button>
- </view>
- </view>
-
- <!-- 使用封装的订阅弹窗组件 -->
- <subscriptionPopup ref="subscriptionPopup"
- :chapterList="chapterList"
- :currentChapter="currentChapterInfo"
- :currentIndex="currentIndex"
- :bookId="id"
- @maskClick="toggleFullScreen"
- @subscribe="goToSubscription"
- @batchSubscribe="handleBatchSubscribe"
- @videoUnlock="handleVideoUnlock" />
-
- <novelVotePopup ref="novelVotePopup" />
-
- <chapterPopup ref="chapterPopup" :chapterList="chapterList" :currentIndex="currentIndex"
- @selectChapter="selectChapter" />
- </view>
- </template>
-
- <script>
- import chapterPopup from '../components/novel/chapterPopup.vue'
- import novelVotePopup from '../components/novel/novelVotePopup.vue'
- import subscriptionPopup from '../components/novel/subscriptionPopup.vue'
- import themeMixin from '@/mixins/themeMode.js' // 导入主题混合器
-
- export default {
- components: {
- chapterPopup,
- novelVotePopup,
- subscriptionPopup,
- },
- mixins: [themeMixin], // 使用主题混合器
- data() {
- return {
- isFullScreen: false,
- popupShown: false, // 只弹一次
- currentChapter: "",
- readProgress: 15, // 阅读进度百分比
- paragraphs: [],
- id: 0,
- cid: 0,
- novelData: {},
- chapterList: [],
-
- // 是否需要购买
- isPay : false,
-
- isBooshelf : false,
-
- // 自动加载下一章相关
- autoLoadNext: true, // 是否启用自动加载下一章
- lastScrollTop: 0, // 记录上次滚动位置
- isAutoLoading: false, // 是否正在自动加载
- autoLoadTimer: null, // 自动加载计时器
- countdown: 3, // 倒计时秒数
- scrollThrottle: null, // 滚动节流计时器
- triggerChecked: false, // 是否已经检查过触发条件
- bottomStayTime: 0, // 在底部停留时间
- bottomCheckTimer: null, // 底部停留检测计时器
- isAtBottom: false, // 是否在底部区域
-
- // 阅读位置记录相关
- savePositionThrottle: null, // 保存位置节流计时器
- restorePositionTimer: null, // 恢复位置计时器
- }
- },
- computed: {
- currentIndex() {
- for (var index = 0; index < this.chapterList.length; index++) {
- var element = this.chapterList[index];
- if (element.id == this.cid) return index
- }
-
- return -1
- },
- currentChapterInfo() {
- return this.chapterList.find(chapter => chapter.id == this.cid) || {}
- },
- hasNextChapter() {
- return this.currentIndex >= 0 && this.currentIndex < this.chapterList.length - 1
- }
- },
- onLoad({
- id,
- cid
- }) {
- this.id = id
- this.cid = cid
- this.getDateil()
- this.getBookCatalogDetail()
- },
- onShow() {
- this.getBookCatalogList()
- this.isAddBook()
- },
- onPageScroll(e) {
- const scrollTop = e.scrollTop;
-
- // 保存阅读位置
- if (scrollTop > 100) { // 滚动超过100rpx才开始记录,避免记录无意义的顶部位置
- this.saveReadingPosition(scrollTop);
- }
-
- // 页面滚动监听,实现自动加载下一章
- if (!this.autoLoadNext || this.isAutoLoading || this.isPay || !this.hasNextChapter || this.triggerChecked) {
- return;
- }
-
- // 节流处理,避免频繁执行
- if (this.scrollThrottle) {
- clearTimeout(this.scrollThrottle);
- }
-
- this.scrollThrottle = setTimeout(() => {
- this.checkAutoLoadTrigger(scrollTop);
- }, 100);
- },
- mounted() {
- // 初始设置为全屏模式
- this.isFullScreen = true;
- },
- methods: {
- getDateil() {
- this.$fetch('getBookDetail', {
- id: this.id
- }).then(res => {
- this.novelData = res
- })
- },
- async isAddBook(){
- this.$fetch('isAddBook', {
- bookId: this.id
- }).then(res => {
- this.isBooshelf = res
- })
- },
- async getBookCatalogDetail() {
-
- this.isPay = await this.$fetch('getMyShopNovel', {
- bookId : this.id,
- novelId : this.cid,
- })
-
- this.isPay = !this.isPay
-
- this.$fetch('getBookCatalogDetail', {
- id: this.cid
- }).then(res => {
-
- this.paragraphs = res.details && res.details.split('\n')
- this.currentChapter = res.title
-
- if(res.isPay != 'Y'){
- this.isPay = false
- }
-
- this.updateSub()
-
- // 更新阅读进度到书架
- this.updateReadProgress()
-
- // 重置自动加载相关状态
- this.lastScrollTop = 0;
- if (this.autoLoadTimer) {
- clearInterval(this.autoLoadTimer);
- this.autoLoadTimer = null;
- }
- if (this.scrollThrottle) {
- clearTimeout(this.scrollThrottle);
- this.scrollThrottle = null;
- }
- if (this.savePositionThrottle) {
- clearTimeout(this.savePositionThrottle);
- this.savePositionThrottle = null;
- }
- if (this.restorePositionTimer) {
- clearTimeout(this.restorePositionTimer);
- this.restorePositionTimer = null;
- }
- this.clearBottomTimer(); // 清除底部计时器
- this.isAutoLoading = false;
- this.countdown = 3;
- this.triggerChecked = false; // 重置触发检查标记
- this.isAtBottom = false; // 重置底部状态
- this.bottomStayTime = 0; // 重置停留时间
-
- // 滚动到顶部或恢复阅读位置
- this.$nextTick(() => {
- // 先尝试恢复阅读位置,如果没有保存的位置则滚动到顶部
- const key = this.getReadingPositionKey();
- let hasSavedPosition = false;
-
- try {
- const positionData = uni.getStorageSync(key);
- hasSavedPosition = positionData && positionData.scrollTop > 100;
- } catch (error) {
- console.warn('检查保存位置失败:', error);
- }
-
- if (hasSavedPosition) {
- // 有保存的位置,恢复到上次阅读位置
- this.restoreReadingPosition();
- } else {
- // 没有保存的位置,滚动到顶部
- uni.pageScrollTo({
- scrollTop: 0,
- duration: 0
- });
- }
- })
- })
- },
- getBookCatalogList() {
- this.$fetch('getBookCatalogList', {
- bookId: this.id,
- pageNo: 1,
- pageSize: 9999999,
- reverse: 0,
- }).then(res => {
- this.chapterList = res.records
- })
- },
- handleContentClick() {
- this.toggleFullScreen();
- },
- toggleFullScreen() {
- this.isFullScreen = !this.isFullScreen
- },
- // 检查自动加载触发条件
- checkAutoLoadTrigger(scrollTop) {
- // 使用更严格的触发条件:距离底部很近且停留一段时间
- uni.createSelectorQuery().select('.chapter-content').boundingClientRect((rect) => {
- if (rect) {
- const windowHeight = uni.getSystemInfoSync().windowHeight;
- const contentBottom = rect.bottom;
- const distanceToBottom = contentBottom - windowHeight;
-
- // 严格条件:距离底部100rpx以内才算到达底部
- const isNearBottom = distanceToBottom < 100;
-
- if (isNearBottom) {
- // 如果刚到达底部,开始计时
- if (!this.isAtBottom) {
- this.isAtBottom = true;
- this.bottomStayTime = 0;
- console.log('到达章节底部,开始计时...');
- this.startBottomTimer();
- }
- } else {
- // 如果离开底部区域,重置状态
- if (this.isAtBottom) {
- this.isAtBottom = false;
- this.bottomStayTime = 0;
- this.clearBottomTimer();
- console.log('离开章节底部,重置计时');
- }
- }
- }
-
- this.lastScrollTop = scrollTop;
- }).exec();
- },
-
- // 开始底部停留计时
- startBottomTimer() {
- this.clearBottomTimer(); // 清除之前的计时器
-
- this.bottomCheckTimer = setInterval(() => {
- // 只有在底部状态下才继续计时
- if (this.isAtBottom) {
- this.bottomStayTime += 0.1; // 每100ms增加0.1秒
- console.log('底部停留时间:', this.bottomStayTime.toFixed(1) + 's');
-
- // 在底部停留2秒后触发自动加载
- if (this.bottomStayTime >= 2) {
- console.log('底部停留足够时间,触发自动加载');
- this.clearBottomTimer();
- this.autoLoadNextChapter();
- }
- } else {
- // 如果不在底部,停止计时
- this.clearBottomTimer();
- }
- }, 100);
- },
-
- // 清除底部计时器
- clearBottomTimer() {
- if (this.bottomCheckTimer) {
- clearInterval(this.bottomCheckTimer);
- this.bottomCheckTimer = null;
- }
- },
-
- // 生成阅读位置存储key
- getReadingPositionKey() {
- return `novel_reading_position_${this.id}_${this.cid}`;
- },
-
- // 保存阅读位置(带节流)
- saveReadingPosition(scrollTop) {
- // 节流处理,避免频繁保存
- if (this.savePositionThrottle) {
- clearTimeout(this.savePositionThrottle);
- }
-
- this.savePositionThrottle = setTimeout(() => {
- const key = this.getReadingPositionKey();
- const positionData = {
- scrollTop: scrollTop,
- timestamp: Date.now(),
- chapterTitle: this.currentChapter
- };
-
- try {
- uni.setStorageSync(key, positionData);
- console.log('保存阅读位置:', scrollTop, '章节:', this.currentChapter);
- } catch (error) {
- console.warn('保存阅读位置失败:', error);
- }
- }, 1000); // 1秒节流
- },
-
- // 恢复阅读位置
- restoreReadingPosition() {
- const key = this.getReadingPositionKey();
-
- try {
- const positionData = uni.getStorageSync(key);
-
- if (positionData && positionData.scrollTop > 0) {
- console.log('恢复阅读位置:', positionData.scrollTop, '章节:', positionData.chapterTitle);
-
- // 检查是否是最近保存的(5分钟内),如果是则显示提示
- const now = Date.now();
- const saveTime = positionData.timestamp || 0;
- const shouldShowToast = (now - saveTime) > 5 * 60 * 1000; // 5分钟
-
- // 等待DOM更新后再恢复位置
- this.$nextTick(() => {
- // 延迟恢复,确保内容已渲染
- this.restorePositionTimer = setTimeout(() => {
- uni.pageScrollTo({
- scrollTop: positionData.scrollTop,
- duration: 0
- });
-
- // 只有在距离上次保存超过5分钟时才显示提示
- if (shouldShowToast) {
- uni.showToast({
- title: '已恢复阅读位置',
- icon: 'none',
- duration: 1500
- });
- }
- }, 500);
- });
- }
- } catch (error) {
- console.warn('恢复阅读位置失败:', error);
- }
- },
-
- // 清除当前章节的阅读位置记录
- clearReadingPosition() {
- const key = this.getReadingPositionKey();
-
- try {
- uni.removeStorageSync(key);
- console.log('清除阅读位置记录:', this.currentChapter);
- } catch (error) {
- console.warn('清除阅读位置失败:', error);
- }
- },
-
- // 重置阅读位置(清除记录并回到顶部)
- resetReadingPosition() {
- uni.showModal({
- title: '重新阅读',
- content: '确定要清除当前章节的阅读记录并回到开头吗?',
- confirmText: '确定',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- // 清除阅读位置记录
- this.clearReadingPosition();
-
- // 回到顶部
- uni.pageScrollTo({
- scrollTop: 0,
- duration: 0
- });
-
- uni.showToast({
- title: '已重置到章节开头',
- icon: 'success',
- duration: 1500
- });
- }
- }
- });
- },
- // 自动加载下一章
- autoLoadNextChapter() {
- if (this.isAutoLoading || !this.hasNextChapter || this.triggerChecked) return;
-
- this.triggerChecked = true; // 标记已触发,避免重复
- this.isAutoLoading = true;
- this.countdown = 3;
-
- // 清除之前的计时器
- if (this.autoLoadTimer) {
- clearInterval(this.autoLoadTimer);
- }
-
- console.log('开始自动加载下一章倒计时');
-
- // 开始倒计时
- this.startCountdown();
- },
-
- // 开始倒计时
- startCountdown() {
- const countdownInterval = setInterval(() => {
- this.countdown--;
-
- if (this.countdown <= 0) {
- clearInterval(countdownInterval);
- // 清除当前章节的阅读记录,因为用户已经读完了
- this.clearReadingPosition();
- this.nextChapter(1);
- this.isAutoLoading = false;
- this.countdown = 3;
- }
- }, 1000);
-
- // 保存interval引用以便取消
- this.autoLoadTimer = countdownInterval;
- },
- // 取消自动加载
- cancelAutoLoad() {
- if (this.autoLoadTimer) {
- clearInterval(this.autoLoadTimer);
- this.autoLoadTimer = null;
- }
- if (this.savePositionThrottle) {
- clearTimeout(this.savePositionThrottle);
- this.savePositionThrottle = null;
- }
- if (this.restorePositionTimer) {
- clearTimeout(this.restorePositionTimer);
- this.restorePositionTimer = null;
- }
- this.clearBottomTimer(); // 清除底部计时器
- this.isAutoLoading = false;
- this.countdown = 3;
- this.triggerChecked = false; // 重置触发标记,允许重新触发
- this.isAtBottom = false; // 重置底部状态
- this.bottomStayTime = 0; // 重置停留时间
- uni.showToast({
- title: '已取消自动跳转',
- icon: 'none'
- });
- },
- // 切换自动加载功能
- toggleAutoLoad() {
- this.autoLoadNext = !this.autoLoadNext;
-
- // 如果关闭自动加载且正在加载中,则取消加载
- if (!this.autoLoadNext && this.isAutoLoading) {
- this.cancelAutoLoad();
- }
-
- uni.showToast({
- title: this.autoLoadNext ? '已开启自动加载' : '已关闭自动加载',
- icon: 'none'
- });
- },
- async goToSubscription() {
- await this.$fetch('buyNovel', {
- bookId : this.id,
- novelId : this.cid,
- })
-
- this.isPay = false
- this.updateSub()
- },
- selectChapter({
- item,
- index
- }) {
- // 保存当前章节ID用于清除记录
- const previousCid = this.cid;
-
- this.cid = item.id
- this.isFullScreen = true
- this.getBookCatalogDetail()
- },
- nextChapter(next) {
- let index = this.currentIndex + next
- if(index < 0 || index >= this.chapterList.length){
- uni.showToast({
- title: '到底了',
- icon: 'none'
- })
- return
- }
- this.cid = this.chapterList[index].id
- this.isFullScreen = true
- this.getBookCatalogDetail()
- // 跳转后重置自动加载状态
- this.isAutoLoading = false;
- this.triggerChecked = false;
- if (this.autoLoadTimer) {
- clearInterval(this.autoLoadTimer);
- this.autoLoadTimer = null;
- }
- if (this.scrollThrottle) {
- clearTimeout(this.scrollThrottle);
- this.scrollThrottle = null;
- }
- if (this.savePositionThrottle) {
- clearTimeout(this.savePositionThrottle);
- this.savePositionThrottle = null;
- }
- if (this.restorePositionTimer) {
- clearTimeout(this.restorePositionTimer);
- this.restorePositionTimer = null;
- }
- this.clearBottomTimer(); // 清除底部计时器
- this.isAtBottom = false; // 重置底部状态
- this.bottomStayTime = 0; // 重置停留时间
- },
- addToBookshelf() {
- this.$fetch('addReadBook', {
- shopId: this.id,
- name: this.novelData.name,
- image: this.novelData.image,
- novelId : this.fastCatalog && this.fastCatalog.id
- }).then(res => {
- this.isBooshelf = true
- uni.showToast({
- title: '已加入书架',
- icon: 'success'
- })
- })
- },
- // 更新阅读进度到书架
- updateReadProgress() {
- if (!this.id || !this.cid) return;
-
- this.$fetch('saveOrUpdateReadBook', {
- shopId: this.id, // 书籍id
- novelId: this.cid, // 章节id
- name: this.novelData.name,
- image: this.novelData.image
- }).then(res => {
- console.log('阅读进度已更新');
- }).catch(err => {
- console.error('更新阅读进度失败:', err);
- });
- },
- updateSub(){
- if(this.isPay){
- this.$refs.subscriptionPopup.open()
- }else{
- this.$refs.subscriptionPopup.close()
- }
- },
-
- // 处理批量订阅
- async handleBatchSubscribe(batchCount) {
- try {
- // 获取从当前章节开始的连续章节ID
- const chapterIds = [];
- const startIndex = this.currentIndex;
-
- for (let i = 0; i < batchCount && (startIndex + i) < this.chapterList.length; i++) {
- const chapter = this.chapterList[startIndex + i];
- if (chapter.isPay === 'Y' && !chapter.pay) {
- chapterIds.push(chapter.id);
- }
- }
-
- if (chapterIds.length === 0) {
- uni.showToast({
- title: '没有需要购买的章节',
- icon: 'none'
- });
- return;
- }
-
- // 调用批量订阅接口
- await this.$fetch('buyNovel', {
- bookId: this.id,
- novelId: chapterIds.join(',')
- });
-
- uni.showToast({
- title: `成功订阅${chapterIds.length}章`,
- icon: 'success'
- });
-
- // 刷新章节列表状态
- this.getBookCatalogList();
- this.isPay = false;
- this.updateSub();
-
- } catch (error) {
- console.error('批量订阅失败:', error);
- uni.showToast({
- title: '订阅失败,请重试',
- icon: 'none'
- });
- }
- },
-
- // 处理视频解锁
- async handleVideoUnlock() {
- try {
- // await this.$fetch('openBookCatalog', {
- // bookId: this.id,
- // catalogId: this.cid
- // });
-
- uni.showToast({
- title: '暂未开放',
- icon: 'none'
- });
-
- // this.isPay = false;
- // this.updateSub();
-
- } catch (error) {
- console.error('视频解锁失败:', error);
- uni.showToast({
- title: '解锁失败,请重试',
- icon: 'none'
- });
- }
- },
- },
- beforeDestroy() {
- // 组件销毁时清除所有计时器
- if (this.autoLoadTimer) {
- clearInterval(this.autoLoadTimer);
- this.autoLoadTimer = null;
- }
- if (this.scrollThrottle) {
- clearTimeout(this.scrollThrottle);
- this.scrollThrottle = null;
- }
- if (this.savePositionThrottle) {
- clearTimeout(this.savePositionThrottle);
- this.savePositionThrottle = null;
- }
- if (this.restorePositionTimer) {
- clearTimeout(this.restorePositionTimer);
- this.restorePositionTimer = null;
- }
- this.clearBottomTimer(); // 清除底部计时器
- }
- }
- </script>
-
- <style lang="scss" scoped>
- .reader-container {
- min-height: 100vh;
- background: #fff;
- display: flex;
- flex-direction: column;
- position: relative;
- overflow: hidden;
-
- &.dark-mode {
- background: #1a1a1a;
-
- .top-controls {
- background: rgba(34, 34, 34, 0.98);
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.2);
-
- .controls-inner {
- .center {
- .title {
- color: #eee;
- }
-
- .chapter {
- color: #bbb;
- }
- }
- }
-
- .progress-bar {
- background: #333;
-
- .progress-inner {
- background: #4a90e2;
- }
- }
- }
-
- .chapter-content {
- color: #ccc;
-
- .chapter-content-item {
- .chapter-title {
- color: #eee;
- }
-
- .paragraph-content {
- .paragraph {
- color: #bbb;
- }
- }
- }
- }
-
- .bottom-bar {
- background: #222;
- box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.2);
-
- .bottom-left {
- .bar-item {
- .bar-label {
- color: #999;
- }
- }
- }
-
- .bottom-right {
- .outline-btn {
- background: #222;
- color: #999;
- border: 2rpx solid #999;
-
- .btn-text {
- color: #999;
- border-bottom: 2rpx solid #999;
- }
- }
- }
- }
-
-
-
- .load-more-area {
- .load-more-content {
- .load-more-line {
- background: linear-gradient(to right, transparent, #444, transparent);
- }
-
- .load-more-text {
- color: #666;
- }
- }
-
- .waiting-content {
- background: #4a3728;
- border-color: #6b5b47;
-
- .waiting-text {
- color: #ff8a80;
- }
-
- .tip-text {
- color: #999;
- }
- }
-
- .loading-content {
- background: #2a2a2a;
-
- .loading-text {
- color: #4a90e2;
- }
-
- .cancel-text {
- color: #999;
- }
- }
- }
- }
-
- .top-controls {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background: rgba(255, 255, 255, 0.98);
- padding-top: calc(var(--status-bar-height) + 10rpx);
- z-index: 100000;
- transform: translateY(0);
- transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease;
- box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-
- .controls-inner {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx 32rpx;
- position: relative;
-
- .left {
- width: 100rpx;
- display: flex;
- justify-content: flex-start;
- align-items: center;
- }
-
- .center {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-
- .title {
- font-size: 32rpx;
- font-weight: 500;
- color: #333;
- margin-bottom: 4rpx;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 320rpx;
- }
-
- .chapter {
- font-size: 24rpx;
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 320rpx;
- }
- }
-
- .right {
- width: 100rpx;
- display: flex;
- justify-content: flex-end;
- align-items: center;
- }
- }
-
- .progress-bar {
- height: 4rpx;
- background: #f0f0f0;
- width: 100%;
- position: relative;
-
- .progress-inner {
- height: 100%;
- background: #4a90e2;
- transition: width 0.3s;
- }
- }
-
- &.top-controls-hidden {
- transform: translateY(-100%);
- opacity: 0;
- }
- }
-
- .chapter-content {
- flex: 1;
- padding: 0 32rpx;
- font-size: 28rpx;
- color: #222;
- line-height: 2.2;
- padding-top: 160rpx;
- /* 为导航栏预留空间,不随状态变化 */
- padding-bottom: 180rpx;
- width: 100%;
- box-sizing: border-box;
- overflow-x: hidden;
- transition: color 0.3s ease, background-color 0.3s ease;
-
- .chapter-content-item {
- width: 100%;
-
- .chapter-title {
- font-size: 36rpx;
- font-weight: bold;
- margin: 20rpx 0 40rpx 0;
- text-align: center;
- word-break: break-word;
- white-space: normal;
- transition: color 0.3s ease;
- }
-
- .paragraph-content {
- width: 100%;
-
- .paragraph {
- text-indent: 2em;
- margin-bottom: 30rpx;
- line-height: 1.8;
- font-size: 30rpx;
- color: #333;
- word-wrap: break-word;
- word-break: normal;
- white-space: normal;
- transition: color 0.3s ease;
- }
- }
- }
-
- &.full-content {
- /* 不再修改顶部padding,保持内容位置不变 */
- }
- }
-
- .bottom-bar {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- background: #fff;
- display: flex;
- justify-content: center;
- align-items: center;
- height: 180rpx;
- box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
- z-index: 100000;
- padding: 0 40rpx 10rpx 40rpx;
- transform: translateY(0);
- transition: transform 0.3s ease-in-out, background-color 0.3s ease;
-
- &.bottom-bar-hidden {
- transform: translateY(100%);
- }
-
- .bottom-left {
- display: flex;
- align-items: flex-end;
- gap: 48rpx;
-
- .bar-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: flex-end;
-
- .bar-icon {
- width: 48rpx;
- height: 48rpx;
- margin-bottom: 4rpx;
- margin-right: 1rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .bar-label {
- font-size: 22rpx;
- color: #b3b3b3;
- margin-top: 2rpx;
- transition: color 0.3s ease;
- }
- }
- }
-
- .bottom-right {
- display: flex;
- align-items: flex-end;
- gap: 22rpx;
- margin-left: 40rpx;
- text-overflow: ellipsis;
-
- .outline-btn {
- flex-shrink: 0;
- min-width: 110rpx;
- padding: 0 26rpx;
- height: 60rpx;
- line-height: 60rpx;
- background: #fff;
- color: #223a7a;
- border: 2rpx solid #223a7a;
- border-radius: 32rpx;
- font-size: 26rpx;
- font-weight: bold;
- margin: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
-
- .btn-text {
- font-weight: bold;
- color: #223a7a;
- font-size: 26rpx;
- border-bottom: 2rpx solid #223a7a;
- padding-bottom: 2rpx;
- transition: color 0.3s ease, border-color 0.3s ease;
- }
- }
- }
- }
-
-
-
- /* 加载更多区域样式 */
- .load-more-area {
- margin-top: 60rpx;
- padding: 40rpx 0;
- width: 100%;
-
- .load-more-content {
- display: flex;
- align-items: center;
- justify-content: center;
-
- .load-more-line {
- flex: 1;
- height: 2rpx;
- background: linear-gradient(to right, transparent, #e0e0e0, transparent);
- }
-
- .load-more-text {
- font-size: 24rpx;
- color: #999;
- margin: 0 30rpx;
- white-space: nowrap;
- }
- }
-
- .waiting-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 20rpx;
- background: #fff3cd;
- border-radius: 16rpx;
- margin: 0 60rpx;
- border: 2rpx solid #ffeaa7;
-
- .waiting-text {
- font-size: 28rpx;
- color: #ff6b6b;
- margin: 10rpx 0 5rpx 0;
- font-weight: 500;
- }
-
- .tip-text {
- font-size: 22rpx;
- color: #666;
- font-style: italic;
- }
- }
-
- .loading-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 20rpx;
- background: #f8f9fa;
- border-radius: 16rpx;
- margin: 0 60rpx;
-
- .loading-text {
- font-size: 28rpx;
- color: #4a90e2;
- margin: 10rpx 0 5rpx 0;
- font-weight: 500;
- }
-
- .cancel-text {
- font-size: 22rpx;
- color: #666;
- text-decoration: underline;
- }
- }
- }
- }
- </style>
|