惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
MyScale Blog
MyScale Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
爱范儿
爱范儿
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
Last Week in AI
Last Week in AI
罗磊的独立博客
G
Google Developers Blog
Y
Y Combinator Blog
博客园 - 【当耐特】
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
博客园 - 叶小钗
J
Java Code Geeks
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
美团技术团队
宝玉的分享
宝玉的分享
Jina AI
Jina AI
小众软件
小众软件
T
Tailwind CSS Blog
A
About on SuperTechFans

LINUX DO - 最新话题

谷歌云盘下载700g数据集,求方法 OpenAI推出了100美元的Pro订阅后,plus的Codex 5小时限额大幅缩水 之前买的super grok居然还没掉 关于CPA认证文件周限 佬们,默认CDK的要求是什么等级啊? 最新版本的微信群聊机器人方案 有没有人知道如何free号没有封,那么是否可以循环使用,因为我看主要是周限 L站改版了?吓我一跳,我以为我浏览器崩了 淘宝这种宽带可信吗,500兆移动宽带月费8元到2099年 docker内部应用访问宿主机mysql和redis时被拒绝connection refuse Erp全栈想转行做Ai有什么推荐的吗 boost有bug 佬们,有没有靠谱点的 Plus 购买渠道 大妈,狗妈用的 lg 服务有源头开源项目吗? 有人有能过验证码打码的嘛 上次帖里好像发过通过大模型来打码的 gpt plus 封号似乎也太快了点,一天就给封号了 按流量/token收费的国产官方AI推荐 我算是知道了为什么Oracle总是ABC了 佬友们帮我分析一下 ChatGPT Team账号只有一个人使用和4个席位邀请满了使用的总额度是一样的吗? gpt-free 10个带rt CPA反代claude是默认1m吗? 我终于敢说我做出来windows上tmux的替代了,目标windows/全平台最强的终端Ai编程工具 claude pro升级max,除了原来的$20,好像还能再领一次$100 关于AI agent的知识框架 独乐乐不如众乐乐,分享一下我的的AI对话程序 佬们自建网站支付问题是怎么解决的 怎么能让gpt模仿claude风格输出 codex free已经死了,下一个会是plus或者team吗 请问chatgpt pro里的fast模式,速度快了,降智吗
天气卡片(这次是有真的天气)
F-droid (𝓕-𝓓𝓻𝓸𝓲𝓭) · 2026-06-22 · via LINUX DO - 最新话题
<!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,后续完善就靠大家了。