



























<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气卡片</title>
<style>
:root {
--bg: #0f172a;
--card: rgba(255, 255, 255, 0.06);
--text: #e2e8f0;
--sub: #94a3b8;
--accent: #38bdf8;
--green: #22c55e;
--radius: 14px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: var(--bg);
background-image: radial-gradient(ellipse at 30% 20%, rgba(56, 189, 248, .07) 0%, transparent 50%),
radial-gradient(ellipse at 70% 80%, rgba(139, 92, 246, .05) 0%, transparent 50%);
color: var(--text);
min-height: 100vh;
display: flex;
justify-content: center;
padding: 20px 14px;
}
.wrap {
width: 100%;
max-width: 420px;
display: flex;
flex-direction: column;
gap: 12px;
}
.top {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.top h1 {
font-size: 20px;
font-weight: 700;
}
.btns {
display: flex;
gap: 6px;
margin-left: auto;
}
.btns button {
padding: 7px 16px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, .1);
background: var(--card);
color: var(--sub);
font-size: 13px;
cursor: pointer;
backdrop-filter: blur(8px);
transition: all .2s;
}
.btns button.on {
background: rgba(56, 189, 248, .15);
border-color: var(--accent);
color: var(--accent);
font-weight: 700;
}
.refresh {
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, .1);
background: var(--card);
color: var(--text);
cursor: pointer;
font-size: 16px;
backdrop-filter: blur(8px);
}
.refresh:active {
transform: scale(.9);
}
.card {
background: var(--card);
border: 1px solid rgba(255, 255, 255, .08);
border-radius: var(--radius);
padding: 16px;
backdrop-filter: blur(12px);
}
.now-card {
text-align: center;
padding: 24px 16px 18px;
}
.now-icon {
font-size: 56px;
line-height: 1;
}
.now-temp {
font-size: 50px;
font-weight: 800;
letter-spacing: -2px;
}
.now-temp u {
font-size: 20px;
font-weight: 500;
text-decoration: none;
vertical-align: super;
}
.now-desc {
color: var(--sub);
margin: 4px 0 10px;
font-size: 14px;
}
.now-meta {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
font-size: 12px;
color: var(--sub);
}
.now-meta b {
color: var(--text);
font-weight: 600;
}
.update {
font-size: 10px;
color: #64748b;
margin-top: 8px;
}
.sec {
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
color: var(--sub);
margin-bottom: 8px;
}
.aqi-row {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.aqi-badge {
padding: 5px 12px;
border-radius: 999px;
font-size: 13px;
font-weight: 700;
background: rgba(34, 197, 94, .15);
color: var(--green);
}
.aqi-detail {
font-size: 11px;
color: var(--sub);
}
.hourly {
display: flex;
gap: 10px;
overflow-x: auto;
padding-bottom: 2px;
}
.hourly::-webkit-scrollbar {
height: 0;
}
.hr {
flex: 0 0 auto;
text-align: center;
min-width: 48px;
font-size: 11px;
color: var(--sub);
}
.hr .t {
font-weight: 600;
color: var(--text);
}
.hr .ic {
font-size: 22px;
margin: 2px 0;
}
.hr .tp {
font-weight: 700;
color: var(--text);
font-size: 13px;
}
.hr .rn {
color: #38bdf8;
font-size: 10px;
}
.day {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 2px;
font-size: 12px;
}
.day .dw {
width: 36px;
font-weight: 600;
flex-shrink: 0;
}
.day .di {
font-size: 22px;
width: 28px;
text-align: center;
flex-shrink: 0;
}
.day .dd {
flex: 1;
color: var(--sub);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.day .dr {
font-size: 10px;
color: #38bdf8;
width: 30px;
text-align: right;
flex-shrink: 0;
}
.day .dt {
font-weight: 600;
flex-shrink: 0;
}
.day .dt .lo {
color: var(--sub);
font-weight: 400;
margin-left: 2px;
}
.idx-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 6px;
}
.idx {
background: rgba(255, 255, 255, .03);
border-radius: 8px;
padding: 8px 10px;
font-size: 11px;
}
.idx .in {
font-weight: 600;
color: var(--text);
font-size: 12px;
}
.idx .is {
color: var(--accent);
font-weight: 600;
}
.idx .id {
color: var(--sub);
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.sun-row {
display: flex;
justify-content: center;
gap: 16px;
font-size: 13px;
color: var(--sub);
}
.loading {
text-align: center;
padding: 50px;
color: var(--sub);
}
.dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
margin: 0 3px;
animation: bounce 1.4s infinite ease-in-out both;
}
.dot:nth-child(1) {
animation-delay: -.32s;
}
.dot:nth-child(2) {
animation-delay: -.16s;
}
@keyframes bounce {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}
.err {
text-align: center;
padding: 18px;
color: #ef4444;
font-size: 13px;
}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<h1>天气卡片</h1>
<div class="btns">
<button data-code="01012601" class="on">上海</button>
<button data-code="01010101">北京</button>
</div>
<button class="refresh" id="refresh">🔄</button>
</div>
<div id="content">
<div class="loading">
<div class="dot"></div><div class="dot"></div><div class="dot"></div>
<p style="margin-top:8px;">加载中...</p>
</div>
</div>
</div>
<script>
(function() {
const KEY = '2132f238d61d1add4d0323f492c0f916';
const API = 'https://sh1pub.zuimeitianqi.com/pubDataServer/getweatherpub';
const ICON = { 1: '☀️', 2: '🌤️', 3: '⛅', 4: '🌥️', 5: '☁️', 6: '🌦️', 7: '⛅', 8: '☁️', 9: '🌫️', 10: '🌧️',
11: '🌧️', 12: '⛈️', 13: '🌨️', 14: '🌨️', 15: '❄️', 16: '❄️', 46: '🌧️', 47: '🌧️',
48: '🌧️', 75: '☁️' };
function ico(w) { return ICON[w] || '🌡️'; }
async function fetchW(code) {
const r = await fetch(`${API}?apikey=${KEY}&lon=&lat=&citycode=${code}`, {
headers: { 'Accept': 'application/json' }
});
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const j = await r.json();
if (j.resultcode !== '0') throw new Error(j.resultinfo || 'API error');
return j.data;
}
function render(d) {
const c = d.condition || {};
const dailys = (d.dailys && d.dailys.dailyweathers) || [];
const hrs = (d.hourlys && d.hourlys.hourlyweathers) || [];
const aqi = d.aqi || {};
const idxs = (d.additionalLiveInfos || []).slice(0, 10);
const tdy = dailys[0] || {};
const sr = (tdy.sunRiseFmt || '').match(/T(\d{2}:\d{2})/);
const ss = (tdy.sunSetFmt || '').match(/T(\d{2}:\d{2})/);
let h = '';
h += `<div class="card now-card">`;
h += `<div class="now-icon">${ico(c.weatherid)}</div>`;
h += `<div class="now-temp">${c.temperature??'--'}<u>℃</u></div>`;
h += `<div class="now-desc">${c.weathertext||''} · 体感 ${c.realfeel??'--'}℃</div>`;
h +=
`<div class="now-meta"><span>💧 湿度 <b>${c.humidity??'--'}%</b></span><span>🌬️ <b>${c.winddirtext||c.winddir||''} ${c.windlevel||''}级</b></span><span>👁️ <b>${c.visibility??'--'}km</b></span><span>🔽 <b>${c.pressure??'--'}hPa</b></span></div>`;
if (c.updatetimeFmt) h +=
`<div class="update">📡 ${c.updatetimeFmt.replace('T',' ').substring(0,16)}</div>`;
h += `</div>`;
if (aqi.aqivalue !== undefined) {
h +=
`<div class="card"><div class="sec">🍃 空气</div><div class="aqi-row"><span class="aqi-badge">AQI ${aqi.aqivalue} · ${aqi.aqivaluetext||'优'}</span><span class="aqi-detail">PM2.5:${aqi.pm25} PM10:${aqi.pm10} O₃:${aqi.o3} NO₂:${aqi.no2}</span></div></div>`;
}
if (sr && ss) {
h +=
`<div class="card"><div class="sun-row"><span>🌅 ${sr[1]}</span><span>🌇 ${ss[1]}</span></div></div>`;
}
if (hrs.length) {
h += `<div class="card"><div class="sec">⏰ 逐小时</div><div class="hourly">`;
for (const x of hrs.slice(0, 24)) {
const m = (x.dateFmt || '').match(/T(\d{2}):/);
const tl = m ? m[1] + ':00' : '';
const rn = x.precipitation > 0 ? `<div class="rn">${x.precipitation.toFixed(1)}mm</div>` :
`<div class="rn" style="color:#64748b;">·</div>`;
h +=
`<div class="hr"><div class="t">${tl}</div><div class="ic">${ico(x.weatherid)}</div><div class="tp">${x.temp}°</div>${rn}</div>`;
}
h += `</div></div>`;
}
if (dailys.length) {
h += `<div class="card"><div class="sec">📅 ${dailys.length}天</div>`;
const lbs = ['今天', '明天', '后天'];
for (let i = 0; i < Math.min(dailys.length, 8); i++) {
const dy = dailys[i];
const dd = dy.conditionDay || {};
const nn = dy.conditionNight || {};
const rt = parseFloat(dd.rain || 0) + parseFloat(nn.rain || 0);
const rs = rt > 0 ? `${rt.toFixed(1)}mm` : '';
const ds = (dy.publictimeFmt || '').substring(0, 10);
const ps = ds.split('-');
const sd = ps.length === 3 ? `${parseInt(ps[1])}/${parseInt(ps[2])}` : '';
let lb = lbs[i] || '';
if (!lb && ds) lb = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][new Date(
ds).getDay()];
h +=
`<div class="day"><div class="dw">${lb}<br><span style="font-size:10px;font-weight:400;color:var(--sub)">${sd}</span></div><div class="di">${ico(dd.weatherid)}</div><div class="dd">${dd.weathertext||''} / ${nn.weathertext||''}</div><div class="dr">${rs}</div><div class="dt">${dy.maxtemp}°<span class="lo">${dy.mintemp}°</span></div></div>`;
}
h += `</div>`;
}
if (idxs.length) {
h += `<div class="card"><div class="sec">📋 生活指数</div><div class="idx-grid">`;
const now = new Date();
for (const x of idxs) {
const td = (x.levelList || []).find(l => new Date(l.day).toDateString() === now
.toDateString()) || (x.levelList || [])[0];
if (!td) continue;
h +=
`<div class="idx"><div class="in">${x.name}</div><div class="is">${td.status||''}</div><div class="id">${td.content||''}</div></div>`;
}
h += `</div></div>`;
}
document.getElementById('content').innerHTML = h;
}
function showErr(msg) {
document.getElementById('content').innerHTML =
`<div class="card err">❌ ${msg}</div>`;
}
function showLoading() {
document.getElementById('content').innerHTML =
`<div class="loading"><div class="dot"></div><div class="dot"></div><div class="dot"></div><p style="margin-top:8px;">加载中...</p></div>`;
}
let curCode = '01012601';
async function load(code) {
showLoading();
try {
const d = await fetchW(code);
curCode = code;
render(d);
document.querySelectorAll('.btns button').forEach(b => {
b.classList.toggle('on', b.dataset.code === code);
});
} catch (e) {
showErr(e.message);
}
}
document.querySelectorAll('.btns button').forEach(b => {
b.addEventListener('click', () => load(b.dataset.code));
});
document.getElementById('refresh').addEventListener('click', () => load(curCode));
load('01012601');
})();
</script>
</body>
</html>
天气卡片里面没有天气,就像老婆饼里面没有老婆。这个的Demo已经接入了API,后续完善就靠大家了。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。