切换城市功能

pull/123/head
Maziang 5 months ago
parent 38f73a359c
commit b34f40ff57

@ -16,7 +16,8 @@ from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QMessageBox, QSplitter, QTabWidget, QStatusBar,
QToolBar, QPushButton, QLabel, QFrame, QApplication,
QDesktopWidget, QStyleFactory, QStyle, QGroupBox, QProgressBar,
QScrollArea, QTextBrowser, QFontComboBox, QComboBox, QColorDialog)
QScrollArea, QTextBrowser, QFontComboBox, QComboBox, QColorDialog,
QDialog, QLineEdit, QFormLayout, QListWidget, QHBoxLayout)
from PyQt5.QtCore import (Qt, QTimer, pyqtSignal, QThread, QObject, QUrl,
QSettings, QPoint, QSize, QEvent, QPropertyAnimation,
QEasingCurve, QRect)
@ -369,12 +370,14 @@ class MarkTextSideBar(QWidget):
tools_layout.setSpacing(6)
self.weather_btn = QPushButton("🌤 天气信息")
self.select_city_btn = QPushButton("🏙 选择城市")
self.quote_btn = QPushButton("📖 每日名言")
self.insert_weather_btn = QPushButton("🌈 插入天气")
self.insert_quote_btn = QPushButton("✨ 插入名言")
self.snake_game_btn = QPushButton("🐍 贪吃蛇游戏")
tools_layout.addWidget(self.weather_btn)
tools_layout.addWidget(self.select_city_btn)
tools_layout.addWidget(self.quote_btn)
tools_layout.addWidget(self.insert_weather_btn)
tools_layout.addWidget(self.insert_quote_btn)
@ -397,6 +400,7 @@ class MarkTextSideBar(QWidget):
self.import_file_btn.clicked.connect(self.parent.import_file)
self.weather_btn.clicked.connect(self.parent.show_weather_info)
self.select_city_btn.clicked.connect(self.parent.select_city_and_refresh_weather)
self.quote_btn.clicked.connect(self.parent.show_quote_info)
self.insert_weather_btn.clicked.connect(self.parent.insert_weather_to_editor)
self.insert_quote_btn.clicked.connect(self.parent.insert_quote_to_editor)
@ -1457,18 +1461,25 @@ class MarkTextMainWindow(QMainWindow):
weather_info = self.network_service.get_weather_info()
if weather_info:
# 基础天气信息
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"
weather_text = f"🌤 {weather_info['city']} 天气详情\n\n"
weather_text += f"温度: {weather_info['temperature']}°C\n"
weather_text += f"天气: {weather_info['description']}\n"
weather_text += f"湿度: {weather_info['humidity']}%\n"
weather_text += f"风速: {weather_info['wind_speed']}m/s\n"
# 添加生活提示
lifetips = weather_info.get('lifetips', [])
if lifetips:
weather_text += "\n\n🌟 生活提示:"
weather_text += "\n🌟 生活提示:\n"
for tip in lifetips:
weather_text += f"\n{tip}"
weather_text += f"{tip}\n"
# 添加未来天气预报(如果有)
forecast = weather_info.get('forecast', [])
if forecast and len(forecast) > 0:
weather_text += f"\n📅 未来预报:\n"
for day in forecast[:3]: # 显示未来3天
weather_text += f"{day['date']}: {day['high']}/{day['low']}, {day['type']}\n"
QMessageBox.information(self, "天气详细信息", weather_text)
else:
@ -1581,6 +1592,76 @@ class MarkTextMainWindow(QMainWindow):
except Exception as e:
QMessageBox.critical(self, "错误", f"无法打开贪吃蛇游戏: {str(e)}")
def select_city_and_refresh_weather(self):
"""选择城市并刷新天气信息"""
if not self.network_service:
QMessageBox.warning(self, "提示", "网络服务正在初始化中,请稍后再试")
return
# 城市列表(沿用现有的城市映射)
cities = [
"北京", "上海", "广州", "深圳", "杭州", "南京", "成都", "武汉",
"西安", "重庆", "天津", "苏州", "青岛", "大连", "沈阳", "哈尔滨",
"长春", "石家庄", "太原", "郑州", "济南", "合肥", "南昌", "长沙",
"福州", "厦门", "南宁", "海口", "贵阳", "昆明", "拉萨", "兰州",
"西宁", "银川", "乌鲁木齐", "呼和浩特"
]
# 创建选择对话框
dialog = QDialog(self)
dialog.setWindowTitle("选择城市")
dialog.setFixedSize(300, 400)
layout = QVBoxLayout()
# 标题
title_label = QLabel("选择城市查看天气")
title_label.setStyleSheet("font-size: 16px; font-weight: bold; margin-bottom: 10px;")
layout.addWidget(title_label)
# 城市列表
city_list = QListWidget()
city_list.addItems(cities)
city_list.setCurrentRow(0) # 默认选中第一个
layout.addWidget(city_list)
# 按钮组
button_layout = QHBoxLayout()
auto_locate_btn = QPushButton("自动定位")
select_btn = QPushButton("选择")
cancel_btn = QPushButton("取消")
button_layout.addWidget(auto_locate_btn)
button_layout.addWidget(select_btn)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
dialog.setLayout(layout)
# 连接信号
def on_auto_locate():
dialog.accept()
self.refresh_weather_info() # 使用自动定位
def on_select():
current_item = city_list.currentItem()
if current_item:
selected_city = current_item.text()
dialog.accept()
self.refresh_weather_info_by_city(selected_city)
def on_cancel():
dialog.reject()
auto_locate_btn.clicked.connect(on_auto_locate)
select_btn.clicked.connect(on_select)
cancel_btn.clicked.connect(on_cancel)
# 显示对话框
dialog.exec_()
def insert_quote_to_editor(self):
@ -1625,6 +1706,42 @@ class MarkTextMainWindow(QMainWindow):
QMessageBox.warning(self, "提示", "无法刷新天气信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新天气信息失败: {str(e)}")
def refresh_weather_info_by_city(self, city_name):
"""根据城市名刷新天气信息"""
if not self.network_service:
QMessageBox.warning(self, "提示", "网络服务正在初始化中,请稍后再试")
return
try:
# 清除缓存并获取新城市的天气
self.network_service.clear_weather_cache()
weather_data = self.network_service.get_weather_info_by_city(city_name)
if weather_data and weather_data.get('city'):
# 更新底部信息栏显示
self.update_info_display()
self.statusBar().showMessage(f"已切换到 {city_name} 的天气信息", 3000)
else:
QMessageBox.warning(self, "提示", f"无法获取 {city_name} 的天气信息,请检查城市名称或网络连接")
except Exception as e:
QMessageBox.critical(self, "错误", f"获取天气信息时发生错误: {str(e)}")
def refresh_weather_info(self):
"""刷新当前天气信息(使用自动定位)"""
if not self.network_service:
QMessageBox.warning(self, "提示", "网络服务正在初始化中,请稍后再试")
return
try:
# 清除缓存并重新获取天气
self.network_service.clear_weather_cache()
self.show_weather_info()
QMessageBox.information(self, "成功", "已使用自动定位刷新天气信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"刷新天气信息时发生错误: {str(e)}")
def export_as_format(self, editor, format_type: str, dialog):
"""导出为指定格式"""

@ -90,6 +90,13 @@ class NetworkService:
"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]]:
"""获取天气信息,支持缓存机制"""
@ -98,9 +105,27 @@ class NetworkService:
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定位
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}")
@ -135,147 +160,172 @@ class NetworkService:
"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()
# 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"
# 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)
# 如果找到城市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)
# 如果找到城市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": "重庆"
}
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}")
print(f"天气API返回错误状态: {weather_data.get('status')}")
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": 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 # 生活提示列表
}
print(f"成功获取天气数据: {formatted_weather}")
# 缓存天气数据
self.set_weather_cache(formatted_weather, self._cached_location)
return formatted_weather
else:
print(f"天气API返回错误状态: {weather_data.get('status')}")
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
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]:

Loading…
Cancel
Save