使用 Electron 构建天气桌面小软件:调用公开 API 实现跨平台实时天气查询V1.0.0

在这里插入图片描述

在这里插入图片描述

前言

摘要:本文将带你从零开始,使用 Electron + 免费天气 API(Open-Meteo)构建一个轻量级、跨平台的桌面天气小工具。项目支持自动定位、城市搜索、7 天预报,并具备系统托盘常驻、低资源占用等桌面应用特性,适合初学者掌握 Electron 网络请求、本地存储与 UI 交互开发。


一、为什么选择 Electron 做天气工具?

  • 跨平台:一套代码运行于 Windows / macOS / Linux
  • Web 技术栈:用熟悉的 HTML/CSS/JS 快速开发
  • 系统集成:可常驻托盘、支持通知、访问剪贴板
  • 离线可用:缓存上次查询结果,弱网也能看

相比网页版天气,桌面端能提供更沉浸、更低干扰的体验。


二、技术选型

模块选型说明
主框架Electron 28+最新稳定版
天气 APIOpen-Meteo免费、无 Key、支持全球坐标
定位服务navigator.geolocation浏览器原生 API(Electron 支持)
数据存储localStorage轻量级,保存最近城市
UI 样式Tailwind CSS CDN快速美化界面

Open-Meteo 示例请求:
https://api.open-meteo.com/v1/forecast?latitude=39.9&longitude=116.4&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=Asia/Shanghai


三、项目结构

weather-desktop/
├── main.js                # 主进程:窗口 + 托盘管理
├── index.html             # 渲染进程:UI 界面
├── renderer.js            # 前端逻辑:定位、API 调用、渲染
├── styles.css             # 自定义样式(或使用 Tailwind)
├── package.json
└── README.md

四、完整代码实现

1. package.json

{
"name": "weather-desktop",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^28.0.0"
}
}

2. main.js —— 主进程(创建窗口 + 托盘)

const { app, BrowserWindow, Tray, Menu, nativeImage } = require('electron');
const path = require('path');
let mainWindow;
let tray = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 400,
height: 500,
resizable: false,
webPreferences: {
contextIsolation: true
}
});
mainWindow.loadFile('index.html');
// 开发时打开 DevTools
// mainWindow.webContents.openDevTools();
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// 创建系统托盘
function createTray() {
const iconPath = path.join(__dirname, 'icon.png'); // 可选:准备一个天气图标
tray = new Tray(nativeImage.createFromPath(iconPath) || nativeImage.createEmpty());
const contextMenu = Menu.buildFromTemplate([
{ label: '显示', click: () => mainWindow.show() },
{ label: '退出', click: () => app.quit() }
]);
tray.setToolTip('天气小工具');
tray.setContextMenu(contextMenu);
tray.on('click', () => mainWindow.show());
}
app.whenReady().then(() => {
createWindow();
createTray(); // 启动托盘
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

注:若无 icon.png,托盘将显示空白图标,不影响功能。


3. index.html —— UI 界面

<!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>天气小工具</title>
    <script src="https://cdn.tailwindcss.com"></script>
      <style>
        body { background: linear-gradient(to bottom, #74b9ff, #0984e3); color: white; }
        .weather-icon { font-size: 3rem; margin: 10px 0; }
      </style>
    </head>
      <body class="font-sans">
        <div class="container mx-auto px-4 py-6 max-w-md">
      <h1 class="text-2xl font-bold text-center mb-4">️ 天气小工具</h1>
        <!-- 城市输入 -->
            <div class="flex mb-4">
              <input type="text" id="cityInput" placeholder="输入城市名(如:北京)"
              class="flex-1 px-3 py-2 rounded-l focus:outline-none text-gray-800">
              <button id="searchBtn" class="bg-white text-blue-600 px-4 py-2 rounded-r font-bold">
              搜索
            </button>
          </div>
          <!-- 当前天气 -->
              <div id="currentWeather" class="text-center hidden">
            <div id="location" class="text-xl font-bold"></div>
            <div class="weather-icon" id="weatherIcon">☀️</div>
            <div id="temp" class="text-4xl font-bold"></div>
            <div id="description" class="opacity-90"></div>
            </div>
            <!-- 7天预报 -->
                <div id="forecast" class="mt-6 hidden">
              <h2 class="text-lg font-semibold mb-2">7 天预报</h2>
              <div id="forecastList" class="space-y-2"></div>
              </div>
              <!-- 加载/错误提示 -->
              <div id="status" class="text-center mt-4"></div>
              </div>
            <script src="renderer.js"></script>
            </body>
          </html>

4. renderer.js —— 核心逻辑

// 天气代码映射(来自 Open-Meteo 文档)
const WEATHER_CODES = {
0: '☀️', 1: '️', 2: '⛅', 3: '☁️',
45: '️', 48: '️',
51: '️', 53: '️', 55: '️',
61: '️', 63: '️', 65: '⛈️',
71: '❄️', 73: '️', 75: '❄️', 77: '️',
80: '️', 81: '️', 82: '⛈️',
85: '️', 86: '️',
95: '⛈️', 96: '⛈️', 99: '⛈️'
};
// 获取地理编码(城市 → 坐标)
async function getCoordinates(city) {
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=zh&format=json`;
const res = await fetch(url);
const data = await res.json();
if (data.results && data.results.length > 0) {
return data.results[0];
}
throw new Error('城市未找到');
}
// 获取天气数据
async function fetchWeather(lat, lon, timezone = 'Asia/Shanghai') {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=${timezone}&forecast_days=7`;
const res = await fetch(url);
return await res.json();
}
// 渲染天气
function renderWeather(data, locationName) {
const { daily } = data;
const today = {
temp: Math.round((daily.temperature_2m_max[0] + daily.temperature_2m_min[0]) / 2),
code: daily.weathercode[0],
desc: getWeatherDescription(daily.weathercode[0])
};
document.getElementById('location').textContent = locationName;
document.getElementById('temp').textContent = `${today.temp}°C`;
document.getElementById('weatherIcon').textContent = WEATHER_CODES[today.code] || '❓';
document.getElementById('description').textContent = today.desc;
// 7天预报
let forecastHTML = '';
for (let i = 0; i < 7; i++) {
const date = new Date(daily.time[i]).toLocaleDateString('zh-CN', { weekday: 'short' });
const min = Math.round(daily.temperature_2m_min[i]);
const max = Math.round(daily.temperature_2m_max[i]);
const icon = WEATHER_CODES[daily.weathercode[i]] || '❓';
forecastHTML += `
<div class="flex justify-between items-center bg-white/20 px-3 py-2 rounded">
<span>${date}</span>
<span>${icon} ${min}°/${max}°</span>
</div>
`;
}
document.getElementById('forecastList').innerHTML = forecastHTML;
document.getElementById('currentWeather').classList.remove('hidden');
document.getElementById('forecast').classList.remove('hidden');
}
function getWeatherDescription(code) {
if ([0, 1, 2, 3].includes(code)) return '晴或多云';
if ([51, 53, 55, 61, 63, 65, 80, 81, 82].includes(code)) return '降雨';
if ([71, 73, 75, 77, 85, 86].includes(code)) return '降雪';
if ([95, 96, 99].includes(code)) return '雷暴';
return '未知';
}
// 显示状态
function showStatus(msg, isError = false) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = `text-center mt-4 ${isError ? 'text-red-300' : 'text-yellow-200'}`;
}
// 搜索按钮
document.getElementById('searchBtn').addEventListener('click', async () => {
const city = document.getElementById('cityInput').value.trim();
if (!city) return showStatus('请输入城市名');
try {
showStatus('正在查询...');
const loc = await getCoordinates(city);
const weather = await fetchWeather(loc.latitude, loc.longitude, loc.timezone);
renderWeather(weather, loc.name);
// 保存到 localStorage
localStorage.setItem('lastCity', city);
} catch (err) {
console.error(err);
showStatus('城市未找到或网络错误', true);
}
});
// 回车搜索
document.getElementById('cityInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') document.getElementById('searchBtn').click();
});
// 页面加载时尝试自动定位
window.addEventListener('load', async () => {
// 优先使用上次城市
const lastCity = localStorage.getItem('lastCity');
if (lastCity) {
document.getElementById('cityInput').value = lastCity;
document.getElementById('searchBtn').click();
return;
}
// 尝试自动定位
if (navigator.geolocation) {
showStatus('正在获取位置...');
navigator.geolocation.getCurrentPosition(
async (pos) => {
try {
const weather = await fetchWeather(pos.coords.latitude, pos.coords.longitude);
// 反查城市名(简化:用坐标代替)
renderWeather(weather, '当前位置');
} catch (err) {
showStatus('定位成功,但天气获取失败', true);
}
},
() => {
showStatus('请手动输入城市');
}
);
} else {
showStatus('浏览器不支持定位,请手动输入城市');
}
});

五、运行与打包

开发运行

npm install
npm start

打包为可执行文件(可选)

npm install -g electron-packager
electron-packager . WeatherApp --platform=win32 --arch=x64 --out=dist

测试

网页端
在这里插入图片描述

真机端

在这里插入图片描述

六、功能亮点

功能说明
自动定位首次启动自动获取当前位置天气
城市搜索支持中文城市名模糊匹配
7 天预报直观展示未来一周气温趋势
本地缓存记住上次查询城市,提升体验
系统托盘关闭窗口后仍可从托盘唤出
免费 API无需注册 Key,无调用限制

七、扩展建议

  • 添加“刷新”按钮
  • 支持多城市切换(标签页)
  • 集成系统通知(高温/降雨提醒)
  • 自定义主题(浅色/深色模式)
  • 导出天气报告为图片

八、结语

通过这个项目,你不仅学会了如何用 Electron 调用网络 API,还掌握了定位、本地存储、UI 交互、系统集成等关键技能。更重要的是,你拥有了一个真正实用的桌面工具!

代码即产品,创造即价值

posted @ 2025-12-27 08:35  gccbuaa  阅读(68)  评论(0)    收藏  举报