<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>
|
|
|
|
|
|
|
|
|
|
<scroll-view :scroll-y="!isPay" :key="'scroll-' + cid" id="chapter-scroll" class="chapter-content" :class="{'full-content': isFullScreen}"
|
|
:scroll-top="scrollTop" @scroll="handleScroll" @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>
|
|
|
|
</scroll-view>
|
|
|
|
<view class="bottom-bar" :class="{'bottom-bar-hidden': isFullScreen}">
|
|
<view class="bottom-left">
|
|
<view class="bar-item">
|
|
<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>
|
|
<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"
|
|
@maskClick="toggleFullScreen"
|
|
@subscribe="goToSubscription" />
|
|
|
|
<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: [],
|
|
scrollTop: 0, // 滚动位置
|
|
|
|
// 是否需要购买
|
|
isPay : false,
|
|
}
|
|
},
|
|
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
|
|
},
|
|
},
|
|
onLoad({
|
|
id,
|
|
cid
|
|
}) {
|
|
this.id = id
|
|
this.cid = cid
|
|
this.getDateil()
|
|
this.getBookCatalogDetail()
|
|
},
|
|
onShow() {
|
|
this.getBookCatalogList()
|
|
},
|
|
mounted() {
|
|
// 初始设置为全屏模式
|
|
this.isFullScreen = true;
|
|
},
|
|
methods: {
|
|
getDateil() {
|
|
this.$fetch('getBookDetail', {
|
|
id: this.id
|
|
}).then(res => {
|
|
this.novelData = 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.$nextTick(() => {
|
|
this.scrollTop = 0; // 设置scroll-view滚动到顶部
|
|
})
|
|
})
|
|
},
|
|
getBookCatalogList() {
|
|
this.$fetch('getBookCatalogList', {
|
|
bookId: this.id,
|
|
pageNo: 1,
|
|
pageSize: 9999999,
|
|
reverse: 0,
|
|
}).then(res => {
|
|
this.chapterList = res.records
|
|
})
|
|
},
|
|
handleContentClick() {
|
|
this.toggleFullScreen();
|
|
},
|
|
handleScroll(e) {
|
|
// 获取滚动位置
|
|
const scrollTop = e.detail.scrollTop;
|
|
this.scrollTop = scrollTop; // 更新当前滚动位置
|
|
|
|
// 滚动时触发订阅弹窗
|
|
if (scrollTop > 50 && !this.popupShown) {
|
|
this.$refs.subscriptionPopup.open();
|
|
this.popupShown = true;
|
|
}
|
|
},
|
|
toggleFullScreen() {
|
|
this.isFullScreen = !this.isFullScreen
|
|
},
|
|
async goToSubscription() {
|
|
await this.$fetch('buyNovel', {
|
|
bookId : this.id,
|
|
novelId : this.cid,
|
|
})
|
|
|
|
this.isPay = false
|
|
this.updateSub()
|
|
},
|
|
selectChapter({
|
|
item,
|
|
index
|
|
}) {
|
|
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()
|
|
},
|
|
|
|
// 更新阅读进度到书架
|
|
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()
|
|
}
|
|
},
|
|
},
|
|
}
|
|
</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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|