diff --git a/src/ui/word_style_ui.py b/src/ui/word_style_ui.py index b4e9938..39a4b9f 100644 --- a/src/ui/word_style_ui.py +++ b/src/ui/word_style_ui.py @@ -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): """获取多个城市的天气数据""" diff --git a/src/word_main_window.py b/src/word_main_window.py index c9dfe19..930cd59 100644 --- a/src/word_main_window.py +++ b/src/word_main_window.py @@ -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 = {