吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5181|回复: 82
收起左侧

[Python 原创] 小说摸鱼阅读器 2.0

  [复制链接]
phantomxjc 发表于 2025-10-9 11:34
本帖最后由 phantomxjc 于 2025-10-14 08:37 编辑

应吾爱友友们需求,新增功能
1.增加书籍管理功能,记录上一次阅读的书籍和章节,右键可删除记录
2.增加极简模式下进入下一章的功能,按快捷键ctrl+a即可进入上一章,ctrl+d即可进入下一章



本来我只是想做一个小说阅读器的,不过看了一下评论很多的友友注重摸鱼去了,那我就根据评论需求优化一下:(这篇文字是AI写的,偷懒哈哈哈哈)
1. 核心功能概览
这款小说阅读器旨在为用户提供一站式的阅读解决方案,其主要功能模块如下:
功能模块
               主要特点小说搜索
        支持在线搜索海量小说资源章节导航
        清晰的章节列表和快速跳转阅读界面
        可自定义字体、背景色、支持极简模式文件管理
        支持本地TXT文件阅读和管理个性化设置
      字体调节、背景色选择、窗口透明度调整
2. 在线阅读功能详解
2.1 智能搜索与发现
应用程序内置了强大的搜索引擎,可以从多个网络源搜索小说资源。用户只需输入小说名称或作者,即可获取丰富的搜索结果列表。搜索过程在后台线程中进行,不会阻塞界面操作。
2.2 章节列表与导航
选定小说后,应用会自动获取并展示清晰的章节列表。每个章节都支持点击跳转,方便快速定位到想阅读的内容。这一设计避免了传统阅读器需要手动查找章节的麻烦。
3. 阅读界面设计与交互优化
3.1 个性化阅读设置
阅读器提供了丰富的个性化设置选项,包括:
• 字体调节:支持字体大小、字体家族的实时调整
• 背景色选择:可自定义背景颜色,减少长时间阅读的视觉疲劳
• 界面布局:可调节的界面元素和布局,适应不同阅读习惯
3.2 极简阅读模式
ctrl+f调出极简模式,极简模式是这款阅读器的一大亮点,它提供了无边框、可调节透明度的阅读环境。在此模式下:
• 界面元素最大化隐藏,只保留纯文本内容
• 支持窗口透明度调节,方便多任务操作
• 可通过鼠标拖动调整窗口位置和大小
• 使用鼠标滚轮或键盘快捷键进行滚动
3.3 智能翻页与进度管理
阅读器会自动记录阅读进度,并提供上一章/下一章的便捷导航功能。同时,书签和进度保存功能确保用户可以随时中断和继续阅读。
4. 本地文件管理功能
除了在线阅读,该应用还提供了强大的本地文件管理能力:
4.1 多功能文件操作
通过集成的多功能按钮,用户可以:
• 右键点击选择并保存本地文件路径
• 左键点击打开已保存的本地文件直接阅读
• 文件路径会持久化保存,方便下次快速访问
结语
这款基于Python开发的小说阅读器,结合了现代阅读软件的诸多优点,提供了高度可定制的阅读体验。无论是作为个人项目学习GUI开发,还是作为日常阅读工具,都具有很高的实用价值。
注:本项目仅供学习和交流使用,请尊重版权,合法获取阅读资源。
小说摸鱼阅读器 1.0
百度网盘:https://pan.baidu.com/s/1IfIeeoP8j1xT7Z02qck0PA?pwd=dmhd 提取码: dmhd
夸克链接:https://pan.quark.cn/s/c1c2a8081325

小说摸鱼阅读器 2.0
百度网盘: https://pan.baidu.com/s/1x1QBDvsKHyX0ul04Gf_A8Q?pwd=gak1 提取码: gak1
夸克链接:https://pan.quark.cn/s/c732db40a89d


[Python] 纯文本查看 复制代码
import sys
import os
import time
from urllib.parse import quote
import requests
import re
import json
from bs4 import BeautifulSoup
from requests import Session
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QLineEdit, QPushButton,
                             QListWidget, QTextEdit, QStackedWidget, QFrame,
                             QScrollArea, QScrollBar, QProgressBar, QMessageBox,
                             QDialog, QTextBrowser, QFileDialog, QSplitter, QInputDialog, QColorDialog, QFontDialog)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QPropertyAnimation, QEasingCurve, QRect, QPoint
from PyQt5.QtGui import QFont, QPalette, QColor, QIcon, QPixmap, QCursor, QMouseEvent

# 爬虫部分(保持不变)
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
}

session = Session()


def session_ask():
    try:
        url = 'http://m.xqishuta.net'
        session.get(url=url, headers=headers, timeout=30)
    except Exception as e:
        print(f"Session初始化失败: {e}")


def novel(name):
    novels_list = []
    try:
        url = 'http://m.xqishuta.net/search.html'
        data = {"searchkey": name, "type": "articlename"}
        response = session.post(url=url, headers=headers, data=data, timeout=30)
        htmls = BeautifulSoup(response.text, 'html.parser').find('div', class_='searchresult').find_all('p')

        for i, html in enumerate(htmls, 1):
            novel_name = html.a.text
            href = 'http://m.xqishuta.net' + html.a['href']
            author = html.span.text
            novel_list = [i, novel_name, author, href]
            novels_list.append(novel_list)

    except Exception as e:
        print(f"搜索小说失败: {e}")

    return novels_list


def single_novel(url):
    chapters_list = []
    try:
        response = session.get(url=url, headers=headers)
        response.encoding = 'utf-8'
        options = BeautifulSoup(response.text, 'html.parser').find('select').find_all('option')

        i = 1
        for option in options:
            value = 'http://m.xqishuta.net/' + option.get('value')
            response = session.get(url=value, headers=headers)
            response.encoding = 'utf-8'
            chapters = BeautifulSoup(response.text, 'html.parser').find_all('div', class_='info_menu1')

            if len(chapters) > 1:
                chapters = chapters[1].find('div', class_='list_xm').find_all('li')
                for chapter in chapters:
                    chapter_name = chapter.a.text
                    url = 'http://m.xqishuta.net' + chapter.a['href']
                    chapter_list = [i, chapter_name, url]
                    chapters_list.append(chapter_list)
                    i += 1

    except Exception as e:
        print(f"获取章节列表失败: {e}")

    return chapters_list


def text(url):
    all_text = ""
    try:
        while True:
            response = session.get(url=url, headers=headers)
            response.encoding = 'utf-8'
            all_text += clear_text(response)

            soup = BeautifulSoup(response.text, 'html.parser').find('p', class_='p1 p3')
            if soup and soup.text == '下一页' and soup.a:
                url = 'http://m.xqishuta.net' + soup.a['href']
            else:
                break
    except Exception as e:
        print(f"获取小说内容失败: {e}")
        all_text = f"获取内容失败: {e}"

    return all_text


def clear_text(response):
    try:
        soup = BeautifulSoup(response.text, 'html.parser').find('div', class_='novelcontent').find('p')
        text = str(soup.text)
        text = re.sub(r'最新网址:\S*\s*', '', text)
        text = re.sub(r'第[^章]+章\s*[^(]*\s*\(第\d+/\d+页\)\s*', '', text)
        text = re.sub(r'(本章未完,请点击下一页继续阅读)', '', text)

        return text
    except Exception as e:
        return f"内容解析失败: {e}"


# 多线程处理网络请求
class SearchThread(QThread):
    finished = pyqtSignal(list)
    error = pyqtSignal(str)

    def __init__(self, novel_name):
        super().__init__()
        self.novel_name = novel_name

    def run(self):
        try:
            result = novel(self.novel_name)
            self.finished.emit(result)
        except Exception as e:
            self.error.emit(str(e))


class ChapterThread(QThread):
    finished = pyqtSignal(list)
    error = pyqtSignal(str)

    def __init__(self, url):
        super().__init__()
        self.url = url

    def run(self):
        try:
            result = single_novel(self.url)
            self.finished.emit(result)
        except Exception as e:
            self.error.emit(str(e))


class ContentThread(QThread):
    finished = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, url):
        super().__init__()
        self.url = url

    def run(self):
        try:
            result = text(self.url)
            self.finished.emit(result)
        except Exception as e:
            self.error.emit(str(e))


# 自定义多功能按钮类
class MultiFunctionButton(QPushButton):
    def __init__(self, text, parent=None):
        super().__init__(text, parent)
        self.saved_file_path = ""  # 保存的文件路径
        self.config_file = "saved_file_path.txt"  # 配置文件路径

        # 尝试读取之前保存的文件路径
        self.load_saved_path()

    def load_saved_path(self):
        """加载之前保存的文件路径"""
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as file:
                    self.saved_file_path = file.read().strip()
        except Exception as e:
            print(f"加载保存的文件路径失败: {e}")

    def mousePressEvent(self, event):
        if event.button() == Qt.RightButton:
            # 右键:浏览文件并保存路径
            self.browse_and_save_file()
        else:
            # 左键:打开保存的文件
            super().mousePressEvent(event)
            self.open_saved_file()

    def browse_and_save_file(self):
        """右键功能:浏览文件并保存路径到txt"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择文件", "", "所有文件 (*.*)"
        )

        if file_path:
            try:
                # 保存文件路径到txt文件
                with open(self.config_file, 'w', encoding='utf-8') as file:
                    file.write(file_path)

                self.saved_file_path = file_path
                QMessageBox.information(self, "成功", f"文件路径已保存: {file_path}")

            except Exception as e:
                QMessageBox.critical(self, "错误", f"保存文件路径失败: {str(e)}")

    def open_saved_file(self):
        """左键功能:打开保存的文件"""
        if not self.saved_file_path or not os.path.exists(self.saved_file_path):
            QMessageBox.warning(self, "提示", "请先右键点击按钮选择文件")
            return

        try:
            # 读取文件内容
            with open(self.saved_file_path, 'r', encoding='utf-8') as file:
                content = file.read()

            # 获取父组件(ReadingWidget)并显示文件内容
            parent_widget = self.parent()
            while parent_widget and not isinstance(parent_widget, ReadingWidget):
                parent_widget = parent_widget.parent()

            if parent_widget and hasattr(parent_widget, 'content_text'):
                parent_widget.content_text.setPlainText(content)
                parent_widget.novel_title_label.setText(f"本地文件: {os.path.basename(self.saved_file_path)}")
                QMessageBox.information(self, "成功", f"已打开文件: {os.path.basename(self.saved_file_path)}")

        except Exception as e:
            QMessageBox.critical(self, "错误", f"打开文件失败: {str(e)}")


# 阅读界面 - 增强极简模式功能
class ReadingWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.chapter_list_visible = True
        self.buttons_visible = True
        self.minimal_mode = False  # 极简模式标志
        self.drag_position = None
        self.resize_border = 8  # 调整大小的边界宽度
        self.resize_direction = None
        self.custom_bg_color = None
        self.original_window_flags = None
        self.original_geometry = None
        self.current_font = QFont("微软雅黑", 12)  # 默认字体
        self.initUI()

    def initUI(self):
        main_layout = QHBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        # 使用QSplitter来管理左右布局
        self.splitter = QSplitter(Qt.Horizontal)

        # 左侧章节列表(可隐藏)
        self.left_frame = QFrame()
        self.left_frame.setFrameStyle(QFrame.StyledPanel)
        left_layout = QVBoxLayout(self.left_frame)
        left_layout.setContentsMargins(10, 10, 10, 10)

        chapter_label = QLabel("章节列表")
        chapter_label.setFont(QFont("微软雅黑", 12, QFont.Bold))
        chapter_label.setStyleSheet("color: #2c3e50; margin-bottom: 10px;")
        left_layout.addWidget(chapter_label)

        self.chapter_list = QListWidget()
        self.chapter_list.setFont(QFont("微软雅黑", 10))
        self.chapter_list.setStyleSheet("""
            QListWidget {
                border: 1px solid #ced4da;
                border-radius: 4px;
                background-color: white;
            }
            QListWidget::item {
                padding: 8px;
                border-bottom: 1px solid #e9ecef;
            }
            QListWidget::item:selected {
                background-color: #3498db;
                color: white;
            }
            QListWidget::item:hover {
                background-color: #e9ecef;
            }
        """)
        self.chapter_list.itemClicked.connect(self.on_chapter_clicked)
        left_layout.addWidget(self.chapter_list)

        # 隐藏/显示章节列表按钮
        self.toggle_chapter_btn = QPushButton("隐藏列表")
        self.toggle_chapter_btn.setFont(QFont("微软雅黑", 10))
        self.toggle_chapter_btn.setStyleSheet("""
            QPushButton {
                background-color: #6c757d;
                color: white;
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #5a6268;
            }
        """)
        self.toggle_chapter_btn.clicked.connect(self.toggle_chapter_list)
        left_layout.addWidget(self.toggle_chapter_btn)

        self.splitter.addWidget(self.left_frame)

        # 右侧内容区域
        self.right_frame = QFrame()
        self.right_frame.setFrameStyle(QFrame.StyledPanel)
        right_layout = QVBoxLayout(self.right_frame)
        right_layout.setContentsMargins(15, 15, 15, 15)

        # 顶部导航栏
        nav_layout = QHBoxLayout()

        self.back_button = QPushButton("返回详情")
        self.back_button.setFont(QFont("微软雅黑", 10))
        self.back_button.setStyleSheet("""
            QPushButton {
                background-color: #95a5a6;
                color: white;
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #7f8c8d;
            }
        """)
        self.back_button.clicked.connect(self.go_back_to_detail)
        nav_layout.addWidget(self.back_button)

        self.novel_title_label = QLabel()
        self.novel_title_label.setFont(QFont("微软雅黑", 14, QFont.Bold))
        self.novel_title_label.setStyleSheet("color: #2c3e50;")
        nav_layout.addWidget(self.novel_title_label)

        nav_layout.addStretch()

        # 字体调节按钮
        self.font_button = QPushButton("字体调节")
        self.font_button.setFont(QFont("微软雅黑", 10))
        self.font_button.setStyleSheet("""
            QPushButton {
                background-color: #17a2b8;
                color: white;
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #138496;
            }
        """)
        self.font_button.clicked.connect(self.adjust_font)
        nav_layout.addWidget(self.font_button)

        # 增强的显示/隐藏按钮(控制按钮组和章节列表)
        self.toggle_components_btn = QPushButton("隐藏组件")
        self.toggle_components_btn.setFont(QFont("微软雅黑", 10))
        self.toggle_components_btn.setStyleSheet("""
            QPushButton {
                background-color: #6c757d;
                color: white;
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #5a6268;
            }
        """)
        self.toggle_components_btn.clicked.connect(self.toggle_components)
        nav_layout.addWidget(self.toggle_components_btn)

        # 背景色拾取按钮
        self.bg_color_btn = QPushButton("背景色")
        self.bg_color_btn.setFont(QFont("微软雅黑", 10))
        self.bg_color_btn.setStyleSheet("""
            QPushButton {
                background-color: #9b59b6;
                color: white;
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #8e44ad;
            }
        """)
        self.bg_color_btn.clicked.connect(self.pick_background_color)
        nav_layout.addWidget(self.bg_color_btn)

        # 极简模式按钮
        self.minimal_mode_btn = QPushButton("极简模式")
        self.minimal_mode_btn.setFont(QFont("微软雅黑", 10))
        self.minimal_mode_btn.setStyleSheet("""
            QPushButton {
                background-color: #e74c3c;
                color: white;
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #c0392b;
            }
        """)
        self.minimal_mode_btn.clicked.connect(self.toggle_minimal_mode)
        nav_layout.addWidget(self.minimal_mode_btn)

        right_layout.addLayout(nav_layout)

        # 按钮组
        self.button_frame = QFrame()
        button_layout = QHBoxLayout(self.button_frame)
        button_layout.setContentsMargins(0, 10, 0, 10)

        self.prev_btn = QPushButton("上一章")
        self.prev_btn.setFont(QFont("微软雅黑", 10))
        self.prev_btn.setStyleSheet("""
            QPushButton {
                background-color: #17a2b8;
                color: white;
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #138496;
            }
            QPushButton:disabled {
                background-color: #6c757d;
            }
        """)
        self.prev_btn.clicked.connect(self.prev_chapter)
        button_layout.addWidget(self.prev_btn)

        self.next_btn = QPushButton("下一章")
        self.next_btn.setFont(QFont("微软雅黑", 10))
        self.next_btn.setStyleSheet("""
            QPushButton {
                background-color: #28a745;
                color: white;
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #218838;
            }
            QPushButton:disabled {
                background-color: #6c757d;
            }
        """)
        self.next_btn.clicked.connect(self.next_chapter)
        button_layout.addWidget(self.next_btn)

        # 多功能按钮
        self.multi_function_btn = MultiFunctionButton("文件管理")
        self.multi_function_btn.setFont(QFont("微软雅黑", 10))
        self.multi_function_btn.setStyleSheet("""
            QPushButton {
                background-color: #ffc107;
                color: #212529;
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #e0a800;
            }
        """)
        button_layout.addWidget(self.multi_function_btn)

        right_layout.addWidget(self.button_frame)

        # 内容显示区域
        self.content_text = QTextEdit()
        self.content_text.setFont(self.current_font)
        self.content_text.setStyleSheet("""
            QTextEdit {
                border: 1px solid #ced4da;
                border-radius: 4px;
                padding: 15px;
                background-color: white;
                line-height: 1.6;
            }
        """)
        self.content_text.setReadOnly(True)
        right_layout.addWidget(self.content_text)

        self.splitter.addWidget(self.right_frame)

        # 设置分割比例
        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 3)

        # 保存初始大小
        self.splitter.setSizes([200, 600])

        main_layout.addWidget(self.splitter)
        self.setLayout(main_layout)

        # 设置鼠标跟踪
        self.setMouseTracking(True)
        self.content_text.setMouseTracking(True)

    def adjust_font(self):
        """字体调节功能"""
        font, ok = QFontDialog.getFont(self.current_font, self)
        if ok:
            self.current_font = font
            self.content_text.setFont(font)
            # 极简模式也会使用这个字体设置

    def set_content(self, novel_info, chapter_info, chapters):
        self.novel_info = novel_info
        self.chapter_info = chapter_info
        self.chapters = chapters
        self.current_index = chapter_info[0] - 1  # 转换为0-based索引

        # 更新界面
        self.novel_title_label.setText(f"{novel_info[1]} - {chapter_info[1]}")

        # 加载章节列表
        self.chapter_list.clear()
        for chapter in chapters:
            self.chapter_list.addItem(f"{chapter[0]}. {chapter[1]}")

        # 高亮当前章节
        if 0 <= self.current_index < self.chapter_list.count():
            self.chapter_list.setCurrentRow(self.current_index)

        # 更新按钮状态
        self.update_button_states()

        # 加载内容
        self.parent.progress_bar.setVisible(True)
        self.content_thread = ContentThread(chapter_info[2])
        self.content_thread.finished.connect(self.on_content_loaded)
        self.content_thread.error.connect(self.on_content_error)
        self.content_thread.start()

    def on_content_loaded(self, content):
        self.parent.progress_bar.setVisible(False)
        if content:
            # 格式化内容
            formatted_content = content.replace('\n', '\n\n')
            self.content_text.setPlainText(formatted_content)

            # 滚动到顶部
            self.content_text.verticalScrollBar().setValue(0)
        else:
            self.content_text.setPlainText("加载内容失败,请重试")

    def on_content_error(self, error_msg):
        self.parent.progress_bar.setVisible(False)
        self.content_text.setPlainText(f"加载内容失败: {error_msg}")

    def on_chapter_clicked(self, item):
        index = self.chapter_list.currentRow()
        if index != self.current_index and 0 <= index < len(self.chapters):
            self.current_index = index
            self.chapter_info = self.chapters[index]
            self.set_content(self.novel_info, self.chapter_info, self.chapters)

    def prev_chapter(self):
        if self.current_index > 0:
            self.current_index -= 1
            self.chapter_info = self.chapters[self.current_index]
            self.set_content(self.novel_info, self.chapter_info, self.chapters)

    def next_chapter(self):
        if self.current_index < len(self.chapters) - 1:
            self.current_index += 1
            self.chapter_info = self.chapters[self.current_index]
            self.set_content(self.novel_info, self.chapter_info, self.chapters)

    def update_button_states(self):
        """更新按钮状态"""
        self.prev_btn.setEnabled(self.current_index > 0)
        self.next_btn.setEnabled(self.current_index < len(self.chapters) - 1)

    def toggle_chapter_list(self):
        """切换章节列表的显示/隐藏"""
        if self.chapter_list_visible:
            # 隐藏章节列表
            self.splitter.setSizes([0, 800])
            self.toggle_chapter_btn.setText("显示列表")
            self.chapter_list_visible = False
        else:
            # 显示章节列表
            self.splitter.setSizes([200, 600])
            self.toggle_chapter_btn.setText("隐藏列表")
            self.chapter_list_visible = True

    def toggle_components(self):
        """增强的显示/隐藏功能:同时控制按钮组和章节列表"""
        if self.buttons_visible and self.chapter_list_visible:
            # 隐藏所有组件
            self.button_frame.hide()
            self.splitter.setSizes([0, 800])
            self.toggle_components_btn.setText("显示组件")
            self.buttons_visible = False
            self.chapter_list_visible = False
            # 同步更新章节列表按钮状态
            self.toggle_chapter_btn.setText("显示列表")
        else:
            # 显示所有组件
            self.button_frame.show()
            self.splitter.setSizes([200, 600])
            self.toggle_components_btn.setText("隐藏组件")
            self.buttons_visible = True
            self.chapter_list_visible = True
            # 同步更新章节列表按钮状态
            self.toggle_chapter_btn.setText("隐藏列表")

    def pick_background_color(self):
        """背景色拾取功能"""
        color = QColorDialog.getColor()
        if color.isValid():
            self.custom_bg_color = color
            self.apply_custom_background()
            QMessageBox.information(self, "成功", f"已设置背景色: {color.name()}")

    def apply_custom_background(self):
        """应用自定义背景色到内容区域"""
        if self.custom_bg_color:
            self.content_text.setStyleSheet(f"""
                QTextEdit {{
                    border: 1px solid #ced4da;
                    border-radius: 4px;
                    padding: 15px;
                    background-color: {self.custom_bg_color.name()};
                    line-height: 1.6;
                }}
            """)
        else:
            self.content_text.setStyleSheet("""
                QTextEdit {
                    border: 1px solid #ced4da;
                    border-radius: 4px;
                    padding: 15px;
                    background-color: white;
                    line-height: 1.6;
                }
            """)

    def toggle_minimal_mode(self):
        """切换极简模式"""
        if not self.minimal_mode:
            self.enter_minimal_mode()
        else:
            self.exit_minimal_mode()

    def enter_minimal_mode(self):
        """进入极简模式"""
        self.minimal_mode = True
        self.minimal_mode_btn.setText("退出极简")

        # 保存原始状态
        self.original_window_flags = self.parent.windowFlags()
        self.original_geometry = self.parent.geometry()

        # 隐藏所有控件,包括返回详情按钮和滚动条
        self.left_frame.hide()
        self.button_frame.hide()
        self.toggle_components_btn.hide()
        self.bg_color_btn.hide()
        self.minimal_mode_btn.hide()
        self.novel_title_label.hide()
        self.back_button.hide()  # 隐藏返回详情按钮
        self.font_button.hide()  # 隐藏字体调节按钮

        # 设置无边框窗口
        self.parent.setWindowFlags(Qt.FramelessWindowHint)

        # 完全去除内容区域的边框和滚动条
        self.content_text.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)  # 隐藏垂直滚动条
        self.content_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)  # 隐藏水平滚动条

        # 应用当前字体设置到极简模式
        self.content_text.setFont(self.current_font)

        # 设置完全无边框的内容区域样式
        bg_color = self.custom_bg_color.name() if self.custom_bg_color else 'white'
        self.content_text.setStyleSheet(f"""
            QTextEdit {{
                border: none;
                padding: 20px;
                background-color: {bg_color};
                line-height: 1.8;
                font-size: {self.current_font.pointSize()}px;
                margin: 0;
            }}
        """)

        # 设置右侧框架无边框
        self.right_frame.setFrameStyle(QFrame.NoFrame)
        self.right_frame.layout().setContentsMargins(0, 0, 0, 0)

        # 调整窗口大小以适应内容
        self.parent.resize(800, 600)

        # 显示窗口
        self.parent.show()

        # 显示提示信息
        QMessageBox.information(self, "提示",
                                "已进入极简模式\n\n"
                                "操作说明:\n"
                                "&#8226; 按Ctrl+F切换极简模式\n"
                                "&#8226; 鼠标拖动窗口移动\n"
                                "&#8226; 在窗口边缘调整大小\n"
                                "&#8226; 鼠标滚轮滚动内容\n"
                                "&#8226; 右键点击退出极简模式")

    def exit_minimal_mode(self):
        """退出极简模式"""
        self.minimal_mode = False
        self.minimal_mode_btn.setText("极简模式")

        # 恢复原始窗口标志和几何信息
        if self.original_window_flags:
            self.parent.setWindowFlags(self.original_window_flags)
        if self.original_geometry:
            self.parent.setGeometry(self.original_geometry)

        # 显示所有控件
        self.left_frame.show()
        self.button_frame.show()
        self.toggle_components_btn.show()
        self.bg_color_btn.show()
        self.minimal_mode_btn.show()
        self.novel_title_label.show()
        self.back_button.show()  # 显示返回详情按钮
        self.font_button.show()  # 显示字体调节按钮

        # 恢复滚动条
        self.content_text.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.content_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)

        # 恢复内容区域样式
        self.right_frame.setFrameStyle(QFrame.StyledPanel)
        self.right_frame.layout().setContentsMargins(15, 15, 15, 15)
        self.apply_custom_background()

        # 显示窗口
        self.parent.show()

    def get_resize_direction(self, pos):
        """获取鼠标位置对应的调整方向[8](@ref)"""
        x, y = pos.x(), pos.y()
        width, height = self.parent.width(), self.parent.height()

        # 定义调整区域边界
        border = self.resize_border

        # 检查各个方向的调整区域
        if x <= border and y <= border:
            return "top-left"
        elif x >= width - border and y <= border:
            return "top-right"
        elif x <= border and y >= height - border:
            return "bottom-left"
        elif x >= width - border and y >= height - border:
            return "bottom-right"
        elif x <= border:
            return "left"
        elif x >= width - border:
            return "right"
        elif y <= border:
            return "top"
        elif y >= height - border:
            return "bottom"
        else:
            return None

    def update_cursor_shape(self, direction):
        """根据调整方向更新鼠标光标形状[8](@ref)"""
        if direction == "top-left" or direction == "bottom-right":
            self.parent.setCursor(Qt.SizeFDiagCursor)
        elif direction == "top-right" or direction == "bottom-left":
            self.parent.setCursor(Qt.SizeBDiagCursor)
        elif direction == "left" or direction == "right":
            self.parent.setCursor(Qt.SizeHorCursor)
        elif direction == "top" or direction == "bottom":
            self.parent.setCursor(Qt.SizeVerCursor)
        else:
            self.parent.setCursor(Qt.ArrowCursor)

    def mousePressEvent(self, event):
        """鼠标按下事件 - 支持窗口拖动和调整大小[8](@ref)"""
        if self.minimal_mode:
            if event.button() == Qt.LeftButton:
                # 检查是否在调整区域
                self.resize_direction = self.get_resize_direction(event.pos())
                if self.resize_direction:
                    # 开始调整大小
                    self.resize_start_pos = event.globalPos()
                    self.resize_start_geometry = self.parent.geometry()
                    event.accept()
                    return
                else:
                    # 记录拖动起始位置
                    self.drag_position = event.globalPos() - self.parent.frameGeometry().topLeft()
                    event.accept()
            elif event.button() == Qt.RightButton:
                # 右键退出极简模式
                self.exit_minimal_mode()
                event.accept()
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        """鼠标移动事件 - 处理窗口拖动和调整大小[8](@ref)"""
        if self.minimal_mode:
            if event.buttons() == Qt.LeftButton and self.resize_direction:
                # 调整窗口大小
                delta = event.globalPos() - self.resize_start_pos
                new_geometry = self.resize_start_geometry

                if "left" in self.resize_direction:
                    new_geometry.setLeft(new_geometry.left() + delta.x())
                if "right" in self.resize_direction:
                    new_geometry.setRight(new_geometry.right() + delta.x())
                if "top" in self.resize_direction:
                    new_geometry.setTop(new_geometry.top() + delta.y())
                if "bottom" in self.resize_direction:
                    new_geometry.setBottom(new_geometry.bottom() + delta.y())

                # 确保窗口有最小尺寸
                if new_geometry.width() < 200:
                    if "right" in self.resize_direction:
                        new_geometry.setRight(new_geometry.left() + 200)
                    else:
                        new_geometry.setLeft(new_geometry.right() - 200)
                if new_geometry.height() < 150:
                    if "bottom" in self.resize_direction:
                        new_geometry.setBottom(new_geometry.top() + 150)
                    else:
                        new_geometry.setTop(new_geometry.bottom() - 150)

                self.parent.setGeometry(new_geometry)
                event.accept()
            elif event.buttons() == Qt.LeftButton and self.drag_position is not None:
                # 拖动窗口
                self.parent.move(event.globalPos() - self.drag_position)
                event.accept()
            else:
                # 更新鼠标光标形状
                direction = self.get_resize_direction(event.pos())
                self.update_cursor_shape(direction)
                event.accept()
        else:
            super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        """鼠标释放事件 - 重置拖动状态[8](@ref)"""
        if self.minimal_mode and event.button() == Qt.LeftButton:
            self.drag_position = None
            self.resize_direction = None
            event.accept()
        else:
            super().mouseReleaseEvent(event)

    def wheelEvent(self, event):
        """鼠标滚轮事件 - 在极简模式下滚动内容"""
        if self.minimal_mode:
            # 直接使用文本编辑器的滚动功能
            delta = event.angleDelta().y()
            scrollbar = self.content_text.verticalScrollBar()
            scrollbar.setValue(scrollbar.value() - delta // 3)
            event.accept()
        else:
            super().wheelEvent(event)

    def keyPressEvent(self, event):
        """键盘事件处理 - 支持Ctrl+F快捷键切换极简模式"""
        if event.key() == Qt.Key_F and event.modifiers() == Qt.ControlModifier:
            self.toggle_minimal_mode()
        else:
            super().keyPressEvent(event)

    def go_back_to_detail(self):
        # 退出极简模式(如果处于极简模式)
        if self.minimal_mode:
            self.exit_minimal_mode()
        # 返回详情界面
        self.parent.show_novel_detail()


# 搜索界面
class SearchWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(15)

        # 标题
        title_label = QLabel("小说摸鱼阅读器")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setFont(QFont("微软雅黑", 20, QFont.Bold))
        title_label.setStyleSheet("color: #2c3e50; margin-bottom: 20px;")
        layout.addWidget(title_label)

        # 搜索框
        search_layout = QHBoxLayout()
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("请输入小说名称...")
        self.search_input.setFont(QFont("微软雅黑", 12))
        self.search_input.setStyleSheet("""
            QLineEdit {
                padding: 10px;
                border: 2px solid #bdc3c7;
                border-radius: 5px;
                font-size: 14px;
            }
            QLineEdit:focus {
                border-color: #3498db;
            }
        """)
        self.search_input.returnPressed.connect(self.search_novels)

        self.search_button = QPushButton("搜索")
        self.search_button.setFont(QFont("微软雅黑", 12))
        self.search_button.setStyleSheet("""
            QPushButton {
                background-color: #3498db;
                color: white;
                padding: 10px 20px;
                border: none;
                border-radius: 5px;
                font-weight: bold;
            }
            QPushButton:hover {
                background-color: #2980b9;
            }
            QPushButton:pressed {
                background-color: #21618c;
            }
        """)
        self.search_button.clicked.connect(self.search_novels)

        search_layout.addWidget(self.search_input)
        search_layout.addWidget(self.search_button)
        layout.addLayout(search_layout)

        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: 1px solid #bdc3c7;
                border-radius: 5px;
                text-align: center;
                background-color: #ecf0f1;
            }
            QProgressBar::chunk {
                background-color: #3498db;
                border-radius: 3px;
            }
        """)
        layout.addWidget(self.progress_bar)

        # 结果标签
        self.result_label = QLabel("搜索结果将显示在这里")
        self.result_label.setFont(QFont("微软雅黑", 10))
        self.result_label.setStyleSheet("color: #7f8c8d;")
        layout.addWidget(self.result_label)

        # 搜索结果列表
        self.result_list = QListWidget()
        self.result_list.setFont(QFont("微软雅黑", 11))
        self.result_list.setStyleSheet("""
            QListWidget {
                border: 1px solid #bdc3c7;
                border-radius: 5px;
                background-color: white;
                padding: 5px;
            }
            QListWidget::item {
                padding: 10px;
                border-bottom: 1px solid #ecf0f1;
            }
            QListWidget::item:selected {
                background-color: #3498db;
                color: white;
                border-radius: 3px;
            }
            QListWidget::item:hover {
                background-color: #ecf0f1;
                border-radius: 3px;
            }
        """)
        self.result_list.itemDoubleClicked.connect(self.on_novel_selected)
        layout.addWidget(self.result_list)

        self.setLayout(layout)

    def search_novels(self):
        novel_name = self.search_input.text().strip()
        if not novel_name:
            QMessageBox.warning(self, "提示", "请输入小说名称")
            return

        self.progress_bar.setVisible(True)
        self.result_list.clear()
        self.result_label.setText("搜索中...")

        # 使用多线程进行搜索
        self.search_thread = SearchThread(novel_name)
        self.search_thread.finished.connect(self.on_search_finished)
        self.search_thread.error.connect(self.on_search_error)
        self.search_thread.start()

    def on_search_finished(self, result):
        self.progress_bar.setVisible(False)
        self.result_list.clear()

        if not result:
            self.result_label.setText("未找到相关小说")
            QMessageBox.information(self, "提示", "未找到相关小说")
            return

        self.result_label.setText(f"找到 {len(result)} 部相关小说")

        for novel_info in result:
            self.result_list.addItem(f"{novel_info[0]}. 《{novel_info[1]}》 - {novel_info[2]}")

        # 保存搜索结果供后续使用
        self.parent.search_results = result

    def on_search_error(self, error_msg):
        self.progress_bar.setVisible(False)
        self.result_label.setText("搜索失败")
        QMessageBox.critical(self, "错误", f"搜索失败: {error_msg}")

    def on_novel_selected(self, item):
        index = self.result_list.currentRow()
        if index >= 0 and hasattr(self.parent, 'search_results'):
            novel_info = self.parent.search_results[index]
            self.parent.show_novel_detail(novel_info)


# 小说详情界面
class NovelDetailWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(20, 20, 20, 20)
        layout.setSpacing(15)

        # 返回按钮
        back_button = QPushButton("返回搜索")
        back_button.setFont(QFont("微软雅黑", 10))
        back_button.setStyleSheet("""
            QPushButton {
                background-color: #95a5a6;
                color: white;
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #7f8c8d;
            }
        """)
        back_button.clicked.connect(self.parent.show_search)
        layout.addWidget(back_button)

        # 小说信息
        self.novel_info_label = QLabel()
        self.novel_info_label.setFont(QFont("微软雅黑", 14, QFont.Bold))
        self.novel_info_label.setStyleSheet("color: #2c3e50; margin: 10px 0;")
        self.novel_info_label.setWordWrap(True)
        layout.addWidget(self.novel_info_label)

        # 章节列表标签
        chapter_label = QLabel("章节列表")
        chapter_label.setFont(QFont("微软雅黑", 16, QFont.Bold))
        chapter_label.setStyleSheet("color: #2c3e50; margin: 10px 0;")
        layout.addWidget(chapter_label)

        # 章节列表
        self.chapter_list = QListWidget()
        self.chapter_list.setFont(QFont("微软雅黑", 10))
        self.chapter_list.setStyleSheet("""
            QListWidget {
                border: 1px solid #bdc3c7;
                border-radius: 5px;
                background-color: white;
                padding: 5px;
            }
            QListWidget::item {
                padding: 8px;
                border-bottom: 1px solid #ecf0f1;
            }
            QListWidget::item:selected {
                background-color: #3498db;
                color: white;
                border-radius: 3px;
            }
            QListWidget::item:hover {
                background-color: #ecf0f1;
                border-radius: 3px;
            }
        """)
        self.chapter_list.itemDoubleClicked.connect(self.on_chapter_selected)
        layout.addWidget(self.chapter_list)

        self.setLayout(layout)

    def set_novel_info(self, novel_info):
        self.novel_info = novel_info
        self.novel_info_label.setText(f"书名:《{novel_info[1]}》\n作者:{novel_info[2]}")

        # 加载章节列表
        self.parent.progress_bar.setVisible(True)
        self.chapter_thread = ChapterThread(novel_info[3])
        self.chapter_thread.finished.connect(self.on_chapters_loaded)
        self.chapter_thread.error.connect(self.on_chapters_error)
        self.chapter_thread.start()

    def on_chapters_loaded(self, chapters):
        self.parent.progress_bar.setVisible(False)
        self.chapter_list.clear()

        if not chapters:
            QMessageBox.warning(self, "错误", "加载章节失败")
            return

        self.chapters = chapters
        for chapter in chapters:
            self.chapter_list.addItem(f"{chapter[0]}. {chapter[1]}")

    def on_chapters_error(self, error_msg):
        self.parent.progress_bar.setVisible(False)
        QMessageBox.critical(self, "错误", f"加载章节失败: {error_msg}")

    def on_chapter_selected(self, item):
        index = self.chapter_list.currentRow()
        if index >= 0 and hasattr(self, 'chapters'):
            chapter_info = self.chapters[index]
            self.parent.show_reading(self.novel_info, chapter_info, self.chapters)


# 主窗口
class NovelReader(QMainWindow):
    def __init__(self):
        super().__init__()
        self.search_results = []
        self.initUI()
        session_ask()  # 初始化会话

    def initUI(self):
        self.setWindowTitle("小说在线阅读器")
        self.setGeometry(100, 100, 1200, 800)

        # 设置应用样式
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f8f9fa;
            }
        """)

        # 创建中央部件和布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        layout.setContentsMargins(0, 0, 0, 0)

        # 全局进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: none;
                background-color: #e9ecef;
                height: 3px;
            }
            QProgressBar::chunk {
                background-color: #3498db;
            }
        """)
        layout.addWidget(self.progress_bar)

        # 创建堆叠窗口
        self.stacked_widget = QStackedWidget()
        layout.addWidget(self.stacked_widget)

        # 创建三个界面
        self.search_widget = SearchWidget(self)
        self.detail_widget = NovelDetailWidget(self)
        self.reading_widget = ReadingWidget(self)

        # 添加到堆叠窗口
        self.stacked_widget.addWidget(self.search_widget)
        self.stacked_widget.addWidget(self.detail_widget)
        self.stacked_widget.addWidget(self.reading_widget)

        # 显示搜索界面
        self.stacked_widget.setCurrentIndex(0)

    def show_search(self):
        self.stacked_widget.setCurrentIndex(0)

    def show_novel_detail(self, novel_info=None):
        if novel_info is not None:
            self.detail_widget.set_novel_info(novel_info)
        self.stacked_widget.setCurrentIndex(1)

    def show_reading(self, novel_info, chapter_info, chapters):
        self.reading_widget.set_content(novel_info, chapter_info, chapters)
        self.stacked_widget.setCurrentIndex(2)


if __name__ == '__main__':
    app = QApplication(sys.argv)

    # 设置应用字体
    font = QFont("微软雅黑", 10)
    app.setFont(font)

    reader = NovelReader()
    reader.show()
    sys.exit(app.exec_())




1.png 2.png 4.png 5.png

免费评分

参与人数 14吾爱币 +22 热心值 +14 收起 理由
sss孙家大少 + 1 + 1 谢谢@Thanks!
z429083705 + 1 + 1 谢谢@Thanks!
motuo86 + 1 + 1 谢谢@Thanks!
celvana + 1 + 1 谢谢@Thanks!
堕落小南 + 1 + 1 谢谢@Thanks!
hrh123 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
malio9950 + 1 + 1 谢谢@Thanks!
jiangyu_2006 + 1 + 1 谢谢@Thanks!
w360 + 1 + 1 用心讨论,共获提升!
SakuraYaaa + 1 + 1 热心回复!
事愿人违 + 1 + 1 我很赞同!
1102121 + 1 + 1 谢谢@Thanks!
Qiaoyuexuan + 1 + 1 谢谢@Thanks!
lenbin521 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Raohz520 发表于 2025-10-9 14:08
根据楼主提供的百度网盘下载到lanzou上了,
链接: https://pan.baidu.com/s/1IfIeeoP8j1xT7Z02qck0PA?pwd=dmhd 提取码: dmhd 复制这段内容后打开百度网盘手机App,操作更方便哦
下载:https://wwvi.lanzoue.com/igsuP37znq3i 密码:huw4
mebyan 发表于 2026-1-15 14:58
phantomxjc 发表于 2025-10-11 22:44
不过你可以用ai帮我完善一下

修改on_content_loaded方法:

将:
formatted_content = content.replace('\n', '\n\n')

改为:
# 将两个以上的换行符替换为两个换行符(即一个空行)
import re
formatted_content = re.sub(r'\n{2,}', '\n\n', content)

这样,段落之间就只有一个空行了。

但是,注意:原来的单个换行符(即段落内的换行)不会被改变,而多个换行符(空行)会被压缩为一个空行。

这样修改后,我们就不需要再替换单个换行符了,因为单个换行符是段落内的换行,我们不应该在段落内增加空行。

所以,我们只需要压缩多个换行符即可。
 楼主| phantomxjc 发表于 2025-10-9 11:39
Raohz520 发表于 2025-10-9 11:50
有无打包后的程序
 楼主| phantomxjc 发表于 2025-10-9 11:53
Raohz520 发表于 2025-10-9 11:50
有无打包后的程序

忘记加了  吃完饭回来发
lenbin521 发表于 2025-10-9 11:55
楼主好人 等一个程序
 楼主| phantomxjc 发表于 2025-10-9 11:55
Raohz520 发表于 2025-10-9 11:50
有无打包后的程序

通过网盘分享的文件:小说摸鱼阅读器.exe
链接: https://pan.baidu.com/s/1IfIeeoP8j1xT7Z02qck0PA?pwd=dmhd 提取码: dmhd 复制这段内容后打开百度网盘手机App,操作更方便哦
 楼主| phantomxjc 发表于 2025-10-9 11:56
打包后程序如下:通过网盘分享的文件:小说摸鱼阅读器.exe
链接: https://pan.baidu.com/s/1IfIeeoP8j1xT7Z02qck0PA?pwd=dmhd 提取码: dmhd 复制这段内容后打开百度网盘手机App,操作更方便哦
smallmouse228 发表于 2025-10-9 13:35
有没有其他网盘的?
林泽西 发表于 2025-10-9 13:55
失败……
微信截图_20251009135453.png
共享世界 发表于 2025-10-9 13:57
好东西摸鱼的首选。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-1-28 07:34

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表