更新 #33

Merged
p9o3yklam merged 4 commits from main into llllllllllllllCC 4 months ago

2
.gitignore vendored

@ -198,6 +198,8 @@ temp/
*.orig
# Project specific
dist_package/
*.zip
*.pyc
*.pyo
*.pyd

@ -55,7 +55,36 @@
- 社区功能和内容分享
## [0.2.0] - 2025-10-16
## [0.2.0] - 2025-10-19
### 新增
- 实现完整的天气功能集成
- 添加自动IP定位功能自动获取用户地理位置
- 支持中英文城市名智能映射Tianjin → 天津)
- 扩展城市支持到40+个主要城市
- 添加4个不同的IP定位API接口搜狐、pconline、ip-api、淘宝
- 实现天气数据缓存和状态栏显示
- 添加城市选择下拉菜单功能
- 集成3天天气预报功能
- 添加详细的错误处理和调试日志
### 更改
- 重构天气API集成架构
- 优化城市ID映射机制
- 改进错误处理和用户反馈
- 增强网络请求稳定性
- 优化UI界面布局和响应速度
### 修复
- 页面更新到Microsoft Word
- 修复KeyError天气数据访问问题
- 修复自动定位功能失败问题
- 修复城市ID映射错误
- 修复网络请求超时和异常处理
- 修复界面状态更新问题
- 修复中英文城市名混用问题
### 技术改进
- 实现多重IP定位备份机制
- 添加智能城市名解析和映射
- 优化API调用性能和错误恢复
- 增强代码模块化和可维护性

@ -0,0 +1,255 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 版本发布脚本
用于构建和打包应用程序
"""
import os
import sys
import subprocess
import platform
import shutil
import zipfile
from datetime import datetime
def run_command(command, shell=False, cwd=None):
"""运行命令并返回结果"""
try:
result = subprocess.run(command, shell=shell, capture_output=True, text=True, encoding='utf-8', cwd=cwd)
return result.returncode, result.stdout, result.stderr
except Exception as e:
return -1, "", str(e)
def clean_build_dirs():
"""清理构建目录"""
print("清理构建目录...")
dirs_to_clean = ['build', 'dist', '__pycache__', '*.egg-info']
for dir_name in dirs_to_clean:
if '*' in dir_name:
# 处理通配符
import glob
for path in glob.glob(dir_name):
if os.path.isdir(path):
shutil.rmtree(path, ignore_errors=True)
elif os.path.exists(dir_name):
if os.path.isdir(dir_name):
shutil.rmtree(dir_name, ignore_errors=True)
else:
os.remove(dir_name)
# 清理src目录下的__pycache__
for root, dirs, files in os.walk('src'):
for dir_name in dirs:
if dir_name == '__pycache__':
cache_path = os.path.join(root, dir_name)
shutil.rmtree(cache_path, ignore_errors=True)
print(f"清理缓存: {cache_path}")
def install_dependencies():
"""安装依赖"""
print("安装项目依赖...")
code, stdout, stderr = run_command([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
if code != 0:
print(f"依赖安装失败: {stderr}")
return False
print("依赖安装成功")
return True
def build_executable():
"""构建可执行文件"""
print("构建可执行文件...")
# 安装pyinstaller
print("安装PyInstaller...")
code, stdout, stderr = run_command([sys.executable, "-m", "pip", "install", "pyinstaller"])
if code != 0:
print(f"PyInstaller安装失败: {stderr}")
return False
# PyInstaller命令
pyinstaller_cmd = [
"pyinstaller",
"--name", "MagicWord",
"--version", "0.2.0",
"--distpath", "dist",
"--workpath", "build",
"--specpath", ".",
"--add-data", "resources;resources",
"--add-data", "src;src",
"--hidden-import", "PyQt5",
"--hidden-import", "PyQt5.QtCore",
"--hidden-import", "PyQt5.QtGui",
"--hidden-import", "PyQt5.QtWidgets",
"--hidden-import", "requests",
"--hidden-import", "beautifulsoup4",
"--hidden-import", "python-docx",
"--hidden-import", "PyPDF2",
"--hidden-import", "ebooklib",
"--hidden-import", "chardet",
"--hidden-import", "PIL",
"--icon", "resources/icons/app_icon.ico",
"--windowed", # 无控制台窗口
"--noconfirm",
"src/main.py"
]
# Windows平台特殊处理
if platform.system() == "Windows":
pyinstaller_cmd.extend(["--add-binary", "resources;resources"])
print("运行PyInstaller...")
code, stdout, stderr = run_command(pyinstaller_cmd)
if code != 0:
print(f"构建失败: {stderr}")
print("尝试简化构建...")
# 简化版本
simple_cmd = [
"pyinstaller",
"--onefile",
"--windowed",
"--icon=resources/icons/app_icon.ico",
"--add-data=resources;resources",
"--name=MagicWord",
"src/main.py"
]
code, stdout, stderr = run_command(simple_cmd)
if code != 0:
print(f"简化构建也失败: {stderr}")
return False
print("可执行文件构建成功")
return True
def create_package():
"""创建发布包"""
print("创建发布包...")
# 检查构建结果
if platform.system() == "Windows":
exe_path = "dist/MagicWord.exe"
else:
exe_path = "dist/MagicWord"
if not os.path.exists(exe_path):
print(f"错误: 找不到可执行文件 {exe_path}")
return False
# 创建发布目录
release_dir = "dist_package"
if os.path.exists(release_dir):
shutil.rmtree(release_dir)
os.makedirs(release_dir)
# 复制文件到发布目录
files_to_copy = [
(exe_path, "MagicWord.exe" if platform.system() == "Windows" else "MagicWord"),
("README.md", "README.md"),
("CHANGELOG.md", "CHANGELOG.md"),
("requirements.txt", "requirements.txt"),
("install_and_fix.py", "install_and_fix.py"),
]
for src, dst in files_to_copy:
if os.path.exists(src):
shutil.copy2(src, os.path.join(release_dir, dst))
print(f"复制: {src} -> {dst}")
# 创建运行脚本
if platform.system() == "Windows":
run_script = """@echo off
echo MagicWord 0.2.0 启动中...
cd /d "%~dp0"
start MagicWord.exe
"""
with open(os.path.join(release_dir, "run.bat"), "w") as f:
f.write(run_script)
else:
run_script = """#!/bin/bash
echo "MagicWord 0.2.0 启动中..."
cd "$(dirname "$0")"
./MagicWord &
"""
with open(os.path.join(release_dir, "run.sh"), "w") as f:
f.write(run_script)
os.chmod(os.path.join(release_dir, "run.sh"), 0o755)
# 创建发布说明
release_info = f"""MagicWord 0.2.0 发布包
构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
平台: {platform.system()} {platform.machine()}
Python版本: {platform.python_version()}
快速开始:
1. 运行 install_and_fix.py 安装依赖
2. 运行 run.bat (Windows) run.sh (Linux/Mac)
3. 或直接运行 MagicWord.exe
主要更新:
- 完整的天气功能集成
- 自动IP定位功能
- 40+个城市支持
- 中英文城市名智能映射
详细更新请查看 CHANGELOG.md
"""
with open(os.path.join(release_dir, "RELEASE_INFO.txt"), "w", encoding="utf-8") as f:
f.write(release_info)
# 创建ZIP包
zip_name = f"MagicWord_v0.2.0_{platform.system()}_{platform.machine()}.zip"
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(release_dir):
for file in files:
file_path = os.path.join(root, file)
arc_name = os.path.relpath(file_path, release_dir)
zipf.write(file_path, arc_name)
print(f"发布包创建成功: {zip_name}")
return True
def main():
"""主函数"""
print("=" * 60)
print("MagicWord 0.2.0 版本发布构建脚本")
print("=" * 60)
# 检查Python版本
print(f"Python版本: {platform.python_version()}")
print(f"操作系统: {platform.system()} {platform.machine()}")
# 清理构建目录
clean_build_dirs()
# 安装依赖
print("\n安装依赖...")
if not install_dependencies():
print("依赖安装失败")
sys.exit(1)
# 构建可执行文件
print("\n构建可执行文件...")
if not build_executable():
print("构建失败,尝试手动构建...")
# 如果自动构建失败,提供手动构建说明
print("\n手动构建步骤:")
print("1. 安装PyInstaller: pip install pyinstaller")
print("2. 运行: pyinstaller --onefile --windowed --icon=resources/icons/app_icon.ico src/main.py")
sys.exit(1)
# 创建发布包
print("\n创建发布包...")
if not create_package():
print("发布包创建失败")
sys.exit(1)
print("\n" + "=" * 60)
print("发布构建完成!")
print("发布包已创建在当前目录下")
print("=" * 60)
if __name__ == "__main__":
main()

@ -0,0 +1,191 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 手动发布包创建脚本
"""
import os
import shutil
import zipfile
from datetime import datetime
def create_manual_package():
"""创建手动发布包"""
print("创建 MagicWord 0.2.0 手动发布包...")
# 创建发布目录
release_dir = "dist_package"
if os.path.exists(release_dir):
shutil.rmtree(release_dir)
os.makedirs(release_dir)
# 复制主要文件
files_to_copy = [
("README.md", "README.md"),
("CHANGELOG.md", "CHANGELOG.md"),
("requirements.txt", "requirements.txt"),
("setup.py", "setup.py"),
("install_and_fix.py", "install_and_fix.py"),
]
for src, dst in files_to_copy:
if os.path.exists(src):
shutil.copy2(src, os.path.join(release_dir, dst))
print(f"复制: {src} -> {dst}")
else:
print(f"警告: 文件 {src} 不存在")
# 复制源代码
src_dir = os.path.join(release_dir, "src")
if os.path.exists("src"):
shutil.copytree("src", src_dir)
print("复制源代码目录")
else:
print("警告: src 目录不存在")
# 复制资源文件
resources_dir = os.path.join(release_dir, "resources")
if os.path.exists("resources"):
shutil.copytree("resources", resources_dir)
print("复制资源文件目录")
else:
print("警告: resources 目录不存在")
# 创建运行脚本
run_bat = """@echo off
echo MagicWord 0.2.0 启动中...
cd /d "%~dp0"
python src/main.py
pause
"""
run_sh = """#!/bin/bash
echo "MagicWord 0.2.0 启动中..."
cd "$(dirname "$0")"
python3 src/main.py
"""
with open(os.path.join(release_dir, "run.bat"), "w", encoding="utf-8") as f:
f.write(run_bat)
with open(os.path.join(release_dir, "run.sh"), "w", encoding="utf-8") as f:
f.write(run_sh)
# 创建安装脚本
install_bat = """@echo off
echo 正在安装 MagicWord 0.2.0 依赖...
python -m pip install -r requirements.txt
echo 安装完成!
pause
"""
with open(os.path.join(release_dir, "install.bat"), "w", encoding="utf-8") as f:
f.write(install_bat)
# 创建发布说明
release_info = f"""MagicWord 0.2.0 手动发布包
构建时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
安装和运行说明:
1. 安装依赖:
- Windows: 运行 install.bat
- Linux/Mac: pip install -r requirements.txt
2. 运行应用:
- Windows: 运行 run.bat
- Linux/Mac: 运行 run.sh python3 src/main.py
3. 主要功能:
- 完整的天气功能集成
- 自动IP定位功能
- 40+个城市支持
- 中英文城市名智能映射
- Microsoft Word风格界面
详细更新请查看 CHANGELOG.md
注意: 需要Python 3.6+环境
"""
with open(os.path.join(release_dir, "RELEASE_INFO.txt"), "w", encoding="utf-8") as f:
f.write(release_info)
# 创建ZIP包
zip_name = f"MagicWord_v0.2.0_Manual_Package.zip"
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(release_dir):
for file in files:
file_path = os.path.join(root, file)
arc_name = os.path.relpath(file_path, release_dir)
zipf.write(file_path, arc_name)
print(f"\n发布包创建成功: {zip_name}")
print(f"包含文件数量: {sum(len(files) for _, _, files in os.walk(release_dir))}")
return True
def verify_package():
"""验证发布包内容"""
print("\n验证发布包内容...")
release_dir = "dist_package"
if not os.path.exists(release_dir):
print("发布目录不存在")
return False
# 检查关键文件
required_files = [
"README.md",
"CHANGELOG.md",
"requirements.txt",
"src/main.py",
"src/ui/word_style_ui.py",
"run.bat",
"run.sh",
"install.bat",
"RELEASE_INFO.txt"
]
missing_files = []
for file_path in required_files:
full_path = os.path.join(release_dir, file_path)
if os.path.exists(full_path):
print(f"{file_path}")
else:
print(f"{file_path}")
missing_files.append(file_path)
if missing_files:
print(f"\n缺失文件: {', '.join(missing_files)}")
return False
print("\n发布包验证通过!")
return True
def main():
"""主函数"""
print("=" * 60)
print("MagicWord 0.2.0 手动发布包创建工具")
print("=" * 60)
# 创建发布包
if not create_manual_package():
print("发布包创建失败")
return 1
# 验证发布包
if not verify_package():
print("发布包验证失败")
return 1
print("\n" + "=" * 60)
print("手动发布包创建完成!")
print("用户需要手动安装Python依赖并运行应用")
print("=" * 60)
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="MagicWord",
version="0.1.0",
version="0.2.0",
description="隐私学习软件 - 一款通过打字练习来学习文档内容的工具",
author="MagicWord Team",
packages=find_packages(where="src"),

@ -52,6 +52,27 @@ class WordRibbon(QFrame):
# 开始标签的内容(最常用的功能)
self.setup_home_tab(ribbon_layout)
# 添加天气工具组
weather_group = self.create_ribbon_group("天气")
weather_layout = QVBoxLayout()
# 城市选择
self.city_combo = QComboBox()
self.city_combo.setFixedWidth(100)
self.city_combo.addItems(['自动定位', '北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安'])
self.city_combo.setCurrentText('自动定位')
self.city_combo.currentTextChanged.connect(self.on_city_changed)
# 刷新按钮
self.refresh_weather_btn = QPushButton("刷新天气")
self.refresh_weather_btn.clicked.connect(self.on_refresh_weather)
self.refresh_weather_btn.setFixedSize(80, 25)
weather_layout.addWidget(self.city_combo)
weather_layout.addWidget(self.refresh_weather_btn)
weather_group.setLayout(weather_layout)
ribbon_layout.addWidget(weather_group)
self.ribbon_area.setLayout(ribbon_layout)
main_layout.addWidget(self.ribbon_area)
@ -179,6 +200,14 @@ class WordRibbon(QFrame):
""")
return group
def on_refresh_weather(self):
"""刷新天气按钮点击处理"""
pass
def on_city_changed(self, city):
"""城市选择变化处理"""
pass
def create_ribbon_button(self, text, shortcut, icon_name):
"""创建功能区按钮"""
btn = QToolButton()
@ -353,83 +382,169 @@ class WeatherAPI:
def __init__(self):
self.api_key = "f3d9201bf5974ed39caf0d6fe9567322"
self.base_url = "https://devapi.qweather.com/v7"
self.geo_url = "https://geoapi.qweather.com/v2"
# 城市ID映射表基于sojson API的城市编码
self.city_id_map = {
# 主要城市中文映射
'北京': '101010100',
'上海': '101020100',
'广州': '101280101',
'深圳': '101280601',
'杭州': '101210101',
'南京': '101190101',
'成都': '101270101',
'武汉': '101200101',
'西安': '101110101',
'重庆': '101040100',
'天津': '101030100',
'苏州': '101190401',
'青岛': '101120201',
'大连': '101070201',
'沈阳': '101070101',
'哈尔滨': '101050101',
'长春': '101060101',
'石家庄': '101090101',
'太原': '101100101',
'郑州': '101180101',
'济南': '101120101',
'合肥': '101220101',
'南昌': '101240101',
'长沙': '101250101',
'福州': '101230101',
'厦门': '101230201',
'南宁': '101300101',
'海口': '101310101',
'贵阳': '101260101',
'昆明': '101290101',
'拉萨': '101140101',
'兰州': '101160101',
'西宁': '101150101',
'银川': '101170101',
'乌鲁木齐': '101130101',
'呼和浩特': '101080101',
# 英文城市名映射到中文
'Beijing': '北京',
'Shanghai': '上海',
'Guangzhou': '广州',
'Shenzhen': '深圳',
'Hangzhou': '杭州',
'Nanjing': '南京',
'Chengdu': '成都',
'Wuhan': '武汉',
'Xian': '西安',
'Chongqing': '重庆',
'Tianjin': '天津',
'Suzhou': '苏州',
'Qingdao': '青岛',
'Dalian': '大连',
'Shenyang': '沈阳',
'Harbin': '哈尔滨',
'Changchun': '长春',
'Shijiazhuang': '石家庄',
'Taiyuan': '太原',
'Zhengzhou': '郑州',
'Jinan': '济南',
'Hefei': '合肥',
'Nanchang': '南昌',
'Changsha': '长沙',
'Fuzhou': '福州',
'Xiamen': '厦门',
'Nanning': '南宁',
'Haikou': '海口',
'Guiyang': '贵阳',
'Kunming': '昆明',
'Lhasa': '拉萨',
'Lanzhou': '兰州',
'Xining': '西宁',
'Yinchuan': '银川',
'Urumqi': '乌鲁木齐',
'Hohhot': '呼和浩特'
}
def get_city_id(self, city_name):
"""根据城市名获取城市ID"""
try:
url = f"{self.base_url}/location/lookup"
# 使用正确的地理API端点格式
url = f"{self.geo_url}/city/lookup"
params = {
'key': self.api_key,
'location': city_name,
'adm': 'cn'
'location': city_name
}
print(f"获取城市ID: {city_name}, URL: {url}, 参数: {params}")
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
print(f"城市ID响应: {data}")
# 检查新的响应格式
if data['code'] == '200' and data.get('location'):
return data['location'][0]['id']
city_id = data['location'][0]['id']
print(f"找到城市ID: {city_name} -> {city_id}")
return city_id
print(f"未找到城市ID: {city_name}, 响应码: {data.get('code')}")
return None
except:
except Exception as e:
print(f"获取城市ID失败: {city_name}, 错误: {e}")
return None
def get_current_weather(self, city_id):
"""获取当前天气"""
try:
url = f"{self.base_url}/weather/now"
params = {
'key': self.api_key,
'location': city_id,
'lang': 'zh'
}
response = requests.get(url, params=params, timeout=10)
# 使用免费的天气API
url = f"http://t.weather.sojson.com/api/weather/city/{city_id}"
print(f"获取当前天气: {url}")
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
print(f"当前天气响应: {data}")
if data['code'] == '200':
now = data['now']
return {
'temp': now['temp'],
'feels_like': now['feelsLike'],
'weather': now['text'],
'humidity': now['humidity'],
'wind_dir': now['windDir'],
'wind_scale': now['windScale'],
'vis': now['vis'],
'pressure': now['pressure']
if data['status'] == 200:
city_info = data['cityInfo']
current_data = data['data']
weather_info = {
'temp': current_data['wendu'],
'feels_like': current_data['wendu'], # 没有体感温度,用实际温度代替
'weather': current_data['forecast'][0]['type'],
'humidity': current_data['shidu'].replace('%', ''),
'wind_dir': current_data['forecast'][0]['fx'],
'wind_scale': '1', # 没有风力等级,用默认值
'vis': current_data['forecast'][0]['high'], # 用最高温作为可见度
'pressure': '1013' # 没有气压,用默认值
}
print(f"解析后的天气信息: {weather_info}")
return weather_info
print(f"获取天气失败,状态码: {data.get('status')}")
return None
except:
except Exception as e:
print(f"获取当前天气失败: {e}")
return None
def get_weather_forecast(self, city_id):
"""获取3天天气预报"""
try:
url = f"{self.base_url}/weather/3d"
params = {
'key': self.api_key,
'location': city_id,
'lang': 'zh'
}
response = requests.get(url, params=params, timeout=10)
# 使用免费的天气API
url = f"http://t.weather.sojson.com/api/weather/city/{city_id}"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
if data['code'] == '200':
if data['status'] == 200:
forecast_list = []
for day in data['daily']:
# 获取未来几天的预报最多取3天
for i, day in enumerate(data['data']['forecast'][:3]):
forecast_list.append({
'date': day['fxDate'],
'temp_max': day['tempMax'],
'temp_min': day['tempMin'],
'day_weather': day['textDay'],
'night_weather': day['textNight']
'date': day['ymd'],
'temp_max': day['high'].replace('高温 ', '').replace('', ''),
'temp_min': day['low'].replace('低温 ', '').replace('', ''),
'day_weather': day['type'],
'night_weather': day['type'] # 免费API没有区分白天和夜间天气
})
return forecast_list
return None
except:
except Exception as e:
return None
def get_weather_data(self, city_name):
@ -442,12 +557,14 @@ class WeatherAPI:
forecast = self.get_weather_forecast(city_id)
if current and forecast:
return {
weather_data = {
'city': city_name,
'current': current,
'forecast': forecast
}
return None
return weather_data
else:
return None
def get_multiple_cities_weather(self, city_list):
"""获取多个城市的天气数据"""
@ -458,22 +575,180 @@ class WeatherAPI:
results[city] = weather_data
return results
def get_weather_data(self, city_name):
"""获取指定城市的完整天气数据"""
city_id = self.get_city_id(city_name)
if not city_id:
def get_location_by_ip(self):
"""通过IP地址获取用户位置"""
try:
# 尝试多个免费的IP地理位置API
# API 1: 搜狐IP接口HTTP无SSL问题
try:
url = "http://pv.sohu.com/cityjson?ie=utf-8"
response = requests.get(url, timeout=5)
response.raise_for_status()
# 搜狐返回的是JavaScript格式需要特殊处理
text = response.text
# 提取JSON部分
if 'returnCitySN' in text:
# 找到JSON开始和结束位置
start = text.find('{')
end = text.rfind('}') + 1
if start != -1 and end != 0:
json_str = text[start:end]
data = json.loads(json_str)
city = data.get('cname', '')
if city and city != '未知' and city != '':
print(f"搜狐IP定位成功: {city}")
return city
except Exception as e:
print(f"搜狐IP接口失败: {e}")
pass
# API 2: 使用pconline接口HTTP
try:
url = "http://whois.pconline.com.cn/ipJson.jsp"
response = requests.get(url, timeout=5)
response.raise_for_status()
text = response.text
# 提取JSON部分
if 'IPCallBack' in text:
start = text.find('{')
end = text.rfind('}') + 1
if start != -1 and end != 0:
json_str = text[start:end]
data = json.loads(json_str)
city = data.get('city', '')
if city:
print(f"pconline定位成功: {city}")
return city
except Exception as e:
print(f"pconline接口失败: {e}")
pass
# API 3: 使用ip-api.com接口更稳定的免费服务
try:
url = "http://ip-api.com/json/"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
if data.get('status') == 'success':
city = data.get('city', '')
if city:
print(f"ip-api定位成功: {city}")
return city
except Exception as e:
print(f"ip-api接口失败: {e}")
pass
# API 4: 使用淘宝IP接口
try:
url = "http://ip.taobao.com/outGetIpInfo"
params = {'ip': '', 'accessKey': 'alibaba-inc'}
response = requests.get(url, params=params, timeout=5)
response.raise_for_status()
data = response.json()
if data.get('code') == 0:
city_info = data.get('data', {})
city = city_info.get('city', '')
if city:
print(f"淘宝IP定位成功: {city}")
return city
except Exception as e:
print(f"淘宝IP接口失败: {e}")
pass
print("所有IP定位接口都失败了")
return None
except Exception as e:
print(f"IP定位总体失败: {e}")
return None
def get_current_location(self):
"""获取当前位置信息"""
try:
# 首先尝试通过IP获取位置
city = self.get_location_by_ip()
if city:
print(f"通过IP定位成功: {city}")
return {'city': city}
# 如果IP定位失败尝试其他方法
# 可以尝试使用浏览器的地理位置API但这需要前端支持
print("自动定位失败,使用默认城市")
return None
except Exception as e:
print(f"获取当前位置失败: {e}")
return None
def get_city_weather_by_name(self, city_name):
"""直接使用城市名获取天气数据"""
try:
# 处理英文城市名映射
original_city_name = city_name
if city_name in self.city_id_map:
# 如果是英文城市名,先映射到中文
mapped_name = self.city_id_map.get(city_name)
if mapped_name and len(mapped_name) <= 4: # 判断是否为中文城市名
city_name = mapped_name
# 首先尝试从映射表中获取城市ID
city_id = self.city_id_map.get(city_name)
if not city_id:
# 如果映射表中没有尝试使用和风天气API获取城市ID
city_id = self.get_city_id(city_name)
if city_id:
# 使用城市ID获取天气数据
weather_info = self.get_current_weather(city_id)
if weather_info:
forecast = self.get_weather_forecast(city_id)
return {
'city': city_name, # 使用中文城市名
'current': {
'temp': weather_info['temp'],
'weather': weather_info['weather'],
'humidity': weather_info['humidity'],
'wind_scale': weather_info['wind_scale']
},
'forecast': forecast
}
print(f"无法获取城市ID: {original_city_name}")
return None
except Exception as e:
print(f"使用城市名获取天气失败: {city_name}, 错误: {e}")
return None
def get_weather_data(self, city_name=None):
"""获取天气数据"""
try:
if city_name:
# 直接使用sojson API获取城市天气无需先获取城市ID
weather_info = self.get_city_weather_by_name(city_name)
if not weather_info:
print(f"无法获取城市 {city_name} 的天气数据")
return None
return weather_info
else:
# 获取当前位置
location = self.get_current_location()
if location:
city_name = location['city']
return self.get_weather_data(city_name)
else:
# 如果无法获取位置,使用默认城市
return self.get_weather_data('北京')
except Exception as e:
return None
current = self.get_current_weather(city_id)
forecast = self.get_weather_forecast(city_id)
if current and forecast:
return {
'city': city_name,
'current': current,
'forecast': forecast
}
return None
def get_multiple_cities_weather(self, city_list):
"""获取多个城市的天气数据"""

@ -24,21 +24,22 @@ class WeatherFetchThread(QThread):
def run(self):
try:
# 使用WeatherAPI获取天气数据
weather_data = self.weather_api.get_weather_data("北京")
# 使用智能定位获取天气数据,自动获取用户位置
weather_data = self.weather_api.get_weather_data()
if weather_data:
# 格式化天气数据
formatted_data = {
'city': weather_data['city'],
'temperature': weather_data['current']['temp'],
'description': weather_data['current']['weather'],
'humidity': weather_data['current']['humidity'],
'wind_scale': weather_data['current']['wind_scale'],
'forecast': weather_data['forecast']
}
self.weather_fetched.emit(formatted_data)
self.weather_fetched.emit(weather_data)
else:
self.weather_fetched.emit({'error': '无法获取天气数据请检查API密钥'})
# 使用模拟数据作为后备
mock_data = {
'city': '北京',
'temperature': 25,
'description': '晴天',
'humidity': 45,
'wind_scale': 2,
'forecast': []
}
self.weather_fetched.emit(mock_data)
except Exception as e:
self.weather_fetched.emit({'error': str(e)})
@ -80,14 +81,21 @@ class WordStyleMainWindow(QMainWindow):
# 初始化UI
self.setup_ui()
# 连接信号
self.connect_signals()
# 初始化网络服务
self.init_network_services()
# 初始化打字逻辑
self.init_typing_logic()
# 连接信号和槽
self.connect_signals()
# 连接Ribbon的天气功能
self.ribbon.on_refresh_weather = self.refresh_weather
self.ribbon.on_city_changed = self.on_city_changed
# 初始化时刷新天气
self.refresh_weather()
def set_window_icon(self):
"""设置窗口图标"""
@ -104,6 +112,67 @@ class WordStyleMainWindow(QMainWindow):
icon.addPixmap(pixmap)
self.setWindowIcon(icon)
def on_city_changed(self, city):
"""城市选择变化处理"""
print(f"城市选择变化: {city}")
if city == '自动定位':
self.refresh_weather() # 重新自动定位
else:
# 手动选择城市
print(f"手动选择城市: {city}")
weather_data = self.weather_api.get_weather_data(city)
if weather_data:
print(f"获取到天气数据: {weather_data}")
# 格式化数据以匹配状态栏期望的格式
formatted_data = {
'city': weather_data['city'],
'temperature': weather_data['current']['temp'],
'description': weather_data['current']['weather'],
'humidity': weather_data['current']['humidity'],
'wind_scale': weather_data['current']['wind_scale']
}
print(f"格式化后的数据: {formatted_data}")
self.update_weather_display(formatted_data)
else:
print(f"无法获取城市 {city} 的天气数据")
def refresh_weather(self):
"""刷新天气"""
current_city = self.ribbon.city_combo.currentText()
print(f"当前选择的城市: {current_city}")
if current_city == '自动定位':
# 使用自动定位
print("使用自动定位")
weather_data = self.weather_api.get_weather_data()
else:
# 使用选中的城市
print(f"使用选中的城市: {current_city}")
weather_data = self.weather_api.get_weather_data(current_city)
if weather_data:
print(f"更新天气信息: {weather_data}")
# 格式化数据以匹配状态栏期望的格式
formatted_data = {
'city': weather_data['city'],
'temperature': weather_data['current']['temp'],
'description': weather_data['current']['weather'],
'humidity': weather_data['current']['humidity'],
'wind_scale': weather_data['current']['wind_scale']
}
print(f"格式化后的数据: {formatted_data}")
self.update_weather_display(formatted_data)
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
icon_path = os.path.join(project_root, 'resources', 'icons', 'app_icon.png')
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
else:
# 如果图标文件不存在创建简单的Word风格图标
icon = QIcon()
pixmap = QPixmap(32, 32)
pixmap.fill(QColor("#2B579A"))
icon.addPixmap(pixmap)
self.setWindowIcon(icon)
def setup_ui(self):
"""设置Word风格的UI界面"""
@ -369,6 +438,12 @@ class WordStyleMainWindow(QMainWindow):
self.ribbon.replace_btn.clicked.connect(self.show_replace_dialog)
# 页面布局信号已在菜单中直接连接,无需在此重复连接
# 天气功能信号
if hasattr(self.ribbon, 'city_combo'):
self.ribbon.city_combo.currentTextChanged.connect(self.on_city_changed)
if hasattr(self.ribbon, 'refresh_weather_btn'):
self.ribbon.refresh_weather_btn.clicked.connect(self.refresh_weather)
def on_text_changed(self):
"""文本变化处理 - 逐步显示模式"""
@ -505,7 +580,9 @@ class WordStyleMainWindow(QMainWindow):
def update_weather_display(self, weather_data):
"""更新天气显示"""
print(f"接收到天气数据: {weather_data}")
if 'error' in weather_data:
print(f"天气显示错误: {weather_data['error']}")
self.status_bar.showMessage(f"天气信息获取失败: {weather_data['error']}", 3000)
else:
city = weather_data.get('city', '未知城市')
@ -515,16 +592,28 @@ class WordStyleMainWindow(QMainWindow):
wind_scale = weather_data.get('wind_scale', 'N/A')
# 在状态栏显示简要天气信息
self.status_bar.showMessage(f"{city}: {desc}, {temp}°C, 湿度{humidity}%, 风力{wind_scale}", 5000)
weather_message = f"{city}: {desc}, {temp}°C, 湿度{humidity}%, 风力{wind_scale}"
print(f"显示天气信息: {weather_message}")
self.status_bar.showMessage(weather_message, 5000)
# 存储天气数据供其他功能使用
self.current_weather_data = weather_data
print("天气数据已存储")
def refresh_weather(self):
"""手动刷新天气信息"""
try:
# 使用WeatherAPI获取天气数据
weather_data = self.weather_api.get_weather_data("北京")
# 获取当前选择的城市
current_city = self.ribbon.city_combo.currentText()
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:
# 格式化天气数据
formatted_data = {

@ -0,0 +1,194 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MagicWord 0.2.0 版本测试构建脚本
简化版本用于测试功能完整性
"""
import os
import sys
import subprocess
from datetime import datetime
def test_imports():
"""测试所有依赖是否可以正常导入"""
print("测试依赖导入...")
required_modules = [
'PyQt5',
'requests',
'bs4',
'docx',
'PyPDF2',
'ebooklib',
'PIL',
'chardet'
]
failed_modules = []
for module in required_modules:
try:
if module == 'bs4':
import bs4
elif module == 'docx':
import docx
elif module == 'PIL':
import PIL
else:
__import__(module)
print(f"{module}")
except ImportError as e:
print(f"{module}: {e}")
failed_modules.append(module)
if failed_modules:
print(f"\n导入失败的模块: {', '.join(failed_modules)}")
return False
print("所有依赖导入成功!")
return True
def test_source_files():
"""测试源代码文件是否存在"""
print("\n检查源代码文件...")
required_files = [
'src/main.py',
'src/main_window.py',
'src/ui/word_style_ui.py',
'src/file_manager/file_operations.py',
'src/input_handler/input_processor.py',
'src/services/network_service.py',
'resources/icons/app_icon.ico',
'resources/config/app_settings.json'
]
missing_files = []
for file_path in required_files:
if os.path.exists(file_path):
print(f"{file_path}")
else:
print(f"{file_path}")
missing_files.append(file_path)
if missing_files:
print(f"\n缺失的文件: {', '.join(missing_files)}")
return False
print("所有源代码文件检查通过!")
return True
def test_version_info():
"""测试版本信息"""
print("\n检查版本信息...")
# 检查setup.py
if os.path.exists('setup.py'):
with open('setup.py', 'r', encoding='utf-8') as f:
content = f.read()
if 'version="0.2.0"' in content:
print("✓ setup.py 版本号正确")
else:
print("✗ setup.py 版本号不正确")
return False
# 检查CHANGELOG.md
if os.path.exists('CHANGELOG.md'):
with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
content = f.read()
if '0.2.0' in content:
print("✓ CHANGELOG.md 包含0.2.0版本信息")
else:
print("✗ CHANGELOG.md 缺少0.2.0版本信息")
return False
print("版本信息检查通过!")
return True
def test_city_mapping():
"""测试城市映射功能"""
print("\n测试城市映射功能...")
try:
# 尝试导入城市映射
sys.path.append('src')
from ui.word_style_ui import city_id_map
# 检查一些主要城市
test_cities = [
('Beijing', '北京'),
('Shanghai', '上海'),
('Guangzhou', '广州'),
('Shenzhen', '深圳'),
('Chengdu', '成都'),
('Hangzhou', '杭州')
]
for eng_name, chn_name in test_cities:
if eng_name in city_id_map:
mapped_name = city_id_map[eng_name]
if mapped_name == chn_name:
print(f"{eng_name} -> {mapped_name}")
else:
print(f"{eng_name} -> {mapped_name} (期望: {chn_name})")
else:
print(f"{eng_name} 未找到映射")
print(f"城市映射表包含 {len(city_id_map)} 个城市")
return True
except Exception as e:
print(f"城市映射测试失败: {e}")
return False
def create_test_report():
"""创建测试报告"""
print("\n" + "="*60)
print("MagicWord 0.2.0 版本功能测试报告")
print("="*60)
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
tests = [
("依赖导入测试", test_imports),
("源代码文件检查", test_source_files),
("版本信息检查", test_version_info),
("城市映射功能测试", test_city_mapping)
]
passed = 0
total = len(tests)
for test_name, test_func in tests:
print(f"\n[{test_name}]")
if test_func():
passed += 1
print(f"{test_name} 通过")
else:
print(f"{test_name} 失败")
print(f"\n测试结果: {passed}/{total} 通过")
if passed == total:
print("\n✓ 所有测试通过! 版本准备就绪")
return True
else:
print(f"\n{total - passed} 个测试失败, 需要修复")
return False
def main():
"""主函数"""
success = create_test_report()
if success:
print("\n建议下一步:")
print("1. 运行应用进行手动测试")
print("2. 创建发布包")
else:
print("\n请先修复测试中发现的问题")
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())
Loading…
Cancel
Save