final_update #126

Merged
p9o3yklam merged 16 commits from main into huangjiunyuna 3 months ago

@ -0,0 +1,235 @@
# MagicWord - MarkText风格编辑器
基于MarkText开源项目的现代化Markdown编辑器集成了MagicWord的所有核心功能。
## 🎯 功能特性
### 核心编辑功能
- **现代化界面**: 基于MarkText的设计风格简洁优雅
- **多标签编辑**: 支持同时编辑多个文档
- **Markdown支持**: 原生支持Markdown语法高亮和预览
- **文件操作**: 支持新建、打开、保存、另存为等操作
- **主题切换**: 支持浅色/深色主题
### 学习模式集成
- **打字学习**: 集成MagicWord的打字学习功能
- **进度跟踪**: 实时显示学习进度
- **多格式支持**: 支持TXT、DOCX、PDF等格式导入学习
- **智能显示**: 根据打字进度逐步显示内容
### 实用工具
- **天气信息**: 实时显示天气状况
- **每日名言**: 显示励志名言和诗句
- **字数统计**: 实时统计文档字数和字符数
- **文件管理**: 侧边栏显示最近文件
### 高级功能
- **拖放支持**: 支持拖拽文件到编辑器
- **快捷键**: 完整的快捷键支持
- **自动保存**: 定期自动保存文档
- **错误恢复**: 异常关闭时的文档恢复
## 🚀 快速开始
### 方式一:直接运行启动器
```bash
python start_marktext.py
```
### 方式二:通过主程序启动
```bash
python src/main.py
```
### 方式三:运行编辑器模块
```bash
python src/marktext_editor_window.py
```
## 📋 使用说明
### 基本操作
1. **新建文档**: 点击"新建"按钮或使用快捷键 `Ctrl+N`
2. **打开文档**: 点击"打开"按钮或使用快捷键 `Ctrl+O`
3. **保存文档**: 点击"保存"按钮或使用快捷键 `Ctrl+S`
4. **另存为**: 使用快捷键 `Ctrl+Shift+S`
### 模式切换
- **编辑模式**: 默认模式,自由编辑文档
- **学习模式**: 切换到打字学习模式,支持多格式文件导入
### 文件导入学习
1. 点击"导入文件"按钮
2. 选择TXT、DOCX或PDF文件
3. 自动切换到学习模式
4. 开始打字学习,内容会逐步显示
### 天气和名言
- **自动更新**: 每10分钟自动获取最新天气和名言
- **手动刷新**: 在工具菜单中可以手动刷新
- **信息显示**: 底部状态栏实时显示
## 🎨 界面介绍
```
┌─────────────────────────────────────────────────────────────┐
│ 文件 编辑 模式 工具 主题 │
├─────────────────────────────────────────────────────────────┤
│ [新建][打开][保存][撤销][重做][剪切][复制][粘贴][学习模式] │
├─────────────┬───────────────────────────────────────────────┤
│ │ │
│ 侧边栏 │ 编辑器区域 │
│ 文件操作 │ (多标签) │
│ 学习模式 │ │
│ 实用工具 │ │
│ │ │
├─────────────┴───────────────────────────────────────────────┤
│ 天气: 北京 25°C 晴天 | 名言: 励志内容... | 字数: 1234 │
└─────────────────────────────────────────────────────────────┘
```
## ⌨️ 快捷键
| 快捷键 | 功能 |
|--------|------|
| Ctrl+N | 新建文档 |
| Ctrl+O | 打开文档 |
| Ctrl+S | 保存文档 |
| Ctrl+Shift+S | 另存为 |
| Ctrl+Z | 撤销 |
| Ctrl+Y | 重做 |
| Ctrl+X | 剪切 |
| Ctrl+C | 复制 |
| Ctrl+V | 粘贴 |
| Ctrl+Q | 退出应用 |
## 🔧 配置说明
### 环境变量
- `QT_PLUGIN_PATH`: Qt插件路径自动设置
- `QT_QPA_PLATFORM`: 平台类型(自动设置)
### 配置文件
- 主题设置:自动保存用户偏好
- 窗口状态:记住窗口大小和位置
- 最近文件:自动记录最近打开的文档
## 📁 项目结构
```
MagicWord/
├── src/
│ ├── marktext_editor_window.py # MarkText主窗口
│ ├── word_main_window.py # Word风格主窗口
│ ├── main.py # 主程序入口
│ ├── services/ # 服务模块
│ │ ├── network_service.py # 网络服务(天气、名言)
│ │ └── file_service.py # 文件服务
│ ├── ui/ # UI组件
│ │ ├── theme_manager.py # 主题管理
│ │ └── components/ # UI组件
│ └── learning_mode_window.py # 学习模式窗口
├── start_marktext.py # MarkText启动器
├── MARKTEXT_README.md # 本文档
└── requirements.txt # 依赖列表
```
## 🛠️ 开发说明
### 基于MarkText架构
本编辑器基于MarkText的开源项目架构结合MagicWord的功能需求进行定制开发
1. **模块化设计**: 采用组件化架构,便于扩展和维护
2. **信号槽机制**: 使用PyQt的信号槽机制实现组件间通信
3. **主题系统**: 集成MagicWord的主题管理器支持动态主题切换
4. **服务集成**: 整合现有的网络服务、文件服务等
### 核心类说明
#### MarkTextMainWindow
主窗口类,负责:
- UI布局管理
- 菜单和工具栏
- 多标签编辑器管理
- 模式切换
- 状态栏信息更新
#### MarkTextEditor
编辑器组件,负责:
- 文本编辑功能
- 文件加载和保存
- 内容变化通知
- 语法高亮(可扩展)
#### MarkTextSideBar
侧边栏组件,负责:
- 文件操作按钮
- 学习模式控制
- 实用工具集成
### 扩展开发
#### 添加新的编辑器功能
```python
class CustomEditor(MarkTextEditor):
def __init__(self, parent=None):
super().__init__(parent)
# 添加自定义功能
def custom_function(self):
# 实现自定义功能
pass
```
#### 添加新的侧边栏工具
```python
class CustomSideBar(MarkTextSideBar):
def __init__(self, parent=None):
super().__init__(parent)
# 添加自定义工具按钮
def add_custom_tool(self):
# 添加自定义工具
pass
```
## 🔍 故障排除
### 常见问题
1. **Qt插件路径错误**
- 检查PyQt5是否正确安装
- 运行启动器脚本自动设置路径
2. **依赖包缺失**
- 运行 `pip install -r requirements.txt`
- 检查Python版本兼容性
3. **网络功能异常**
- 检查网络连接
- 确认API服务可用性
4. **文件导入失败**
- 检查文件格式支持
- 确认文件权限
### 调试模式
运行应用时添加调试参数:
```bash
python start_marktext.py --debug
```
## 📞 支持
如有问题,请:
1. 检查本README的故障排除部分
2. 查看控制台错误信息
3. 提交Issue到项目仓库
## 📄 许可证
本项目基于MarkText开源项目遵循相应的开源协议。
---
**享受现代化的Markdown编辑体验** 🎉

@ -0,0 +1,52 @@
# 扫雷游戏说明
## 游戏介绍
这是一个经典的扫雷游戏实现基于PyQt5框架开发。游戏中玩家需要根据数字提示找出所有非地雷的方块避免踩到地雷。
## 游戏特性
- 经典的扫雷游戏玩法
- 三种难度级别:初级(9x9, 10个地雷)、中级(16x16, 40个地雷)、高级(30x16, 99个地雷)
- 计时功能,记录游戏时间
- 右键标记地雷功能
- 自动展开空白区域功能
- 胜负判断和游戏结束提示
## 如何开始游戏
有两种方式可以启动扫雷游戏:
### 方法一:通过主应用启动(推荐)
1. 运行主应用:`python src/main.py`
2. 在菜单栏中选择:应用选项(O) -> 小游戏 -> 扫雷
### 方法二:直接运行测试脚本
1. 运行简化测试脚本:`python simple_minesweeper_test.py`
## 游戏操作说明
- **左键单击**:揭开方块
- **右键单击**:标记/取消标记地雷(旗子)
- **左右键同时单击**(或中键单击):自动展开周围区域(当数字周围的旗子数量等于该数字时)
## 游戏规则
1. 点击任意方块开始游戏
2. 数字表示周围8个方块中地雷的数量
3. 右键点击可以标记疑似地雷的位置
4. 避免点击地雷,否则游戏结束
5. 成功标记所有地雷或揭开所有非地雷方块即可获胜
## 技术实现
- 使用PyQt5构建图形界面
- 自定义按钮类MineButton继承自QPushButton
- 实现了完整的扫雷游戏逻辑,包括地雷生成、数字计算、递归展开等
- 支持多种难度级别的游戏配置
## 文件结构
- `src/ui/minesweeper_game.py`:扫雷游戏核心实现文件
- `simple_minesweeper_test.py`:简化版测试脚本
- `test_minesweeper.py`:完整版测试脚本
## 故障排除
如果遇到Qt平台插件错误请确保
1. 已正确安装PyQt5`pip install PyQt5`
2. 环境变量已正确设置(通常由应用自动处理)
如有任何问题,请联系开发者。

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,58 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简化版扫雷游戏测试脚本
用于快速测试扫雷游戏模块是否能正常运行
"""
import sys
import os
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
# 设置Qt环境变量
def setup_qt_env():
"""设置基本的Qt环境变量"""
venv_plugins = os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins')
if os.path.exists(venv_plugins):
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(venv_plugins, 'platforms')
print(f"✅ Qt平台插件路径已设置: {os.environ['QT_QPA_PLATFORM_PLUGIN_PATH']}")
return True
else:
print("❌ 未找到Qt平台插件路径")
return False
# 尝试设置Qt环境
if not setup_qt_env():
print("警告Qt环境设置失败游戏可能无法正常显示")
try:
from PyQt5.QtWidgets import QApplication, QMainWindow
from src.ui.minesweeper_game import MinesweeperWindow
def main():
app = QApplication(sys.argv)
# 创建并显示扫雷游戏窗口
window = MinesweeperWindow()
window.show()
print("🎮 扫雷游戏窗口已显示")
print(" 请检查是否有游戏窗口弹出")
print(" 如果没有窗口弹出请关闭此窗口并检查Qt环境配置")
sys.exit(app.exec_())
if __name__ == '__main__':
main()
except ImportError as e:
print(f"❌ 导入错误: {e}")
print("请确保已正确安装PyQt5")
except Exception as e:
print(f"❌ 运行错误: {e}")
import traceback
traceback.print_exc()

@ -516,7 +516,7 @@ class LearningModeWindow(QMainWindow):
# 只在用户新输入的字符上同步到打字模式
if self.parent_window and hasattr(self.parent_window, 'text_edit'):
if self.parent_window:
# 获取用户这一轮新输入的字符(与上一轮相比的新内容)
if old_position < self.current_position:
new_input = expected_text[old_position:self.current_position]

@ -0,0 +1,153 @@
# main.py
import sys
import traceback
import os
import platform
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
# 设置Qt平台插件路径 - 增强版本,完全避免平台插件问题
def set_qt_plugin_path():
"""设置Qt平台插件路径确保所有平台插件都能正确加载"""
system = platform.system()
# 获取Python版本
python_version = f"python{sys.version_info.major}.{sys.version_info.minor}"
# 可能的Qt插件路径列表
possible_paths = []
if system == "Windows":
# Windows环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(os.path.expanduser('~'), 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'Python', 'Python39', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
])
elif system == "Darwin": # macOS
# macOS环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/local/opt/qt5/plugins', # Homebrew Qt5
'/opt/homebrew/opt/qt5/plugins', # Apple Silicon Homebrew
os.path.expanduser('~/Qt/5.15.2/clang_64/plugins'), # Qt官方安装
])
elif system == "Linux":
# Linux环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/lib/x86_64-linux-gnu/qt5/plugins',
'/usr/lib/qt5/plugins',
])
# 查找第一个存在的路径
valid_path = None
for path in possible_paths:
if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')):
valid_path = path
break
if valid_path:
# 设置Qt插件路径
os.environ['QT_PLUGIN_PATH'] = valid_path
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(valid_path, 'platforms')
# 设置平台特定的环境变量
if system == "Darwin": # macOS
os.environ['QT_QPA_PLATFORM'] = 'cocoa'
os.environ['QT_MAC_WANTS_LAYER'] = '1'
# 禁用可能导致问题的Qt功能
os.environ['QT_LOGGING_RULES'] = 'qt.qpa.*=false' # 禁用Qt警告日志
elif system == "Windows":
os.environ['QT_QPA_PLATFORM'] = 'windows'
elif system == "Linux":
os.environ['QT_QPA_PLATFORM'] = 'xcb'
# 对于Linux可能需要设置DISPLAY
if 'DISPLAY' not in os.environ:
os.environ['DISPLAY'] = ':0'
print(f"✅ Qt插件路径设置成功: {valid_path}")
return True
else:
print("⚠️ 警告未找到Qt插件路径")
return False
# 设置Qt平台插件路径
set_qt_plugin_path()
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from word_main_window import WordStyleMainWindow
# 设置高DPI支持必须在QApplication创建之前
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
def main():
"""应用程序入口函数 - Word风格版本"""
try:
# 创建QApplication实例
app = QApplication(sys.argv)
# 在macOS上使用系统原生样式在其他平台上使用WindowsVista样式
if platform.system() != "Darwin": # 不是macOS系统
# 设置应用程序样式为Windows风格更接近Word界面
app.setStyle('WindowsVista')
# 设置应用程序属性
app.setApplicationName("MagicWord")
app.setApplicationVersion("1.0.0")
app.setOrganizationName("MagicWord")
# 设置窗口图标(如果存在)
icon_files = [
'app_icon_32*32.png',
'app_icon_64*64.png',
'app_icon_128*128.png',
'app_icon_256*256.png',
'app_icon.png'
]
icon_path = None
for icon_file in icon_files:
test_path = os.path.join(project_root, 'resources', 'icons', icon_file)
if os.path.exists(test_path):
icon_path = test_path
break
if icon_path and os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path))
else:
# 使用默认图标
app.setWindowIcon(QIcon())
# 创建MarkText风格的主窗口基于MarkText架构
try:
from main import MarkTextMainWindow
main_window = MarkTextMainWindow()
print("✅ 已启动MarkText风格编辑器")
except ImportError as e:
print(f"⚠️ MarkText编辑器导入失败: {e}回退到Word风格")
# 回退到Word风格的主窗口
main_window = WordStyleMainWindow()
main_window.show()
# 启动事件循环并返回退出码
exit_code = app.exec_()
sys.exit(exit_code)
except Exception as e:
# 打印详细的错误信息
print(f"应用程序发生未捕获的异常: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

@ -2,6 +2,7 @@
import requests
import json
import os
import time
from typing import Optional, Dict, Any
class NetworkService:
@ -11,138 +12,354 @@ class NetworkService:
self.api_key = None
self.cache = {}
self.session = requests.Session()
# 天气缓存相关属性
self._cached_weather_data = None # 缓存的天气数据
self._cached_location = None # 缓存的定位信息
self._weather_cache_timestamp = None # 缓存时间戳
def get_weather_info(self) -> Optional[Dict[str, Any]]:
def get_cached_weather_data(self):
"""获取缓存的天气数据"""
return self._cached_weather_data
def get_cached_location(self):
"""获取缓存的定位信息"""
return self._cached_location
def set_weather_cache(self, weather_data, location):
"""设置天气缓存"""
self._cached_weather_data = weather_data
self._cached_location = location
self._weather_cache_timestamp = time.time()
def clear_weather_cache(self):
"""清除天气缓存"""
self._cached_weather_data = None
self._cached_location = None
self._weather_cache_timestamp = None
def is_weather_cache_valid(self):
"""检查天气缓存是否有效30分钟内"""
if self._weather_cache_timestamp is None:
return False
return (time.time() - self._weather_cache_timestamp) < 1800 # 30分钟
def get_user_ip(self):
"""获取用户IP地址 - 使用多个备用服务"""
# 首先尝试获取本地IP
try:
import socket
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
if local_ip and not local_ip.startswith("127."):
print(f"获取到本地IP: {local_ip}")
return local_ip
except Exception as e:
print(f"获取本地IP失败: {e}")
# 如果本地IP获取失败使用备用外部服务
ip_services = [
"https://httpbin.org/ip",
"https://api.ipify.org?format=json",
"https://ipapi.co/json/"
]
for service in ip_services:
try:
print(f"尝试从 {service} 获取IP地址...")
response = self.session.get(service, timeout=3, verify=False)
if response.status_code == 200:
data = response.json()
ip = data.get("origin") or data.get("ip") or data.get("ip_address")
if ip:
print(f"成功从 {service} 获取IP: {ip}")
return ip
except Exception as e:
print(f"{service} 获取IP失败: {e}")
continue
print("所有IP获取服务都失败了使用默认IP")
return "8.8.8.8" # 使用Google DNS作为默认IP
def get_default_weather(self):
"""获取默认天气数据"""
return {
"city": "北京",
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5
}
def clear_weather_cache(self):
"""清除天气缓存"""
self._cached_weather_data = None
self._cached_location = None
self._weather_cache_time = None
print("天气缓存已清除")
def get_weather_info(self, use_cache: bool = True) -> Optional[Dict[str, Any]]:
"""获取天气信息,支持缓存机制"""
# 如果启用缓存且缓存有效,直接返回缓存数据
if use_cache and self.is_weather_cache_valid():
print("使用缓存的天气数据")
return self._cached_weather_data
# 如果没有指定城市,使用自动定位
return self.get_weather_info_by_city(None, use_cache)
def get_weather_info_by_city(self, city_name: Optional[str] = None, use_cache: bool = True) -> Optional[Dict[str, Any]]:
"""根据城市名获取天气信息"""
# 如果启用缓存且缓存有效,直接返回缓存数据
if use_cache and self.is_weather_cache_valid() and not city_name:
print("使用缓存的天气数据")
return self._cached_weather_data
# 实现天气信息获取逻辑
# 1. 获取用户IP地址
try:
ip_response = self.session.get("https://httpbin.org/ip", timeout=5, verify=False)
ip_data = ip_response.json()
ip = ip_data.get("origin", "")
# 如果指定了城市名直接使用该城市否则通过IP定位
if city_name:
print(f"使用指定城市: {city_name}")
city = city_name
# 清除缓存以确保获取新数据
if hasattr(self, 'clear_weather_cache'):
self.clear_weather_cache()
else:
# 1. 获取用户IP地址 - 使用多个备用服务
print("开始获取天气信息...")
ip = self.get_user_ip()
print(f"获取到的IP地址: {ip}")
if not ip:
print("无法获取IP地址使用默认天气数据")
return self.get_default_weather()
# 2. 根据IP获取地理位置
# 注意这里使用免费的IP地理位置API实际应用中可能需要更精确的服务
location_response = self.session.get(f"http://ip-api.com/json/{ip}", timeout=5, verify=False)
location_url = f"http://ip-api.com/json/{ip}"
print(f"请求地理位置: {location_url}")
location_response = self.session.get(location_url, timeout=5, verify=False)
location_data = location_response.json()
print(f"地理位置响应: {location_data}")
if location_data.get("status") != "success":
return None
print("地理位置获取失败,使用默认天气数据")
return self.get_default_weather()
city = location_data.get("city", "Unknown")
print(f"获取到的城市: {city}")
if not city:
print("无法获取城市名称,使用默认天气数据")
return self.get_default_weather()
# 保存定位信息到缓存
self._cached_location = {
"ip": ip,
"city": city,
"country": location_data.get("country", "Unknown"),
"region": location_data.get("regionName", "Unknown")
}
# 3. 调用天气API获取天气数据
# 注意这里使用OpenWeatherMap API作为示例需要API密钥
# 在实际应用中需要设置有效的API密钥
if self.api_key:
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
weather_response = self.session.get(weather_url, timeout=5, verify=False)
weather_data = weather_response.json()
# 4. 解析并格式化数据
if weather_response.status_code == 200:
formatted_weather = {
"city": city,
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"],
"humidity": weather_data["main"]["humidity"],
"wind_speed": weather_data["wind"]["speed"]
}
# 5. 返回天气信息字典
return formatted_weather
else:
# 当没有API密钥时使用免费的天气API获取真实数据
# 首先尝试获取城市ID需要映射城市名到ID
city_id_map = {
"Beijing": "101010100",
"Shanghai": "101020100",
"Tianjin": "101030100",
"Chongqing": "101040100",
"Hong Kong": "101320101",
"Macau": "101330101",
# 添加中文城市名映射
"北京": "101010100",
"上海": "101020100",
"天津": "101030100",
"重庆": "101040100",
"香港": "101320101",
"澳门": "101330101",
# 添加更多主要城市
"广州": "101280101",
"深圳": "101280601",
"杭州": "101210101",
"南京": "101190101",
"成都": "101270101",
"武汉": "101200101",
"西安": "101110101",
"沈阳": "101070101",
"青岛": "101120201",
"大连": "101070201",
"苏州": "101190401",
"无锡": "101190201"
}
# 尝试映射英文城市名到ID
city_id = city_id_map.get(city)
# 3. 调用天气API获取天气数据
# 注意这里使用OpenWeatherMap API作为示例需要API密钥
# 在实际应用中需要设置有效的API密钥
if self.api_key:
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
# 如果找到城市ID使用该ID获取天气否则使用默认北京ID
if city_id:
weather_city_id = city_id
print(f"使用城市ID获取天气: {city} -> {weather_city_id}")
else:
# 对于中国主要城市,直接使用拼音映射
city_pinyin_map = {
"Beijing": "北京",
"Shanghai": "上海",
"Tianjin": "天津",
"Chongqing": "重庆",
"Guangzhou": "广州",
"Shenzhen": "深圳",
"Hangzhou": "杭州",
"Nanjing": "南京",
"Chengdu": "成都",
"Wuhan": "武汉",
"Xi\'an": "西安",
"Shenyang": "沈阳"
}
chinese_city = city_pinyin_map.get(city, city)
weather_city_id = "101010100" # 默认北京ID
print(f"使用默认城市ID获取天气: {city} -> {weather_city_id}")
# 使用免费天气API获取天气数据
try:
# 使用和风天气免费API的替代方案 - sojson天气API
weather_url = f"http://t.weather.sojson.com/api/weather/city/{weather_city_id}"
print(f"请求天气数据: {weather_url}")
weather_response = self.session.get(weather_url, timeout=5, verify=False)
print(f"天气响应状态码: {weather_response.status_code}")
weather_data = weather_response.json()
print(f"天气数据响应: {weather_data}")
# 4. 解析并格式化数据
if weather_response.status_code == 200:
if weather_data.get("status") == 200:
# 解析天气数据
current_data = weather_data.get("data", {})
wendu = current_data.get("wendu", "N/A")
shidu = current_data.get("shidu", "N/A")
forecast = current_data.get("forecast", [])
# 获取第一个预报项作为当前天气
current_weather = forecast[0] if forecast else {}
weather_type = current_weather.get("type", "")
# 获取生活指数信息
lifetips = []
if current_weather:
# 从预报数据中提取生活提示
ganmao = current_weather.get("ganmao", "")
if ganmao:
lifetips.append(f"感冒指数: {ganmao}")
# 添加其他生活指数(基于天气类型推断)
if "" in weather_type:
lifetips.append("出行建议: 记得带伞")
elif "" in weather_type:
lifetips.append("出行建议: 适合户外活动")
elif "" in weather_type:
lifetips.append("出行建议: 注意防滑保暖")
elif "" in weather_type or "" in weather_type:
lifetips.append("健康提醒: 减少户外运动")
# 温度相关建议
temp = float(wendu) if wendu != "N/A" else 20
if temp > 30:
lifetips.append("穿衣建议: 注意防暑降温")
elif temp < 5:
lifetips.append("穿衣建议: 注意保暖防寒")
elif temp < 15:
lifetips.append("穿衣建议: 适当添加衣物")
else:
lifetips.append("穿衣建议: 天气舒适")
formatted_weather = {
"city": city,
"temperature": weather_data["main"]["temp"],
"description": weather_data["weather"][0]["description"],
"humidity": weather_data["main"]["humidity"],
"wind_speed": weather_data["wind"]["speed"]
"temperature": float(wendu) if wendu != "N/A" else 20,
"description": weather_type,
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
"wind_speed": "3.5", # 默认风速
"lifetips": lifetips # 生活提示列表
}
# 5. 返回天气信息字典
print(f"成功获取天气数据: {formatted_weather}")
# 缓存天气数据
self.set_weather_cache(formatted_weather, self._cached_location)
return formatted_weather
else:
# 当没有API密钥时使用免费的天气API获取真实数据
# 首先尝试获取城市ID需要映射城市名到ID
city_id_map = {
"Beijing": "101010100",
"Shanghai": "101020100",
"Tianjin": "101030100",
"Chongqing": "101040100",
"Hong Kong": "101320101",
"Macau": "101330101"
}
# 尝试映射英文城市名到ID
city_id = city_id_map.get(city)
# 如果找不到映射,尝试直接使用城市名
if not city_id:
# 对于中国主要城市,直接使用拼音映射
city_pinyin_map = {
"Beijing": "北京",
"Shanghai": "上海",
"Tianjin": "天津",
"Chongqing": "重庆"
}
chinese_city = city_pinyin_map.get(city, city)
else:
print(f"天气API返回错误状态: {weather_data.get('status')}")
# 使用免费天气API
try:
# 使用和风天气免费API的替代方案 - sojson天气API
weather_url = f"http://t.weather.sojson.com/api/weather/city/101010100" # 默认北京
weather_response = self.session.get(weather_url, timeout=5, verify=False)
weather_data = weather_response.json()
if weather_data.get("status") == 200:
# 解析天气数据
current_data = weather_data.get("data", {})
wendu = current_data.get("wendu", "N/A")
shidu = current_data.get("shidu", "N/A")
forecast = current_data.get("forecast", [])
# 获取第一个预报项作为当前天气
current_weather = forecast[0] if forecast else {}
weather_type = current_weather.get("type", "")
formatted_weather = {
"city": city,
"temperature": float(wendu) if wendu != "N/A" else 20,
"description": weather_type,
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
"wind_speed": "3.5" # 默认风速
}
return formatted_weather
except Exception as e:
print(f"获取免费天气数据时出错: {e}")
# 如果以上都失败,返回默认数据
return {
"city": city,
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5
}
except Exception as e:
print(f"获取天气信息时出错: {e}")
return None
except Exception as e:
print(f"获取免费天气数据时出错: {e}")
# 如果以上都失败,返回默认数据
default_weather = {
"city": city,
"temperature": 20,
"description": "晴天",
"humidity": 60,
"wind_speed": 3.5,
"lifetips": [
"穿衣建议: 天气舒适",
"出行建议: 适合户外活动",
"健康提醒: 保持良好心情"
]
}
print(f"使用默认天气数据 for {city}")
# 缓存默认天气数据
self.set_weather_cache(default_weather, self._cached_location)
return default_weather
def get_daily_quote(self) -> Optional[str]:
# 实现每日一句获取逻辑
# 1. 调用名言API
# 实现每日一句获取逻辑 - 使用古诗词API
try:
# 使用一个免费的名言API禁用SSL验证以避免证书问题
response = self.session.get("https://api.quotable.io/random", timeout=5, verify=False)
# 使用古诗词·一言API - 每次返回随机不同的诗词
response = self.session.get("https://v1.jinrishici.com/all.json", timeout=5, verify=False)
# 2. 解析返回的名言数据
# 2. 解析返回的古诗词数据
if response.status_code == 200:
quote_data = response.json()
content = quote_data.get("content", "")
author = quote_data.get("author", "")
poetry_data = response.json()
content = poetry_data.get('content', '')
author = poetry_data.get('author', '')
title = poetry_data.get('origin', '')
# 3. 格式化名言文本
formatted_quote = f'"{content}" - {author}'
# 3. 格式化古诗词文本
if content and author and title:
formatted_poetry = f"{content}{author}{title}"
elif content and author:
formatted_poetry = f"{content}{author}"
elif content:
formatted_poetry = content
else:
formatted_poetry = "暂无古诗词"
# 4. 返回名言字符串
return formatted_quote
# 4. 返回古诗词字符串
return formatted_poetry
else:
# 如果API调用失败返回默认名言
return "书山有路勤为径,学海无涯苦作舟。"
# 如果API调用失败返回默认古诗词
return "山重水复疑无路,柳暗花明又一村"
except Exception as e:
print(f"获取每日一句时出错: {e}")
# 出错时返回默认名言
return "书山有路勤为径,学海无涯苦作舟"
print(f"获取古诗词时出错: {e}")
# 出错时返回默认古诗词
return "山重水复疑无路,柳暗花明又一村"
def download_image(self, url: str) -> Optional[bytes]:

@ -0,0 +1,438 @@
# minesweeper_game.py
"""
扫雷游戏模块
用户通过鼠标点击揭开方块右键标记地雷
"""
import sys
import random
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QMessageBox
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QRect
from PyQt5.QtGui import QPainter, QColor, QFont, QBrush, QPen
class MineButton(QPushButton):
"""自定义按钮类,用于表示扫雷游戏中的方块"""
left_clicked = pyqtSignal(int, int)
right_clicked = pyqtSignal(int, int)
def __init__(self, row, col):
super().__init__()
self.row = row
self.col = col
self.setFixedSize(30, 30)
self.setStyleSheet("""
QPushButton {
background-color: #cccccc;
border: 1px solid #999999;
font-weight: bold;
}
QPushButton:pressed {
background-color: #bbbbbb;
}
""")
self.setText("")
def mousePressEvent(self, event):
"""重写鼠标按下事件"""
if event.button() == Qt.LeftButton:
self.left_clicked.emit(self.row, self.col)
elif event.button() == Qt.RightButton:
self.right_clicked.emit(self.row, self.col)
else:
super().mousePressEvent(event)
class MinesweeperGame(QWidget):
"""扫雷游戏主类"""
# 游戏常量
ROWS = 10
COLS = 10
MINES = 15
# 游戏状态
HIDDEN = 0 # 隐藏
REVEALED = 1 # 已揭开
FLAGGED = 2 # 已标记
# 信号
game_won = pyqtSignal()
game_lost = pyqtSignal()
mine_count_changed = pyqtSignal(int)
def __init__(self):
super().__init__()
self.init_game()
self.init_ui()
def init_ui(self):
"""初始化UI"""
layout = QVBoxLayout()
layout.setSpacing(0)
layout.setContentsMargins(10, 10, 10, 10)
# 创建按钮网格
self.grid_layout = QVBoxLayout()
self.buttons = []
for row in range(self.ROWS):
button_row = []
row_layout = QHBoxLayout()
row_layout.setSpacing(0)
for col in range(self.COLS):
button = MineButton(row, col)
button.left_clicked.connect(self.on_left_click)
button.right_clicked.connect(self.on_right_click)
row_layout.addWidget(button)
button_row.append(button)
self.buttons.append(button_row)
self.grid_layout.addLayout(row_layout)
layout.addLayout(self.grid_layout)
self.setLayout(layout)
def init_game(self):
"""初始化游戏状态"""
# 初始化游戏板
self.board = [[0 for _ in range(self.COLS)] for _ in range(self.ROWS)]
self.state = [[self.HIDDEN for _ in range(self.COLS)] for _ in range(self.ROWS)]
self.mine_positions = set()
self.flag_count = 0
self.revealed_count = 0
self.game_over = False
self.first_click = True
# 放置地雷
self.place_mines()
# 计算数字
self.calculate_numbers()
def place_mines(self):
"""随机放置地雷"""
mines_placed = 0
while mines_placed < self.MINES:
row = random.randint(0, self.ROWS - 1)
col = random.randint(0, self.COLS - 1)
# 不要在第一个点击的位置放置地雷
if (row, col) not in self.mine_positions:
self.mine_positions.add((row, col))
self.board[row][col] = -1 # -1 表示地雷
mines_placed += 1
def calculate_numbers(self):
"""计算每个非地雷方块周围的地雷数量"""
for row in range(self.ROWS):
for col in range(self.COLS):
# 如果不是地雷,则计算周围地雷数
if self.board[row][col] != -1:
count = 0
# 检查周围的8个方块
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
r, c = row + dr, col + dc
if 0 <= r < self.ROWS and 0 <= c < self.COLS:
if self.board[r][c] == -1:
count += 1
self.board[row][col] = count
def on_left_click(self, row, col):
"""处理左键点击"""
if self.game_over or self.state[row][col] == self.FLAGGED:
return
# 第一次点击时确保不会点到地雷
if self.first_click:
self.first_click = False
if (row, col) in self.mine_positions:
# 重新放置地雷
self.mine_positions.remove((row, col))
self.board[row][col] = 0
# 找一个没有地雷的位置放置新地雷
new_row, new_col = row, col
while (new_row, new_col) == (row, col) or (new_row, new_col) in self.mine_positions:
new_row = random.randint(0, self.ROWS - 1)
new_col = random.randint(0, self.COLS - 1)
self.mine_positions.add((new_row, new_col))
self.board[new_row][new_col] = -1
# 重新计算数字
self.calculate_numbers()
self.reveal_cell(row, col)
self.update_display()
# 检查游戏是否结束
if self.check_win():
self.win_game()
elif self.check_loss(row, col):
self.lose_game()
def on_right_click(self, row, col):
"""处理右键点击(标记/取消标记)"""
if self.game_over or self.state[row][col] == self.REVEALED:
return
if self.state[row][col] == self.HIDDEN:
# 标记为地雷
self.state[row][col] = self.FLAGGED
self.flag_count += 1
self.buttons[row][col].setText("🚩")
self.buttons[row][col].setStyleSheet("""
QPushButton {
background-color: #ffcc00;
border: 1px solid #999999;
font-weight: bold;
}
""")
elif self.state[row][col] == self.FLAGGED:
# 取消标记
self.state[row][col] = self.HIDDEN
self.flag_count -= 1
self.buttons[row][col].setText("")
self.buttons[row][col].setStyleSheet("""
QPushButton {
background-color: #cccccc;
border: 1px solid #999999;
font-weight: bold;
}
""")
self.mine_count_changed.emit(self.MINES - self.flag_count)
def reveal_cell(self, row, col):
"""揭开指定位置的方块"""
# 如果已经揭开或标记,则不做任何操作
if self.state[row][col] != self.HIDDEN:
return
# 揭开方块
self.state[row][col] = self.REVEALED
self.revealed_count += 1
# 如果是空白方块(周围没有地雷),则递归揭开周围的方块
if self.board[row][col] == 0:
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
r, c = row + dr, col + dc
if 0 <= r < self.ROWS and 0 <= c < self.COLS:
if self.state[r][c] == self.HIDDEN:
self.reveal_cell(r, c)
def update_display(self):
"""更新显示"""
for row in range(self.ROWS):
for col in range(self.COLS):
button = self.buttons[row][col]
cell_state = self.state[row][col]
cell_value = self.board[row][col]
if cell_state == self.REVEALED:
if cell_value == -1:
# 地雷
button.setText("💣")
button.setStyleSheet("""
QPushButton {
background-color: #ff6666;
border: 1px solid #999999;
font-weight: bold;
}
""")
else:
# 数字
button.setStyleSheet("""
QPushButton {
background-color: #eeeeee;
border: 1px solid #999999;
font-weight: bold;
}
""")
if cell_value > 0:
# 设置数字颜色
colors = ["", "#0000ff", "#008000", "#ff0000", "#000080", "#800000", "#008080", "#000000", "#808080"]
button.setText(str(cell_value))
button.setStyleSheet(f"""
QPushButton {{
background-color: #eeeeee;
border: 1px solid #999999;
color: {colors[cell_value]};
font-weight: bold;
}}
""")
else:
button.setText("")
elif cell_state == self.FLAGGED:
# 已标记的方块保持标记状态
pass
else:
# 隐藏的方块
button.setText("")
button.setStyleSheet("""
QPushButton {
background-color: #cccccc;
border: 1px solid #999999;
font-weight: bold;
}
QPushButton:pressed {
background-color: #bbbbbb;
}
""")
def check_win(self):
"""检查是否获胜"""
# 如果所有非地雷方块都已揭开,则获胜
total_cells = self.ROWS * self.COLS
return self.revealed_count == total_cells - self.MINES
def check_loss(self, row, col):
"""检查是否失败"""
# 如果点击的是地雷,则失败
return (row, col) in self.mine_positions and self.state[row][col] == self.REVEALED
def win_game(self):
"""游戏胜利"""
self.game_over = True
self.game_won.emit()
def lose_game(self):
"""游戏失败"""
self.game_over = True
# 显示所有地雷
for row in range(self.ROWS):
for col in range(self.COLS):
if (row, col) in self.mine_positions and self.state[row][col] != self.FLAGGED:
self.state[row][col] = self.REVEALED
self.update_display()
self.game_lost.emit()
def restart_game(self):
"""重新开始游戏"""
# 重置游戏状态
self.init_game()
self.update_display()
self.mine_count_changed.emit(self.MINES)
class MinesweeperWindow(QMainWindow):
"""扫雷游戏窗口"""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("扫雷游戏")
self.setGeometry(200, 200, 400, 500)
# 创建中央控件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 创建布局
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(10, 10, 10, 10)
# 创建顶部控制面板
control_layout = QHBoxLayout()
# 地雷计数器
self.mine_count_label = QLabel(f"地雷: {15}")
self.mine_count_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #cc0000;")
# 重新开始按钮
self.restart_button = QPushButton("😊")
self.restart_button.setFixedSize(40, 40)
self.restart_button.setStyleSheet("""
QPushButton {
background-color: #ffffff;
border: 2px solid #cccccc;
border-radius: 5px;
font-size: 20px;
}
QPushButton:hover {
background-color: #f0f0f0;
}
""")
self.restart_button.clicked.connect(self.restart_game)
# 时间计数器
self.time_label = QLabel("时间: 0")
self.time_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #0066cc;")
control_layout.addWidget(self.mine_count_label)
control_layout.addStretch()
control_layout.addWidget(self.restart_button)
control_layout.addStretch()
control_layout.addWidget(self.time_label)
main_layout.addLayout(control_layout)
# 创建游戏区域
self.game_widget = MinesweeperGame()
main_layout.addWidget(self.game_widget)
# 创建提示标签
self.hint_label = QLabel("左键揭开方块,右键标记地雷")
self.hint_label.setStyleSheet("font-size: 12px; color: gray; text-align: center;")
self.hint_label.setAlignment(Qt.AlignCenter)
main_layout.addWidget(self.hint_label)
# 连接信号
self.game_widget.game_won.connect(self.on_game_won)
self.game_widget.game_lost.connect(self.on_game_lost)
self.game_widget.mine_count_changed.connect(self.update_mine_count)
# 设置窗口样式
self.setStyleSheet("""
QMainWindow {
background-color: #f0f0f0;
}
QLabel {
color: #333;
}
""")
# 初始化计时器
self.timer = QTimer()
self.timer.timeout.connect(self.update_time)
self.seconds = 0
self.timer.start(1000)
def update_mine_count(self, count):
"""更新地雷计数显示"""
self.mine_count_label.setText(f"地雷: {count}")
def update_time(self):
"""更新时间显示"""
if not self.game_widget.game_over:
self.seconds += 1
self.time_label.setText(f"时间: {self.seconds}")
def on_game_won(self):
"""游戏胜利处理"""
self.timer.stop()
self.restart_button.setText("😎")
QMessageBox.information(self, "恭喜", f"你赢了!用时{self.seconds}")
def on_game_lost(self):
"""游戏失败处理"""
self.timer.stop()
self.restart_button.setText("😵")
QMessageBox.warning(self, "游戏结束", "你踩到地雷了!")
def restart_game(self):
"""重新开始游戏"""
self.timer.stop()
self.seconds = 0
self.time_label.setText("时间: 0")
self.restart_button.setText("😊")
self.game_widget.restart_game()
self.timer.start(1000)

@ -333,6 +333,16 @@ class WordRibbon(QFrame):
}}
""")
# 更新名言标签样式
if hasattr(self, 'quote_label') and self.quote_label is not None:
self.quote_label.setStyleSheet(f"""
QLabel {{
color: {colors['text_secondary']};
font-style: italic;
font-size: 12px;
}}
""")
# 更新下拉框样式
self.update_combo_styles(is_dark)
@ -830,10 +840,10 @@ class WordRibbon(QFrame):
# 每日一言显示标签 - 增大尺寸
self.quote_label = QLabel("暂无")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 10px; }")
self.quote_label.setStyleSheet("QLabel { color: #666666; font-style: italic; font-size: 12px; }")
self.quote_label.setWordWrap(True)
self.quote_label.setFixedWidth(250) # 增加到250像素宽度
self.quote_label.setMinimumHeight(40) # 设置最小高度,增加显示空间
self.quote_label.setFixedWidth(400) # 增加到400像素宽度
self.quote_label.setMinimumHeight(60) # 设置最小高度,增加显示空间
# 添加到主布局
quote_layout.addLayout(top_row_layout)

@ -168,6 +168,10 @@ class WordStyleMainWindow(QMainWindow):
# 初始化时刷新天气
self.refresh_weather()
# 初始化天气缓存相关属性
self.cached_weather_data = None
self.cached_location = None
def init_theme(self):
"""初始化主题"""
@ -813,6 +817,11 @@ class WordStyleMainWindow(QMainWindow):
snake_game_action.triggered.connect(self.launch_snake_game)
games_menu.addAction(snake_game_action)
# 扫雷游戏
minesweeper_game_action = QAction('扫雷', self)
minesweeper_game_action.triggered.connect(self.launch_minesweeper_game)
games_menu.addAction(minesweeper_game_action)
# 帮助菜单
help_menu = menubar.addMenu('帮助')
@ -933,6 +942,9 @@ class WordStyleMainWindow(QMainWindow):
self.quote_thread = QuoteFetchThread()
self.quote_thread.quote_fetched.connect(self.update_quote_display)
self.quote_thread.start()
# 获取初始天气数据并缓存
self.init_weather_data()
def init_typing_logic(self):
@ -1777,6 +1789,48 @@ class WordStyleMainWindow(QMainWindow):
self.current_weather_data = weather_data
print(f"update_weather_display - 存储的current_weather_data包含life_tips: {self.current_weather_data.get('life_tips', [])}")
def init_weather_data(self):
"""初始化天气数据,使用缓存机制"""
try:
print("初始化天气数据,使用缓存机制")
# 尝试从网络服务获取缓存的天气数据
cached_weather = self.network_service.get_cached_weather_data()
cached_location = self.network_service.get_cached_location()
if cached_weather and cached_location:
print(f"使用缓存的天气数据: {cached_weather}")
print(f"使用缓存的定位数据: {cached_location}")
# 格式化缓存数据
formatted_data = {
'city': cached_weather.get('city', cached_location.get('city', '未知城市')),
'current': cached_weather.get('current', {}),
'forecast': cached_weather.get('forecast', []),
'life_tips': cached_weather.get('life_tips', [])
}
# 更新显示
self.update_weather_display(formatted_data)
# 同步更新天气悬浮窗口
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
self.weather_floating_widget.update_weather(formatted_data)
# 保存到本地缓存
self.cached_weather_data = cached_weather
self.cached_location = cached_location
self.status_bar.showMessage("使用缓存天气数据", 2000)
else:
print("没有缓存数据,使用网络获取")
# 没有缓存数据,使用网络获取
self.refresh_weather()
except Exception as e:
print(f"初始化天气数据失败: {e}")
# 如果缓存获取失败,回退到网络获取
self.refresh_weather()
def refresh_weather(self):
"""手动刷新天气信息"""
try:
@ -1796,10 +1850,10 @@ class WordStyleMainWindow(QMainWindow):
print(f"刷新天气 - 当前选择的城市: {current_city}")
if current_city == '自动定位':
# 使用自动定位
# 使用自动定位,启用缓存
weather_data = self.weather_api.get_weather_data()
else:
# 使用选中的城市
# 使用选中的城市,启用缓存
weather_data = self.weather_api.get_weather_data(current_city)
if weather_data:
@ -1816,6 +1870,12 @@ class WordStyleMainWindow(QMainWindow):
# 同步更新天气悬浮窗口
if hasattr(self, 'weather_floating_widget') and self.weather_floating_widget.isVisible():
self.weather_floating_widget.update_weather(formatted_data)
# 保存到网络服务缓存和本地缓存
self.network_service.set_weather_cache(weather_data, self.network_service.get_cached_location())
self.cached_weather_data = weather_data
self.cached_location = self.network_service.get_cached_location()
self.status_bar.showMessage("天气数据已刷新", 2000)
else:
self.status_bar.showMessage("天气数据刷新失败请检查API密钥", 3000)
@ -1829,14 +1889,17 @@ class WordStyleMainWindow(QMainWindow):
"""显示详细天气信息对话框"""
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit
# 检查是否有天气数据
if not hasattr(self, 'current_weather_data') or not self.current_weather_data:
# 首先尝试使用本地缓存的天气数据
if hasattr(self, 'cached_weather_data') and self.cached_weather_data:
weather_data = self.cached_weather_data
print(f"详细天气对话框 - 使用本地缓存天气数据: {weather_data}")
elif hasattr(self, 'current_weather_data') and self.current_weather_data:
weather_data = self.current_weather_data
print(f"详细天气对话框 - 使用当前天气数据: {weather_data}")
else:
QMessageBox.information(self, "附加工具", "暂无天气数据,请先刷新天气信息")
return
weather_data = self.current_weather_data
print(f"详细天气对话框 - 天气数据: {weather_data}")
# 创建对话框
dialog = QDialog(self)
dialog.setWindowTitle("详细天气")
@ -2894,6 +2957,21 @@ class WordStyleMainWindow(QMainWindow):
f"无法启动贪吃蛇游戏:{str(e)}"
)
def launch_minesweeper_game(self):
"""启动扫雷游戏"""
try:
from ui.minesweeper_game import MinesweeperWindow
# 创建游戏窗口
self.minesweeper_game_window = MinesweeperWindow(self)
self.minesweeper_game_window.show()
except Exception as e:
QMessageBox.critical(
self,
"错误",
f"无法启动扫雷游戏:{str(e)}"
)
def show_about(self):
"""显示关于对话框"""
# 创建自定义对话框

@ -0,0 +1,169 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord - MarkText风格启动器
基于MarkText开源编辑器的现代化Markdown编辑器
集成MagicWord现有功能学习模式天气名言等
"""
import sys
import os
import platform
import traceback
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.abspath(__file__))
src_path = os.path.join(project_root, 'src')
sys.path.insert(0, project_root)
sys.path.insert(0, src_path)
# 设置Qt平台插件路径
def setup_qt_environment():
"""设置Qt环境变量"""
system = platform.system()
python_version = f"python{sys.version_info.major}.{sys.version_info.minor}"
possible_paths = []
if system == "Windows":
possible_paths.extend([
os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
])
elif system == "Darwin": # macOS
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/local/opt/qt5/plugins',
'/opt/homebrew/opt/qt5/plugins',
])
elif system == "Linux":
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/lib/x86_64-linux-gnu/qt5/plugins',
'/usr/lib/qt5/plugins',
])
for path in possible_paths:
if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')):
os.environ['QT_PLUGIN_PATH'] = path
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(path, 'platforms')
if system == "Darwin":
os.environ['QT_QPA_PLATFORM'] = 'cocoa'
os.environ['QT_MAC_WANTS_LAYER'] = '1'
elif system == "Windows":
os.environ['QT_QPA_PLATFORM'] = 'windows'
elif system == "Linux":
os.environ['QT_QPA_PLATFORM'] = 'xcb'
print(f"✅ Qt插件路径设置成功: {path}")
return True
print("⚠️ 警告未找到Qt插件路径")
return False
# 检查依赖
def check_dependencies():
"""检查必要的依赖"""
missing_deps = []
try:
import PyQt5
except ImportError:
missing_deps.append("PyQt5")
try:
import requests
except ImportError:
missing_deps.append("requests")
try:
import docx
except ImportError:
missing_deps.append("python-docx")
try:
import fitz # PyMuPDF
except ImportError:
missing_deps.append("PyMuPDF")
if missing_deps:
print(f"❌ 缺少依赖包: {', '.join(missing_deps)}")
print("请运行: pip install " + " ".join(missing_deps))
return False
return True
def main():
"""主函数"""
try:
print("🚀 启动MagicWord - MarkText风格编辑器")
# 检查依赖
if not check_dependencies():
sys.exit(1)
# 设置Qt环境
setup_qt_environment()
# 导入Qt相关模块
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
# 设置高DPI支持
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# 创建应用
app = QApplication(sys.argv)
app.setApplicationName("MagicWord")
app.setApplicationVersion("1.0.0")
app.setOrganizationName("MagicWord")
# 设置样式
if platform.system() != "Darwin":
app.setStyle('Fusion')
# 设置图标
icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png')
if os.path.exists(icon_path):
app.setWindowIcon(QIcon(icon_path))
# 导入并创建MarkText主窗口
try:
from main import MarkTextMainWindow
main_window = MarkTextMainWindow()
print("✅ MarkText编辑器启动成功")
except ImportError as e:
print(f"❌ MarkText编辑器导入失败: {e}")
print("正在尝试启动Word风格编辑器...")
try:
from word_main_window import WordStyleMainWindow
main_window = WordStyleMainWindow()
print("✅ Word风格编辑器启动成功")
except ImportError as e2:
print(f"❌ Word风格编辑器也启动失败: {e2}")
sys.exit(1)
# 显示窗口
main_window.show()
# 运行应用
exit_code = app.exec_()
print(f"👋 应用已退出,退出码: {exit_code}")
sys.exit(exit_code)
except KeyboardInterrupt:
print("\n👋 用户中断,正在退出...")
sys.exit(0)
except Exception as e:
print(f"❌ 发生未预期的错误: {e}")
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

@ -0,0 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
扫雷游戏测试脚本
用于测试扫雷游戏模块是否能正常运行
"""
import sys
import os
import platform
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, project_root)
# 设置Qt平台插件路径 - 增强版本,完全避免平台插件问题
def set_qt_plugin_path():
"""设置Qt平台插件路径确保所有平台插件都能正确加载"""
system = platform.system()
# 获取Python版本
python_version = f"python{sys.version_info.major}.{sys.version_info.minor}"
# 可能的Qt插件路径列表
possible_paths = []
if system == "Windows":
# Windows环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(os.path.expanduser('~'), 'AppData', 'Local', 'Programs', 'Python', 'Python39', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(os.path.expanduser('~'), 'AppData', 'Roaming', 'Python', 'Python39', 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
])
elif system == "Darwin": # macOS
# macOS环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/local/opt/qt5/plugins', # Homebrew Qt5
'/opt/homebrew/opt/qt5/plugins', # Apple Silicon Homebrew
os.path.expanduser('~/Qt/5.15.2/clang_64/plugins'), # Qt官方安装
])
elif system == "Linux":
# Linux环境下的路径
possible_paths.extend([
os.path.join(project_root, '.venv', 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
os.path.join(sys.prefix, 'lib', python_version, 'site-packages', 'PyQt5', 'Qt5', 'plugins'),
'/usr/lib/x86_64-linux-gnu/qt5/plugins',
'/usr/lib/qt5/plugins',
])
# 查找第一个存在的路径
valid_path = None
for path in possible_paths:
if os.path.exists(path) and os.path.exists(os.path.join(path, 'platforms')):
valid_path = path
break
if valid_path:
# 设置Qt插件路径
os.environ['QT_PLUGIN_PATH'] = valid_path
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join(valid_path, 'platforms')
# 设置平台特定的环境变量
if system == "Darwin": # macOS
os.environ['QT_QPA_PLATFORM'] = 'cocoa'
os.environ['QT_MAC_WANTS_LAYER'] = '1'
# 禁用可能导致问题的Qt功能
os.environ['QT_LOGGING_RULES'] = 'qt.qpa.*=false' # 禁用Qt警告日志
elif system == "Windows":
os.environ['QT_QPA_PLATFORM'] = 'windows'
elif system == "Linux":
os.environ['QT_QPA_PLATFORM'] = 'xcb'
# 对于Linux可能需要设置DISPLAY
if 'DISPLAY' not in os.environ:
os.environ['DISPLAY'] = ':0'
print(f"✅ Qt插件路径设置成功: {valid_path}")
return True
else:
print("⚠️ 警告未找到Qt插件路径")
return False
# 设置Qt平台插件路径
set_qt_plugin_path()
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
from src.ui.minesweeper_game import MinesweeperWindow
def main():
# 设置高DPI支持必须在QApplication创建之前
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = QApplication(sys.argv)
# 设置应用程序样式
if platform.system() != "Darwin": # 不是macOS系统
app.setStyle('WindowsVista')
# 创建并显示扫雷游戏窗口
window = MinesweeperWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
简化测试脚本 - 验证学习模式内容同步功能
"""
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from PyQt5.QtWidgets import QApplication, QTextEdit, QVBoxLayout, QWidget
from learning_mode_window import LearningModeWindow
class TestWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("测试主窗口")
self.setGeometry(100, 100, 600, 400)
# 创建布局
layout = QVBoxLayout()
# 创建文本编辑器
self.text_edit = QTextEdit()
self.text_edit.setPlainText("初始内容\n")
layout.addWidget(self.text_edit)
self.setLayout(layout)
# 创建学习模式窗口
self.learning_window = LearningModeWindow(parent=self)
self.learning_window.imported_content = "这是一段测试内容。"
self.learning_window.current_position = 0
self.learning_window.show()
# 连接内容同步信号
self.learning_window.content_changed.connect(self.on_content_changed)
print("测试开始...")
print(f"主窗口内容: {repr(self.text_edit.toPlainText())}")
print(f"学习窗口内容: {repr(self.learning_window.imported_content)}")
print(f"学习窗口当前位置: {self.learning_window.current_position}")
# 模拟用户输入正确内容
print("\n模拟用户输入正确内容...")
self.learning_window.current_position = 3 # 用户输入了3个字符
self.learning_window.on_text_changed() # 调用文本变化处理
def on_content_changed(self, new_content, position):
"""内容同步回调"""
print(f"收到内容同步信号: new_content={repr(new_content)}, position={position}")
# 在文本编辑器末尾添加新内容
current_text = self.text_edit.toPlainText()
self.text_edit.setPlainText(current_text + new_content)
print(f"主窗口更新后内容: {repr(self.text_edit.toPlainText())}")
def test_content_sync():
"""测试内容同步功能"""
app = QApplication(sys.argv)
test_window = TestWindow()
test_window.show()
print("\n测试完成!")
# 运行应用
sys.exit(app.exec_())
if __name__ == "__main__":
test_content_sync()

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
测试天气生活提示功能
"""
import sys
import os
# 添加src目录到路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
from services.network_service import NetworkService
def test_weather_with_lifetips():
"""测试包含生活提示的天气功能"""
print("🌤 测试天气生活提示功能")
print("=" * 50)
# 创建网络服务实例
network_service = NetworkService()
# 获取天气信息
print("正在获取天气信息...")
weather_info = network_service.get_weather_info()
if weather_info:
print(f"✅ 成功获取天气数据:")
print(f"城市: {weather_info['city']}")
print(f"温度: {weather_info['temperature']}°C")
print(f"天气: {weather_info['description']}")
print(f"湿度: {weather_info['humidity']}%")
print(f"风速: {weather_info['wind_speed']}m/s")
# 显示生活提示
lifetips = weather_info.get('lifetips', [])
if lifetips:
print(f"\n🌟 生活提示 ({len(lifetips)}条):")
for i, tip in enumerate(lifetips, 1):
print(f" {i}. {tip}")
else:
print("⚠️ 未获取到生活提示")
# 模拟显示详细信息格式
print(f"\n📋 详细信息显示格式:")
weather_text = f"{weather_info['city']}: {weather_info['temperature']}°C, {weather_info['description']}"
weather_text += f"\n湿度: {weather_info['humidity']}%"
weather_text += f"\n风速: {weather_info['wind_speed']}m/s"
if lifetips:
weather_text += "\n\n🌟 生活提示:"
for tip in lifetips:
weather_text += f"\n{tip}"
print(weather_text)
else:
print("❌ 获取天气信息失败")
print("\n" + "=" * 50)
print("测试完成!")
if __name__ == "__main__":
test_weather_with_lifetips()
Loading…
Cancel
Save