前端2026年7月29日
一个漂亮日期组件
久流
给大家分享一个漂亮的日期组件
主要是使用了和风天气的api,首先新建一个 noAuthAxios.js 用来存放api, 确保不会被其他属性影响。
javascript
// 天气apiimport axios from 'axios'
const noAuthAxios = axios.create({ baseURL: 'https://devapi.qweather.com', // 可设置第三方API基础路径 timeout: 8000})
export default noAuthAxios根据和风天气的图标说明,天气状况分为白天和夜晚,每种天气状况都有对应的图标代码。以下是常见的天气状况及其图标分配:
| 天气状况 | 图标代码(白天) | 图标代码(夜晚) | 图标类型 |
|---|---|---|---|
| 晴 | 100 | 150 | sunny |
| 多云 | 101 | 151 | cloudy |
| 少云 | 102 | 152 | cloudy |
| 晴间多云 | 103 | 153 | cloudy |
| 阴 | 104 | - | cloudy |
| 阵雨 | 300 | 350 | stormy |
| 强阵雨 | 301 | 351 | stormy |
| 雷阵雨 | 302 | - | stormy |
| 强雷阵雨 | 303 | - | stormy |
| 雷阵雨伴有冰雹 | 304 | - | stormy |
| 小雨 | 305 | - | stormy |
| 中雨 | 306 | - | stormy |
| 大雨 | 307 | - | stormy |
| 极端降雨 | 308 | - | stormy |
| 毛毛雨/细雨 | 309 | - | stormy |
| 暴雨 | 310 | - | stormy |
| 大暴雨 | 311 | - | stormy |
| 特大暴雨 | 312 | - | stormy |
| 冻雨 | 313 | - | stormy |
| 小到中雨 | 314 | - | stormy |
| 中到大雨 | 315 | - | stormy |
| 大到暴雨 | 316 | - | stormy |
| 暴雨到大暴雨 | 317 | - | stormy |
| 大暴雨到特大暴雨 | 318 | - | stormy |
| 小雪 | 400 | - | snowy |
| 中雪 | 401 | - | snowy |
| 大雪 | 402 | - | snowy |
| 暴雪 | 403 | - | snowy |
| 雨夹雪 | 404 | - | snowy |
| 雨雪天气 | 405 | - | snowy |
| 阵雨夹雪 | 406 | - | snowy |
| 阵雪 | 407 | - | snowy |
| 小到中雪 | 408 | - | snowy |
| 中到大雪 | 409 | - | snowy |
| 大到暴雪 | 410 | - | snowy |
| 薄雾 | 500 | - | default |
| 雾 | 501 | - | default |
| 霾 | 502 | - | default |
| 扬沙 | 503 | - | default |
| 浮尘 | 504 | - | default |
| 沙尘暴 | 507 | - | default |
| 强沙尘暴 | 508 | - | default |
| 浓雾 | 509 | - | default |
| 强浓雾 | 510 | - | default |
| 中度霾 | 511 | - | default |
| 重度霾 | 512 | - | default |
| 严重霾 | 513 | - | default |
| 大雾 | 514 | - | default |
| 特强浓雾 | 515 | - | default |
| 热 | 900 | - | default |
| 冷 | 901 | - | default |
| 未知 | 999 | - | default |
Vue组件代码具体如下:
模板部分
- 根据
weatherIcon的值动态渲染对应的天气图标(sunny、cloudy、snowy、stormy、supermoon)。 - 显示加载状态或错误信息。
JS部分
temperature:从weatherData中提取温度。weatherStatus:从weatherData中提取天气描述。weatherIcon:根据天气描述和时间(夜晚时强制显示supermoon)返回对应的图标类型。isNight:判断当前时间是否为夜晚(18:00 到 06:00)。fetchWeather:调用 API 获取天气数据并更新weatherData。
vue
<template> <div class="h-full w-min justify-center"> <div v-if="loading">加载中...</div> <div v-else-if="error" class="error">{{ error }}</div> <div v-else> <div v-if="weatherData" class="text-nowrap flex"> <!-- 根据天气状况显示图标 --> <div v-if="weatherIcon === 'sunny'" icon="sunny"> <span class="sun"></span> </div> <div v-else-if="weatherIcon === 'cloudy'" icon="cloudy"> <span class="cloud"></span> <span class="cloud"></span> </div> <div v-else-if="weatherIcon === 'snowy'" icon="snowy"> <span class="snowman"></span> <ul> <li></li><li></li><li></li><li></li><li></li><li></li> <li></li><li></li><li></li><li></li><li></li><li></li><li></li> </ul> </div> <div v-else-if="weatherIcon === 'stormy'" icon="stormy"> <span class="cloud"></span> <ul> <li></li><li></li><li></li><li></li><li></li> </ul> </div> <div v-else-if="weatherIcon === 'supermoon'" icon="supermoon"> <span class="moon"></span> <span class="meteor"></span> </div> </div> </div> </div></template>
<script setup>import { ref, computed, onMounted } from 'vue'import axios from 'axios'import noAuthAxios from '../utils/noAuthAxios'
const API_KEY = '' // 替换为你的和风天气API Keyconst city = ref('101120201') // 默认城市编码const weatherData = ref(null)const loading = ref(false)const error = ref('')
// 计算属性const temperature = computed(() => { return weatherData.value?.realtime?.temp?.toFixed(1) || '--'})
const weatherStatus = computed(() => { return '天气:' + weatherData.value?.realtime?.weather || '--'})
const weatherClass = computed(() => { if (!weatherData.value) return {}; const temp = parseFloat(temperature.value) || 0; return { 'winter': temp < 0, 'spring': temp >= 5 && temp <= 15, 'warm': temp > 16 }})
// 判断是否为夜晚const isNight = () => { const now = new Date(); const hours = now.getHours(); return hours >= 18 || hours < 6; // 18:00 到 06:00 为夜晚}
// 根据天气状况返回对应的图标const weatherIcon = computed(() => { if (isNight()) return 'supermoon'; // 夜晚强制显示 supermoon const weather = weatherStatus.value.toLowerCase(); if (weather.includes('晴') || weather.includes('热') || weather.includes('冷') || weather.includes('未知')) return 'sunny'; if (weather.includes('薄雾') || weather.includes('雾') || weather.includes('霾') || weather.includes('扬沙') || weather.includes('浮尘') || weather.includes('沙尘暴') || weather.includes('强沙尘暴') || weather.includes('浓雾') || weather.includes('强浓雾') || weather.includes('中度霾') || weather.includes('重度霾') || weather.includes('严重霾') || weather.includes('大雾') || weather.includes('特强浓雾') || weather.includes('多云') || weather.includes('少云') || weather.includes('晴间多云') || weather.includes('阴')) return 'cloudy'; if (weather.includes('雪') || weather.includes('雨夹雪') || weather.includes('雨雪天气') || weather.includes('阵雪') || weather.includes('阵雨夹雪')) return 'snowy'; if (weather.includes('雨') || weather.includes('雷') || weather.includes('阵雨') || weather.includes('强阵雨') || weather.includes('雷阵雨') || weather.includes('强雷阵雨') || weather.includes('雷阵雨伴有冰雹') || weather.includes('小雨') || weather.includes('中雨') || weather.includes('大雨') || weather.includes('极端降雨') || weather.includes('毛毛雨/细雨') || weather.includes('暴雨') || weather.includes('大暴雨') || weather.includes('特大暴雨') || weather.includes('冻雨') || weather.includes('小到中雨') || weather.includes('中到大雨') || weather.includes('大到暴雨') || weather.includes('暴雨到大暴雨') || weather.includes('大暴雨到特大暴雨')) return 'stormy'; return 'default'; // 默认图标});
const fetchWeather = async () => { try { // 参数校验增强 if (!/^\d{9}$/.test(city.value)) { throw new Error('请输入9位城市编码(LocationID)'); } const response = await noAuthAxios.get('/v7/weather/now', { params: { location: city.value, key: API_KEY } });
// 强化错误处理 if (response.data.code !== '200') { throw new Error(`[${response.data.code}]${response.data.message}`); }
// 数据结构重构 weatherData.value = { city: response.data?.location?.name || '未知城市', realtime: { temp: parseFloat(response.data?.now?.temp), weather: response.data?.now?.text } }; } catch (err) { console.error(err); error.value = `请求失败:${err.message}`; }};
onMounted(fetchWeather)</script>
<style>:root { --shadow: #fd6f21 !important; --ring: rgb(232, 232, 232) !important; --blend1: #fc5830 !important; --blend2: #f98c24 !important; --blend-from: 0% !important; --blend-to: 100% !important; --blend-dir: top right !important;}
[icon] { flex: none !important; display: none !important; position: relative !important; font-size: calc(10em + 1vmin) !important; width: 1em !important; height: 1em !important; margin: .3em !important; border-radius: 100% !important; box-shadow: 0 0 0 .05em var(--ring) inset, 0 0 .3em -.03em var(--shadow) !important; background: linear-gradient(to var(--blend-dir), var(--blend1) var(--blend-from), var(--blend2) var(--blend-to)) !important;}
[icon]::after { content: attr(data-label) !important; position: absolute !important; top: calc(100% + 1vmin) !important; left: 50% !important; transform: translateX(-50%) !important; font: inherit !important; font-size: .15em !important;}
[icon='sunny'] { --shadow: #fd6f21 !important; --blend1: #fc5830 !important; --blend2: #f98c24 !important; --blend-to: 65% !important;}
[icon='cloudy'] { --shadow: #1378bb !important; --blend1: #1b9ce2 !important; --blend2: #1378bb !important; --blend-to: 65% !important;}
[icon='snowy'] { --shadow: #c9e8de !important; --blend1: #758595 !important; --blend2: #e0e2e5 !important; --blend-to: 90% !important; --blend-dir: bottom left !important;}
[icon='stormy'] { --shadow: #34c6d8 !important; --blend1: #4b9cc2 !important; --blend2: #9adbd9 !important;}
[icon='supermoon'] { --shadow: #5133a5 !important; --blend1: #4054b2 !important; --blend2: #aa4cba !important; --blend-to: 65% !important; --blend-dir: bottom right !important;}
/* SUNNY */.sun { position: absolute !important; top: 20% !important; left: 80% !important; transform: translate(-50%, -50%) !important; width: 40% !important; height: 40% !important; border-radius: 100% !important; background: #ffeb3b !important; box-shadow: 0 0 0 .02em var(--ring) inset, 0 0 .3em -.03em var(--shadow) !important; transform-origin: .1em .1em !important;}
.sun::after { content: '' !important; position: absolute !important; top: .1em !important; left: 0 !important; will-change: transform !important; transform: translate(-50%, -50%) !important; width: .1em !important; height: .1em !important; border-radius: 100% !important; background: rgba(255, 255, 255, .1) !important; box-shadow: 0 0 .1em 0 rgba(255, 255, 255, .3) inset, -.1em -.1em 0 .2em rgba(255, 255, 255, .1) !important; animation: flare 12000ms infinite alternate linear !important;}
/* CLOUDY */.cloud { position: absolute; top: .1em; left: 65%; width: .37em; height: .13em; border-radius: .1em; background-color: #fff !important; box-shadow: 0 0 .1em .02em var(--ring) inset, 0 0 .3em -.03em var(--shadow); animation: move 3000ms infinite ease-in-out;}
.cloud+.cloud { top: 25%; left: 40%; animation: move 3700ms infinite linear;}
.cloud::before,.cloud::after { content: ''; position: inherit; border-radius: inherit; background-color: inherit; box-shadow: inherit; bottom: 30%;}
.cloud::before { left: .05em; width: .2em; height: .2em;}
.cloud::after { left: .15em; width: .15em; height: .15em;}
/* SNOWY */[icon='snowy'] ul { position: absolute; list-style: none; top: 0%; left: 10%; right: 0%; height: 100%; margin: 0; padding: 0;}
[icon='snowy'] li::before,[icon='snowy'] li::after { content: '' !important; position: absolute !important; list-style: none; width: .015em; height: .01em; border-radius: 100%; background-color: var(--ring) !important; will-change: transform, opacity; animation: snow 3700ms infinite ease-out !important; opacity: 0;}
[icon='snowy'] li:nth-child(2n+1)::before,[icon='snowy'] li:nth-child(13n+11)::after { top: -7%; left: 40%;}
[icon='snowy'] li:nth-child(3n+2)::before,[icon='snowy'] li:nth-child(11n+7)::after { top: 5%; left: 90%; animation-delay: 1000ms;}
[icon='snowy'] li:nth-child(5n+3)::before,[icon='snowy'] li:nth-child(7n+5)::after { top: -10%; left: 80%; animation-delay: 2000ms;}
[icon='snowy'] li:nth-child(7n+5)::before,[icon='snowy'] li:nth-child(5n+3)::after { top: 10%; left: 10%; animation-delay: 1300ms;}
[icon='snowy'] li:nth-child(11n+7)::before,[icon='snowy'] li:nth-child(3n+2)::after { top: 20%; left: 70%; animation-delay: 1500ms;}
[icon='snowy'] li:nth-child(13n+11)::before,[icon='snowy'] li:nth-child(2n+1)::after { top: 35%; left: 20%; animation-delay: 500ms;}
.snowman { position: absolute; bottom: 30%; left: 40%; width: .15em; height: .15em; opacity: .9; background: var(--ring) !important; border-radius: 100%;}
.snowman::after { content: ''; position: absolute; top: 90%; left: 30%; transform: translate(-50%, 0%); width: .275em; height: .3em; border-radius: inherit; background-color: var(--ring) !important;}
.snowman::before { content: ''; position: absolute; top: 0%; left: 50%; transform: translate(-55%, -50%); width: .45em; height: .4em; border-radius: 60%; border: .02em solid transparent; border-bottom-color: var(--blend1); will-change: border-radius; animation: snowman 9000ms infinite ease-in;}
/* STORMY */[icon='stormy']::before { --shadow: rgba(255, 255, 255, 0) !important; content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: .05em; border-radius: 100%; opacity: .4; will-change: background-color; animation: flash 2300ms infinite linear 80ms;}
[icon='stormy'] .cloud { --shadow: #c9e8de !important; --ring: #f0f2f0 !important; background-color: var(--shadow) !important; font-size: 1.3em; left: 50%; will-change: background-color, transform, opacity; animation: flash 2300ms infinite linear, move 3700ms infinite linear;}
[icon='stormy'] ul { position: absolute; list-style: none; top: 0%; left: 70%; right: 0%; height: 100%; margin: 0; padding: 0;}
[icon='stormy'] li,[icon='stormy'] li::before,[icon='stormy'] li::after { position: absolute; width: .005em; height: .02em; border-radius: 10%; background-color: #eee !important; opacity: 0; will-change: transform, opacity; animation: rain 2000ms infinite linear; transform: rotate(25deg);}
[icon='stormy'] li::before,[icon='stormy'] li::after { content: '';}
[icon='stormy'] li:nth-child(5n+3)::before,[icon='stormy'] li:nth-child(11n+7)::after,[icon='stormy'] li:nth-child(2n+1) { top: 10%; left: 68%; animation-delay: 500ms;}
[icon='stormy'] li:nth-child(3n+2)::after,[icon='stormy'] li:nth-child(7n+5)::after,[icon='stormy'] li:nth-child(3n+2) { top: 5%; left: 45%; animation-delay: 1250ms;}
[icon='stormy'] li:nth-child(2n+1)::before,[icon='stormy'] li:nth-child(5n+3)::after,[icon='stormy'] li:nth-child(7n+5) { top: 4%; left: 82%; animation-delay: 750ms;}
[icon='stormy'] li:nth-child(11n+7)::before,[icon='stormy'] li:nth-child(3n+2)::after,[icon='stormy'] li:nth-child(7n+5) { top: 15%; left: 15%; animation-delay: 2000ms;}
[icon='stormy'] li:nth-child(7n+5)::before,[icon='stormy'] li:nth-child(2n+1)::after,[icon='stormy'] li:nth-child(11n+7) { top: 10%; left: 33%; animation-delay: 2500ms;}
/* SUPERMOON */[icon="supermoon"]::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-image: radial-gradient(1px 1px at 50% 20%, #fff, rgba(0, 0, 0, 0)), radial-gradient(1px 1px at 30% 65%, #fff, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 15% 05%, #fff, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 37% 35%, #fff, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 65% 47%, #fff, rgba(0, 0, 0, 0)), radial-gradient(1px 1px at 42% 29%, #fff, rgba(0, 0, 0, 0)), radial-gradient(1px 1px at 73% 56%, #fff, rgba(0, 0, 0, 0)), radial-gradient(1px 1px at 24% 19%, #fff, rgba(0, 0, 0, 0)), radial-gradient(2px 2px at 31% 47%, #fff, rgba(0, 0, 0, 0)), radial-gradient(1px 1px at 18% 39%, #fff, rgba(0, 0, 0, 0)) !important; background-repeat: repeat; will-change: transform; animation: revolve 120000ms linear infinite;}
.moon { position: absolute; top: 20%; left: 80%; transform: translate(-50%, -50%); width: 40%; height: 40%; border-radius: 100%; background: radial-gradient(circle at bottom left, var(--ring), #fef07e) !important; box-shadow: 0 0 0 .02em var(--ring) inset, 0 0 .3em -.03em var(--blend2) !important;}
.moon::before,.moon::after { content: ''; position: absolute; border-radius: 100%; background-color: var(--blend1); box-shadow: .01em .01em .1em 0 var(--blend1);}
.moon::before { top: 15%; left: 55%; width: 20%; height: 20%; opacity: .3;}
.moon::after { bottom: 50%; left: 25%; width: 15%; height: 15%; opacity: .2;}
.meteor { position: absolute; background-color: #fff !important; opacity: 0; top: 20%; left: 55%; width: 1px; height: 15px; transform: rotate(45deg); will-change: transform, opacity; animation: meteor 6250ms infinite ease-in;}
[icon="sunny"],[icon="cloudy"],[icon="snowy"],[icon="stormy"],[icon="supermoon"] { display: block !important;}
@keyframes flare { to { transform: translate(-.3em, .3em); opacity: .4; font-size: .2em; }}
@keyframes move { 50% { transform: translateX(-.05em); }}
@keyframes snow { 50% { opacity: 1; } 100% { transform: translate(-.1em, 15vmin); }}
@keyframes snowman { 50% { border-radius: 60% 60% 30% 50% !important; }}
@keyframes flash { 49% { background-color: var(--shadow) !important; } 51% { background-color: var(--ring) !important; } 53% { background-color: var(--shadow) !important; } 57% { background-color: var(--ring) !important; } 85% { background-color: var(--shadow) !important; }}
@keyframes rain { 10% { opacity: .4; } 50% { opacity: 1; } 100% { transform: translate(-.1em, .5em) !important; }}
@keyframes revolve { to { transform: rotate(360deg) !important; }}
@keyframes meteor { 5% { opacity: 1; } 8% { transform: translate(-.6em, .6em) rotate(45deg) !important; opacity: 0; }}</style>评论
0 个评论在此篇文章
暂无评论