- <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 class="chapter-content" :class="{'full-content': isFullScreen}" @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" @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: [],
- }
- },
- 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
- })
- },
- getBookCatalogDetail() {
- this.$fetch('getBookCatalogDetail', {
- id: this.cid
- }).then(res => {
- this.paragraphs = res.details && res.details.split('\n')
- this.currentChapter = res.title
- })
- },
- getBookCatalogList() {
- this.$fetch('getBookCatalogList', {
- bookId: this.id,
- pageNo: 1,
- pageSize: 9999999,
- reverse: 0,
- }).then(res => {
- this.chapterList = res.records
- this.catalog = res.records[res.records.length - 1]
- this.fastCatalog = res.records[0]
- })
- },
- handleContentClick() {
- this.toggleFullScreen();
- },
- handleScroll(e) {
- // 获取滚动位置
- const scrollTop = e.detail.scrollTop;
-
- // 滚动时触发订阅弹窗
- if (scrollTop > 50 && !this.popupShown) {
- this.$refs.subscriptionPopup.open();
- this.popupShown = true;
- }
- },
- toggleFullScreen() {
- this.isFullScreen = !this.isFullScreen
- },
- goToSubscription() {
- uni.navigateTo({
- url: '/pages_order/novel/SubscriptionInformation'
- })
- },
- 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()
- },
- },
- }
- </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: #4a90e2;
- border: 2rpx solid #4a90e2;
-
- .btn-text {
- color: #4a90e2;
- border-bottom: 2rpx solid #4a90e2;
- }
- }
- }
- }
- }
-
- .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: 100;
- 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: 10;
- 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: 32rpx;
- margin-left: 40rpx;
-
- .outline-btn {
- min-width: 110rpx;
- padding: 0 28rpx;
- height: 60rpx;
- line-height: 60rpx;
- background: #fff;
- color: #223a7a;
- border: 2rpx solid #223a7a;
- border-radius: 32rpx;
- font-size: 28rpx;
- 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: 28rpx;
- border-bottom: 2rpx solid #223a7a;
- padding-bottom: 2rpx;
- transition: color 0.3s ease, border-color 0.3s ease;
- }
- }
- }
- }
- }
- </style>
|