更新:增加“我的书单”功能
This commit is contained in:
888
pages/book/reader.vue
Normal file
888
pages/book/reader.vue
Normal file
@@ -0,0 +1,888 @@
|
||||
<template>
|
||||
<view class="reader-page" :class="themeClass">
|
||||
<!-- 顶部标题栏 (可隐藏) -->
|
||||
<view class="reader-header" :style="{ top: notchHeight + 'px' }">
|
||||
<wd-icon name="arrow-left" size="20px" @click="goBack" />
|
||||
<text class="chapter-title">{{ currentChapterTitle }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 阅读内容区域 -->
|
||||
<view class="reader-content" @click="toggleControls">
|
||||
<!-- 左右翻页模式 -->
|
||||
<view
|
||||
v-if="readMode === 'page'"
|
||||
class="page-mode"
|
||||
:style="{ height: wrapHeight + 'px' }"
|
||||
@touchstart="onTouchStart"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<view
|
||||
class="content-wrapper"
|
||||
:style="{ top: currentTop + 'px' }"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in contentList"
|
||||
:key="index"
|
||||
:style="contentStyle"
|
||||
class="content-item"
|
||||
>
|
||||
<text v-if="!isImage(item.content)">{{ item.content }}</text>
|
||||
<image
|
||||
v-else
|
||||
:src="item.content"
|
||||
:style="getImageStyle(item.otherContent)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 页码指示器 -->
|
||||
<view v-show="showControls" class="page-indicator">
|
||||
{{ currentPage }} / {{ totalPages }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上下滚动模式 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
scroll-y
|
||||
class="scroll-mode"
|
||||
:style="{ height: wrapHeightScroll + 'px' }"
|
||||
:scroll-into-view="scrollTarget"
|
||||
@scrolltolower="onScrollToBottom"
|
||||
>
|
||||
<view id="content-top" class="content-wrapper">
|
||||
<view
|
||||
v-for="(item, index) in contentList"
|
||||
:key="index"
|
||||
:style="contentStyle"
|
||||
class="content-item"
|
||||
>
|
||||
<text v-if="!isImage(item.content)">{{ item.content }}</text>
|
||||
<image
|
||||
v-else
|
||||
:src="item.content"
|
||||
:style="getImageStyle(item.otherContent)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 (可隐藏) -->
|
||||
<view v-show="showControls" class="reader-footer">
|
||||
<view class="footer-btn" :class="{ active: catalogVisible }" @click="showCatalog">
|
||||
<text>{{ $t('book.contents') }}</text>
|
||||
</view>
|
||||
<view class="divider" />
|
||||
<view class="footer-btn" :class="{ active: settingsVisible }" @click="showSettings">
|
||||
<text>{{ $t('book.set') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 目录弹窗 -->
|
||||
<wd-popup v-model="catalogVisible" position="bottom" @close="closeCatalog">
|
||||
<view class="catalog-popup">
|
||||
<text class="popup-title">{{ $t('book.zjContents') }}</text>
|
||||
<scroll-view scroll-y class="chapter-list">
|
||||
<view
|
||||
v-for="(chapter, index) in chapterList"
|
||||
:key="chapter.id"
|
||||
class="chapter-item"
|
||||
:class="{ active: index === currentChapterIndex }"
|
||||
@click="switchChapter(chapter, index)"
|
||||
>
|
||||
<text class="chapter-text" :class="{ locked: isLocked(index) }">
|
||||
{{ chapter.chapter }}{{ chapter.content ? ' - ' + chapter.content : '' }}
|
||||
</text>
|
||||
<wd-icon v-if="isLocked(index)" name="lock" size="20px" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
|
||||
<!-- 设置弹窗 -->
|
||||
<wd-popup v-model="settingsVisible" position="bottom" @close="closeSettings">
|
||||
<view class="settings-popup">
|
||||
<!-- 切换语言 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">{{ $t('book.language') }}</text>
|
||||
<wd-radio-group v-model="currentLanguage" shape="button" custom-class="bg-[transparent]" @change="changeBookLanguage">
|
||||
<wd-radio v-for="lang in bookLanguages" :key="lang.language" :value="lang.language">{{ lang.language }}</wd-radio>
|
||||
</wd-radio-group>
|
||||
</view>
|
||||
|
||||
<!-- 字体大小 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">{{ $t('book.font') }}</text>
|
||||
<slider
|
||||
:value="fontSizeLevel"
|
||||
:min="1"
|
||||
:max="8"
|
||||
activeColor="#54a966"
|
||||
backgroundColor="#cad1bf"
|
||||
block-color="#54a966"
|
||||
block-size="18"
|
||||
@change="onFontSizeChange"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">{{ $t('book.bgColor') }}</text>
|
||||
<view class="theme-options">
|
||||
<view
|
||||
v-for="theme in themes"
|
||||
:key="theme.key"
|
||||
class="theme-btn"
|
||||
:class="[theme.key, { active: currentTheme === theme.key }]"
|
||||
@click="changeTheme(theme.key)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 阅读模式 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">{{ $t('book.type') }}</text>
|
||||
<view class="mode-options">
|
||||
<view
|
||||
class="mode-btn"
|
||||
:class="{ active: readMode === 'scroll' }"
|
||||
@click="changeReadMode('scroll')"
|
||||
>
|
||||
<text>{{ $t('book.type_1') }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="mode-btn"
|
||||
:class="{ active: readMode === 'page' }"
|
||||
@click="changeReadMode('page')"
|
||||
>
|
||||
<text>{{ $t('book.type_2') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="setting-ooter-placeholder"></view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="chapterList.length === 0 && !loading" class="empty-state">
|
||||
<text>{{ nullStatus }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onLoad, onShow, onHide, onBackPress } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useBookStore } from '@/stores/book'
|
||||
import { bookApi } from '@/api/modules/book'
|
||||
import type { IChapter, IChapterContent, IReadProgress } from '@/types/book'
|
||||
|
||||
const { t } = useI18n()
|
||||
const bookStore = useBookStore()
|
||||
|
||||
// 路由参数
|
||||
const bookId = ref(0)
|
||||
const isBuy = ref('0')
|
||||
const count = ref(0)
|
||||
|
||||
// 数据状态
|
||||
const chapterList = ref<IChapter[]>([])
|
||||
const contentList = ref<IChapterContent[]>([])
|
||||
const currentChapterIndex = ref(0)
|
||||
const currentChapterId = ref(0)
|
||||
const currentContentId = ref(0)
|
||||
const loading = ref(false)
|
||||
const nullStatus = ref('')
|
||||
|
||||
// 阅读设置
|
||||
const fontSizeLevel = ref(1)
|
||||
const fontSize = ref(1)
|
||||
const lineHeight = ref(34)
|
||||
const currentTheme = ref('default')
|
||||
const readMode = ref<'scroll' | 'page'>('scroll')
|
||||
|
||||
// 翻页模式相关
|
||||
const currentPage = ref(1)
|
||||
const totalPages = ref(0)
|
||||
const currentTop = ref(0)
|
||||
const wrapHeight = ref(0)
|
||||
const wrapHeightScroll = ref(0)
|
||||
const contentHeight = ref(0)
|
||||
|
||||
// UI状态
|
||||
const showControls = ref(false)
|
||||
const catalogVisible = ref(false)
|
||||
const settingsVisible = ref(false)
|
||||
const scrollTarget = ref('')
|
||||
const notchHeight = ref(0)
|
||||
|
||||
// 触摸相关
|
||||
const startX = ref(0)
|
||||
const threshold = 50
|
||||
|
||||
// 主题配置
|
||||
const themes = [
|
||||
{ key: 'default' },
|
||||
{ key: 'blue' },
|
||||
{ key: 'green' },
|
||||
{ key: 'purple' },
|
||||
{ key: 'night' }
|
||||
]
|
||||
|
||||
// 字体大小映射
|
||||
const FONT_SIZE_MAP: Record<number, { fontSize: number; lineHeight: number }> = {
|
||||
1: { fontSize: 1, lineHeight: 34 },
|
||||
2: { fontSize: 1.25, lineHeight: 40 },
|
||||
3: { fontSize: 1.45, lineHeight: 44 },
|
||||
4: { fontSize: 1.6, lineHeight: 50 },
|
||||
5: { fontSize: 1.65, lineHeight: 50 },
|
||||
6: { fontSize: 1.7, lineHeight: 54 },
|
||||
7: { fontSize: 1.75, lineHeight: 54 },
|
||||
8: { fontSize: 1.8, lineHeight: 54 }
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const themeClass = computed(() => `theme-${currentTheme.value}`)
|
||||
const contentStyle = computed(() => ({
|
||||
fontSize: `${fontSize.value}rem`,
|
||||
lineHeight: `${lineHeight.value}px`
|
||||
}))
|
||||
const currentChapterTitle = computed(() => {
|
||||
const chapter = chapterList.value[currentChapterIndex.value]
|
||||
if (!chapter) return ''
|
||||
return chapter.chapter + (chapter.content ? ' - ' + chapter.content : '')
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onLoad((options: any) => {
|
||||
if (options.bookId) bookId.value = Number(options.bookId)
|
||||
if (options.isBuy) isBuy.value = options.isBuy
|
||||
if (options.count) count.value = Number(options.count)
|
||||
|
||||
// 获取刘海高度
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
notchHeight.value = systemInfo.safeArea?.top || 0
|
||||
|
||||
// 恢复阅读设置
|
||||
bookStore.restoreSettings()
|
||||
const settings = bookStore.readerSettings
|
||||
fontSizeLevel.value = settings.fontSize
|
||||
setFontSize(settings.fontSize)
|
||||
currentTheme.value = settings.theme
|
||||
readMode.value = settings.readMode
|
||||
|
||||
loadReadProgress()
|
||||
getBookLanguages()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
if (readMode.value === 'scroll') {
|
||||
scrollTarget.value = 'content-top'
|
||||
setTimeout(() => {
|
||||
scrollTarget.value = ''
|
||||
}, 300)
|
||||
} else {
|
||||
currentPage.value = 1
|
||||
calculatePages()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initHeights()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
saveProgress()
|
||||
})
|
||||
|
||||
onBackPress(() => {
|
||||
saveProgress()
|
||||
return false
|
||||
})
|
||||
|
||||
// 初始化高度
|
||||
function initHeights() {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const windowHeight = systemInfo.windowHeight
|
||||
|
||||
// 翻页模式高度
|
||||
const math = Math.floor((windowHeight - (notchHeight.value + 130)) / lineHeight.value)
|
||||
wrapHeight.value = math * lineHeight.value
|
||||
|
||||
// 滚动模式高度
|
||||
wrapHeightScroll.value = windowHeight - (notchHeight.value + 75)
|
||||
}
|
||||
|
||||
// 加载阅读进度
|
||||
async function loadReadProgress() {
|
||||
try {
|
||||
const res = await bookApi.getReadProgress(bookId.value)
|
||||
if (res.bookReadRate) {
|
||||
currentChapterId.value = res.bookReadRate.chapterId
|
||||
currentContentId.value = res.bookReadRate.contentId
|
||||
}
|
||||
await loadChapterList()
|
||||
} catch (error) {
|
||||
console.error('Failed to load read progress:', error)
|
||||
await loadChapterList()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载章节列表
|
||||
async function loadChapterList() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await bookApi.getBookChapter({
|
||||
bookId: bookId.value,
|
||||
language: currentLanguage.value
|
||||
})
|
||||
|
||||
if (res.chapterList && res.chapterList.length > 0) {
|
||||
chapterList.value = res.chapterList
|
||||
|
||||
// 找到上次阅读的章节
|
||||
if (currentChapterId.value) {
|
||||
const index = chapterList.value.findIndex(ch => ch.id === currentChapterId.value)
|
||||
if (index !== -1) {
|
||||
currentChapterIndex.value = index
|
||||
await loadChapterContent(currentChapterId.value, index)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 默认加载第一章
|
||||
await loadChapterContent(chapterList.value[0].id, 0)
|
||||
} else {
|
||||
nullStatus.value = t('common.data_null')
|
||||
}
|
||||
} catch (error) {
|
||||
nullStatus.value = t('common.data_null')
|
||||
console.error('Failed to load chapter list:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载章节内容
|
||||
async function loadChapterContent(chapterId: number, index: number) {
|
||||
try {
|
||||
uni.showLoading({ title: t('global.loading') })
|
||||
const res = await bookApi.getChapterContent(chapterId)
|
||||
uni.hideLoading()
|
||||
|
||||
if (res.contentPage && res.contentPage.length > 0) {
|
||||
contentList.value = res.contentPage
|
||||
currentChapterId.value = chapterId
|
||||
currentChapterIndex.value = index
|
||||
currentContentId.value = res.contentPage[0].id
|
||||
|
||||
// 重置页码
|
||||
currentPage.value = 1
|
||||
|
||||
// 计算页数
|
||||
if (readMode.value === 'page') {
|
||||
setTimeout(() => {
|
||||
calculatePages()
|
||||
}, 100)
|
||||
} else {
|
||||
scrollTarget.value = 'content-top'
|
||||
setTimeout(() => {
|
||||
scrollTarget.value = ''
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
catalogVisible.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
console.error('Failed to load chapter content:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换章节
|
||||
function switchChapter(chapter: IChapter, index: number) {
|
||||
if (isLocked(index)) {
|
||||
uni.showToast({
|
||||
title: t('book.afterPurchase'),
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
loadChapterContent(chapter.id, index)
|
||||
}
|
||||
|
||||
// 判断章节是否锁定
|
||||
function isLocked(index: number): boolean {
|
||||
return isBuy.value === '1' && index + 1 > count.value
|
||||
}
|
||||
|
||||
// 判断是否是图片
|
||||
function isImage(content: string): boolean {
|
||||
return content.includes('oss-cn-beijing') || content.includes('http')
|
||||
}
|
||||
|
||||
// 获取图片样式
|
||||
function getImageStyle(otherContent?: string) {
|
||||
if (!otherContent) return {}
|
||||
|
||||
const [width, height] = otherContent.split(',').map(Number)
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const maxWidth = systemInfo.windowWidth - 30
|
||||
|
||||
if (width > maxWidth) {
|
||||
const ratio = maxWidth / width
|
||||
return {
|
||||
width: `${maxWidth}px`,
|
||||
height: `${height * ratio}px`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
}
|
||||
}
|
||||
|
||||
// 计算页数
|
||||
function calculatePages() {
|
||||
uni.createSelectorQuery()
|
||||
.select('.content-wrapper')
|
||||
.boundingClientRect((data: any) => {
|
||||
if (data) {
|
||||
contentHeight.value = data.height
|
||||
totalPages.value = Math.ceil(contentHeight.value / wrapHeight.value)
|
||||
currentTop.value = 0
|
||||
}
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
|
||||
// 触摸开始
|
||||
function onTouchStart(event: any) {
|
||||
startX.value = event.touches[0].clientX
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
function onTouchEnd(event: any) {
|
||||
const endX = event.changedTouches[0].clientX
|
||||
const distanceX = endX - startX.value
|
||||
|
||||
if (Math.abs(distanceX) > threshold) {
|
||||
if (distanceX > 0) {
|
||||
// 上一页
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
currentTop.value = -wrapHeight.value * (currentPage.value - 1)
|
||||
}
|
||||
} else {
|
||||
// 下一页
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
currentTop.value = -wrapHeight.value * (currentPage.value - 1)
|
||||
} else if (currentPage.value === totalPages.value) {
|
||||
onScrollToBottom()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到底部
|
||||
function onScrollToBottom() {
|
||||
if (isBuy.value === '1' && currentChapterIndex.value + 1 === count.value) {
|
||||
uni.showModal({
|
||||
title: t('common.limit_title'),
|
||||
content: t('book.endText'),
|
||||
cancelText: t('common.cancel_text'),
|
||||
confirmText: t('common.confirm_text'),
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/book/order?id=${bookId.value}`
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 切换控制栏显示
|
||||
function toggleControls() {
|
||||
showControls.value = !showControls.value
|
||||
}
|
||||
|
||||
// 显示目录
|
||||
function showCatalog() {
|
||||
catalogVisible.value = true
|
||||
settingsVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭目录
|
||||
function closeCatalog() {
|
||||
catalogVisible.value = false
|
||||
showControls.value = false
|
||||
}
|
||||
|
||||
// 显示设置
|
||||
function showSettings() {
|
||||
console.log('currentLanguage', currentLanguage.value)
|
||||
settingsVisible.value = true
|
||||
catalogVisible.value = false
|
||||
}
|
||||
// 关闭设置
|
||||
function closeSettings() {
|
||||
settingsVisible.value = false
|
||||
showControls.value = false
|
||||
}
|
||||
|
||||
// 字体大小改变
|
||||
function onFontSizeChange(e: any) {
|
||||
const level = e.detail.value
|
||||
fontSizeLevel.value = level
|
||||
setFontSize(level)
|
||||
|
||||
// 保存设置
|
||||
bookStore.updateReaderSettings({ fontSize: level, lineHeight: lineHeight.value })
|
||||
|
||||
// 重新计算页数
|
||||
if (readMode.value === 'page') {
|
||||
setTimeout(() => {
|
||||
initHeights()
|
||||
calculatePages()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置字体大小
|
||||
function setFontSize(level: number) {
|
||||
const config = FONT_SIZE_MAP[level] || FONT_SIZE_MAP[1]
|
||||
fontSize.value = config.fontSize
|
||||
lineHeight.value = config.lineHeight
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
function changeTheme(theme: string) {
|
||||
currentTheme.value = theme
|
||||
bookStore.updateReaderSettings({ theme: theme as any })
|
||||
}
|
||||
|
||||
// 切换阅读模式
|
||||
function changeReadMode(mode: 'scroll' | 'page') {
|
||||
readMode.value = mode
|
||||
bookStore.updateReaderSettings({ readMode: mode })
|
||||
|
||||
if (mode === 'page') {
|
||||
setTimeout(() => {
|
||||
calculatePages()
|
||||
}, 100)
|
||||
} else {
|
||||
scrollTarget.value = 'content-top'
|
||||
setTimeout(() => {
|
||||
scrollTarget.value = ''
|
||||
}, 300)
|
||||
}
|
||||
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
// 语言配置
|
||||
const bookLanguages = ref([])
|
||||
const currentLanguage = ref('')
|
||||
onMounted(() => {
|
||||
currentLanguage.value = uni.getStorageSync('currentBookLanguage') || ''
|
||||
console.log('currentLanguage', currentLanguage.value)
|
||||
})
|
||||
const getBookLanguages = async () => {
|
||||
const res = await bookApi.getBookLanguages(bookId.value)
|
||||
bookLanguages.value = res.languageList || []
|
||||
if (bookLanguages.value.length > 0 && !currentLanguage.value) {
|
||||
currentLanguage.value = bookLanguages.value[0].language
|
||||
}
|
||||
}
|
||||
const changeBookLanguage = (language: any) => {
|
||||
uni.setStorageSync('currentBookLanguage', language.value)
|
||||
loadChapterList()
|
||||
}
|
||||
|
||||
// 保存进度
|
||||
async function saveProgress() {
|
||||
if (isBuy.value === '0') {
|
||||
try {
|
||||
await bookApi.saveReadProgress(bookId.value, currentChapterId.value, currentContentId.value)
|
||||
} catch (error) {
|
||||
console.error('Failed to save progress:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回
|
||||
function goBack() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/book/detail?id=' + bookId.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.reader-page {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
color: #333;
|
||||
|
||||
&.theme-blue {
|
||||
background: #e8e3d0;
|
||||
}
|
||||
|
||||
&.theme-green {
|
||||
background: #d1edd1;
|
||||
}
|
||||
|
||||
&.theme-purple {
|
||||
background: #dae4ee;
|
||||
}
|
||||
|
||||
&.theme-night {
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.reader-header {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20rpx;
|
||||
height: 44px;
|
||||
|
||||
.chapter-title {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.reader-content {
|
||||
padding-top: 44px;
|
||||
box-sizing: border-box;
|
||||
.page-mode {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
padding: 0 50rpx;
|
||||
transition: top 0.3s ease;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
position: fixed;
|
||||
bottom: 5px;
|
||||
left: 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-mode {
|
||||
.content-wrapper {
|
||||
padding: 0 50rpx 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content-item {
|
||||
color: inherit;
|
||||
text-indent: 2em;
|
||||
letter-spacing: 0.2rem;
|
||||
text-align: justify;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
image {
|
||||
display: block;
|
||||
margin: 20rpx auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reader-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 55px;
|
||||
background: #f7faf9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
|
||||
.footer-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.active text {
|
||||
color: #54a966;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1rpx;
|
||||
height: 45rpx;
|
||||
background: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.catalog-popup {
|
||||
max-height: 70vh;
|
||||
|
||||
.popup-title {
|
||||
display: block;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: #54a966;
|
||||
border-bottom: 1rpx solid #54a966;
|
||||
}
|
||||
|
||||
.chapter-list {
|
||||
max-height: calc(70vh - 50px);
|
||||
|
||||
.chapter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #dcdfe6;
|
||||
|
||||
&.active .chapter-text {
|
||||
color: #54a966;
|
||||
}
|
||||
|
||||
.chapter-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
line-height: 50rpx;
|
||||
color: #333;
|
||||
|
||||
&.locked {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-popup {
|
||||
padding: 40rpx 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.setting-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.setting-label {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.theme-options {
|
||||
display: flex;
|
||||
gap: 36rpx;
|
||||
|
||||
.theme-btn {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid transparent;
|
||||
|
||||
&.active {
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
&.default {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&.blue {
|
||||
background: #e8e3d0;
|
||||
}
|
||||
|
||||
&.green {
|
||||
background: #d1edd1;
|
||||
}
|
||||
|
||||
&.purple {
|
||||
background: #dae4ee;
|
||||
}
|
||||
|
||||
&.night {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mode-options {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
.mode-btn {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
border: 1rpx solid #ddd;
|
||||
border-radius: 10rpx;
|
||||
|
||||
&.active {
|
||||
border-color: #54a966;
|
||||
|
||||
text {
|
||||
color: #54a966;
|
||||
}
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
.setting-ooter-placeholder {
|
||||
height: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user