基于Vue的天气应用开发:组件化、数据流与HBuilder部署
2026/9/13 17:53:36 网站建设 项目流程

简介:基于Html、JavaScript、Vue与CSS构建的天气应用设计源码,面向前端初中级开发者和计算机相关专业学生,定位为一个可直接运行的完整实战项目。它演示了如何从零搭建具备实时天气查询、城市切换、响应式界面等能力的Web应用,内容覆盖组件化开发、前后端数据联调与项目工程化配置等关键环节。资源包共33个文件,包括9个JavaScript脚本、6个Vue组件、4个JSON配置、3个CSS样式表、2个HTML模板以及SVG图标、字体文件和说明文档;其中JavaScript主要用于数据请求与用户交互,Vue组件实现界面模块化复用,CSS负责视觉样式与响应式布局,JSON与YAML配置管理路由、接口等工程参数,压缩包整体仅145KB,目录按stores、router、apis、views等模块划分,结构清晰。目前已有450人浏览学习。通过研读全部源码,可掌握Vue组件通信、前端路由配置、状态管理、异步请求封装及CSS美化等实用技能;项目自带工程化配置、字体图标与说明文档,特别适合作为课程设计、毕业设计或独立开发天气类应用时的参考蓝本。

1. 天气应用源码真正要读的不是“天气”,是组件划分

“基于Html+JavaScript+Vue+CSS的天气应用设计源码”这个标题,乍看是给学生交作业用的前端练手项目,但把关键词拆开之后会发现另一层价值:Html负责语义骨架、CSS管视觉还原、JavaScript处理原生交互、Vue把数据与视图绑定起来。四者凑齐,恰好组成一套完整的现代前端技术栈切片。这个项目大部分从业者30分钟就能写完,但它真正值得看的是组件边界怎么切、状态放在哪一层、CSS作用域怎么控制。新手照着敲一遍能理解数据流,老手则能借它快速搭出一个演示级的面板类应用,换个接口就能变成监控页、报表页或设备状态页。下文按框架搭建、数据接入、交互配置、部署验证的顺序展开,最终落到一个有用的调试技巧上。

2. 先立框架:Vue 组件树与 HTML/CSS 的承载关系

2.1 为什么选 Vue 而不是原生 JavaScript

天气应用的核心需求是“根据城市 ID 拿数据,然后更新页面上的温度、湿度、天气图标和未来预报”。用原生 JavaScript 写也能实现,核心逻辑就是fetch后操作 DOM,但代码会随功能增多快速膨胀,尤其是“多城市切换”和“实时刷新”这两个并发状态出现时,原生写法需要手动维护 DOM 状态与数据状态的一致性,非常容易漏更新。

Vue 解决的是“视图与数据同步”的问题。

v-for渲染 7 天预报,用v-if控制加载状态,用computed推导“今天最高温最低温”,这些都是模板语法层面的声明式操作。组件化把页面切成WeatherCard.vueForecastList.vueCitySearch.vue三个独立文件,各管各的 props 和 emit,互相之间不直接操作 DOM。对于维护一个小型前端应用来说,这是最省心的选型。

2.2 页面骨架里 HTML 和 CSS 各自负责什么

Vue 文件本身是一个单文件组件,内部包含<template><script><style>三个模块,但这不代表 HTML 和 CSS “消失了”——<template>编译后就是标准 HTML,<style>在构建阶段会被抽出成 CSS 文件。这个项目的源码里所谓“源码”,通常就是指一组.vue文件、一个index.html、一个main.js入口和vite.config.js配置。

一个典型的目录结构如下:

weather-app/ ├── index.html ├── package.json ├── vite.config.js └── src/ ├── main.js ├── App.vue ├── api/ │ └── weather.js ├── components/ │ ├── WeatherCard.vue │ ├── ForecastList.vue │ └── CitySearch.vue └── styles/ └── global.css

index.html只保留一个挂载点,main.js负责创建 Vue 实例,App.vue作为根组件承载整体布局。所有组件的<style scoped>会被编译成带有>npm create vue@latest weather-app

这个命令会进入交互式问答,选择是否使用 TypeScript、Vue Router、Pinia、ESLint。对于纯前端练手项目,推荐的选择是:TypeScript 选 No、Vue Router 选 No、Pinia 选 No、ESLint 选 Yes。

回答完进入项目并安装依赖:

cd weather-app npm install npm run dev

npm run dev启动开发服务器,默认端口通常是5173。在浏览器打开http://localhost:5173会看到 Vue 官方的欢迎页,此时框架已经跑通。这个步骤是整个标题里“Vue”字样的落地前提。

提示:如果只想看最小可运行的按钮,不想要 ESLint 和 Prettier,直接在脚手架问答中全部选 No,生成的代码量更少,适合快速验证思路。

3. 数据流打通:天气 API 接入与 Vue 组件传值渲染

3.1 天气数据从哪来

天气应用必须有数据源。免费方案中最常见的是使用和风天气或 OpenWeatherMap 的开发者 API,前者需要注册后获取一个 API Key,后者同样需要 Key 但文档和字段更直观。本项目源码中通常使用axios或原生fetch请求当前天气与逐日预报两个接口,再把返回数据交给 Vue 组件渲染。

以下是src/api/weather.js中常见的封装方式:

import axios from 'axios' // 创建 axios 实例,统一设置基础路径 const request = axios.create({ baseURL: 'https://api.openweathermap.org/data/2.5', timeout: 10000 // 超过 10 秒请求中断,避免长时间 loading }) // 查询当前天气 export function getCurrentWeather(city) { return request.get('/weather', { params: { q: city, // 城市名,如 'Beijing' appid: 'YOUR_API_KEY', // 换成你自己的密钥 units: 'metric', // 使用摄氏度,而不是华氏度 lang: 'zh_cn' // 返回中文描述 } }) } // 查询未来 7 天预报 export function getForecast(city) { return request.get('/forecast/daily', { params: { q: city, appid: 'YOUR_API_KEY', cnt: 7, // 7 天的数据 units: 'metric', lang: 'zh_cn' } }) }

axios.create的作用是生成一个带默认配置的请求实例,后续所有请求都共用同一个 baseURL 和 timeout,不需要在每个接口函数里重复写完整域名。“units=metric”是温度单位的关键参数,缺了它返回的将是开尔文温度,页面上会出现二三百度的怪数值。“lang=zh_cn”控制天气描述语言,不传时默认为英文。

3.2 App.vue 里如何协调子组件

天气应用的核心交互流程是“在城市搜索框输入城市名,拿到数据后把当前天气和预报分别传给两个子组件”。这个流程在 Vue 中属于典型的“父组件负责请求,子组件负责展示”模式。App.vue持有城市名、当前天气数据、预报数据和加载状态这四个响应式变量,数据请求成功后通过 props 分发给子组件。

核心逻辑如下:

<script setup> import { ref } from 'vue' import { getCurrentWeather, getForecast } from './api/weather' import WeatherCard from './components/WeatherCard.vue' import ForecastList from './components/ForecastList.vue' import CitySearch from './components/CitySearch.vue' const city = ref('Beijing') const currentWeather = ref(null) const forecastList = ref([]) const loading = ref(false) async function fetchWeather(cityName) { loading.value = true try { const [currentRes, forecastRes] = await Promise.all([ getCurrentWeather(cityName), getForecast(cityName) ]) currentWeather.value = currentRes.data forecastList.value = forecastRes.data.list } catch (error) { console.error('天气数据请求失败:', error) currentWeather.value = null forecastList.value = [] } finally { loading.value = false } } function handleSearch(val) { city.value = val fetchWeather(val) } fetchWeather(city.value) </script> <template> <div class="weather-app"> <CitySearch :city="city" @search="handleSearch" /> <div v-if="loading" class="loading-message">正在加载天气数据...</div> <WeatherCard v-if="currentWeather" :data="currentWeather" /> <ForecastList v-if="forecastList.length" :list="forecastList" /> </div> </template>

这段代码有四个关键点。第一,Promise.all并行请求当前天气和预报数据,总耗时取决于较慢的接口,而不是两者之和。第二,v-if="currentWeather"确保子组件在数据到达之前不渲染,避免空对象访问字段导致报错。第三,@search监听子组件派发的事件,在 Vue 3 的<script setup>语法中不需要注册emits选项,子组件用defineEmits(['search'])声明即可。第四,finally块确保请求成功或失败都会退出加载状态,否则失败后页面会一直停留在 loading 画面。

3.3 子组件接收 props 并格式化输出

WeatherCard.vue是页面上最显眼的卡片,通常展示城市名、当前温度、天气描述和最高最低温。这个组件内部需要做两件事:接收父组件传下来的data对象,再把数据格式化成可读的文案。

示例代码如下:

<script setup> import { computed } from 'vue' const props = defineProps({ data: { type: Object, required: true } }) // 温度四舍五入为整数,去掉小数位 const temp = computed(() => Math.round(props.data.main.temp)) const maxTemp = computed(() => Math.round(props.data.main.temp_max)) const minTemp = computed(() => Math.round(props.data.main.temp_min)) const weatherDesc = computed(() => props.data.weather[0].description) </script> <template> <div class="weather-card"> <h2 class="city-name">{{ data.name }}</h2> <p class="temperature">{{ temp }}°C</p> <p class="description">{{ weatherDesc }}</p> <p class="temp-range">最高 {{ maxTemp }}°C / 最低 {{ minTemp }}°C</p> </div> </template> <style scoped> .weather-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; color: #ffffff; padding: 24px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); } .city-name { font-size: 24px; margin: 0 0 8px; } .temperature { font-size: 64px; font-weight: 200; margin: 0; } </style>

computed在这里的好处是:父组件传入的data对象更新时,所有派生值自动重算,不需要额外写 setter。Math.round让温度显示更干净,符合大多数天气产品的 UI 习惯。样式方面使用渐变背景和浅色文字,效果接近主流天气应用卡片,这是 CSS 在组件内作用域的典型用法。

4. 交互落地:CSS 布局、城市切换与 HBuilder 部署

4.1 城市搜索组件的本地交互

城市搜索是这个应用里最有交互属性的部分。用户输入城市名后点击搜索或者按回车,组件把值抛给父组件,父组件再触发新的数据请求。子组件内部还需要做防抖处理,避免每敲一个字母就发一次请求。

CitySearch.vue实现如下:

<script setup> import { ref } from 'vue' const emit = defineEmits(['search']) const keyword = ref('') const timer = ref(null) function onSearch() { const cityName = keyword.value.trim() if (!cityName) return emit('search', cityName) } </script> <template> <div class="city-search"> <input v-model="keyword" class="search-input" type="text" placeholder="输入城市名,例如 Beijing" /> <button class="search-btn" @click="onSearch">查询</button> </div> </template>

这里没有加防抖逻辑,是因为“查询”按钮本身就是一次显式的用户动作。如果后续改成输入时自动搜索,就需要用watch(keyword)配合 300ms 的定时器,这是防抖的常见写法。emit('search', cityName)把数据传给父组件,父组件在模板里通过@search="handleSearch"接收。

4.2 用 localStorage 记住上次搜索的城市

天气应用的一个高频需求是“刷新后保留上次查询的城市”。这个需求不需要后端支持,浏览器自带的localStorage就能解决。在App.vue中对city变量做持久化处理:

<script setup> import { ref, watch } from 'vue' const city = ref(localStorage.getItem('lastCity') || 'Beijing') watch(city, (newCity) => { localStorage.setItem('lastCity', newCity) }) </script>

localStorage.getItem('lastCity')在初始化阶段读取缓存,watch在 city 变化时把新值写回。写回操作是同步的,对 tiny 应用完全够用。如果需要存储更复杂的数据结构,比如用户最近搜索的 5 个城市,可以改用JSON.stringify存入字符串数组,读取时再用JSON.parse还原。

注意:localStorage是同步操作,每次写入都会阻塞主线程,但对于“城市名字符串”这种 KB 级数据完全感受不到。不要用它存天气接口的返回数据,因为缓存过期策略需要自己实现,复杂度不值得。

4.3 CSS 布局的常见写法与关键参数

天气应用通常是一个居中的窄卡片布局,外层背景可以使用整屏渐变色或者动态背景图。布局上推荐 flex 而不是 grid,因为组件数量少、层级浅,flex 一行代码就能实现垂直居中。

全局样式styles/global.css典型内容:

* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; background: linear-gradient(180deg, #74b9ff 0%, #a29bfe 100%); min-height: 100vh; display: flex; justify-content: center; align-items: flex-start; padding: 40px 16px; } #app { width: 100%; max-width: 420px; }

box-sizing: border-box让元素宽度包含内边距,避免width: 100%padding后容器超出父级。min-height: 100vh确保渐变背景铺满整个视口,即使页面内容不足一屏也不会在底部露出白边。max-width: 420px让卡片在桌面浏览器里维持适合阅读的宽度,在手机上则占满屏幕。

4.4 用 HBuilder 运行与打包 Vue 项目

热搜词里大量出现“HBuilder 配置 html、css、javascript”,说明不少读者打算用 HBuilder 跑这个项目。HBuilder 主要用于 uni-app 开发,但它同样可以直接管理普通的前端项目,只是不承担 Vue 项目的编译任务——编译和构建仍然交给 Vite 完成。

常用操作流程如下:

  1. 在 HBuilder 中点击“文件 – 打开目录”,选择项目根目录
  2. 打开内置终端,输入npm run dev
  3. 点击终端输出的本地地址,在浏览器中查看结果

HBuilder 本质上是一个编辑器加集成终端,npm run dev启动的 Vite 开发服务器才是真正干活的进程。若要在 HBuilder 中调试,需要提前安装 Node.js 并配置好 npm 镜像,否则执行 npm 命令会报 “node 不是内部或外部命令”。

生产环境的构建命令是:

npm run build

执行后会在项目根目录生成dist/文件夹,里面是编译压缩后的静态文件,可直接部署到任意静态服务器。

4.5 部署时的资源路径问题

npm run build生成的 HTML 默认使用绝对路径/assets/xxx.js。如果把dist/目录放在服务器子目录下,比如https://example.com/weather-app/,资源路径会变成https://example.com/assets/xxx.js,导致 404。

解决办法是在vite.config.js中设置 base:

export default { base: './' }

'./'是相对路径,构建后的 HTML 会引用./assets/xxx.js,这样放在任何子目录下都能正常工作。这是部署阶段最容易踩的一个坑,标题里的“CSS”相关资源也会因为路径错误一并加载失败,表现是页面空白且控制台报大量Failed to load resource

5. 进阶技巧:用 Vue DevTools 验证数据绑定与调试组件边界

这个项目本身功能简单,真正的价值在于调试过程和代码组织方式。遇到“组件没渲染”“数据没更新”的问题时,不要一上来就加console.log,先打开 Vue DevTools 看组件树和数据状态。

5.1 安装与检查范围

Vue DevTools 是浏览器扩展,支持 Chrome 和 Edge 系的浏览器。安装后在地址栏旁边出现 Vue 图标。点击后会显示项目里的组件树,左侧是组件结构,右侧是 props、data、computed 的实时值。它比 console 更适合排查“数据更新但视图没变”一类的问题。

打开开发者工具,切换 Vue 面板,展开App.vue节点,能看到citycurrentWeatherforecastList三个变量。如果currentWeathernull,说明请求没成功,问题在 API 层;如果currentWeather有值但页面不显示,问题在模板层;如果数据更新了但页面还是老值,大概率是v-if的判断条件写反了。

组件边界问题在 DevTools 里也会暴露出常见的错误写法。有些初学者把WeatherCard里的data属性名写成props,然后模板里用data.name,实际应该用props.data.name。DevTools 中会看到该组件显示 “Undeclared props” 或者展开后没有对应字段,此时回到代码检查 defineProps 里的声明即可。

5.2 非响应式数据的处理技巧

天气接口返回的某些字段可能不适合直接展示,比如日出日落时间是用 Unix 时间戳表示的。在组件内部不需要新增一个 computed 属性,可以在computed里直接转换:

<script setup> import { computed } from 'vue' const props = defineProps({ data: Object }) // 把 Unix 时间戳转成 HH:MM 格式 const sunriseTime = computed(() => { const date = new Date(props.data.sys.sunrise * 1000) return date.toTimeString().slice(0, 5) }) </script>

这里* 1000是因为 OpenWeatherMap 返回的sys.sunrise是秒级时间戳,而 JavaScript 的Date需要毫秒级。toTimeString().slice(0, 5)截取 “HH:MM” 格式,避免展示完整的时区信息。

如果在调试这个项目之后想扩展功能,比较顺理成章的方向是增加“最近搜索城市”的历史列表,把城市名数组存入localStorage,并在 CitySearch 下方用v-for渲染标签。再进一步可以接入高德地图的 IP 定位接口,进入页面时自动定位到当前城市,省掉手动输入的步骤。这些改动都能保持现有组件结构不变,只需要增加一个请求函数和一个小列表组件,框定在原有四件套范畴之内。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询