天气模块设计文档
概述
天气模块是一个显示郑州地区天气信息的小部件(站长身处郑州),集成在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调用
- 可以进一步实现本地存储缓存
错误处理
- 完善的错误边界处理
- 用户友好的错误提示和重试机制
评论区
评论加载中...