每日天气成功

Horse861 4 months ago
parent d3281f6769
commit 20fcaaa8c6

@ -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 = {

Loading…
Cancel
Save