Merge branch 'xinglin-shi' of https://bdgit.educoder.net/p9o3yklam/Curriculum_Design into xinglin-shi

pull/122/head
石兴霖 3 months ago
commit 9ecbb00d8d

@ -137,15 +137,31 @@ class DeepSeekDialogWindow(QDialog):
# 主布局
main_layout = QVBoxLayout()
# 标题栏容器 - 包含标题和主题切换按钮
title_container = QFrame()
title_container_layout = QHBoxLayout()
title_container_layout.setContentsMargins(0, 0, 10, 0)
# 标题
title_label = QLabel("DeepSeek AI对话助手")
title_font = QFont()
title_font.setPointSize(16)
title_font.setBold(True)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("padding: 10px; background-color: #f0f0f0;")
main_layout.addWidget(title_label)
title_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
title_container_layout.addWidget(title_label)
title_container_layout.addStretch()
# 主题切换按钮
theme_button = QPushButton("🌓 切换主题")
theme_button.clicked.connect(self.toggle_theme)
theme_button.setFixedSize(120, 32)
theme_button.setToolTip("点击切换深色/浅色主题")
title_container_layout.addWidget(theme_button)
title_container.setLayout(title_container_layout)
main_layout.addWidget(title_container)
# 分割器
splitter = QSplitter(Qt.Vertical)
@ -162,13 +178,14 @@ class DeepSeekDialogWindow(QDialog):
# 状态栏
status_layout = QHBoxLayout()
self.status_label = QLabel("就绪")
self.status_label.setStyleSheet("color: #666; padding: 5px;")
self.status_label.setObjectName("status_label")
status_layout.addWidget(self.status_label)
status_layout.addStretch()
# API密钥管理按钮
api_key_button = QPushButton("管理API密钥")
api_key_button = QPushButton("🔑 管理API密钥")
api_key_button.clicked.connect(self.manage_api_key)
api_key_button.setFixedSize(120, 32)
status_layout.addWidget(api_key_button)
main_layout.addLayout(status_layout)
@ -176,145 +193,138 @@ class DeepSeekDialogWindow(QDialog):
self.setLayout(main_layout)
# 设置样式
self.apply_theme("light") # 默认使用浅色主题
# 添加主题切换按钮
theme_button = QPushButton("切换主题")
theme_button.clicked.connect(self.toggle_theme)
status_layout.addWidget(theme_button)
self.apply_theme() # 使用主题管理器自动检测主题
# 连接信号
self.streaming_finished.connect(self.on_streaming_finished)
def toggle_theme(self):
"""切换黑白主题"""
if hasattr(self, 'current_theme') and self.current_theme == "dark":
self.apply_theme("light")
else:
self.apply_theme("dark")
"""切换黑白主题 - 使用主题管理器"""
try:
from .ui.theme_manager import theme_manager
except ImportError:
# 处理直接运行的情况
from ui.theme_manager import theme_manager
# 切换主题
current_is_dark = theme_manager.is_dark_theme()
theme_manager.set_dark_theme(not current_is_dark)
# 重新应用主题
self.apply_theme()
# 更新对话显示
self.rebuild_conversation_display()
def apply_theme(self, theme):
"""应用主题样式"""
self.current_theme = theme
if theme == "dark":
# 深色主题样式
self.setStyleSheet("""
QDialog {
background-color: #1e1e1e;
color: #ffffff;
}
QLabel {
color: #ffffff;
}
QTextEdit {
background-color: #2d2d2d;
color: #ffffff;
border: 1px solid #444;
border-radius: 4px;
padding: 10px;
font-family: 'Microsoft YaHei', sans-serif;
font-size: 12px;
}
QLineEdit {
background-color: #2d2d2d;
color: #ffffff;
border: 1px solid #444;
border-radius: 4px;
padding: 8px;
font-size: 12px;
}
QPushButton {
background-color: #0078d7;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 12px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
QScrollArea {
background-color: #1e1e1e;
border: none;
}
QScrollBar:vertical {
background-color: #2d2d2d;
width: 15px;
margin: 0px;
}
QScrollBar::handle:vertical {
background-color: #555;
border-radius: 7px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-color: #666;
}
""")
else:
# 浅色主题样式
self.setStyleSheet("""
QDialog {
background-color: #ffffff;
color: #000000;
}
QLabel {
color: #000000;
}
QTextEdit {
background-color: #ffffff;
color: #000000;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
font-family: 'Microsoft YaHei', sans-serif;
font-size: 12px;
}
QLineEdit {
background-color: #ffffff;
color: #000000;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
font-size: 12px;
}
QPushButton {
background-color: #0078d7;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 12px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
QScrollArea {
background-color: #ffffff;
border: none;
}
QScrollBar:vertical {
background-color: #f0f0f0;
width: 15px;
margin: 0px;
}
QScrollBar::handle:vertical {
background-color: #c0c0c0;
border-radius: 7px;
min-height: 20px;
}
QScrollBar::handle:vertical:hover {
background-color: #a0a0a0;
}
""")
def apply_theme(self, is_dark=None):
"""应用主题样式 - 与主题管理器同步"""
try:
from .ui.theme_manager import theme_manager
except ImportError:
# 处理直接运行的情况
from ui.theme_manager import theme_manager
if is_dark is None:
is_dark = theme_manager.is_dark_theme()
# 获取主题管理器的样式表
base_stylesheet = theme_manager.get_theme_stylesheet(is_dark)
# 添加对话框特定的样式优化
dialog_stylesheet = base_stylesheet + f"""
/* DeepSeek对话框特定样式 */
QDialog {{
background-color: {'#1c1c1e' if is_dark else '#ffffff'};
}}
/* 标题栏优化 */
QLabel {{
padding: 15px;
background-color: {'#2c2c2e' if is_dark else '#f8f8f8'};
border-bottom: 1px solid {'#3a3a3c' if is_dark else '#e0e0e0'};
font-size: 18px;
font-weight: 600;
color: {'#f0f0f0' if is_dark else '#333333'};
}}
/* 对话区域优化 */
QTextEdit#conversation_text {{
background-color: {'#121212' if is_dark else '#ffffff'};
border: 1px solid {'#3a3a3c' if is_dark else '#e0e0e0'};
border-radius: 12px;
padding: 20px;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 14px;
line-height: 1.6;
color: {'#e8e8ed' if is_dark else '#333333'};
}}
/* 输入框优化 */
QTextEdit#input_edit {{
background-color: {'#2c2c2e' if is_dark else '#f8f8f8'};
border: 1px solid {'#3a3a3c' if is_dark else '#e0e0e0'};
border-radius: 12px;
padding: 15px;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 14px;
color: {'#e8e8ed' if is_dark else '#333333'};
}}
QTextEdit#input_edit:focus {{
border: 2px solid {'#0a84ff' if is_dark else '#0078d7'};
}}
/* 按钮优化 */
QPushButton {{
border-radius: 8px;
padding: 10px 20px;
font-weight: 500;
font-size: 13px;
min-width: 80px;
background-color: {'#3a3a3c' if is_dark else '#f0f0f0'};
color: {'#f0f0f0' if is_dark else '#333333'};
border: 1px solid {'#4a4a4c' if is_dark else '#d0d0d0'};
}}
QPushButton:hover {{
background-color: {'#4a4a4c' if is_dark else '#e0e0e0'};
}}
QPushButton:pressed {{
background-color: {'#5a5a5c' if is_dark else '#d0d0d0'};
}}
QPushButton:default {{
background-color: #0a84ff;
color: #ffffff;
border: 1px solid #0a84ff;
}}
QPushButton:default:hover {{
background-color: #0066cc;
border: 1px solid #0066cc;
}}
QPushButton:default:pressed {{
background-color: #004d99;
border: 1px solid #004d99;
}}
/* 滚动区域优化 */
QScrollArea {{
border: none;
background-color: transparent;
}}
/* 状态栏优化 */
QLabel#status_label {{
color: {'#a0a0a0' if is_dark else '#666666'};
font-size: 12px;
padding: 8px;
}}
"""
self.setStyleSheet(dialog_stylesheet)
def create_conversation_area(self, parent):
"""创建对话显示区域"""
@ -413,7 +423,6 @@ class DeepSeekDialogWindow(QDialog):
self.add_streaming_message_start()
# 在新线程中执行流式请求
import threading
self.streaming_thread = threading.Thread(target=self.call_deepseek_api_stream, args=(message,))
self.streaming_thread.daemon = True
self.streaming_thread.start()
@ -436,10 +445,52 @@ class DeepSeekDialogWindow(QDialog):
cursor.movePosition(QTextCursor.End)
self.conversation_text.setTextCursor(cursor)
# 添加AI助手的消息框架
# 添加AI助手的消息框架 - 优化样式
try:
from .ui.theme_manager import theme_manager
except ImportError:
from ui.theme_manager import theme_manager
is_dark = theme_manager.is_dark_theme()
# 使用与普通消息一致的样式
bg_color = "#3a3a3c" if is_dark else "#f0f0f0"
text_color = "#e8e8ed" if is_dark else "#333333"
box_shadow = "0 2px 8px rgba(0, 0, 0, 0.1);" if not is_dark else "0 2px 8px rgba(0, 0, 0, 0.3);"
self.conversation_text.insertHtml(
f'<div id="{self.streaming_message_id}" style="background-color: #f5f5f5; padding: 10px; margin: 5px; border-radius: 10px;">'
f'<b>AI助手:</b><br><span style="color: #666;">正在思考...</span>'
f'<div id="{self.streaming_message_id}" style="'
f'background-color: {bg_color}; '
f'color: {text_color}; '
f'padding: 16px 20px; '
f'margin: 12px 0; '
f'border-radius: 18px; '
f'max-width: 85%; '
f'margin-left: 0; margin-right: auto; '
f'word-wrap: break-word; '
f'line-height: 1.6; '
f'box-shadow: {box_shadow}'
f'">'
f'<div style="'
f'font-weight: 600; '
f'margin-bottom: 8px; '
f'font-size: 14px; '
f'opacity: 0.9; '
f'display: flex; '
f'align-items: center;'
f'">'
f'<span style="'
f'display: inline-block; '
f'width: 10px; '
f'height: 10px; '
f'border-radius: 50%; '
f'background-color: #ff9500; '
f'margin-right: 8px;'
f'"></span>'
f'AI助手'
f'</div>'
f'<div style="font-size: 15px;"><span style="color: #666;">正在思考...</span></div>'
f'</div>'
)
# 自动滚动到底部
@ -455,37 +506,132 @@ class DeepSeekDialogWindow(QDialog):
self.conversation_text.ensureCursorVisible()
def rebuild_conversation_display(self):
"""重新构建对话显示"""
"""重新构建对话显示 - 优化主题适配"""
try:
from .ui.theme_manager import theme_manager
except ImportError:
# 处理直接运行的情况
from ui.theme_manager import theme_manager
is_dark = theme_manager.is_dark_theme()
html_content = ""
for msg in self.conversation_history:
for i, msg in enumerate(self.conversation_history):
sender = msg["sender"]
message = msg["message"]
is_streaming = msg.get("streaming", False)
# 根据发送者设置不同的样式
# 根据发送者和主题设置不同的样式 - 优化颜色对比度和阴影效果
if sender == "用户":
bg_color = "#e3f2fd" if self.current_theme == "light" else "#2d3e50"
text_color = "#000" if self.current_theme == "light" else "#fff"
bg_color = "#0a84ff" # 统一的用户消息颜色
text_color = "#ffffff"
align_style = "margin-left: auto; margin-right: 0;"
box_shadow = "0 2px 8px rgba(10, 132, 255, 0.3);" if not is_dark else "0 2px 8px rgba(10, 132, 255, 0.5);"
elif sender == "AI助手":
bg_color = "#f5f5f5" if self.current_theme == "light" else "#3d3d3d"
text_color = "#000" if self.current_theme == "light" else "#fff"
bg_color = "#3a3a3c" if is_dark else "#f0f0f0"
text_color = "#e8e8ed" if is_dark else "#333333"
align_style = "margin-left: 0; margin-right: auto;"
box_shadow = "0 2px 8px rgba(0, 0, 0, 0.1);" if not is_dark else "0 2px 8px rgba(0, 0, 0, 0.3);"
else: # 系统消息
bg_color = "#fff3cd" if self.current_theme == "light" else "#5d4e00"
text_color = "#856404" if self.current_theme == "light" else "#ffd700"
bg_color = "#5d4e00" if is_dark else "#fff3cd"
text_color = "#ffd700" if is_dark else "#856404"
align_style = "margin: 0 auto;"
box_shadow = "0 2px 8px rgba(93, 78, 0, 0.2);" if not is_dark else "0 2px 8px rgba(93, 78, 0, 0.4);"
# 格式化消息内容,处理换行和特殊字符
if message:
# 将换行符转换为<br>标签
formatted_message = message.replace('\n', '<br>')
# 处理多个连续空格
formatted_message = formatted_message.replace(' ', '&nbsp;&nbsp;')
else:
formatted_message = "正在思考..." if is_streaming else ""
# 格式化消息内容
formatted_message = message.replace('\n', '<br>') if message else "正在思考..."
# 为每个消息添加唯一的ID便于调试
message_id = f"msg-{i}"
# 优化消息气泡样式 - 添加阴影、更好的边距和动画效果
html_content += f'''
<div style="background-color: {bg_color}; color: {text_color}; padding: 10px; margin: 5px; border-radius: 10px;">
<b>{sender}:</b><br>
{formatted_message}
<div id="{message_id}" style="
background-color: {bg_color};
color: {text_color};
padding: 16px 20px;
margin: 12px 0;
border-radius: 18px;
max-width: 85%;
{align_style}
word-wrap: break-word;
line-height: 1.6;
box-shadow: {box_shadow}
transition: all 0.3s ease;
">
<div style="
font-weight: 600;
margin-bottom: 8px;
font-size: 14px;
opacity: 0.9;
display: flex;
align-items: center;
">
<span style="
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: {'#4cd964' if sender == '用户' else '#ff9500' if sender == 'AI助手' else '#ffcc00'};
margin-right: 8px;
"></span>
{sender}
</div>
<div style="
font-size: 15px;
white-space: pre-wrap;
">{formatted_message}</div>
</div>
'''
# 添加现代滚动条样式和整体容器样式
scrollbar_style = f"""
<style>
/* 整体容器样式 */
body {{
background-color: {'#121212' if is_dark else '#ffffff'};
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
}}
/* 滚动条样式 */
::-webkit-scrollbar {{
width: 10px;
}}
::-webkit-scrollbar-track {{
background: {'#1c1c1e' if is_dark else '#f8f8f8'};
border-radius: 5px;
}}
::-webkit-scrollbar-thumb {{
background: {'#5a5a5c' if is_dark else '#c0c0c0'};
border-radius: 5px;
}}
::-webkit-scrollbar-thumb:hover {{
background: {'#6a6a6c' if is_dark else '#a0a0a0'};
}}
/* 消息进入动画 */
@keyframes fadeIn {{
from {{ opacity: 0; transform: translateY(10px); }}
to {{ opacity: 1; transform: translateY(0); }}
}}
#msg-{len(self.conversation_history)-1} {{
animation: fadeIn 0.3s ease-out;
}}
</style>
"""
# 设置HTML内容
self.conversation_text.setHtml(html_content)
self.conversation_text.setHtml(scrollbar_style + html_content)
def call_deepseek_api_stream(self, message):
"""调用DeepSeek API流式版本"""

@ -0,0 +1,535 @@
# ai_chat_panel.py - AI对话面板组件
import json
import os
import requests
import threading
from datetime import datetime
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLineEdit,
QPushButton, QLabel, QScrollArea, QFrame, QMessageBox
)
from PyQt5.QtCore import Qt, pyqtSignal, QThread, QTimer, QSize, pyqtSlot
from PyQt5.QtGui import QFont, QColor, QTextCursor, QIcon, QPixmap
from PyQt5.QtGui import QTextDocument, QTextCharFormat
# 导入主题管理器
try:
from .theme_manager import theme_manager
except ImportError:
# 处理直接运行的情况
from ui.theme_manager import theme_manager
class AIChatPanel(QWidget):
"""AI对话面板"""
# 信号定义 - 用于线程安全的UI更新
update_chat_display = pyqtSignal(str) # 更新聊天显示信号
def __init__(self, parent=None):
super().__init__(parent)
self.api_key = ""
self.conversation_history = []
self.current_streaming_content = ""
self.is_streaming = False
self.streaming_thread = None
self.streaming_timer = None
# 加载API密钥
self.load_api_key()
self.init_ui()
# 连接信号到槽
self.update_chat_display.connect(self.on_update_chat_display)
# 连接主题变化信号
theme_manager.theme_changed.connect(self.on_theme_changed)
def load_api_key(self):
"""从本地文件加载API密钥"""
config_file = os.path.join(
os.path.dirname(__file__),
"..", "..", "resources", "config", "deepseek_api.json"
)
try:
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
self.api_key = config.get('api_key', '')
except Exception as e:
print(f"加载API密钥失败: {e}")
def init_ui(self):
"""初始化UI"""
main_layout = QVBoxLayout()
main_layout.setContentsMargins(8, 8, 8, 8)
main_layout.setSpacing(8)
# 标题栏
header_layout = QHBoxLayout()
header_label = QLabel("AI 助手")
header_font = QFont()
header_font.setBold(True)
header_font.setPointSize(11)
header_label.setFont(header_font)
header_layout.addWidget(header_label)
header_layout.addStretch()
# 清空历史按钮
clear_btn = QPushButton("清空")
clear_btn.setObjectName("clear_btn") # 设置对象名称
clear_btn.setMaximumWidth(60)
clear_btn.setFont(QFont("微软雅黑", 9))
clear_btn.clicked.connect(self.clear_history)
clear_btn.setStyleSheet("""
QPushButton {
background-color: #f0f0f0;
color: #333333;
border: 1px solid #d0d0d0;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
min-width: 50px;
}
QPushButton:hover {
background-color: #e0e0e0;
border: 1px solid #c0c0c0;
}
QPushButton:pressed {
background-color: #d0d0d0;
border: 1px solid #b0b0b0;
}
/* 深色主题 */
QPushButton[darkTheme="true"] {
background-color: #3c3c3c;
color: #e0e0e0;
border: 1px solid #4c4c4c;
}
QPushButton[darkTheme="true"]:hover {
background-color: #4c4c4c;
border: 1px solid #5c5c5c;
}
QPushButton[darkTheme="true"]:pressed {
background-color: #5c5c5c;
border: 1px solid #6c6c6c;
}
""")
header_layout.addWidget(clear_btn)
main_layout.addLayout(header_layout)
# 分割线
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setStyleSheet("color: #d0d0d0;")
main_layout.addWidget(line)
# 对话显示区域
self.chat_display = QTextEdit()
self.chat_display.setReadOnly(True)
self.chat_display.setStyleSheet("""
QTextEdit {
background-color: #ffffff;
border: 1px solid #d0d0d0;
border-radius: 8px;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 11pt;
padding: 12px;
color: #333333;
line-height: 1.5;
}
/* 深色主题 */
QTextEdit[darkTheme="true"] {
background-color: #1e1e1e;
border: 1px solid #3c3c3c;
color: #e0e0e0;
}
""")
self.chat_display.setMinimumHeight(400)
main_layout.addWidget(self.chat_display)
# 输入区域
input_layout = QVBoxLayout()
input_layout.setSpacing(6)
# 输入框
self.input_field = QLineEdit()
self.input_field.setPlaceholderText("输入您的问题或请求...")
self.input_field.setStyleSheet("""
QLineEdit {
background-color: #f9f9f9;
border: 1px solid #d0d0d0;
border-radius: 8px;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 11pt;
padding: 10px 12px;
color: #333333;
}
QLineEdit:focus {
border: 2px solid #0078d4;
background-color: #ffffff;
}
/* 深色主题 */
QLineEdit[darkTheme="true"] {
background-color: #2d2d2d;
border: 1px solid #3c3c3c;
color: #e0e0e0;
}
QLineEdit[darkTheme="true"]:focus {
border: 2px solid #0078d4;
background-color: #1e1e1e;
}
""")
self.input_field.returnPressed.connect(self.send_user_message)
input_layout.addWidget(self.input_field)
# 按钮区域
button_layout = QHBoxLayout()
button_layout.setSpacing(6)
# 发送按钮
self.send_btn = QPushButton("发送")
self.send_btn.setFont(QFont("微软雅黑", 9))
self.send_btn.setStyleSheet("""
QPushButton {
background-color: #0078d4;
color: white;
border: none;
border-radius: 6px;
padding: 8px 20px;
font-weight: 500;
font-size: 12px;
min-width: 70px;
}
QPushButton:hover {
background-color: #0063b1;
}
QPushButton:pressed {
background-color: #005a9e;
}
/* 深色主题 */
QPushButton[darkTheme="true"] {
background-color: #0078d4;
}
QPushButton[darkTheme="true"]:hover {
background-color: #0063b1;
}
QPushButton[darkTheme="true"]:pressed {
background-color: #005a9e;
}
""")
self.send_btn.clicked.connect(self.send_user_message)
button_layout.addStretch()
button_layout.addWidget(self.send_btn)
input_layout.addLayout(button_layout)
main_layout.addLayout(input_layout)
self.setLayout(main_layout)
# 设置背景颜色
self.setStyleSheet("""
AIChatPanel {
background-color: #f5f5f5;
border-left: 1px solid #d0d0d0;
}
/* 深色主题 */
AIChatPanel[darkTheme="true"] {
background-color: #2d2d2d;
border-left: 1px solid #3c3c3c;
}
""")
# 初始化主题
self.apply_theme()
def apply_theme(self):
"""应用当前主题"""
is_dark = theme_manager.is_dark_theme()
# 设置属性用于样式表选择器
self.setProperty("darkTheme", is_dark)
# 更新子控件的属性
self.chat_display.setProperty("darkTheme", is_dark)
self.input_field.setProperty("darkTheme", is_dark)
self.send_btn.setProperty("darkTheme", is_dark)
self.findChild(QPushButton, "clear_btn").setProperty("darkTheme", is_dark) if self.findChild(QPushButton, "clear_btn") else None
# 重新应用样式表
self.style().unpolish(self)
self.style().polish(self)
self.update()
# 更新聊天显示样式
self.chat_display.style().unpolish(self.chat_display)
self.chat_display.style().polish(self.chat_display)
self.chat_display.update()
# 更新输入框样式
self.input_field.style().unpolish(self.input_field)
self.input_field.style().polish(self.input_field)
self.input_field.update()
# 更新按钮样式
self.send_btn.style().unpolish(self.send_btn)
self.send_btn.style().polish(self.send_btn)
self.send_btn.update()
clear_btn = self.findChild(QPushButton, "clear_btn")
if clear_btn:
clear_btn.style().unpolish(clear_btn)
clear_btn.style().polish(clear_btn)
clear_btn.update()
def on_theme_changed(self, is_dark):
"""主题变化处理"""
self.apply_theme()
def send_user_message(self):
"""发送用户消息"""
if not self.api_key:
QMessageBox.warning(self, "警告", "请先配置DeepSeek API密钥")
return
message = self.input_field.text().strip()
if not message:
return
# 清空输入框
self.input_field.clear()
# 禁用发送按钮
self.send_btn.setEnabled(False)
self.input_field.setEnabled(False)
# 显示用户消息
self.add_message_to_display("用户", message)
# 添加用户消息到对话历史
self.conversation_history.append({"sender": "用户", "message": message})
# 显示AI正在思考
self.add_message_to_display("AI助手", "正在思考...")
self.conversation_history.append({"sender": "AI助手", "message": ""})
# 在新线程中调用API
self.streaming_thread = threading.Thread(
target=self.call_deepseek_api_stream,
args=(message,)
)
self.streaming_thread.daemon = True
self.streaming_thread.start()
# 启动定时器更新显示
self.streaming_timer = QTimer()
self.streaming_timer.timeout.connect(self.update_streaming_display)
self.streaming_timer.start(100) # 每100毫秒更新一次显示
def add_message_to_display(self, sender, message):
"""添加消息到显示区域"""
cursor = self.chat_display.textCursor()
cursor.movePosition(QTextCursor.End)
self.chat_display.setTextCursor(cursor)
# 设置格式
char_format = QTextCharFormat()
char_format.setFont(QFont("微软雅黑", 11))
if sender == "用户":
# 根据主题设置用户消息颜色
if theme_manager.is_dark_theme():
char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色
else:
char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色
char_format.setFontWeight(60) # 中等粗体
prefix = "你: "
else: # AI助手
# 根据主题设置AI消息颜色
if theme_manager.is_dark_theme():
char_format.setForeground(QColor("#e0e0e0")) # 深色主题下的浅灰色
else:
char_format.setForeground(QColor("#000000")) # 浅色主题下的纯黑色,提高可读性
char_format.setFontWeight(50) # 正常粗体
prefix = "AI: "
# 插入时间戳
timestamp_format = QTextCharFormat()
if theme_manager.is_dark_theme():
timestamp_format.setForeground(QColor("#a0a0a0")) # 深色主题下的灰色
else:
timestamp_format.setForeground(QColor("#666666")) # 浅色主题下的深灰色,提高可读性
timestamp_format.setFont(QFont("微软雅黑", 9))
cursor.insertText(f"\n[{datetime.now().strftime('%H:%M:%S')}] ", timestamp_format)
# 插入前缀
cursor.insertText(prefix, char_format)
# 插入消息
cursor.insertText(message, char_format)
# 自动滚动到底部
self.chat_display.verticalScrollBar().setValue(
self.chat_display.verticalScrollBar().maximum()
)
def rebuild_chat_display(self):
"""重新构建聊天显示"""
self.chat_display.clear()
cursor = self.chat_display.textCursor()
for msg in self.conversation_history:
sender = msg["sender"]
message = msg["message"]
# 设置格式
char_format = QTextCharFormat()
char_format.setFont(QFont("微软雅黑", 11))
if sender == "用户":
# 根据主题设置用户消息颜色
if theme_manager.is_dark_theme():
char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色
else:
char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色
char_format.setFontWeight(60) # 中等粗体
prefix = "你: "
else:
# 根据主题设置AI消息颜色
if theme_manager.is_dark_theme():
char_format.setForeground(QColor("#e0e0e0")) # 深色主题下的浅灰色
else:
char_format.setForeground(QColor("#000000")) # 浅色主题下的纯黑色,提高可读性
char_format.setFontWeight(50) # 正常粗体
prefix = "AI: "
# 插入分隔符
if self.chat_display.toPlainText():
cursor.insertText("\n")
# 插入时间戳
timestamp_format = QTextCharFormat()
if theme_manager.is_dark_theme():
timestamp_format.setForeground(QColor("#a0a0a0")) # 深色主题下的灰色
else:
timestamp_format.setForeground(QColor("#666666")) # 浅色主题下的深灰色,提高可读性
timestamp_format.setFont(QFont("微软雅黑", 9))
cursor.insertText(f"[{datetime.now().strftime('%H:%M:%S')}] ", timestamp_format)
# 插入前缀
cursor.insertText(prefix, char_format)
# 插入消息
cursor.insertText(message, char_format)
def call_deepseek_api_stream(self, message):
"""调用DeepSeek API流式版本"""
url = "https://api.deepseek.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
messages = [{"role": "user", "content": message}]
data = {
"model": "deepseek-chat",
"messages": messages,
"stream": True,
"temperature": 0.7,
"max_tokens": 2000
}
self.is_streaming = True
self.current_streaming_content = ""
try:
response = requests.post(url, headers=headers, json=data, stream=True, timeout=30)
if response.status_code == 200:
for line in response.iter_lines():
if line:
line = line.decode('utf-8')
if line.startswith('data: '):
data_str = line[6:]
if data_str == '[DONE]':
break
try:
data_obj = json.loads(data_str)
if 'choices' in data_obj and len(data_obj['choices']) > 0:
delta = data_obj['choices'][0].get('delta', {})
if 'content' in delta:
content = delta['content']
self.current_streaming_content += content
except json.JSONDecodeError:
pass
else:
error_msg = f"API调用失败: {response.status_code}"
self.current_streaming_content = error_msg
except requests.exceptions.Timeout:
self.current_streaming_content = "请求超时,请重试"
except Exception as e:
self.current_streaming_content = f"错误: {str(e)}"
finally:
self.is_streaming = False
# 停止定时器
if self.streaming_timer:
self.streaming_timer.stop()
# 最后更新一次显示,使用信号在主线程中进行
self.update_chat_display.emit(self.current_streaming_content)
@pyqtSlot(str)
def on_update_chat_display(self, content):
"""在主线程中更新聊天显示"""
# 更新最后一条AI消息
if len(self.conversation_history) > 0:
self.conversation_history[-1]["message"] = content
# 重新构建显示
self.rebuild_chat_display()
# 自动滚动到底部
self.chat_display.verticalScrollBar().setValue(
self.chat_display.verticalScrollBar().maximum()
)
# 重新启用输入
self.send_btn.setEnabled(True)
self.input_field.setEnabled(True)
self.input_field.setFocus()
def clear_history(self):
"""清空聊天历史"""
reply = QMessageBox.question(
self,
"确认",
"确定要清空聊天历史吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
self.conversation_history = []
self.chat_display.clear()
self.input_field.clear()
def update_streaming_display(self):
"""更新流式显示"""
if self.is_streaming and self.current_streaming_content:
# 重新显示所有对话
self.rebuild_chat_display()
self.chat_display.verticalScrollBar().setValue(
self.chat_display.verticalScrollBar().maximum()
)

@ -133,12 +133,12 @@ class CalendarFloatingWidget(QWidget):
self.apply_theme()
def apply_theme(self):
"""应用主题样式"""
"""应用主题样式 - 优化Apple设计风格"""
is_dark = theme_manager.is_dark_theme()
colors = theme_manager.get_current_theme_colors()
if is_dark:
# 深色主题样式
# 深色主题样式 - 优化版Apple设计风格
self.main_frame.setStyleSheet(f"""
QFrame#mainFrame {{
background-color: {colors['surface']};
@ -153,7 +153,7 @@ class CalendarFloatingWidget(QWidget):
}}
QLabel#dateLabel {{
color: {colors['text_secondary']};
font-size: 11px;
font-size: 12px;
padding: 4px 6px;
margin: 2px;
}}
@ -174,40 +174,49 @@ class CalendarFloatingWidget(QWidget):
color: white;
border-radius: 6px;
}}
QPushButton#closeButton:pressed {{
background-color: #c50e1f;
}}
QPushButton#todayButton, QPushButton#clearButton, QPushButton#insertButton {{
background-color: {colors['accent']};
color: white;
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-size: 12px;
font-weight: 500;
}}
QPushButton#todayButton:hover, QPushButton#clearButton:hover, QPushButton#insertButton:hover {{
background-color: {colors['accent_hover']};
}}
QPushButton#todayButton:pressed, QPushButton#clearButton:pressed, QPushButton#insertButton:pressed {{
background-color: {colors['accent_pressed']};
}}
""")
# 更新日历控件样式
# 更新日历控件样式 - 深色主题优化版Apple设计风格
self.calendar.setStyleSheet(f"""
QCalendarWidget {{
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 8px;
}}
QCalendarWidget QToolButton {{
height: 30px;
width: 80px;
height: 32px;
width: 85px;
color: {colors['text']};
font-size: 12px;
font-weight: bold;
font-size: 13px;
font-weight: 500;
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 6px;
}}
QCalendarWidget QToolButton:hover {{
background-color: {colors['surface_hover']};
}}
QCalendarWidget QToolButton:pressed {{
background-color: {colors['surface_pressed']};
}}
QCalendarWidget QMenu {{
width: 150px;
left: 20px;
@ -215,15 +224,16 @@ class CalendarFloatingWidget(QWidget):
font-size: 12px;
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 6px;
}}
QCalendarWidget QSpinBox {{
width: 80px;
width: 85px;
font-size: 12px;
background-color: {colors['surface']};
selection-background-color: {colors['accent']};
selection-color: white;
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 6px;
color: {colors['text']};
}}
QCalendarWidget QSpinBox::up-button {{
@ -254,12 +264,15 @@ class CalendarFloatingWidget(QWidget):
background-color: {colors['surface']};
color: {colors['text']};
}}
QCalendarWidget QAbstractItemView:disabled {{
color: {colors['text_disabled']};
}}
QCalendarWidget QWidget#qt_calendar_navigationbar {{
background-color: {colors['surface']};
}}
""")
else:
# 浅色主题样式
# 浅色主题样式 - 优化版Apple设计风格
self.main_frame.setStyleSheet(f"""
QFrame#mainFrame {{
background-color: {colors['surface']};
@ -275,7 +288,7 @@ class CalendarFloatingWidget(QWidget):
}}
QLabel#dateLabel {{
color: {colors['text_secondary']};
font-size: 11px;
font-size: 12px;
padding: 4px 6px;
margin: 2px;
}}
@ -296,40 +309,49 @@ class CalendarFloatingWidget(QWidget):
color: white;
border-radius: 6px;
}}
QPushButton#closeButton:pressed {{
background-color: #c50e1f;
}}
QPushButton#todayButton, QPushButton#clearButton, QPushButton#insertButton {{
background-color: {colors['accent']};
color: white;
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-size: 12px;
font-weight: 500;
}}
QPushButton#todayButton:hover, QPushButton#clearButton:hover, QPushButton#insertButton:hover {{
background-color: {colors['accent_hover']};
}}
QPushButton#todayButton:pressed, QPushButton#clearButton:pressed, QPushButton#insertButton:pressed {{
background-color: {colors['accent_pressed']};
}}
""")
# 更新日历控件样式
# 更新日历控件样式 - 浅色主题优化版Apple设计风格
self.calendar.setStyleSheet(f"""
QCalendarWidget {{
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 8px;
}}
QCalendarWidget QToolButton {{
height: 30px;
width: 80px;
height: 32px;
width: 85px;
color: {colors['text']};
font-size: 12px;
font-weight: bold;
font-size: 13px;
font-weight: 500;
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 6px;
}}
QCalendarWidget QToolButton:hover {{
background-color: {colors['surface_hover']};
}}
QCalendarWidget QToolButton:pressed {{
background-color: {colors['surface_pressed']};
}}
QCalendarWidget QMenu {{
width: 150px;
left: 20px;
@ -337,15 +359,16 @@ class CalendarFloatingWidget(QWidget):
font-size: 12px;
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 6px;
}}
QCalendarWidget QSpinBox {{
width: 80px;
width: 85px;
font-size: 12px;
background-color: {colors['surface']};
selection-background-color: {colors['accent']};
selection-color: white;
border: 1px solid {colors['border']};
border-radius: 4px;
border-radius: 6px;
color: {colors['text']};
}}
QCalendarWidget QSpinBox::up-button {{
@ -376,6 +399,9 @@ class CalendarFloatingWidget(QWidget):
background-color: {colors['surface']};
color: {colors['text']};
}}
QCalendarWidget QAbstractItemView:disabled {{
color: {colors['text_disabled']};
}}
QCalendarWidget QWidget#qt_calendar_navigationbar {{
background-color: {colors['surface']};
}}

@ -277,153 +277,205 @@ class CalendarWidget(QWidget):
theme_manager.theme_changed.connect(self.on_theme_changed)
# 应用当前主题
self.apply_theme()
current_theme = theme_manager.is_dark_theme()
self.apply_theme(current_theme)
def apply_theme(self):
"""应用主题样式"""
def apply_theme(self, is_dark_theme):
"""应用主题样式 - 优化Apple设计风格"""
is_dark = theme_manager.is_dark_theme()
if is_dark:
# 深色主题样式
# 深色主题样式 - 优化版Apple设计风格
self.setStyleSheet("""
QWidget {
background-color: #2c2c2e;
color: #f0f0f0;
background-color: #1c1c1e;
color: #e8e8ed;
}
""")
# 更新日历控件样式
self.calendar.setStyleSheet("""
QCalendarWidget {
background-color: #2c2c2e;
border: 1px solid #404040;
border-radius: 4px;
background-color: #1c1c1e;
border: 1px solid #3a3a3c;
border-radius: 8px;
}
QCalendarWidget QToolButton {
height: 30px;
width: 80px;
color: #f0f0f0;
font-size: 12px;
font-weight: bold;
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
border-radius: 4px;
height: 32px;
width: 85px;
color: #e8e8ed;
font-size: 13px;
font-weight: 500;
background-color: #2c2c2e;
border: 1px solid #3a3a3c;
border-radius: 6px;
}
QCalendarWidget QToolButton:hover {
QCalendarWidget QToolButton:pressed {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
}
QCalendarWidget QMenu {
width: 150px;
left: 20px;
color: #f0f0f0;
font-size: 12px;
QCalendarWidget QToolButton:hover {
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
}
QCalendarWidget QMenu {
width: 160px;
left: 20px;
color: #e8e8ed;
font-size: 13px;
background-color: #2c2c2e;
border: 1px solid #3a3a3c;
border-radius: 6px;
}
QCalendarWidget QMenu::item:selected {
background-color: #0a84ff;
color: #ffffff;
}
QCalendarWidget QSpinBox {
width: 80px;
font-size: 12px;
background-color: #3a3a3c;
width: 85px;
font-size: 13px;
background-color: #2c2c2e;
selection-background-color: #0a84ff;
selection-color: #ffffff;
border: 1px solid #4a4a4c;
border-radius: 4px;
color: #f0f0f0;
border: 1px solid #3a3a3c;
border-radius: 6px;
color: #e8e8ed;
padding: 2px;
}
QCalendarWidget QSpinBox::up-button {
subcontrol-origin: border;
subcontrol-position: top right;
width: 20px;
width: 22px;
border: 1px solid #3a3a3c;
background-color: #2c2c2e;
border-radius: 0 6px 0 0;
}
QCalendarWidget QSpinBox::down-button {
subcontrol-origin: border;
subcontrol-position: bottom right;
width: 20px;
width: 22px;
border: 1px solid #3a3a3c;
background-color: #2c2c2e;
border-radius: 0 0 6px 0;
}
QCalendarWidget QSpinBox::up-button:hover,
QCalendarWidget QSpinBox::down-button:hover {
background-color: #3a3a3c;
}
QCalendarWidget QSpinBox::up-button:pressed,
QCalendarWidget QSpinBox::down-button:pressed {
background-color: #4a4a4c;
}
QCalendarWidget QSpinBox::up-arrow {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}
QCalendarWidget QSpinBox::down-arrow {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}
QCalendarWidget QWidget {
alternate-background-color: #3a3a3c;
alternate-background-color: #2c2c2e;
}
QCalendarWidget QAbstractItemView:enabled {
font-size: 12px;
font-size: 13px;
selection-background-color: #0a84ff;
selection-color: #ffffff;
background-color: #2c2c2e;
color: #f0f0f0;
background-color: #121212;
color: #e8e8ed;
}
QCalendarWidget QAbstractItemView:disabled {
color: #8a8a8d;
}
QCalendarWidget QWidget#qt_calendar_navigationbar {
background-color: #3a3a3c;
background-color: #2c2c2e;
}
""")
# 更新标签样式
self.date_label.setStyleSheet("QLabel { color: #a0a0a0; }")
self.date_label.setStyleSheet("QLabel { color: #8a8a8d; font-size: 12px; font-weight: 500; }")
# 更新按钮样式
self.close_btn.setStyleSheet("""
QPushButton {
background-color: #2c2c2e;
border: 1px solid #3a3a3c;
border-radius: 14px;
font-size: 18px;
font-weight: 600;
color: #e8e8ed;
padding: 6px;
}
QPushButton:hover {
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
border-radius: 12px;
font-size: 16px;
font-weight: bold;
color: #f0f0f0;
}
QPushButton:hover {
QPushButton:pressed {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
}
""")
self.today_btn.setStyleSheet("""
QPushButton {
background-color: #0a84ff;
color: white;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 5px 10px;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #0066cc;
background-color: #0071e3;
}
QPushButton:pressed {
background-color: #0051d5;
}
""")
self.clear_btn.setStyleSheet("""
QPushButton {
background-color: #2c2c2e;
color: #e8e8ed;
border: 1px solid #3a3a3c;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #3a3a3c;
color: #f0f0f0;
border: 1px solid #4a4a4c;
border-radius: 4px;
padding: 5px 10px;
}
QPushButton:hover {
QPushButton:pressed {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
}
""")
self.insert_btn.setStyleSheet("""
QPushButton {
background-color: #32d74b;
color: #000000;
background-color: #34c759;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 5px 10px;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #24b334;
background-color: #30d158;
}
QPushButton:pressed {
background-color: #2eb750;
}
""")
else:
# 浅色主题样式
# 浅色主题样式 - 优化版Apple设计风格
self.setStyleSheet("""
QWidget {
background-color: white;
background-color: #f8f8f8;
color: #333333;
}
""")
@ -431,68 +483,96 @@ class CalendarWidget(QWidget):
# 更新日历控件样式
self.calendar.setStyleSheet("""
QCalendarWidget {
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 6px;
}
QCalendarWidget QToolButton {
height: 30px;
width: 80px;
color: #333;
font-size: 12px;
font-weight: bold;
height: 32px;
width: 85px;
color: #333333;
font-size: 13px;
font-weight: 500;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 6px;
}
QCalendarWidget QToolButton:pressed {
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 4px;
border: 1px solid #c0c0c0;
}
QCalendarWidget QToolButton:hover {
background-color: #e0e0e0;
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
}
QCalendarWidget QMenu {
width: 150px;
width: 160px;
left: 20px;
color: #333;
font-size: 12px;
background-color: white;
border: 1px solid #ccc;
color: #333333;
font-size: 13px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 6px;
}
QCalendarWidget QMenu::item:selected {
background-color: #007aff;
color: #ffffff;
}
QCalendarWidget QSpinBox {
width: 80px;
font-size: 12px;
background-color: #f0f0f0;
selection-background-color: #0078d7;
selection-color: white;
border: 1px solid #ccc;
border-radius: 4px;
color: #333;
width: 85px;
font-size: 13px;
background-color: #ffffff;
selection-background-color: #007aff;
selection-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 6px;
color: #333333;
padding: 2px;
}
QCalendarWidget QSpinBox::up-button {
subcontrol-origin: border;
subcontrol-position: top right;
width: 20px;
width: 22px;
border: 1px solid #e0e0e0;
background-color: #ffffff;
border-radius: 0 6px 0 0;
}
QCalendarWidget QSpinBox::down-button {
subcontrol-origin: border;
subcontrol-position: bottom right;
width: 20px;
width: 22px;
border: 1px solid #e0e0e0;
background-color: #ffffff;
border-radius: 0 0 6px 0;
}
QCalendarWidget QSpinBox::up-button:hover,
QCalendarWidget QSpinBox::down-button:hover {
background-color: #f0f0f0;
}
QCalendarWidget QSpinBox::up-button:pressed,
QCalendarWidget QSpinBox::down-button:pressed {
background-color: #e0e0e0;
}
QCalendarWidget QSpinBox::up-arrow {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}
QCalendarWidget QSpinBox::down-arrow {
width: 10px;
height: 10px;
width: 12px;
height: 12px;
}
QCalendarWidget QWidget {
alternate-background-color: #f0f0f0;
alternate-background-color: #f8f8f8;
}
QCalendarWidget QAbstractItemView:enabled {
font-size: 12px;
selection-background-color: #0078d7;
selection-color: white;
background-color: white;
color: #333;
font-size: 13px;
selection-background-color: #007aff;
selection-color: #ffffff;
background-color: #ffffff;
color: #333333;
}
QCalendarWidget QAbstractItemView:disabled {
color: #999999;
}
QCalendarWidget QWidget#qt_calendar_navigationbar {
background-color: #f8f8f8;
@ -500,65 +580,88 @@ class CalendarWidget(QWidget):
""")
# 更新标签样式
self.date_label.setStyleSheet("QLabel { color: #666; }")
self.date_label.setStyleSheet("QLabel { color: #666666; font-size: 12px; font-weight: 500; }")
# 更新按钮样式
self.close_btn.setStyleSheet("""
QPushButton {
background-color: #f0f0f0;
border: 1px solid #ccc;
border-radius: 12px;
font-size: 16px;
font-weight: bold;
color: #333;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 14px;
font-size: 18px;
font-weight: 600;
color: #333333;
padding: 6px;
}
QPushButton:hover {
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
}
QPushButton:pressed {
background-color: #e0e0e0;
border: 1px solid #c0c0c0;
}
""")
self.today_btn.setStyleSheet("""
QPushButton {
background-color: #0078d7;
color: white;
background-color: #007aff;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 5px 10px;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #005a9e;
background-color: #0056b3;
}
QPushButton:pressed {
background-color: #004494;
}
""")
self.clear_btn.setStyleSheet("""
QPushButton {
background-color: #f0f0f0;
color: #333;
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px 10px;
background-color: #ffffff;
color: #333333;
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #f0f0f0;
border: 1px solid #d0d0d0;
}
QPushButton:pressed {
background-color: #e0e0e0;
border: 1px solid #c0c0c0;
}
""")
self.insert_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
background-color: #34c759;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 5px 10px;
border-radius: 6px;
padding: 6px 12px;
font-weight: 500;
font-size: 12px;
}
QPushButton:hover {
background-color: #45a049;
background-color: #2e8b57;
}
QPushButton:pressed {
background-color: #267349;
}
""")
def on_theme_changed(self, is_dark):
"""主题切换槽函数"""
self.apply_theme()
self.apply_theme(is_dark)
if __name__ == "__main__":

@ -153,67 +153,67 @@ class ThemeManager(QObject):
return self._get_light_stylesheet()
def _get_dark_stylesheet(self):
"""深色主题样式表 - Apple设计风格"""
"""深色主题样式表 - 优化版Apple设计风格"""
return """
/* Apple设计风格深色主题样式 */
/* 优化版Apple设计风格深色主题样式 */
/* 全局文字颜色和字体 - 使用Apple系统字体 */
QWidget {
color: #f0f0f0;
color: #e8e8ed;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 13px;
}
/* 主窗口 - Apple深色背景 */
/* 主窗口 - 优化后的深色背景 */
QMainWindow {
background-color: #2c2c2e;
background-color: #1c1c1e;
}
/* 菜单栏 - Apple深色风格 */
/* 菜单栏 - 优化版Apple深色风格 */
QMenuBar {
background-color: #2c2c2e;
border: none;
border-bottom: 1px solid #404040;
border-bottom: 1px solid #3a3a3c;
font-size: 13px;
color: #f0f0f0;
color: #e8e8ed;
padding: 4px 0;
}
QMenuBar::item {
background-color: transparent;
padding: 6px 12px;
color: #f0f0f0;
border-radius: 4px;
margin: 0 1px;
color: #e8e8ed;
border-radius: 6px;
margin: 0 2px;
}
QMenuBar::item:selected {
background-color: #404040;
color: #f0f0f0;
background-color: #3a3a3c;
color: #e8e8ed;
}
QMenuBar::item:pressed {
background-color: #505050;
color: #f0f0f0;
background-color: #4a4a4c;
color: #e8e8ed;
}
/* 菜单 - Apple深色风格 */
/* 菜单 - 优化版Apple深色风格 */
QMenu {
background-color: #2c2c2e;
border: 1px solid #404040;
border: 1px solid #3a3a3c;
border-radius: 8px;
font-size: 13px;
color: #f0f0f0;
color: #e8e8ed;
padding: 4px 0;
margin: 2px;
}
QMenu::item {
color: #f0f0f0;
color: #e8e8ed;
background-color: transparent;
border-radius: 4px;
border-radius: 6px;
margin: 0 4px;
padding: 4px 20px;
padding: 6px 20px;
}
QMenu::item:selected {
@ -228,22 +228,22 @@ class ThemeManager(QObject):
QMenu::separator {
height: 1px;
background-color: #404040;
background-color: #3a3a3c;
margin: 4px 8px;
}
/* 功能区 */
/* 功能区 - 优化背景色 */
QFrame {
background-color: #2c2c2e;
background-color: #1c1c1e;
border: none;
}
/* 组框 */
/* 组框 - 优化标题颜色 */
QGroupBox {
font-size: 12px;
font-weight: normal;
color: #f0f0f0;
background-color: #2c2c2e;
color: #e8e8ed;
background-color: #1c1c1e;
border: none;
border-radius: 8px;
margin-top: 5px;
@ -254,27 +254,27 @@ class ThemeManager(QObject):
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
color: #a0a0a0;
color: #8a8a8d;
}
/* 工具按钮 - Apple深色风格 */
/* 工具按钮 - 优化版Apple深色风格 */
QToolButton {
border: 1px solid transparent;
border-radius: 6px;
background-color: #3a3a3c;
background-color: #2c2c2e;
font-size: 13px;
color: #f0f0f0;
color: #e8e8ed;
padding: 6px 12px;
}
QToolButton:hover {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
}
QToolButton:pressed {
background-color: #5a5a5c;
border: 1px solid #6a6a6c;
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
}
QToolButton:checked {
@ -283,18 +283,18 @@ class ThemeManager(QObject):
color: #ffffff;
}
/* 切换按钮 */
/* 切换按钮 - 优化样式 */
QToolButton[checkable="true"] {
border: 1px solid #4a4a4c;
border: 1px solid #3a3a3c;
border-radius: 6px;
background-color: #3a3a3c;
background-color: #2c2c2e;
font-size: 12px;
color: #f0f0f0;
color: #e8e8ed;
padding: 6px 12px;
}
QToolButton[checkable="true"]:hover {
background-color: #4a4a4c;
background-color: #3a3a3c;
}
QToolButton[checkable="true"]:checked {
@ -303,26 +303,26 @@ class ThemeManager(QObject):
color: #ffffff;
}
/* 下拉框 - Apple深色风格 */
/* 下拉框 - 优化版Apple深色风格 */
QComboBox {
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
background-color: #2c2c2e;
border: 1px solid #3a3a3c;
border-radius: 6px;
color: #f0f0f0;
color: #e8e8ed;
padding: 4px 8px;
selection-background-color: #0a84ff;
selection-color: #ffffff;
}
QComboBox:hover {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
}
QComboBox::drop-down {
border: none;
width: 20px;
border-left: 1px solid #4a4a4c;
border-left: 1px solid #3a3a3c;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
@ -331,44 +331,44 @@ class ThemeManager(QObject):
image: none;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #a0a0a0;
border-top: 6px solid #8a8a8d;
margin: 6px;
}
QComboBox QAbstractItemView {
background-color: #2c2c2e;
border: 1px solid #4a4a4c;
color: #f0f0f0;
background-color: #1c1c1e;
border: 1px solid #3a3a3c;
color: #e8e8ed;
selection-background-color: #0a84ff;
selection-color: #ffffff;
}
/* 文本编辑区域 - Apple深色风格 */
/* 文本编辑区域 - 优化版Apple深色风格 */
QTextEdit {
background-color: #1c1c1e;
background-color: #121212;
border: none;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
font-size: 15px;
color: #f0f0f0;
color: #e8e8ed;
padding: 32px;
line-height: 1.5;
selection-background-color: #0066cc;
line-height: 1.6;
selection-background-color: #0a84ff;
selection-color: #ffffff;
}
/* 状态栏 - Apple深色风格 */
/* 状态栏 - 优化版Apple深色风格 */
QStatusBar {
background-color: #3a3a3c;
border-top: 1px solid #4a4a4c;
background-color: #2c2c2e;
border-top: 1px solid #3a3a3c;
font-size: 12px;
color: #a0a0a0;
color: #8a8a8d;
font-family: '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Helvetica', 'Arial', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', sans-serif;
padding: 6px 12px;
}
/* 标签 */
/* 标签 - 优化颜色 */
QLabel {
color: #f0f0f0;
color: #e8e8ed;
background-color: transparent;
}
@ -436,24 +436,24 @@ class ThemeManager(QObject):
background: none;
}
/* 按钮 - Apple深色风格 */
/* 按钮 - 优化版Apple深色风格 */
QPushButton {
background-color: #3a3a3c;
color: #f0f0f0;
border: 1px solid #4a4a4c;
background-color: #2c2c2e;
color: #e8e8ed;
border: 1px solid #3a3a3c;
border-radius: 6px;
padding: 6px 16px;
font-size: 13px;
}
QPushButton:hover {
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
background-color: #3a3a3c;
border: 1px solid #4a4a4c;
}
QPushButton:pressed {
background-color: #5a5a5c;
border: 1px solid #6a6a6c;
background-color: #4a4a4c;
border: 1px solid #5a5a5c;
}
QPushButton:default {
@ -815,22 +815,28 @@ class ThemeManager(QObject):
'background': '#1e1e1e',
'surface': '#2d2d2d',
'surface_hover': '#3c3c3c',
'surface_pressed': '#4a4a4c',
'text': '#e0e0e0',
'text_secondary': '#b0b0b0',
'text_disabled': '#8a8a8d',
'border': '#3c3c3c',
'accent': '#0078d4',
'accent_hover': '#106ebe'
'accent_hover': '#106ebe',
'accent_pressed': '#005a9e'
}
else:
return {
'background': '#f3f2f1',
'surface': '#ffffff',
'surface_hover': '#f0f0f0',
'surface_pressed': '#e0e0e0',
'text': '#333333',
'text_secondary': '#666666',
'text_disabled': '#999999',
'border': '#d0d0d0',
'accent': '#0078d7',
'accent_hover': '#005a9e'
'accent_hover': '#005a9e',
'accent_pressed': '#004a99'
}

@ -39,7 +39,7 @@ class WeatherFloatingWidget(QDialog):
"""设置UI界面"""
# 设置窗口属性
self.setWindowTitle("天气")
self.setFixedSize(360, 280) # 调整窗口尺寸使其更紧凑
self.setFixedSize(320, 240) # 调整窗口尺寸使其更紧凑
# 创建主框架,用于实现圆角和阴影效果
self.main_frame = QFrame()
@ -52,25 +52,22 @@ class WeatherFloatingWidget(QDialog):
# 内容布局
content_layout = QVBoxLayout(self.main_frame)
content_layout.setContentsMargins(10, 10, 10, 10) # 减小内边距使布局更紧凑
content_layout.setSpacing(6) # 减小间距使布局更紧凑
# 设置最小尺寸策略
self.main_frame.setMinimumSize(380, 300)
content_layout.setContentsMargins(12, 12, 12, 12) # 优化内边距
content_layout.setSpacing(8) # 优化间距
# 标题栏
title_layout = QHBoxLayout()
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(0)
self.title_label = QLabel("天气信息")
self.title_label.setFont(QFont("Arial", 12, QFont.Bold))
title_layout.addWidget(self.title_label)
title_layout.addStretch()
# 添加一个小的固定空间,使关闭按钮向左移动
title_layout.addSpacing(25) # 向左移动25个单位
# 关闭按钮
# 关闭按钮 - 修复被遮挡问题
self.close_btn = QPushButton("×")
self.close_btn.setFixedSize(20, 20)
self.close_btn.setFixedSize(24, 24)
self.close_btn.setObjectName("closeButton")
title_layout.addWidget(self.close_btn)
@ -80,31 +77,32 @@ class WeatherFloatingWidget(QDialog):
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setObjectName("separator")
separator.setFixedHeight(1)
content_layout.addWidget(separator)
# 天气图标和温度显示区域
weather_display_layout = QHBoxLayout()
weather_display_layout.setSpacing(5) # 减小间距使布局更紧凑
weather_display_layout.setContentsMargins(2, 2, 2, 2) # 减小内边距
weather_display_layout.setSpacing(10)
weather_display_layout.setContentsMargins(0, 0, 0, 0)
self.weather_icon_label = QLabel("🌞")
self.weather_icon_label.setFont(QFont("Arial", 24)) # 稍微减小字体大小
self.weather_icon_label.setFont(QFont("Arial", 28))
self.weather_icon_label.setAlignment(Qt.AlignCenter)
self.weather_icon_label.setFixedSize(50, 50) # 减小尺寸
self.weather_icon_label.setFixedSize(60, 60)
weather_display_layout.addWidget(self.weather_icon_label)
# 温度和城市信息
temp_city_layout = QVBoxLayout()
temp_city_layout.setSpacing(4) # 减小间距使布局更紧凑
temp_city_layout.setSpacing(4)
temp_city_layout.setContentsMargins(0, 0, 0, 0)
self.temperature_label = QLabel("25°C")
self.temperature_label.setFont(QFont("Arial", 18, QFont.Bold)) # 稍微减小字体大小
self.temperature_label.setFont(QFont("Arial", 20, QFont.Bold))
self.temperature_label.setObjectName("temperatureLabel")
temp_city_layout.addWidget(self.temperature_label)
self.city_label = QLabel("北京")
self.city_label.setFont(QFont("Arial", 11)) # 稍微减小字体大小
self.city_label.setFont(QFont("Arial", 12))
self.city_label.setObjectName("cityLabel")
temp_city_layout.addWidget(self.city_label)
@ -115,79 +113,88 @@ class WeatherFloatingWidget(QDialog):
# 天气描述
self.weather_desc_label = QLabel("晴天")
self.weather_desc_label.setFont(QFont("Arial", 11)) # 稍微减小字体大小
self.weather_desc_label.setFont(QFont("Arial", 12))
self.weather_desc_label.setObjectName("weatherDescLabel")
self.weather_desc_label.setAlignment(Qt.AlignCenter)
content_layout.addWidget(self.weather_desc_label)
# 详细信息(湿度、风速)
details_layout = QHBoxLayout()
details_layout.setSpacing(6) # 减小间距使布局更紧凑
details_layout.setContentsMargins(2, 2, 2, 2) # 减小内边距
details_layout.setSpacing(12)
details_layout.setContentsMargins(0, 0, 0, 0)
self.humidity_label = QLabel("湿度: 45%")
self.humidity_label.setFont(QFont("Arial", 10)) # 稍微减小字体大小
self.humidity_label.setFont(QFont("Arial", 11))
self.humidity_label.setObjectName("detailLabel")
details_layout.addWidget(self.humidity_label)
self.wind_label = QLabel("风速: 2级")
self.wind_label.setFont(QFont("Arial", 10)) # 稍微减小字体大小
self.wind_label.setFont(QFont("Arial", 11))
self.wind_label.setObjectName("detailLabel")
details_layout.addWidget(self.wind_label)
details_layout.addStretch()
content_layout.addLayout(details_layout)
# 城市选择区域
city_layout = QHBoxLayout()
city_layout.setSpacing(6) # 减小间距使布局更紧凑
city_layout.setContentsMargins(0, 0, 0, 0)
# 添加弹性空间
content_layout.addStretch()
# 城市选择和按钮区域
control_layout = QHBoxLayout()
control_layout.setSpacing(8)
control_layout.setContentsMargins(0, 0, 0, 0)
# 城市选择下拉框
self.city_combo = QComboBox()
self.city_combo.setObjectName("cityCombo")
# 添加所有省会城市,与主窗口保持一致
self.city_combo.addItems([
'自动定位',
'北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安', # 一线城市
'天津', '重庆', '苏州', '青岛', '大连', '宁波', '厦门', '无锡', '佛山', # 新一线城市
'石家庄', '太原', '呼和浩特', '沈阳', '长春', '哈尔滨', # 东北华北
'合肥', '福州', '南昌', '济南', '郑州', '长沙', '南宁', '海口', # 华东华中华南
'贵阳', '昆明', '拉萨', '兰州', '西宁', '银川', '乌鲁木齐' # 西南西北
'自动定位',
# 直辖市
'北京', '上海', '天津', '重庆',
# 省会城市
'石家庄', '太原', '呼和浩特', '沈阳', '长春', '哈尔滨', '南京', '杭州', '合肥', '福州',
'南昌', '济南', '郑州', '武汉', '长沙', '广州', '南宁', '海口', '成都', '贵阳',
'昆明', '拉萨', '西安', '兰州', '西宁', '银川', '乌鲁木齐',
# 特别行政区
'香港', '澳门',
# 台湾省主要城市
'台北', '高雄',
# 主要地级市和经济中心城市
'深圳', '青岛', '大连', '宁波', '厦门', '苏州', '无锡', '佛山', '东莞', '中山',
'泉州', '南通', '常州', '徐州', '温州', '烟台', '威海', '嘉兴', '湖州', '绍兴',
'金华', '台州', '芜湖', '蚌埠', '安庆', '阜阳', '九江', '赣州', '吉安', '上饶',
'淄博', '枣庄', '东营', '潍坊', '济宁', '泰安', '威海', '日照', '临沂', '德州',
'聊城', '滨州', '菏泽', '洛阳', '平顶山', '安阳', '鹤壁', '新乡', '焦作', '濮阳',
'许昌', '漯河', '三门峡', '商丘', '信阳', '周口', '驻马店', '黄石', '十堰', '宜昌',
'襄阳', '鄂州', '荆门', '孝感', '荆州', '黄冈', '咸宁', '随州', '株洲', '湘潭',
'衡阳', '邵阳', '岳阳', '常德', '张家界', '益阳', '郴州', '永州', '怀化', '娄底',
'韶关', '珠海', '汕头', '惠州', '江门', '湛江', '茂名', '肇庆', '梅州', '汕尾',
'河源', '阳江', '清远', '潮州', '揭阳', '云浮', '柳州', '桂林', '梧州', '北海',
'防城港', '钦州', '贵港', '玉林', '百色', '贺州', '河池', '来宾', '崇左', '三亚',
'儋州', '五指山', '琼海', '文昌', '万宁', '东方'
])
self.city_combo.setFixedWidth(100) # 减小城市选择框宽度使布局更紧凑
city_layout.addWidget(self.city_combo)
city_layout.addStretch()
content_layout.addLayout(city_layout)
# 按钮区域
button_layout = QHBoxLayout()
button_layout.setSpacing(6) # 减小间距使布局更紧凑
button_layout.setContentsMargins(0, 0, 0, 0)
self.city_combo.setCurrentText('自动定位')
self.city_combo.currentTextChanged.connect(self.on_city_changed)
control_layout.addWidget(self.city_combo)
self.refresh_btn = QPushButton("刷新")
self.refresh_btn.setObjectName("refreshButton")
self.refresh_btn.setFixedHeight(26) # 减小按钮高度
button_layout.addWidget(self.refresh_btn)
self.refresh_btn.setFixedHeight(28)
control_layout.addWidget(self.refresh_btn)
button_layout.addStretch()
control_layout.addStretch()
self.detail_btn = QPushButton("详情")
self.detail_btn.setObjectName("detailButton")
self.detail_btn.setFixedHeight(26) # 减小按钮高度
button_layout.addWidget(self.detail_btn)
self.detail_btn.setFixedHeight(28)
control_layout.addWidget(self.detail_btn)
content_layout.addLayout(button_layout)
# 添加弹性空间
content_layout.addStretch()
content_layout.addLayout(control_layout)
def setup_connections(self):
"""设置信号连接"""
self.close_btn.clicked.connect(self.close_window)
self.refresh_btn.clicked.connect(self.on_refresh_clicked)
self.detail_btn.clicked.connect(self.show_detailed_weather)
self.city_combo.currentTextChanged.connect(self.on_city_changed)
def setup_theme(self):
"""设置主题"""
@ -208,7 +215,7 @@ class WeatherFloatingWidget(QDialog):
QFrame#mainFrame {{
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 10px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}}
QLabel {{
@ -219,27 +226,27 @@ class WeatherFloatingWidget(QDialog):
}}
QLabel#temperatureLabel {{
color: {colors['accent']};
font-size: 20px;
font-size: 22px;
font-weight: bold;
padding: 6px 8px;
margin: 3px;
padding: 0px 8px;
margin: 0px 3px;
}}
QLabel#cityLabel {{
color: {colors['text_secondary']};
font-size: 12px;
font-size: 13px;
padding: 4px 6px;
margin: 2px;
}}
QLabel#weatherDescLabel {{
color: {colors['text']};
font-size: 12px;
font-size: 13px;
font-weight: 500;
padding: 4px 6px;
margin: 2px;
}}
QLabel#detailLabel {{
color: {colors['text_secondary']};
font-size: 11px;
font-size: 12px;
padding: 4px 6px;
margin: 2px;
}}
@ -266,35 +273,12 @@ class WeatherFloatingWidget(QDialog):
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-size: 12px;
font-weight: 500;
}}
QPushButton#refreshButton:hover, QPushButton#detailButton:hover {{
background-color: {colors['accent_hover']};
}}
QComboBox#cityCombo {{
background-color: {colors['surface']};
color: {colors['text']};
border: 1px solid {colors['border']};
border-radius: 6px;
padding: 4px 7px;
font-size: 11px;
font-weight: 500;
min-height: 24px;
}}
QComboBox#cityCombo:hover {{
border-color: {colors['accent']};
}}
QComboBox#cityCombo::drop-down {{
border: none;
width: 14px;
}}
QComboBox#cityCombo::down-arrow {{
image: none;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
border-top: 5px solid {colors['text']};
}}
""")
else:
# 浅色主题样式 - 与每日谏言悬浮窗口保持一致
@ -302,7 +286,7 @@ class WeatherFloatingWidget(QDialog):
QFrame#mainFrame {{
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 10px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}}
QLabel {{
@ -313,27 +297,27 @@ class WeatherFloatingWidget(QDialog):
}}
QLabel#temperatureLabel {{
color: {colors['accent']};
font-size: 20px;
font-size: 22px;
font-weight: bold;
padding: 6px 8px;
margin: 3px;
padding: 0px 8px;
margin: 0px 3px;
}}
QLabel#cityLabel {{
color: {colors['text_secondary']};
font-size: 12px;
font-size: 13px;
padding: 4px 6px;
margin: 2px;
}}
QLabel#weatherDescLabel {{
color: {colors['text']};
font-size: 12px;
font-size: 13px;
font-weight: 500;
padding: 4px 6px;
margin: 2px;
}}
QLabel#detailLabel {{
color: {colors['text_secondary']};
font-size: 11px;
font-size: 12px;
padding: 4px 6px;
margin: 2px;
}}
@ -360,35 +344,12 @@ class WeatherFloatingWidget(QDialog):
border: none;
border-radius: 6px;
padding: 6px 16px;
font-size: 11px;
font-size: 12px;
font-weight: 500;
}}
QPushButton#refreshButton:hover, QPushButton#detailButton:hover {{
background-color: {colors['accent_hover']};
}}
QComboBox#cityCombo {{
background-color: {colors['surface']};
color: {colors['text']};
border: 1px solid {colors['border']};
border-radius: 6px;
padding: 4px 7px;
font-size: 11px;
font-weight: 500;
min-height: 24px;
}}
QComboBox#cityCombo:hover {{
border-color: {colors['accent']};
}}
QComboBox#cityCombo::drop-down {{
border: none;
width: 14px;
}}
QComboBox#cityCombo::down-arrow {{
image: none;
border-left: 2px solid transparent;
border-right: 2px solid transparent;
border-top: 5px solid {colors['text']};
}}
""")
def on_theme_changed(self, is_dark):
@ -496,12 +457,8 @@ class WeatherFloatingWidget(QDialog):
def set_current_city(self, city_name):
"""设置当前城市"""
# 阻止信号发射,避免循环调用
self.city_combo.blockSignals(True)
index = self.city_combo.findText(city_name)
if index >= 0:
self.city_combo.setCurrentIndex(index)
self.city_combo.blockSignals(False)
if hasattr(self, 'city_combo'):
self.city_combo.setCurrentText(city_name)
def close_window(self):
"""关闭窗口 - 只是隐藏而不是销毁"""

@ -238,18 +238,19 @@ class WordRibbon(QFrame):
preview_layout.setSpacing(8)
style_items = [
("正文", "font-size:14px;"),
("无间隔", "font-size:14px;"),
("标题 1", "font-size:22px; font-weight:bold; color:#2E75B6;"),
("标题 2", "font-size:18px; color:#2E75B6;"),
("标题 3", "font-size:16px; font-weight:bold;"),
("副标题", "font-size:14px; font-style:italic; color:#555;"),
("强调", "font-size:14px; color:#C0504D;"),
("正文", "font-size:14px;", "body_text_preview_btn"),
("无间隔", "font-size:14px;", "no_spacing_preview_btn"),
("标题 1", "font-size:22px; font-weight:bold; color:#2E75B6;", "heading1_preview_btn"),
("标题 2", "font-size:18px; color:#2E75B6;", "heading2_preview_btn"),
("标题 3", "font-size:16px; font-weight:bold;", "heading3_preview_btn"),
("副标题", "font-size:14px; font-style:italic; color:#555;", "subtitle_preview_btn"),
("强调", "font-size:14px; color:#C0504D;", "emphasis_preview_btn"),
]
for text, style in style_items:
for text, style, obj_name in style_items:
btn = QPushButton(text)
btn.setFixedSize(95, 60)
btn.setObjectName(obj_name)
btn.setStyleSheet(f"""
QPushButton {{
background: white;
@ -264,6 +265,9 @@ class WordRibbon(QFrame):
}}
""")
preview_layout.addWidget(btn)
# 将按钮保存为实例属性,以便主窗口可以连接信号
setattr(self, obj_name, btn)
preview_layout.addStretch()
@ -300,6 +304,9 @@ class WordRibbon(QFrame):
self.update_combo_styles(is_dark)
self.update_font_button_styles(is_dark)
# 更新样式预览按钮样式
self.update_style_preview_buttons(is_dark)
# 更新天气组件样式
if hasattr(self, 'weather_icon_label') and self.weather_icon_label is not None:
self.weather_icon_label.setStyleSheet(f"""
@ -397,6 +404,50 @@ class WordRibbon(QFrame):
# 更新字体工具栏按钮样式
self.update_font_button_styles(is_dark)
def update_style_preview_buttons(self, is_dark):
"""更新样式预览按钮样式"""
colors = theme_manager.get_current_theme_colors()
# 样式预览按钮配置
style_items = [
("正文", "font-size:14px;", "body_text_preview_btn"),
("无间隔", "font-size:14px;", "no_spacing_preview_btn"),
("标题 1", "font-size:22px; font-weight:bold; color:#2E75B6;", "heading1_preview_btn"),
("标题 2", "font-size:18px; color:#2E75B6;", "heading2_preview_btn"),
("标题 3", "font-size:16px; font-weight:bold;", "heading3_preview_btn"),
("副标题", "font-size:14px; font-style:italic; color:#555;", "subtitle_preview_btn"),
("强调", "font-size:14px; color:#C0504D;", "emphasis_preview_btn"),
]
for text, style, obj_name in style_items:
if hasattr(self, obj_name):
btn = getattr(self, obj_name)
# 根据主题调整颜色
if is_dark:
# 黑色模式下的颜色调整
if "color:#2E75B6" in style:
style = style.replace("color:#2E75B6", "color:#5B9BD5")
elif "color:#555" in style:
style = style.replace("color:#555", "color:#999")
elif "color:#C0504D" in style:
style = style.replace("color:#C0504D", "color:#E74C3C")
btn.setStyleSheet(f"""
QPushButton {{
background-color: {colors['surface']};
border: 1px solid {colors['border']};
border-radius: 3px;
text-align: left;
padding: 5px;
color: {colors['text']};
{style}
}}
QPushButton:hover {{
border: 1px solid {colors['accent']};
background-color: {colors['surface_hover']};
}}
""")
def update_font_button_styles(self, is_dark):
"""更新字体工具栏按钮样式"""
colors = theme_manager.get_current_theme_colors()
@ -880,7 +931,9 @@ class WordRibbon(QFrame):
def on_city_changed(self, city):
"""城市选择变化处理"""
pass
# 通知主窗口城市已更改
if hasattr(self.parent(), 'on_city_changed'):
self.parent().on_city_changed(city)
def load_daily_quote(self):
"""加载每日一言"""
@ -1016,8 +1069,10 @@ class WordRibbon(QFrame):
btn.setFixedSize(32, 28) # 单个字符如B、I、U
elif len(text) <= 2:
btn.setFixedSize(48, 28) # 两个字符(如"居中"
elif len(text) <= 3:
btn.setFixedSize(64, 28) # 三个字符(如"左对齐"
else:
btn.setFixedSize(64, 28) # 三个字符及以上(如"左对齐"、"两端对齐"
btn.setFixedSize(80, 28) # 四个字符及以上(如"两端对齐"
# 连接主题切换信号以动态更新样式
theme_manager.theme_changed.connect(lambda: self._update_toggle_button_style(btn))

@ -22,6 +22,7 @@ from ui.calendar_widget import CalendarWidget
from ui.weather_floating_widget import WeatherFloatingWidget
from ui.quote_floating_widget import QuoteFloatingWidget
from ui.calendar_floating_widget import CalendarFloatingWidget
from ui.ai_chat_panel import AIChatPanel
# 导入主题管理器
from ui.theme_manager import theme_manager
@ -359,7 +360,7 @@ class WordStyleMainWindow(QMainWindow):
# 更新日历组件样式
if hasattr(self, 'calendar_widget') and self.calendar_widget is not None:
# 日历组件有自己的主题管理机制,只需触发其主题更新
self.calendar_widget.apply_theme()
self.calendar_widget.apply_theme(is_dark)
def update_ribbon_styles(self, is_dark):
"""更新功能区样式"""
@ -461,6 +462,12 @@ class WordStyleMainWindow(QMainWindow):
def on_city_changed(self, city):
"""城市选择变化处理"""
print(f"城市选择变化: {city}")
# 同步Ribbon和天气悬浮窗口的城市选择
if hasattr(self, 'ribbon') and hasattr(self.ribbon, 'city_combo'):
self.ribbon.city_combo.setCurrentText(city)
if hasattr(self, 'weather_floating_widget') and hasattr(self.weather_floating_widget, 'city_combo'):
self.weather_floating_widget.city_combo.setCurrentText(city)
if city == '自动定位':
self.refresh_weather() # 重新自动定位
else:
@ -572,82 +579,82 @@ class WordStyleMainWindow(QMainWindow):
self.menubar = menubar # 保存为实例变量以便后续样式更新
# 文件菜单
file_menu = menubar.addMenu('文件(F)')
file_menu = menubar.addMenu('文件操作')
self.file_menu = file_menu # 保存为实例变量
# 新建
new_action = QAction('新建(N)', self)
new_action = QAction('新建文档', self)
new_action.setShortcut('Ctrl+N')
new_action.triggered.connect(self.new_document)
file_menu.addAction(new_action)
# 导入文件 - 改为导入功能
open_action = QAction('导入文(I)...', self)
open_action = QAction('导入文本文件...', self)
open_action.setShortcut('Ctrl+O')
open_action.triggered.connect(self.import_file)
file_menu.addAction(open_action)
# 保存
save_action = QAction('保存(S)', self)
save_action = QAction('保存文档', self)
save_action.setShortcut('Ctrl+S')
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
# 另存为
save_as_action = QAction('另存为(A)...', self)
save_as_action = QAction('另存为...', self)
save_as_action.triggered.connect(self.save_as_file)
file_menu.addAction(save_as_action)
file_menu.addSeparator()
# 退出
exit_action = QAction('退出(X)', self)
exit_action = QAction('退出程序', self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 开始菜单
start_menu = menubar.addMenu('开始(S)')
start_menu.setObjectName("startMenu")
self.start_menu = start_menu # 保存为实例变量
# 编辑菜单
edit_menu = menubar.addMenu('编辑操作')
edit_menu.setObjectName("editMenu")
self.start_menu = edit_menu # 保存为实例变量
# 撤销
undo_action = QAction('撤销(U)', self)
undo_action = QAction('撤销操作', self)
undo_action.setShortcut('Ctrl+Z')
undo_action.triggered.connect(self.undo)
start_menu.addAction(undo_action)
edit_menu.addAction(undo_action)
# 重做
redo_action = QAction('重做(R)', self)
redo_action = QAction('重做操作', self)
redo_action.setShortcut('Ctrl+Y')
redo_action.triggered.connect(self.redo)
start_menu.addAction(redo_action)
edit_menu.addAction(redo_action)
start_menu.addSeparator()
edit_menu.addSeparator()
# 剪切
cut_action = QAction('剪切(T)', self)
cut_action = QAction('剪切内容', self)
cut_action.setShortcut('Ctrl+X')
cut_action.triggered.connect(self.cut)
start_menu.addAction(cut_action)
edit_menu.addAction(cut_action)
# 复制
copy_action = QAction('复制(C)', self)
copy_action = QAction('复制内容', self)
copy_action.setShortcut('Ctrl+C')
copy_action.triggered.connect(self.copy)
start_menu.addAction(copy_action)
edit_menu.addAction(copy_action)
# 粘贴
paste_action = QAction('粘贴(P)', self)
paste_action = QAction('粘贴内容', self)
paste_action.setShortcut('Ctrl+V')
paste_action.triggered.connect(self.paste)
start_menu.addAction(paste_action)
edit_menu.addAction(paste_action)
# 视图菜单
view_menu = menubar.addMenu('视图(V)')
view_menu = menubar.addMenu('视图设置')
self.view_menu = view_menu # 保存为实例变量
# 阅读视图
read_view_action = QAction('阅读视图', self)
read_view_action = QAction('切换到阅读视图', self)
read_view_action.triggered.connect(self.toggle_reading_view)
view_menu.addAction(read_view_action)
@ -660,18 +667,18 @@ class WordStyleMainWindow(QMainWindow):
view_menu.addSeparator()
# 模式选择子菜单
theme_menu = view_menu.addMenu('模式')
# 主题模式选择
theme_menu = view_menu.addMenu('主题模式')
# 白色模式
self.light_mode_action = QAction('白色模式', self)
self.light_mode_action = QAction('浅色主题', self)
self.light_mode_action.setCheckable(True)
self.light_mode_action.setChecked(not theme_manager.is_dark_theme()) # 根据当前主题设置
self.light_mode_action.triggered.connect(self.set_light_mode)
theme_menu.addAction(self.light_mode_action)
# 黑色模式
self.dark_mode_action = QAction('黑色模式', self)
self.dark_mode_action = QAction('深色主题', self)
self.dark_mode_action.setCheckable(True)
self.dark_mode_action.setChecked(theme_manager.is_dark_theme()) # 根据当前主题设置
self.dark_mode_action.triggered.connect(self.set_dark_mode)
@ -679,56 +686,56 @@ class WordStyleMainWindow(QMainWindow):
view_menu.addSeparator()
# 视图模式选择
view_mode_menu = view_menu.addMenu('视图模式')
# 工作模式选择
work_mode_menu = view_menu.addMenu('工作模式')
# 打字模式
self.typing_mode_action = QAction('打字模式', self)
self.typing_mode_action = QAction('打字练习模式', self)
self.typing_mode_action.setCheckable(True)
self.typing_mode_action.setChecked(True) # 默认打字模式
self.typing_mode_action.triggered.connect(lambda: self.set_view_mode("typing"))
view_mode_menu.addAction(self.typing_mode_action)
work_mode_menu.addAction(self.typing_mode_action)
# 学习模式
self.learning_mode_action = QAction('学习模式', self)
self.learning_mode_action = QAction('学习记忆模式', self)
self.learning_mode_action.setCheckable(True)
self.learning_mode_action.setChecked(False)
# 设置学习模式快捷键 (Qt会自动在macOS上映射Ctrl为Cmd)
self.learning_mode_action.setShortcut('Ctrl+L')
self.learning_mode_action.triggered.connect(lambda: self.set_view_mode("learning"))
view_mode_menu.addAction(self.learning_mode_action)
work_mode_menu.addAction(self.learning_mode_action)
view_menu.addSeparator()
# 附加工具功能
weather_menu = view_menu.addMenu('附加工具')
tools_menu = view_menu.addMenu('实用工具')
# 刷新天气
refresh_weather_action = QAction('刷新天气', self)
refresh_weather_action.setShortcut('F5')
refresh_weather_action.triggered.connect(self.refresh_weather)
weather_menu.addAction(refresh_weather_action)
tools_menu.addAction(refresh_weather_action)
# 显示详细天气
show_weather_action = QAction('显示详细天气', self)
show_weather_action.triggered.connect(self.show_detailed_weather)
weather_menu.addAction(show_weather_action)
tools_menu.addAction(show_weather_action)
# 天气悬浮窗口
toggle_floating_weather_action = QAction('天气悬浮窗口', self)
toggle_floating_weather_action.triggered.connect(self.toggle_floating_weather)
weather_menu.addAction(toggle_floating_weather_action)
tools_menu.addAction(toggle_floating_weather_action)
# 每日谏言悬浮窗口切换动作
toggle_floating_quote_action = QAction('每日谏言悬浮窗口', self)
toggle_floating_quote_action.triggered.connect(self.toggle_floating_quote)
weather_menu.addAction(toggle_floating_quote_action)
tools_menu.addAction(toggle_floating_quote_action)
# 日历悬浮窗口切换动作
toggle_floating_calendar_action = QAction('日历悬浮窗口', self)
toggle_floating_calendar_action.triggered.connect(self.toggle_floating_calendar)
weather_menu.addAction(toggle_floating_calendar_action)
tools_menu.addAction(toggle_floating_calendar_action)
# 插入菜单
insert_menu = menubar.addMenu('插入(I)')
@ -786,29 +793,17 @@ class WordStyleMainWindow(QMainWindow):
export_docx_action.triggered.connect(self.export_as_docx)
export_menu.addAction(export_docx_action)
# 布局菜单
layout_menu = menubar.addMenu('布局(L)')
# 引用菜单
reference_menu = menubar.addMenu('引用(R)')
reference_menu = menubar.addMenu('AI功能')
# DeepSeek AI对话功能
self.deepseek_dialog_action = QAction('DeepSeek AI对话', self)
self.deepseek_dialog_action = QAction('AI智能对话', self)
self.deepseek_dialog_action.setShortcut('Ctrl+D')
self.deepseek_dialog_action.triggered.connect(self.open_deepseek_dialog)
reference_menu.addAction(self.deepseek_dialog_action)
# 邮件菜单
mail_menu = menubar.addMenu('邮件(M)')
# 审阅菜单
review_menu = menubar.addMenu('审阅(W)')
# 开发工具菜单
developer_menu = menubar.addMenu('开发工具(Q)')
# 应用选项菜单
app_menu = menubar.addMenu('应用选项(O)')
app_menu = menubar.addMenu('应用选项')
# 小游戏子菜单
games_menu = app_menu.addMenu('小游戏')
@ -824,7 +819,7 @@ class WordStyleMainWindow(QMainWindow):
games_menu.addAction(minesweeper_game_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助(H)')
help_menu = menubar.addMenu('帮助')
# 关于
about_action = QAction('关于 MagicWord', self)
@ -833,8 +828,12 @@ class WordStyleMainWindow(QMainWindow):
def create_document_area(self, main_layout):
"""创建文档编辑区域"""
"""创建文档编辑区域和AI对话面板"""
# 创建水平分割器
splitter = QSplitter(Qt.Horizontal)
# ========== 左侧:文档编辑区域 ==========
# 创建滚动区域
from PyQt5.QtWidgets import QScrollArea
@ -911,7 +910,22 @@ class WordStyleMainWindow(QMainWindow):
document_container.setLayout(document_layout)
self.scroll_area.setWidget(document_container)
main_layout.addWidget(self.scroll_area)
# ========== 右侧AI对话面板 ==========
self.ai_chat_panel = AIChatPanel()
self.ai_chat_panel.setMinimumWidth(320)
# 添加左右两部分到分割器
splitter.addWidget(self.scroll_area)
splitter.addWidget(self.ai_chat_panel)
# 设置分割器大小比例(文档区:对话区 = 70:30
splitter.setSizes([700, 300])
splitter.setStretchFactor(0, 2) # 文档区可伸缩
splitter.setStretchFactor(1, 1) # 对话区可伸缩
# 添加分割器到主布局
main_layout.addWidget(splitter)
def init_network_services(self):
"""初始化网络服务"""
@ -925,6 +939,7 @@ class WordStyleMainWindow(QMainWindow):
self.quote_thread.quote_fetched.connect(self.update_quote_display)
self.quote_thread.start()
def init_typing_logic(self):
"""初始化打字逻辑"""
# 使用默认内容初始化打字逻辑
@ -964,6 +979,22 @@ class WordStyleMainWindow(QMainWindow):
if hasattr(self.ribbon, 'body_text_btn'):
self.ribbon.body_text_btn.clicked.connect(self.on_body_text_clicked)
# 样式预览按钮信号
if hasattr(self.ribbon, 'body_text_preview_btn'):
self.ribbon.body_text_preview_btn.clicked.connect(self.on_body_text_clicked)
if hasattr(self.ribbon, 'no_spacing_preview_btn'):
self.ribbon.no_spacing_preview_btn.clicked.connect(self.on_no_spacing_clicked)
if hasattr(self.ribbon, 'heading1_preview_btn'):
self.ribbon.heading1_preview_btn.clicked.connect(self.on_heading1_clicked)
if hasattr(self.ribbon, 'heading2_preview_btn'):
self.ribbon.heading2_preview_btn.clicked.connect(self.on_heading2_clicked)
if hasattr(self.ribbon, 'heading3_preview_btn'):
self.ribbon.heading3_preview_btn.clicked.connect(self.on_heading3_clicked)
if hasattr(self.ribbon, 'subtitle_preview_btn'):
self.ribbon.subtitle_preview_btn.clicked.connect(self.on_subtitle_clicked)
if hasattr(self.ribbon, 'emphasis_preview_btn'):
self.ribbon.emphasis_preview_btn.clicked.connect(self.on_emphasis_clicked)
# 查找和替换按钮信号
if hasattr(self.ribbon, 'find_btn'):
self.ribbon.find_btn.clicked.connect(self.show_find_dialog)
@ -1472,6 +1503,18 @@ class WordStyleMainWindow(QMainWindow):
"""正文按钮点击处理"""
self.apply_body_text_style()
def on_subtitle_clicked(self):
"""副标题按钮点击处理"""
self.apply_subtitle_style()
def on_emphasis_clicked(self):
"""强调按钮点击处理"""
self.apply_emphasis_style()
def on_no_spacing_clicked(self):
"""无间隔按钮点击处理"""
self.apply_no_spacing_style()
def on_align_left_clicked(self):
"""左对齐按钮点击处理"""
self.apply_alignment(Qt.AlignLeft)
@ -1579,6 +1622,84 @@ class WordStyleMainWindow(QMainWindow):
self.text_edit.setCurrentCharFormat(char_format)
self.text_edit.textCursor().setBlockFormat(block_format)
def apply_subtitle_style(self):
"""应用副标题样式"""
cursor = self.text_edit.textCursor()
# 创建字符格式
char_format = QTextCharFormat()
char_format.setFontPointSize(16) # 副标题字号
char_format.setFontWeight(QFont.Bold) # 加粗
char_format.setFontItalic(True) # 斜体
# 创建块格式(段落格式)
block_format = QTextBlockFormat()
block_format.setTopMargin(12)
block_format.setBottomMargin(8)
# 应用格式
if cursor.hasSelection():
# 如果有选中文本,只更改选中文本的格式
cursor.mergeCharFormat(char_format)
else:
# 如果没有选中文本,更改当前段落的格式
cursor.setBlockFormat(block_format)
cursor.mergeCharFormat(char_format)
# 将光标移动到段落末尾并添加换行
cursor.movePosition(QTextCursor.EndOfBlock)
cursor.insertText("\n")
# 设置文本编辑器的默认格式
self.text_edit.setCurrentCharFormat(char_format)
self.text_edit.textCursor().setBlockFormat(block_format)
def apply_emphasis_style(self):
"""应用强调样式"""
cursor = self.text_edit.textCursor()
# 创建字符格式
char_format = QTextCharFormat()
char_format.setFontPointSize(12) # 正文字号
char_format.setFontWeight(QFont.Bold) # 加粗
char_format.setFontItalic(True) # 斜体
char_format.setForeground(QColor(0, 0, 128)) # 深蓝色
# 应用格式(只影响字符格式,不影响段落格式)
if cursor.hasSelection():
# 如果有选中文本,只更改选中文本的格式
cursor.mergeCharFormat(char_format)
else:
# 如果没有选中文本,设置默认字符格式供后续输入使用
self.text_edit.setCurrentCharFormat(char_format)
def apply_no_spacing_style(self):
"""应用无间隔样式"""
cursor = self.text_edit.textCursor()
# 创建字符格式
char_format = QTextCharFormat()
char_format.setFontPointSize(12) # 正文字号
char_format.setFontWeight(QFont.Normal) # 正常粗细
# 创建块格式(段落格式)
block_format = QTextBlockFormat()
block_format.setTopMargin(0) # 无上边距
block_format.setBottomMargin(0) # 无下边距
block_format.setLineHeight(100, QTextBlockFormat.SingleHeight) # 行高100%,无额外间距
# 应用格式
if cursor.hasSelection():
# 如果有选中文本,只更改选中文本的格式
cursor.mergeCharFormat(char_format)
else:
# 如果没有选中文本,更改当前段落的格式
cursor.setBlockFormat(block_format)
cursor.mergeCharFormat(char_format)
# 设置文本编辑器的默认格式
self.text_edit.setCurrentCharFormat(char_format)
self.text_edit.textCursor().setBlockFormat(block_format)
def apply_alignment(self, alignment):
"""应用段落对齐方式"""
cursor = self.text_edit.textCursor()
@ -1819,6 +1940,9 @@ class WordStyleMainWindow(QMainWindow):
# 如果有天气数据,更新显示
if hasattr(self, 'current_weather_data') and self.current_weather_data:
self.weather_floating_widget.update_weather(self.current_weather_data)
# 刷新天气数据以确保显示最新信息
else:
self.refresh_weather()
def on_weather_floating_closed(self):
"""天气悬浮窗口关闭时的处理"""

Loading…
Cancel
Save