|
@ -22,8 +22,8 @@ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<scroll-view class="chapter-content" :class="{'full-content': isFullScreen}" |
|
|
|
|
|
@scroll="handleScroll" @tap="handleContentClick"> |
|
|
|
|
|
|
|
|
<view class="chapter-content" :class="{'full-content': isFullScreen}" |
|
|
|
|
|
@tap="handleContentClick"> |
|
|
|
|
|
|
|
|
<view class="chapter-content-item"> |
|
|
<view class="chapter-content-item"> |
|
|
<view class="chapter-title">{{ currentChapter }}</view> |
|
|
<view class="chapter-title">{{ currentChapter }}</view> |
|
@ -32,9 +32,28 @@ |
|
|
{{ paragraph }} |
|
|
{{ paragraph }} |
|
|
</view> |
|
|
</view> |
|
|
</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> |
|
|
|
|
|
|
|
|
</scroll-view> |
|
|
|
|
|
|
|
|
</view> |
|
|
|
|
|
|
|
|
<view class="bottom-bar" :class="{'bottom-bar-hidden': isFullScreen}"> |
|
|
<view class="bottom-bar" :class="{'bottom-bar-hidden': isFullScreen}"> |
|
|
<view class="bottom-left"> |
|
|
<view class="bottom-left"> |
|
@ -48,6 +67,18 @@ |
|
|
</view> |
|
|
</view> |
|
|
<text class="bar-label">{{ isDarkMode ? '白天' : '夜间' }}</text> |
|
|
<text class="bar-label">{{ isDarkMode ? '白天' : '夜间' }}</text> |
|
|
</view> |
|
|
</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> |
|
|
<view class="bottom-right"> |
|
|
<view class="bottom-right"> |
|
|
<button class="outline-btn" |
|
|
<button class="outline-btn" |
|
@ -110,7 +141,23 @@ |
|
|
// 是否需要购买 |
|
|
// 是否需要购买 |
|
|
isPay : false, |
|
|
isPay : false, |
|
|
|
|
|
|
|
|
isBooshelf : 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: { |
|
|
computed: { |
|
@ -124,6 +171,9 @@ |
|
|
}, |
|
|
}, |
|
|
currentChapterInfo() { |
|
|
currentChapterInfo() { |
|
|
return this.chapterList.find(chapter => chapter.id == this.cid) || {} |
|
|
return this.chapterList.find(chapter => chapter.id == this.cid) || {} |
|
|
|
|
|
}, |
|
|
|
|
|
hasNextChapter() { |
|
|
|
|
|
return this.currentIndex >= 0 && this.currentIndex < this.chapterList.length - 1 |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
onLoad({ |
|
|
onLoad({ |
|
@ -139,6 +189,28 @@ |
|
|
this.getBookCatalogList() |
|
|
this.getBookCatalogList() |
|
|
this.isAddBook() |
|
|
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() { |
|
|
mounted() { |
|
|
// 初始设置为全屏模式 |
|
|
// 初始设置为全屏模式 |
|
|
this.isFullScreen = true; |
|
|
this.isFullScreen = true; |
|
@ -183,12 +255,54 @@ |
|
|
// 更新阅读进度到书架 |
|
|
// 更新阅读进度到书架 |
|
|
this.updateReadProgress() |
|
|
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(() => { |
|
|
this.$nextTick(() => { |
|
|
uni.pageScrollTo({ |
|
|
|
|
|
scrollTop: 0, |
|
|
|
|
|
duration: 0 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// 先尝试恢复阅读位置,如果没有保存的位置则滚动到顶部 |
|
|
|
|
|
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 |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
}, |
|
|
}, |
|
@ -205,12 +319,258 @@ |
|
|
handleContentClick() { |
|
|
handleContentClick() { |
|
|
this.toggleFullScreen(); |
|
|
this.toggleFullScreen(); |
|
|
}, |
|
|
}, |
|
|
handleScroll(e) { |
|
|
|
|
|
// scroll-view的滚动事件(如果需要处理scroll-view内部的滚动逻辑) |
|
|
|
|
|
// 目前主要的滚动逻辑已移至onPageScroll处理 |
|
|
|
|
|
}, |
|
|
|
|
|
toggleFullScreen() { |
|
|
toggleFullScreen() { |
|
|
this.isFullScreen = !this.isFullScreen |
|
|
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() { |
|
|
async goToSubscription() { |
|
|
await this.$fetch('buyNovel', { |
|
|
await this.$fetch('buyNovel', { |
|
@ -225,6 +585,9 @@ |
|
|
item, |
|
|
item, |
|
|
index |
|
|
index |
|
|
}) { |
|
|
}) { |
|
|
|
|
|
// 保存当前章节ID用于清除记录 |
|
|
|
|
|
const previousCid = this.cid; |
|
|
|
|
|
|
|
|
this.cid = item.id |
|
|
this.cid = item.id |
|
|
this.isFullScreen = true |
|
|
this.isFullScreen = true |
|
|
this.getBookCatalogDetail() |
|
|
this.getBookCatalogDetail() |
|
@ -241,6 +604,28 @@ |
|
|
this.cid = this.chapterList[index].id |
|
|
this.cid = this.chapterList[index].id |
|
|
this.isFullScreen = true |
|
|
this.isFullScreen = true |
|
|
this.getBookCatalogDetail() |
|
|
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() { |
|
|
addToBookshelf() { |
|
|
this.$fetch('addReadBook', { |
|
|
this.$fetch('addReadBook', { |
|
@ -329,18 +714,18 @@ |
|
|
// 处理视频解锁 |
|
|
// 处理视频解锁 |
|
|
async handleVideoUnlock() { |
|
|
async handleVideoUnlock() { |
|
|
try { |
|
|
try { |
|
|
await this.$fetch('openBookCatalog', { |
|
|
|
|
|
bookId: this.id, |
|
|
|
|
|
catalogId: this.cid |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// await this.$fetch('openBookCatalog', { |
|
|
|
|
|
// bookId: this.id, |
|
|
|
|
|
// catalogId: this.cid |
|
|
|
|
|
// }); |
|
|
|
|
|
|
|
|
uni.showToast({ |
|
|
uni.showToast({ |
|
|
title: '视频解锁成功', |
|
|
|
|
|
icon: 'success' |
|
|
|
|
|
|
|
|
title: '暂未开放', |
|
|
|
|
|
icon: 'none' |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.isPay = false; |
|
|
|
|
|
this.updateSub(); |
|
|
|
|
|
|
|
|
// this.isPay = false; |
|
|
|
|
|
// this.updateSub(); |
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('视频解锁失败:', error); |
|
|
console.error('视频解锁失败:', error); |
|
@ -351,6 +736,26 @@ |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
|
|
|
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> |
|
|
</script> |
|
|
|
|
|
|
|
@ -432,6 +837,45 @@ |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.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 { |
|
|
.top-controls { |
|
@ -650,5 +1094,82 @@ |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 加载更多区域样式 */ |
|
|
|
|
|
.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> |
|
|
</style> |