|
@@ -0,0 +1,154 @@
|
|
|
+<template>
|
|
|
+ <div class="max-w-md mx-auto p-6 bg-white rounded-xl shadow-lg">
|
|
|
+ <h2 class="text-2xl font-bold text-center mb-6">天气查询</h2>
|
|
|
+
|
|
|
+ <div class="mb-6">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ v-model="city"
|
|
|
+ placeholder="请输入城市名称"
|
|
|
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ @click="fetchWeather"
|
|
|
+ class="mt-3 w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors"
|
|
|
+ >
|
|
|
+ 查询天气
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="loading" class="text-center py-8">
|
|
|
+ <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
|
+ <p class="mt-2 text-gray-500">加载中...</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="error" class="text-center py-8 text-red-500">
|
|
|
+ {{ error }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="weatherData" class="space-y-4">
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <h3 class="text-xl font-bold">{{ weatherData.city }}, {{ weatherData.country }}</h3>
|
|
|
+ <span class="text-lg font-medium">{{ formatDate(weatherData.date) }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex items-center justify-center space-x-4 py-6">
|
|
|
+ <img
|
|
|
+ :src="getWeatherIcon(weatherData.conditionCode)"
|
|
|
+ alt="天气图标"
|
|
|
+ class="w-20 h-20 object-contain"
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <p class="text-4xl font-bold">{{ weatherData.temperature }}°C</p>
|
|
|
+ <p class="text-lg text-gray-600">{{ weatherData.description }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
+ <div class="bg-gray-50 p-3 rounded-lg">
|
|
|
+ <p class="text-sm text-gray-500">体感温度</p>
|
|
|
+ <p class="text-lg font-medium">{{ weatherData.feelsLike }}°C</p>
|
|
|
+ </div>
|
|
|
+ <div class="bg-gray-50 p-3 rounded-lg">
|
|
|
+ <p class="text-sm text-gray-500">湿度</p>
|
|
|
+ <p class="text-lg font-medium">{{ weatherData.humidity }}%</p>
|
|
|
+ </div>
|
|
|
+ <div class="bg-gray-50 p-3 rounded-lg">
|
|
|
+ <p class="text-sm text-gray-500">风速</p>
|
|
|
+ <p class="text-lg font-medium">{{ weatherData.windSpeed }} km/h</p>
|
|
|
+ </div>
|
|
|
+ <div class="bg-gray-50 p-3 rounded-lg">
|
|
|
+ <p class="text-sm text-gray-500">气压</p>
|
|
|
+ <p class="text-lg font-medium">{{ weatherData.pressure }} hPa</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, computed, onMounted } from 'vue'
|
|
|
+
|
|
|
+// 天气数据接口
|
|
|
+interface WeatherData {
|
|
|
+ city: string
|
|
|
+ country: string
|
|
|
+ date: Date
|
|
|
+ temperature: number
|
|
|
+ feelsLike: number
|
|
|
+ humidity: number
|
|
|
+ windSpeed: number
|
|
|
+ pressure: number
|
|
|
+ description: string
|
|
|
+ conditionCode: number
|
|
|
+}
|
|
|
+
|
|
|
+const city = ref('北京')
|
|
|
+const weatherData = ref<WeatherData | null>(null)
|
|
|
+const loading = ref(false)
|
|
|
+const error = ref('')
|
|
|
+
|
|
|
+// 模拟API请求
|
|
|
+const fetchWeather = async () => {
|
|
|
+ if (!city.value.trim()) {
|
|
|
+ error.value = '请输入城市名称'
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ error.value = ''
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 实际项目中应替换为真实API
|
|
|
+ const response = await simulateWeatherApi(city.value)
|
|
|
+ weatherData.value = response
|
|
|
+ } catch (err: any) {
|
|
|
+ error.value = err.message || '获取天气数据失败'
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 模拟API返回数据
|
|
|
+const simulateWeatherApi = async (city: string): Promise<WeatherData> => {
|
|
|
+ // 实际项目中应使用fetch或axios调用真实API
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ setTimeout(() => {
|
|
|
+ const mockData: WeatherData = {
|
|
|
+ city,
|
|
|
+ country: '中国',
|
|
|
+ date: new Date(),
|
|
|
+ temperature: Math.floor(Math.random() * 30) + 5,
|
|
|
+ feelsLike: Math.floor(Math.random() * 30) + 5,
|
|
|
+ humidity: Math.floor(Math.random() * 80) + 20,
|
|
|
+ windSpeed: Math.floor(Math.random() * 20) + 1,
|
|
|
+ pressure: Math.floor(Math.random() * 50) + 1000,
|
|
|
+ description: ['晴天', '多云', '阴天', '小雨', '中雨', '雷阵雨'][Math.floor(Math.random() * 6)],
|
|
|
+ conditionCode: Math.floor(Math.random() * 40) + 100
|
|
|
+ }
|
|
|
+ resolve(mockData)
|
|
|
+ }, 1000)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 获取天气图标URL
|
|
|
+const getWeatherIcon = (conditionCode: number) => {
|
|
|
+ // 实际项目中应根据天气状况代码返回对应的图标
|
|
|
+ return `https://picsum.photos/seed/${conditionCode}/100/100`
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期
|
|
|
+const formastDate = (date: Date) => {
|
|
|
+ const options: Intl.DateTimeFormatOptions = {
|
|
|
+ weekday: 'long',
|
|
|
+ month: 'long',
|
|
|
+ day: 'numeric'
|
|
|
+ }
|
|
|
+ return new Date(date).toLocaleDateString('zh-CN', options)
|
|
|
+}
|
|
|
+
|
|
|
+// 组件挂载后自动查询一次
|
|
|
+onMounted(() => {
|
|
|
+ fetchWeather()
|
|
|
+})
|
|
|
+</script>
|