From d6d29cfe8bcc8f1048f6ed72965e161869c787a4 Mon Sep 17 00:00:00 2001 From: Lesacm <1500309685@qq.com> Date: Fri, 21 Nov 2025 21:18:29 +0800 Subject: [PATCH 1/9] UI fix114514 --- src/word_main_window.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/word_main_window.py b/src/word_main_window.py index ff65c45..28973f1 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -702,22 +702,7 @@ class WordStyleMainWindow(QMainWindow): # 附加工具功能 weather_menu = view_menu.addMenu('附加工具') - - # 显示天气工具组 - self.show_weather_tools_action = QAction('显示天气工具', self) - self.show_weather_tools_action.setCheckable(True) - self.show_weather_tools_action.setChecked(False) # 默认不显示 - self.show_weather_tools_action.triggered.connect(self.toggle_weather_tools) - weather_menu.addAction(self.show_weather_tools_action) - - # 显示每日一言工具组 - self.show_quote_tools_action = QAction('显示每日一言工具', self) - self.show_quote_tools_action.setCheckable(True) - self.show_quote_tools_action.setChecked(False) # 默认不显示 - self.show_quote_tools_action.triggered.connect(self.toggle_quote_tools) - weather_menu.addAction(self.show_quote_tools_action) - - weather_menu.addSeparator() + # 刷新天气 refresh_weather_action = QAction('刷新天气', self) -- 2.34.1 From 2f8cbcc0038384cce608bb78f0354b12765585d8 Mon Sep 17 00:00:00 2001 From: Lesacm <1500309685@qq.com> Date: Fri, 21 Nov 2025 21:18:48 +0800 Subject: [PATCH 2/9] UI fix114514 --- ...求构思和描述文档-迭代一轮.docx | Bin 196903 -> 199047 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/软件需求构思和描述文档-迭代一轮.docx b/doc/软件需求构思和描述文档-迭代一轮.docx index c783b157e0ef31ad65194310ac820a930092b167..8cf1c39c4406c843173615d9ff732c63ffcaac5a 100644 GIT binary patch delta 23567 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%0DGUi)S@RIt+wJ3?te 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 VLn&Duh!6M-0{c&O4M^cY{{g5Qar^)P -- 2.34.1 From 75dbeb24e99409aa5c3c4bfa9a49581218ee868c Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 00:41:30 +0800 Subject: [PATCH 3/9] Enhance calendar widget and theme manager with optimized Apple design styles - Updated the `CalendarFloatingWidget` and `CalendarWidget` classes to improve dark and light theme styles, aligning with an optimized Apple design aesthetic. - Adjusted font sizes, button styles, and color schemes for better visual consistency and usability. - Enhanced the `ThemeManager` to provide refined styles for various UI components, including menus, buttons, and toolbars. - Implemented responsive styles for button states (hover, pressed) and improved accessibility with disabled text colors. - Ensured that theme changes propagate correctly across the application, particularly in the `WordStyleMainWindow`. --- src/deepseek_dialog_window.py | 298 +++++++++++++------------ src/ui/calendar_floating_widget.py | 76 ++++--- src/ui/calendar_widget.py | 343 +++++++++++++++++++---------- src/ui/theme_manager.py | 150 +++++++------ src/word_main_window.py | 2 +- 5 files changed, 511 insertions(+), 358 deletions(-) diff --git a/src/deepseek_dialog_window.py b/src/deepseek_dialog_window.py index 22cf9f1..6b6e4d0 100644 --- a/src/deepseek_dialog_window.py +++ b/src/deepseek_dialog_window.py @@ -187,134 +187,109 @@ class DeepSeekDialogWindow(QDialog): 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.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'}; + }} + + /* 对话区域优化 */ + QTextEdit#conversation_text {{ + background-color: {'#121212' if is_dark else '#ffffff'}; + border: 1px solid {'#3a3a3c' if is_dark else '#e0e0e0'}; + border-radius: 8px; + padding: 16px; + 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: 8px; + padding: 12px; + 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: 8px 16px; + font-weight: 500; + min-width: 80px; + }} + + QPushButton:default {{ + background-color: #0a84ff; + color: #ffffff; + }} + + /* 滚动区域优化 */ + QScrollArea {{ + border: none; + background-color: transparent; + }} + + /* 消息气泡样式 */ + .user-message {{ + background-color: {'#0a84ff' if is_dark else '#0078d7'}; + color: #ffffff; + border-radius: 12px; + padding: 12px 16px; + margin: 4px 0; + max-width: 80%; + align-self: flex-end; + }} + + .ai-message {{ + background-color: {'#3a3a3c' if is_dark else '#f0f0f0'}; + color: {'#e8e8ed' if is_dark else '#333333'}; + border-radius: 12px; + padding: 12px 16px; + margin: 4px 0; + max-width: 80%; + align-self: flex-start; + }} + """ + + self.setStyleSheet(dialog_stylesheet) def create_conversation_area(self, parent): """创建对话显示区域""" @@ -413,7 +388,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() @@ -455,7 +429,14 @@ 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: @@ -463,29 +444,66 @@ class DeepSeekDialogWindow(QDialog): 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;" 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;" 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;" # 格式化消息内容 formatted_message = message.replace('\n', '
') if message else "正在思考..." + # 优化消息气泡样式 html_content += f''' -
- {sender}:
- {formatted_message} +
+
{sender}:
+
{formatted_message}
''' + # 添加现代滚动条样式 + scrollbar_style = f""" + + """ + # 设置HTML内容 - self.conversation_text.setHtml(html_content) + self.conversation_text.setHtml(scrollbar_style + html_content) def call_deepseek_api_stream(self, message): """调用DeepSeek API(流式版本)""" diff --git a/src/ui/calendar_floating_widget.py b/src/ui/calendar_floating_widget.py index f8ee8ca..90f0cde 100644 --- a/src/ui/calendar_floating_widget.py +++ b/src/ui/calendar_floating_widget.py @@ -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']}; }} diff --git a/src/ui/calendar_widget.py b/src/ui/calendar_widget.py index 5331faf..f3351b0 100644 --- a/src/ui/calendar_widget.py +++ b/src/ui/calendar_widget.py @@ -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__": diff --git a/src/ui/theme_manager.py b/src/ui/theme_manager.py index 0978872..52d4578 100644 --- a/src/ui/theme_manager.py +++ b/src/ui/theme_manager.py @@ -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' } diff --git a/src/word_main_window.py b/src/word_main_window.py index 28973f1..bdf3bbc 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -359,7 +359,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): """更新功能区样式""" -- 2.34.1 From 60be0fda50a1b23abb0959e879595a6f04819afa Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 02:21:45 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AI=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E7=BB=84=E4=BB=B6=E5=B9=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BC=96=E8=BE=91=E5=8C=BA=E5=9F=9F=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/ai_chat_panel.py | 381 ++++++++++++++++++++++++++++++++++++++++ src/word_main_window.py | 25 ++- 2 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 src/ui/ai_chat_panel.py diff --git a/src/ui/ai_chat_panel.py b/src/ui/ai_chat_panel.py new file mode 100644 index 0000000..cc0eb68 --- /dev/null +++ b/src/ui/ai_chat_panel.py @@ -0,0 +1,381 @@ +# 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 + +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) + + 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.setMaximumWidth(50) + clear_btn.setFont(QFont("微软雅黑", 9)) + clear_btn.clicked.connect(self.clear_history) + 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: 4px; + font-family: 'Segoe UI', '微软雅黑', sans-serif; + font-size: 10pt; + padding: 8px; + color: #333333; + } + """) + self.chat_display.setMinimumHeight(400) + main_layout.addWidget(self.chat_display) + + # 输入区域 + input_layout = QVBoxLayout() + input_layout.setSpacing(4) + + # 输入框 + self.input_field = QLineEdit() + self.input_field.setPlaceholderText("输入您的问题或请求...") + self.input_field.setStyleSheet(""" + QLineEdit { + background-color: #f9f9f9; + border: 1px solid #d0d0d0; + border-radius: 4px; + font-family: '微软雅黑', sans-serif; + font-size: 10pt; + padding: 6px; + color: #333333; + } + QLineEdit:focus { + border: 1px solid #0078d4; + background-color: #ffffff; + } + """) + self.input_field.returnPressed.connect(self.send_user_message) + input_layout.addWidget(self.input_field) + + # 按钮区域 + button_layout = QHBoxLayout() + button_layout.setSpacing(4) + + # 发送按钮 + self.send_btn = QPushButton("发送") + self.send_btn.setFont(QFont("微软雅黑", 9)) + self.send_btn.setStyleSheet(""" + QPushButton { + background-color: #0078d4; + color: white; + border: none; + border-radius: 4px; + padding: 6px 16px; + font-weight: bold; + } + QPushButton:hover { + background-color: #0063b1; + } + QPushButton: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; + } + """) + + 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("微软雅黑", 10)) + + if sender == "用户": + char_format.setForeground(QColor("#0078d4")) + char_format.setFontWeight(70) + prefix = "你: " + else: # AI助手 + char_format.setForeground(QColor("#333333")) + prefix = "AI: " + + # 插入时间戳 + timestamp_format = QTextCharFormat() + timestamp_format.setForeground(QColor("#999999")) + timestamp_format.setFont(QFont("微软雅黑", 8)) + 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 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() + ) + + 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("微软雅黑", 10)) + + if sender == "用户": + char_format.setForeground(QColor("#0078d4")) + char_format.setFontWeight(70) + prefix = "你: " + else: + char_format.setForeground(QColor("#333333")) + prefix = "AI: " + + # 插入分隔符 + if self.chat_display.toPlainText(): + cursor.insertText("\n") + + # 插入时间戳 + timestamp_format = QTextCharFormat() + timestamp_format.setForeground(QColor("#999999")) + timestamp_format.setFont(QFont("微软雅黑", 8)) + 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() diff --git a/src/word_main_window.py b/src/word_main_window.py index bdf3bbc..819f300 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -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 @@ -828,8 +829,12 @@ class WordStyleMainWindow(QMainWindow): def create_document_area(self, main_layout): - """创建文档编辑区域""" + """创建文档编辑区域和AI对话面板""" + # 创建水平分割器 + splitter = QSplitter(Qt.Horizontal) + + # ========== 左侧:文档编辑区域 ========== # 创建滚动区域 from PyQt5.QtWidgets import QScrollArea @@ -906,7 +911,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): """初始化网络服务""" @@ -920,6 +940,7 @@ class WordStyleMainWindow(QMainWindow): self.quote_thread.quote_fetched.connect(self.update_quote_display) self.quote_thread.start() + def init_typing_logic(self): """初始化打字逻辑""" # 使用默认内容初始化打字逻辑 -- 2.34.1 From 271a2741ff2dc5a3c1d6f31eee1d9567d0b389ca Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 02:31:40 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=B0=8F=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/deepseek_dialog_window.py | 238 ++++++++++++++++++++++++++-------- src/ui/ai_chat_panel.py | 228 ++++++++++++++++++++++++++------ 2 files changed, 374 insertions(+), 92 deletions(-) diff --git a/src/deepseek_dialog_window.py b/src/deepseek_dialog_window.py index 6b6e4d0..80f4ac5 100644 --- a/src/deepseek_dialog_window.py +++ b/src/deepseek_dialog_window.py @@ -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,12 +193,7 @@ 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) @@ -194,9 +206,13 @@ class DeepSeekDialogWindow(QDialog): # 处理直接运行的情况 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() @@ -221,12 +237,22 @@ class DeepSeekDialogWindow(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: 8px; - padding: 16px; + 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; @@ -237,8 +263,8 @@ class DeepSeekDialogWindow(QDialog): QTextEdit#input_edit {{ background-color: {'#2c2c2e' if is_dark else '#f8f8f8'}; border: 1px solid {'#3a3a3c' if is_dark else '#e0e0e0'}; - border-radius: 8px; - padding: 12px; + 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'}; @@ -251,14 +277,37 @@ class DeepSeekDialogWindow(QDialog): /* 按钮优化 */ QPushButton {{ border-radius: 8px; - padding: 8px 16px; + 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; }} /* 滚动区域优化 */ @@ -267,25 +316,11 @@ class DeepSeekDialogWindow(QDialog): background-color: transparent; }} - /* 消息气泡样式 */ - .user-message {{ - background-color: {'#0a84ff' if is_dark else '#0078d7'}; - color: #ffffff; - border-radius: 12px; - padding: 12px 16px; - margin: 4px 0; - max-width: 80%; - align-self: flex-end; - }} - - .ai-message {{ - background-color: {'#3a3a3c' if is_dark else '#f0f0f0'}; - color: {'#e8e8ed' if is_dark else '#333333'}; - border-radius: 12px; - padding: 12px 16px; - margin: 4px 0; - max-width: 80%; - align-self: flex-start; + /* 状态栏优化 */ + QLabel#status_label {{ + color: {'#a0a0a0' if is_dark else '#666666'}; + font-size: 12px; + padding: 8px; }} """ @@ -410,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'
' - f'AI助手:
正在思考...' + f'
' + f'
' + f'' + f'AI助手' + f'
' + f'
正在思考...
' + f'
' ) # 自动滚动到底部 @@ -439,66 +516,117 @@ class DeepSeekDialogWindow(QDialog): 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 = "#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 = "#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 = "#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);" - # 格式化消息内容 - formatted_message = message.replace('\n', '
') if message else "正在思考..." + # 格式化消息内容,处理换行和特殊字符 + if message: + # 将换行符转换为
标签 + formatted_message = message.replace('\n', '
') + # 处理多个连续空格 + formatted_message = formatted_message.replace(' ', '  ') + else: + formatted_message = "正在思考..." if is_streaming else "" + + # 为每个消息添加唯一的ID便于调试 + message_id = f"msg-{i}" - # 优化消息气泡样式 + # 优化消息气泡样式 - 添加阴影、更好的边距和动画效果 html_content += f''' -
-
{sender}:
-
{formatted_message}
+
+ + {sender} +
+
{formatted_message}
''' - # 添加现代滚动条样式 + # 添加现代滚动条样式和整体容器样式 scrollbar_style = f""" """ diff --git a/src/ui/ai_chat_panel.py b/src/ui/ai_chat_panel.py index cc0eb68..7db7f25 100644 --- a/src/ui/ai_chat_panel.py +++ b/src/ui/ai_chat_panel.py @@ -12,6 +12,13 @@ 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对话面板""" @@ -34,6 +41,9 @@ class AIChatPanel(QWidget): # 连接信号到槽 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密钥""" @@ -68,9 +78,45 @@ class AIChatPanel(QWidget): # 清空历史按钮 clear_btn = QPushButton("清空") - clear_btn.setMaximumWidth(50) + 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) @@ -88,11 +134,19 @@ class AIChatPanel(QWidget): QTextEdit { background-color: #ffffff; border: 1px solid #d0d0d0; - border-radius: 4px; - font-family: 'Segoe UI', '微软雅黑', sans-serif; - font-size: 10pt; - padding: 8px; + 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) @@ -100,7 +154,7 @@ class AIChatPanel(QWidget): # 输入区域 input_layout = QVBoxLayout() - input_layout.setSpacing(4) + input_layout.setSpacing(6) # 输入框 self.input_field = QLineEdit() @@ -109,23 +163,34 @@ class AIChatPanel(QWidget): QLineEdit { background-color: #f9f9f9; border: 1px solid #d0d0d0; - border-radius: 4px; - font-family: '微软雅黑', sans-serif; - font-size: 10pt; - padding: 6px; + 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: 1px solid #0078d4; + 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(4) + button_layout.setSpacing(6) # 发送按钮 self.send_btn = QPushButton("发送") @@ -135,9 +200,11 @@ class AIChatPanel(QWidget): background-color: #0078d4; color: white; border: none; - border-radius: 4px; - padding: 6px 16px; - font-weight: bold; + border-radius: 6px; + padding: 8px 20px; + font-weight: 500; + font-size: 12px; + min-width: 70px; } QPushButton:hover { background-color: #0063b1; @@ -145,6 +212,17 @@ class AIChatPanel(QWidget): 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() @@ -161,7 +239,59 @@ class AIChatPanel(QWidget): 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): """发送用户消息""" @@ -211,20 +341,32 @@ class AIChatPanel(QWidget): # 设置格式 char_format = QTextCharFormat() - char_format.setFont(QFont("微软雅黑", 10)) + char_format.setFont(QFont("微软雅黑", 11)) if sender == "用户": - char_format.setForeground(QColor("#0078d4")) - char_format.setFontWeight(70) + # 根据主题设置用户消息颜色 + if theme_manager.is_dark_theme(): + char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色 + else: + char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色 + char_format.setFontWeight(60) # 中等粗体 prefix = "你: " else: # AI助手 - char_format.setForeground(QColor("#333333")) + # 根据主题设置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() - timestamp_format.setForeground(QColor("#999999")) - timestamp_format.setFont(QFont("微软雅黑", 8)) + 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) # 插入前缀 @@ -237,16 +379,7 @@ class AIChatPanel(QWidget): self.chat_display.verticalScrollBar().setValue( self.chat_display.verticalScrollBar().maximum() ) - - 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() - ) - + def rebuild_chat_display(self): """重新构建聊天显示""" self.chat_display.clear() @@ -258,14 +391,23 @@ class AIChatPanel(QWidget): # 设置格式 char_format = QTextCharFormat() - char_format.setFont(QFont("微软雅黑", 10)) + char_format.setFont(QFont("微软雅黑", 11)) if sender == "用户": - char_format.setForeground(QColor("#0078d4")) - char_format.setFontWeight(70) + # 根据主题设置用户消息颜色 + if theme_manager.is_dark_theme(): + char_format.setForeground(QColor("#4A90E2")) # 深色主题下的蓝色 + else: + char_format.setForeground(QColor("#0078d4")) # 浅色主题下的蓝色 + char_format.setFontWeight(60) # 中等粗体 prefix = "你: " else: - char_format.setForeground(QColor("#333333")) + # 根据主题设置AI消息颜色 + if theme_manager.is_dark_theme(): + char_format.setForeground(QColor("#e0e0e0")) # 深色主题下的浅灰色 + else: + char_format.setForeground(QColor("#000000")) # 浅色主题下的纯黑色,提高可读性 + char_format.setFontWeight(50) # 正常粗体 prefix = "AI: " # 插入分隔符 @@ -274,8 +416,11 @@ class AIChatPanel(QWidget): # 插入时间戳 timestamp_format = QTextCharFormat() - timestamp_format.setForeground(QColor("#999999")) - timestamp_format.setFont(QFont("微软雅黑", 8)) + 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) # 插入前缀 @@ -379,3 +524,12 @@ class AIChatPanel(QWidget): 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() + ) \ No newline at end of file -- 2.34.1 From e1f77f7200e0d8a8d4b4f49f4b10f344da2d8a1b Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 13:24:08 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E9=A1=B6=E9=83=A8UI=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/word_main_window.py | 98 ++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/src/word_main_window.py b/src/word_main_window.py index 819f300..89b018b 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -573,82 +573,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) @@ -661,18 +661,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) @@ -680,56 +680,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)') @@ -787,29 +787,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('小游戏') @@ -820,7 +808,7 @@ class WordStyleMainWindow(QMainWindow): games_menu.addAction(snake_game_action) # 帮助菜单 - help_menu = menubar.addMenu('帮助(H)') + help_menu = menubar.addMenu('帮助') # 关于 about_action = QAction('关于 MagicWord', self) -- 2.34.1 From 880908bbbc982569bb72df6af995c379215dcd28 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 13:59:45 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E6=9C=80=E7=BB=88=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/weather_floating_widget.py | 203 ++++++++++++------------------ src/ui/word_style_ui.py | 4 +- src/word_main_window.py | 9 ++ 3 files changed, 92 insertions(+), 124 deletions(-) diff --git a/src/ui/weather_floating_widget.py b/src/ui/weather_floating_widget.py index bb373de..771bde8 100644 --- a/src/ui/weather_floating_widget.py +++ b/src/ui/weather_floating_widget.py @@ -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): """关闭窗口 - 只是隐藏而不是销毁""" diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 65e0525..be39837 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -880,7 +880,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): """加载每日一言""" diff --git a/src/word_main_window.py b/src/word_main_window.py index 89b018b..452a2b8 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -462,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: @@ -1823,6 +1829,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): """天气悬浮窗口关闭时的处理""" -- 2.34.1 From 5e7f48cc1be337e78baf5c8e7b2764cf9b97c337 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 20:08:43 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=A1=A8=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 67 ++++++++++++++++++++++--- src/word_main_window.py | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 8 deletions(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index be39837..8e832db 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -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() diff --git a/src/word_main_window.py b/src/word_main_window.py index 452a2b8..8e66b19 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -974,6 +974,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) @@ -1482,6 +1498,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) @@ -1589,6 +1617,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() -- 2.34.1 From 5651226e57b6fc810167f26247796c40d830eb33 Mon Sep 17 00:00:00 2001 From: Maziang <929110464@qq.com> Date: Sun, 23 Nov 2025 20:17:02 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E6=AE=B5=E8=90=BD=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/word_style_ui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index 8e832db..0957101 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -1069,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)) -- 2.34.1