| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- <template>
- <view class="reader-container">
- <!-- 阅读模式选择器 -->
- <view class="mode-selector">
- <button @click="readingMode = 'scroll'" :class="{ active: readingMode === 'scroll' }">
- 滚动模式
- </button>
- <button @click="readingMode = 'page'" :class="{ active: readingMode === 'page' }">
- 翻页模式
- </button>
- </view>
-
- <!-- 滚动阅读模式 -->
- <scroll-view
- v-if="readingMode === 'scroll'"
- scroll-y
- class="scroll-reader"
- @scrolltolower="loadNextChapter"
- >
- <rich-text :nodes="formatContent(chapterContent)" />
- </scroll-view>
-
- <!-- 翻页阅读模式 -->
- <swiper
- v-else
- :current="currentPage"
- circular
- class="page-reader"
- @change="onPageChange"
- >
- <swiper-item v-for="(page, index) in paginatedContent" :key="index">
- <rich-text :nodes="page" />
- </swiper-item>
- </swiper>
-
- <!-- 底部广告 -->
- <view v-if="feedAd" class="bottom-ad">
- <ad :unit-id="feedAd.unitId" ad-type="feed" />
- </view>
- </view>
- </template>
-
- <script setup>
- import { ref, computed, onMounted, watch } from 'vue'
- import { useAdManager } from '@/utils/adManager'
- import { useUserStore } from '@/stores/user'
-
- const props = defineProps({
- chapterId: Number,
- novelId: Number
- })
-
- const userStore = useUserStore()
- const { showRewardAd, showFeedAd } = useAdManager()
-
- // 阅读状态
- const readingMode = ref('scroll') // 'scroll' | 'page'
- const currentPage = ref(0)
- const chapterContent = ref('')
- const paginatedContent = ref([])
- const lastReadPosition = ref(0)
- const feedAd = ref(null)
-
- // 获取章节内容
- const fetchChapterContent = async () => {
- const res = await uni.request({
- url: `https://php-backend.aiyadianzi.ltd/chapter/${props.chapterId}`,
- method: 'GET'
- })
-
- // 清洗内容
- chapterContent.value = cleanContent(res.data.content)
-
- // 分页处理
- paginatedContent.value = paginateContent(chapterContent.value)
-
- // 恢复阅读位置
- if (userStore.isLoggedIn) {
- const position = await getReadingPosition()
- currentPage.value = position.page
- lastReadPosition.value = position.scrollTop
- }
- }
-
- // 内容清洗(移除原始网站信息)
- const cleanContent = (content) => {
- return content
- .replace(/最新网址[::]?\s*[a-z0-9.-]+/gi, '')
- .replace(/www\.[a-z0-9]+\.[a-z]{2,}/gi, '')
- }
-
- // 内容分页(每页800字符)
- const paginateContent = (content) => {
- const pages = []
- const pageSize = 800
- let start = 0
-
- while (start < content.length) {
- pages.push(content.substring(start, start + pageSize))
- start += pageSize
- }
-
- return pages
- }
-
- // 翻页事件处理
- const onPageChange = (e) => {
- currentPage.value = e.detail.current
- saveReadingPosition()
- showRewardAd(props.chapterId)
- }
-
- // 保存阅读位置
- const saveReadingPosition = () => {
- if (!userStore.isLoggedIn) return
-
- uni.request({
- url: 'https://api.aiyadianzi.ltd/reading/progress',
- method: 'POST',
- data: {
- userId: userStore.userId,
- novelId: props.novelId,
- chapterId: props.chapterId,
- page: currentPage.value,
- scrollTop: lastReadPosition.value
- }
- })
- }
-
- // 初始化
- onMounted(async () => {
- await fetchChapterContent()
- feedAd.value = showFeedAd()
- })
-
- // VIP状态变化时更新广告
- watch(() => userStore.isVIP, (isVip) => {
- if (isVip) {
- feedAd.value = null
- } else {
- feedAd.value = showFeedAd()
- }
- })
- </script>
-
- <style scoped>
- .reader-container {
- height: 100vh;
- display: flex;
- flex-direction: column;
- }
-
- .mode-selector {
- display: flex;
- padding: 10px;
- background: var(--header-bg);
-
- button {
- flex: 1;
- margin: 0 5px;
- font-size: 14px;
-
- &.active {
- background: var(--primary-color);
- color: white;
- }
- }
- }
-
- .scroll-reader {
- flex: 1;
- padding: 15px;
- overflow-y: auto;
- }
-
- .page-reader {
- flex: 1;
- }
-
- .bottom-ad {
- height: 100px;
- background: #f5f5f5;
- }
- </style>
|