| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- <template>
- <view class="reader-container" :style="containerStyle">
- <!-- 顶部导航栏 -->
- <reader-header :show="showControls" :capsule-info="capsuleInfo" :header-height="headerHeight" @back="goBack"
- @search="handleSearch" @add-to-shelf="handleAddToShelf" @add-bookmark="handleAddBookmark" />
- <!-- 内容区域 -->
- <view class="content-wrapper" @tap="toggleControls">
- <sliding-container :disabled="showControls || showSettingsPanel || showCatalogPanel"
- @slide-prev="handleSlidePrev" @slide-next="handleSlideNext">
- <!-- 上一章内容 -->
- <template #prev>
- <view class="content-area" :style="contentAreaStyle" v-if="prevChapter">
- <view class="chapter-title">{{ prevChapter.title }}</view>
- <view class="chapter-content" :style="contentStyle">{{ prevChapter.content }}</view>
- </view>
- </template>
- <!-- 当前章节内容 -->
- <template #current>
- <view class="content-area" :style="contentAreaStyle">
- <view class="chapter-title" v-if="currentChapter.title">{{ currentChapter.title }}</view>
- <view class="chapter-content" :style="contentStyle">{{ currentChapter.content }}</view>
- </view>
- </template>
- <!-- 下一章内容 -->
- <template #next>
- <view class="content-area" :style="contentAreaStyle" v-if="nextChapter">
- <view class="chapter-title">{{ nextChapter.title }}</view>
- <view class="chapter-content" :style="contentStyle">{{ nextChapter.content }}</view>
- </view>
- </template>
- </sliding-container>
- </view>
- <!-- 底部控制栏 -->
- <reader-footer :show="showControls" @show-catalog="toggleCatalog" @show-notes="toggleNotes"
- @show-progress="toggleProgress" @show-settings="toggleSettings" />
- <!-- 设置面板 -->
- <reader-settings :show="showSettingsPanel" :theme-index="themeIndex" :font-size="fontSize" :margin="margin"
- :line-height="lineHeight" :bg-colors="bgColors" @close="toggleSettings" @theme-change="changeTheme"
- @font-size-change="changeFontSize" @margin-change="changeMargin" @line-height-change="changeLineHeight" />
- <!-- 目录面板 -->
- <reader-catalog :show="showCatalogPanel" :chapters="chapters" :current-chapter-id="currentChapterId"
- @select="selectChapter" @close="toggleCatalog" />
- <!-- 遮罩层 -->
- <view class="mask" v-if="showCatalogPanel || showNotesPanel || showProgressPanel" @tap="closeAllPanels"></view>
- <!-- 翻页提示 -->
- <view class="page-tip" v-if="showPageTip">
- <text>{{ pageTipText }}</text>
- </view>
- </view>
- </template>
- <script>
- import ReaderHeader from './reader-header.vue'
- import ReaderFooter from './reader-footer.vue'
- import ReaderSettings from './reader-settings.vue'
- import ReaderCatalog from './reader-catalog.vue'
- import SlidingContainer from './sliding-container.vue'
- import novelService from './novel-service.js'
- export default {
- name: 'ReaderComponent',
- components: {
- ReaderHeader,
- ReaderFooter,
- ReaderSettings,
- ReaderCatalog,
- SlidingContainer
- },
- props: {
- novelId: {
- type: Number,
- required: true
- }
- },
- data() {
- return {
- currentChapterId: 1,
- chapters: [],
- currentChapter: {
- title: '',
- content: '加载中...'
- },
- showControls: false,
- showSettingsPanel: false,
- showCatalogPanel: false,
- showNotesPanel: false,
- showProgressPanel: false,
- fontSize: 18,
- margin: 20,
- lineHeight: 1.8,
- themeIndex: 0,
- bgColors: [{
- bg: '#f8f4e9',
- text: '#3e3d3b'
- },
- {
- bg: '#ffffff',
- text: '#333333'
- },
- {
- bg: '#e9e9e9',
- text: '#333333'
- },
- {
- bg: '#cce8cf',
- text: '#333333'
- },
- {
- bg: '#333333',
- text: '#c4c4c4'
- },
- ],
- touchStartX: 0,
- touchEndX: 0,
- showPageTip: false,
- pageTipText: '',
- capsuleInfo: {
- height: 0,
- top: 0,
- right: 0,
- statusBarHeight: 0
- },
- headerHeight: 90,
- prevChapter: null,
- nextChapter: null,
- }
- },
- computed: {
- containerStyle() {
- const theme = this.bgColors[this.themeIndex];
- return {
- backgroundColor: theme.bg,
- color: theme.text,
- // padding: `0 ${this.margin}px`,
- minHeight: '100vh'
- }
- },
- contentStyle() {
- return {
- fontSize: `${this.fontSize}px`,
- lineHeight: String(this.lineHeight), // 确保 lineHeight 是字符串
- }
- },
- contentAreaStyle() {
- // #ifdef MP-WEIXIN
- const topPadding = this.capsuleInfo.statusBarHeight + this.headerHeight / 2;
- return {
- paddingTop: `${topPadding}px`,
- paddingBottom: '120rpx',
- minHeight: '100vh',
- boxSizing: 'border-box'
- }
- // #endif
- // #ifndef MP-WEIXIN
- return {
- paddingTop: '100rpx',
- paddingBottom: '120rpx',
- minHeight: '100vh',
- boxSizing: 'border-box'
- }
- // #endif
- }
- },
- methods: {
- initPage() {
- this.getCapsuleInfo();
- this.loadSettings();
- this.loadChapterList();
- this.loadReadingProgress();
- },
- toggleControls() {
- this.showControls = !this.showControls
- if (!this.showControls) {
- this.closeAllPanels()
- }
- },
- closeAllPanels() {
- this.showSettingsPanel = false
- this.showCatalogPanel = false
- this.showNotesPanel = false
- this.showProgressPanel = false
- },
- goBack() {
- this.$emit('back')
- },
- handleSearch() {
- this.$emit('search')
- },
- handleAddToShelf() {
- this.$emit('add-to-shelf')
- },
- handleAddBookmark() {
- novelService.addBookmark(
- this.novelId,
- this.currentChapterId,
- 0,
- this.currentChapter.content.substring(0, 50)
- ).then(() => {
- uni.showToast({
- title: '已添加书签',
- icon: 'success'
- })
- this.$emit('add-bookmark')
- })
- },
- toggleCatalog() {
- this.showCatalogPanel = !this.showCatalogPanel
- this.showSettingsPanel = false
- this.showNotesPanel = false
- this.showProgressPanel = false
- },
- toggleNotes() {
- this.showNotesPanel = !this.showNotesPanel
- this.showSettingsPanel = false
- this.showCatalogPanel = false
- this.showProgressPanel = false
- this.$emit('show-notes')
- },
- toggleProgress() {
- this.showProgressPanel = !this.showProgressPanel
- this.showSettingsPanel = false
- this.showCatalogPanel = false
- this.showNotesPanel = false
- this.$emit('show-progress')
- },
- toggleSettings() {
- this.showSettingsPanel = !this.showSettingsPanel
- },
- changeTheme(index) {
- this.themeIndex = index;
- // 强制更新视图
- this.$forceUpdate();
- // 保存设置
- uni.setStorageSync('reader_theme', index);
- this.$emit('theme-change', index);
- },
- changeFontSize(value) {
- this.fontSize = Number(value)
- uni.setStorageSync('reader_font_size', this.fontSize)
- this.$emit('font-size-change', this.fontSize)
- },
- changeMargin(value) {
- this.margin = Number(value)
- uni.setStorageSync('reader_margin', this.margin)
- this.$emit('margin-change', this.margin)
- },
- changeLineHeight(value) {
- this.lineHeight = Number(value)
- uni.setStorageSync('reader_line_height', this.lineHeight)
- this.$emit('line-height-change', this.lineHeight)
- },
- touchStart(e) {
- this.touchStartX = e.changedTouches[0].clientX
- },
- touchEnd(e) {
- this.touchEndX = e.changedTouches[0].clientX
- const diffX = this.touchEndX - this.touchStartX
- if (this.showControls) return
- if (diffX > 100) {
- this.prevPage()
- } else if (diffX < -100) {
- this.nextPage()
- }
- },
- prevPage() {
- if (this.currentChapterId <= 1) {
- this.showPageTip = true
- this.pageTipText = '已经是第一章了'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- return
- }
- this.loadChapter(this.currentChapterId - 1)
- this.showPageTip = true
- this.pageTipText = '上一章'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- this.$emit('prev-page')
- },
- nextPage() {
- if (this.currentChapterId >= this.chapters.length) {
- this.showPageTip = true
- this.pageTipText = '已经是最后一章了'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- return
- }
- this.loadChapter(this.currentChapterId + 1)
- this.showPageTip = true
- this.pageTipText = '下一章'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- this.$emit('next-page')
- },
- selectChapter(chapter) {
- this.loadChapter(chapter.id)
- this.showCatalogPanel = false
- this.$emit('chapter-change', chapter)
- },
- loadChapter(chapterId) {
- this.currentChapter = {
- title: '',
- content: '加载中...'
- }
- novelService.getChapterContent(chapterId).then(chapter => {
- this.currentChapter = chapter
- this.currentChapterId = chapterId
- // 预加载相邻章节
- this.preloadAdjacentChapters()
- novelService.saveReadingProgress(this.novelId, chapterId, 0)
- this.$emit('chapter-loaded', chapter)
- })
- },
- loadChapterList() {
- novelService.getChapterList(this.novelId).then(chapters => {
- this.chapters = chapters
- this.$emit('chapters-loaded', chapters)
- })
- },
- loadReadingProgress() {
- novelService.getReadingProgress(this.novelId).then(progress => {
- if (progress && progress.chapterId) {
- this.loadChapter(progress.chapterId)
- } else {
- this.loadChapter(1)
- }
- })
- },
- loadSettings() {
- const theme = uni.getStorageSync('reader_theme')
- const fontSize = uni.getStorageSync('reader_font_size')
- const margin = uni.getStorageSync('reader_margin')
- const lineHeight = uni.getStorageSync('reader_line_height')
- this.themeIndex = theme !== '' ? Number(theme) : 0
- this.fontSize = fontSize !== '' ? Number(fontSize) : 18
- this.margin = margin !== '' ? Number(margin) : 20
- this.lineHeight = lineHeight !== '' ? Number(lineHeight) : 1.8
- },
- getCapsuleInfo() {
- // #ifdef MP-WEIXIN
- const systemInfo = uni.getSystemInfoSync();
- const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
- this.capsuleInfo = {
- height: menuButtonInfo.height,
- top: menuButtonInfo.top,
- right: systemInfo.windowWidth - menuButtonInfo.right,
- statusBarHeight: systemInfo.statusBarHeight
- };
- this.headerHeight = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height + 10;
- // #endif
- // #ifndef MP-WEIXIN
- const systemInfo = uni.getSystemInfoSync();
- this.capsuleInfo.statusBarHeight = systemInfo.statusBarHeight;
- this.headerHeight = 90;
- // #endif
- },
- // 处理滑动到上一章
- handleSlidePrev() {
- if (this.currentChapterId <= 1) {
- this.showPageTip = true
- this.pageTipText = '已经是第一章了'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- return
- }
- const targetChapterId = this.currentChapterId - 1
- this.loadChapter(targetChapterId)
- this.$emit('prev-page')
- },
- // 处理滑动到下一章
- handleSlideNext() {
- if (this.currentChapterId >= this.chapters.length) {
- this.showPageTip = true
- this.pageTipText = '已经是最后一章了'
- setTimeout(() => {
- this.showPageTip = false
- }, 1500)
- return
- }
- const targetChapterId = this.currentChapterId + 1
- this.loadChapter(targetChapterId)
- this.$emit('next-page')
- },
- // 预加载相邻章节
- preloadAdjacentChapters() {
- // 预加载上一章
- if (this.currentChapterId > 1) {
- novelService.getChapterContent(this.currentChapterId - 1).then(chapter => {
- this.prevChapter = chapter
- })
- } else {
- this.prevChapter = null
- }
- // 预加载下一章
- if (this.currentChapterId < this.chapters.length) {
- novelService.getChapterContent(this.currentChapterId + 1).then(chapter => {
- this.nextChapter = chapter
- })
- } else {
- this.nextChapter = null
- }
- },
- },
- created() {
- this.initPage()
- },
- onShow() {
- this.getCapsuleInfo()
- },
- onResize() {
- this.getCapsuleInfo()
- }
- }
- </script>
- <style scoped>
- .reader-container {
- position: relative;
- width: 100%;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- transition: all 0.3s ease;
- overflow: hidden;
- }
- .content-area {
- flex: 1;
- min-height: 100vh;
- padding: 20rpx;
- }
- .chapter-title {
- font-size: 36rpx;
- font-weight: bold;
- text-align: center;
- margin: 30rpx 0;
- }
- .chapter-content {
- text-align: justify;
- }
- .mask {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 250;
- }
- .page-tip {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background-color: rgba(0, 0, 0, 0.7);
- color: #fff;
- padding: 20rpx 40rpx;
- border-radius: 10rpx;
- font-size: 28rpx;
- z-index: 300;
- }
- /* 添加新的样式 */
- .content-wrapper {
- flex: 1;
- position: relative;
- overflow: hidden;
- min-height: 100vh;
- }
- </style>
|