[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"
"• 按Ctrl+F切换极简模式\n"
"• 鼠标拖动窗口移动\n"
"• 在窗口边缘调整大小\n"
"• 鼠标滚轮滚动内容\n"
"• 右键点击退出极简模式")
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_())