weather.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <template>
  2. <div class="max-w-md mx-auto p-6 bg-white rounded-xl shadow-lg">
  3. <h2 class="text-2xl font-bold text-center mb-6">天气查询</h2>
  4. <div class="mb-6">
  5. <input
  6. type="text"
  7. v-model="city"
  8. placeholder="请输入城市名称"
  9. class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
  10. >
  11. <button
  12. @click="fetchWeather"
  13. class="mt-3 w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors"
  14. >
  15. 查询天气
  16. </button>
  17. </div>
  18. <div v-if="loading" class="text-center py-8">
  19. <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
  20. <p class="mt-2 text-gray-500">加载中...</p>
  21. </div>
  22. <div v-else-if="error" class="text-center py-8 text-red-500">
  23. {{ error }}
  24. </div>
  25. <div v-else-if="weatherData" class="space-y-4">
  26. <div class="flex justify-between items-center">
  27. <h3 class="text-xl font-bold">{{ weatherData.city }}, {{ weatherData.country }}</h3>
  28. <span class="text-lg font-medium">{{ formatDate(weatherData.date) }}</span>
  29. </div>
  30. <div class="flex items-center justify-center space-x-4 py-6">
  31. <img
  32. :src="getWeatherIcon(weatherData.conditionCode)"
  33. alt="天气图标"
  34. class="w-20 h-20 object-contain"
  35. >
  36. <div>
  37. <p class="text-4xl font-bold">{{ weatherData.temperature }}°C</p>
  38. <p class="text-lg text-gray-600">{{ weatherData.description }}</p>
  39. </div>
  40. </div>
  41. <div class="grid grid-cols-2 gap-4">
  42. <div class="bg-gray-50 p-3 rounded-lg">
  43. <p class="text-sm text-gray-500">体感温度</p>
  44. <p class="text-lg font-medium">{{ weatherData.feelsLike }}°C</p>
  45. </div>
  46. <div class="bg-gray-50 p-3 rounded-lg">
  47. <p class="text-sm text-gray-500">湿度</p>
  48. <p class="text-lg font-medium">{{ weatherData.humidity }}%</p>
  49. </div>
  50. <div class="bg-gray-50 p-3 rounded-lg">
  51. <p class="text-sm text-gray-500">风速</p>
  52. <p class="text-lg font-medium">{{ weatherData.windSpeed }} km/h</p>
  53. </div>
  54. <div class="bg-gray-50 p-3 rounded-lg">
  55. <p class="text-sm text-gray-500">气压</p>
  56. <p class="text-lg font-medium">{{ weatherData.pressure }} hPa</p>
  57. </div>
  58. </div>
  59. </div>
  60. </div>
  61. </template>
  62. <script lang="ts" setup>
  63. import { ref, computed, onMounted } from 'vue'
  64. // 天气数据接口
  65. interface WeatherData {
  66. city: string
  67. country: string
  68. date: Date
  69. temperature: number
  70. feelsLike: number
  71. humidity: number
  72. windSpeed: number
  73. pressure: number
  74. description: string
  75. conditionCode: number
  76. }
  77. const city = ref('北京')
  78. const weatherData = ref<WeatherData | null>(null)
  79. const loading = ref(false)
  80. const error = ref('')
  81. // 模拟API请求
  82. const fetchWeather = async () => {
  83. if (!city.value.trim()) {
  84. error.value = '请输入城市名称'
  85. return
  86. }
  87. loading.value = true
  88. error.value = ''
  89. try {
  90. // 实际项目中应替换为真实API
  91. const response = await simulateWeatherApi(city.value)
  92. weatherData.value = response
  93. } catch (err: any) {
  94. error.value = err.message || '获取天气数据失败'
  95. } finally {
  96. loading.value = false
  97. }
  98. }
  99. // 模拟API返回数据
  100. const simulateWeatherApi = async (city: string): Promise<WeatherData> => {
  101. // 实际项目中应使用fetch或axios调用真实API
  102. return new Promise((resolve) => {
  103. setTimeout(() => {
  104. const mockData: WeatherData = {
  105. city,
  106. country: '中国',
  107. date: new Date(),
  108. temperature: Math.floor(Math.random() * 30) + 5,
  109. feelsLike: Math.floor(Math.random() * 30) + 5,
  110. humidity: Math.floor(Math.random() * 80) + 20,
  111. windSpeed: Math.floor(Math.random() * 20) + 1,
  112. pressure: Math.floor(Math.random() * 50) + 1000,
  113. description: ['晴天', '多云', '阴天', '小雨', '中雨', '雷阵雨'][Math.floor(Math.random() * 6)],
  114. conditionCode: Math.floor(Math.random() * 40) + 100
  115. }
  116. resolve(mockData)
  117. }, 1000)
  118. })
  119. }
  120. // 获取天气图标URL
  121. const getWeatherIcon = (conditionCode: number) => {
  122. // 实际项目中应根据天气状况代码返回对应的图标
  123. return `https://picsum.photos/seed/${conditionCode}/100/100`
  124. }
  125. // 格式化日期
  126. const formastDate = (date: Date) => {
  127. const options: Intl.DateTimeFormatOptions = {
  128. weekday: 'long',
  129. month: 'long',
  130. day: 'numeric'
  131. }
  132. return new Date(date).toLocaleDateString('zh-CN', options)
  133. }
  134. // 组件挂载后自动查询一次
  135. onMounted(() => {
  136. fetchWeather()
  137. })
  138. </script>