天气模块设计文档
概述
天气模块是一个显示郑州地区天气信息的小部件(站长身处郑州),集成在news页面的右侧栏中。该模块通过调用第三方天气API获取实时天气数据,并以美观的卡片形式展示出来。
技术架构
- 图标: Phosphor Icons
- 天气API: 和风天气API (openapi.weathercn.com)
实现步骤
第一步:创建天气组件 (Weather.vue)
文件路径: app/components/widget/Weather.vue
作用: 主要的天气显示组件,负责获取天气数据、处理数据格式化和渲染UI
核心代码结构:
<script setup lang="ts">
interface WeatherData {
Headline: {
EffectiveDate: string
Text: string
Category: string
}
DailyForecasts: Array<{
Date: string
Temperature: {
Minimum: { Value: number; Unit: string }
Maximum: { Value: number; Unit: string }
}
Day: { IconPhrase: string; HasPrecipitation: boolean }
Night: { IconPhrase: string; HasPrecipitation: boolean }
}>
}
const weatherData = ref<WeatherData | null>(null)
const loading = ref(true)
const error = ref<string | null>(null)
const fetchWeather = async () => {
try {
loading.value = true
error.value = null
const response = await fetch('https://openapi.weathercn.com/forecasts/v1/daily/1day/102675.json?apikey=******')
if (!response.ok) {
throw new Error('获取天气数据失败')
}
const data = await response.json()
weatherData.value = data
} catch (err) {
error.value = err instanceof Error ? err.message : '获取天气数据失败'
} finally {
loading.value = false
}
}
const formatTemperature = (temp: number) => `${temp}°C`
const getWeatherIcon = (text: string) => { /* 根据天气描述返回对应图标 */ }
const getWindIcon = (direction: string) => { /* 根据风向返回对应图标 */ }
onMounted(() => {
fetchWeather()
refreshInterval = setInterval(fetchWeather, 60 * 60 * 1000)
})
onUnmounted(() => {
if (refreshInterval) {
clearInterval(refreshInterval)
}
})
</script>
模板结构:
- 加载状态显示
- 错误状态处理
- 城市信息展示
- 今日天气概览(温度、天气图标、描述)
- 天气详情(白天/夜间天气、降水信息)
- 天气提示信息
- 数据更新时间
第二步:创建懒加载包装组件 (LazyWidgetWeather.vue)
文件路径: app/components/widget/LazyWidgetWeather.vue
作用: 使用Vue的defineAsyncComponent和Suspense实现组件的懒加载,提升页面性能
代码实现:
<script setup lang="ts">
defineAsyncComponent(() => import('./Weather.vue'))
</script>
<template>
<Suspense>
<template #default>
<Weather />
</template>
<template #fallback>
<div class="widget-placeholder">
<div class="placeholder-content">
<div class="placeholder-icon">🌤️</div>
<div class="placeholder-text">天气信息加载中...</div>
</div>
</div>
</template>
</Suspense>
</template>
设计优势:
- 减少初始页面加载时间
- 提供友好的加载状态提示
- 使用Suspense处理异步组件状态
第三步:注册组件到组件系统
文件路径: app/composables/useWidgets.ts
作用: 将天气组件注册到全局组件系统中,使其可以被动态调用
关键代码:
import { LazyWidgetWeather } from '#components'
const rawWidgets = {
LazyWidgetWeather,
}
export type WidgetName = RemovePrefix<KebabCase<RawWidgetName>, '-lazy-widget-'>
export default function useWidgets(widgetList: MaybeRefOrGetter<WidgetName[]>) {
const widgets = computed(() => (toValue(widgetList) || []).map(widget => ({
name: widget,
comp: rawWidgets[`LazyWidget${pascal(widget)}` as RawWidgetName],
})))
return { widgets }
}
命名转换规则:
LazyWidgetWeather→weather- 自动处理组件名称的kebab-case转换
第四步:配置布局状态管理
文件路径: app/stores/layout.ts
作用: 管理页面布局状态,包括侧边栏、右侧栏的开关状态和显示的小部件列表
核心功能:
export const useLayoutStore = defineStore('layout', () => {
const asideWidgets = ref<WidgetName[]>([])
const setAside = (widgets?: WidgetName[]) => {
if (widgets) {
asideWidgets.value = widgets ?? []
}
}
return {
asideWidgets,
setAside,
}
})
状态管理:
- 存储当前页面需要显示的右侧栏小部件列表
- 提供
setAside方法动态设置小部件
第五步:在页面中启用天气模块
文件路径: app/pages/news.vue
作用: 在news页面中配置并启用天气小部件
关键代码:
<script setup lang="ts"> const layoutStore = useLayoutStore() layoutStore.setAside(['weather']) </script>
实现逻辑:
- 页面加载时调用
layoutStore.setAside(['weather']) - 将
weather添加到右侧栏小部件列表中 - 触发右侧栏显示天气模块
第六步:渲染右侧栏组件
文件路径: app/components/ZAside.vue
作用: 根据布局状态动态渲染右侧栏中的小部件
核心实现:
<script setup lang="ts">
const layoutStore = useLayoutStore()
const { widgets } = useWidgets(() => layoutStore.asideWidgets)
</script>
<template>
<aside v-if="layoutStore.asideWidgets?.length" id="z-aside">
<TransitionGroup name="float-in">
<component
:is="widget.comp"
v-for="widget in widgets"
:key="widget.name"
/>
</TransitionGroup>
</aside>
</template>
渲染机制:
- 监听
layoutStore.asideWidgets的变化 - 使用
useWidgets组合式函数获取组件实例 - 动态渲染小部件组件
- 支持动画过渡效果
数据流程
1. news.vue 页面加载 ↓ 2. 调用 layoutStore.setAside(['weather']) ↓ 3. 更新 asideWidgets 状态 ↓ 4. ZAside.vue 监听到状态变化 ↓ 5. useWidgets 解析组件名称 ↓ 6. 渲染 LazyWidgetWeather 组件 ↓ 7. Weather.vue 组件挂载 ↓ 8. 调用天气API获取数据 ↓ 9. 渲染天气信息UI
样式设计特点
响应式布局
- 使用CSS Grid和Flexbox实现灵活布局
- 支持移动端和桌面端的不同显示效果
- 使用CSS变量实现主题切换
视觉层次
- 清晰的信息分组和间距
- 使用图标增强可读性
- 合理的颜色对比和字体大小
交互体验
- 加载状态和错误状态的友好提示
- 自动刷新机制(每小时更新一次)
- 重试按钮支持手动刷新
配置说明
天气API配置
- API地址:
https://openapi.weathercn.com/forecasts/v1/daily/1day/{cityCode}.json - 城市代码:
{cityCode}(郑州) - API密钥:
******(需要申请和风天气API密钥) - 数据格式: JSON格式的天气预报数据
组件配置
- 自动刷新间隔: 60分钟
- 默认城市: 郑州
- 温度单位: 摄氏度
- 语言: 中文
扩展性设计
支持多城市
可以通过修改城市代码和API参数来支持其他城市:
const cityCodes = {
'zhengzhou': '{cityCode1}',
'beijing': '{cityCode2}',
'shanghai': '{cityCode3}'
}
性能优化
懒加载
- 使用
defineAsyncComponent延迟加载天气组件 - 减少初始页面加载时间
缓存策略
- 每小时自动刷新一次,避免频繁API调用
- 可以进一步实现本地存储缓存
错误处理
- 完善的错误边界处理
- 用户友好的错误提示和重试机制

评论区
评论加载中...