小说小程序前端代码仓库(小程序)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1175 lines
29 KiB

<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>