本站架站紀錄_第四篇
新增今日天氣、歷史上的今天、留言區。
加入今日天氣
看到別人有加我也想說加個,給自己的blog加點小東西
這邊我們使用Free Weather API
可以自己選擇地點顯示當地天氣
新增一個layout/_widget/weather.ejs因為東西要放在側邊欄
- weather.ejs然後是美化的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33<div layout/_widget/weather.ejs
class="kira-widget-wrap card-weather" data-lat="25.0375" data-lon="121.5637" data-city="台北">
<h3 class="kira-widget-title">
<i class="kirafont icon-weather"></i>
台北 · 今日天氣
</h3>
<div class="weather-body">
<div class="w-top">
<div class="w-left">
<div class="w-temp"><span id="w-now">--</span><span class="unit">°C</span></div>
<div class="w-hilo">H <span id="w-max">--</span>° · L <span id="w-min">--</span>°</div>
</div>
<div class="w-right">
<div id="w-icon" class="w-icon">⛅</div>
<div id="w-desc" class="w-desc">--</div>
<div id="w-time" class="w-time">--:--</div>
</div>
</div>
<!-- 兩條長膠囊:降雨、濕度 -->
<div class="w-grid chips-wide">
<div class="chip">
<span class="label">降雨</span>
<span class="value"><span id="w-pop">--</span><span class="unit">%</span></span>
</div>
<div class="chip">
<span class="label">濕度</span>
<span class="value"><span id="w-rh">--</span><span class="unit">%</span></span>
</div>
</div>
</div>
</div> source/weather-card.css1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33.kira-right-column .kira-widget-wrap.card-weather{ width:100%; }
/* 玻璃感卡片容器 */
.card-weather .weather-body{
border-radius:16px; background: linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.04));
border:1px solid var(--card-border, rgba(255,255,255,.18)); box-shadow:0 10px 22px rgba(0,0,0,.06);
padding:14px 16px;
}
/* 頂部:左大字溫度、右側圖示/描述/時間 */
.card-weather .w-top{ display:flex; align-items:center; justify-content:space-between; gap:12px; }
.card-weather .w-left{ display:flex; flex-direction:column; gap:4px; }
.card-weather .w-temp{ font-weight:900; line-height:1; letter-spacing:.5px; }
.card-weather .w-temp #w-now{ font-size:36px; }
.card-weather .w-temp .unit{ font-size:16px; opacity:.85; margin-left:2px; }
.card-weather .w-hilo{ opacity:.9 }
.card-weather .w-right{ display:flex; flex-direction:column; align-items:flex-end; gap:2px; text-align:right; }
.card-weather .w-icon{ font-size:28px; line-height:1; }
.card-weather .w-desc{ font-size:.95rem; opacity:.95; }
.card-weather .w-time{ font-size:.85rem; opacity:.7 }
/* 兩條長膠囊(單欄) */
.card-weather .w-grid.chips-wide{ display:grid; grid-template-columns: 1fr; row-gap:8px; margin-top:12px; }
.card-weather .chip{ display:flex; align-items:center; justify-content:space-between; gap:12px;
border-radius:12px; padding:10px 12px; background: rgba(255,255,255,.10);
border:1px solid var(--card-border, rgba(255,255,255,.18)); font-size:.95rem; line-height:1; }
.card-weather .chip .label{ font-weight:600; letter-spacing:.3px; white-space:nowrap; opacity:.85; }
.card-weather .chip .value{ font-weight:800; font-variant-numeric: tabular-nums; white-space:nowrap; display:inline-flex; align-items:baseline; gap:4px; }
@media (prefers-color-scheme: light){
.card-weather .weather-body{ background:#fff; border-color:rgba(0,0,0,.08); box-shadow:0 10px 22px rgba(0,0,0,.05); }
.card-weather .chip{ background:#f7f9fb; border-color:rgba(0,0,0,.06); }
}
最後是獲取天氣資料的腳本
- source/js/weather-card.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51(function () {
const box = document.querySelector('.kira-widget-wrap.card-weather');
if (!box) return;
const lat = parseFloat(box.dataset.lat || '25.0375');
const lon = parseFloat(box.dataset.lon || '121.5637');
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}`
+ `¤t=temperature_2m,precipitation,relative_humidity_2m,weather_code,wind_speed_10m`
+ `&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,sunrise,sunset`
+ `&timezone=Asia%2FTaipei`;
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
const codeMap = (code, isNight) => {
const sun='☀️', moon='🌙', cloud='☁️', pcloud='⛅', rain='🌧️', lrain='🌦️', tstorm='⛈️', snow='❄️', fog='🌫️';
const txt = {0:'晴朗',1:'多雲時晴',2:'多雲',3:'陰',45:'霧',48:'霧凇',51:'毛毛雨',53:'小雨',55:'中雨',56:'凍雨(毛毛雨)',57:'凍雨',61:'小雨',63:'中雨',65:'大雨',66:'凍雨',67:'強凍雨',71:'小雪',73:'中雪',75:'大雪',77:'霰',80:'陣雨',81:'強陣雨',82:'暴陣雨',85:'陣雪',86:'強陣雪',95:'雷雨',96:'雷雨雹',99:'劇烈雷雨雹'};
const emoji = code===0 ? (isNight?moon:sun) : [1,2].includes(code)?pcloud : code===3?cloud :
[51,53,55,61,63,65,80,81,82].includes(code) ? (code===51?lrain:rain) :
[95,96,99].includes(code) ? tstorm : [71,73,75,77,85,86].includes(code)?snow : [45,48].includes(code)?fog : cloud;
return { emoji, text: txt[code] || '—' };
};
fetch(url).then(r=>r.json()).then(d=>{
const nowISO = d?.current?.time || new Date().toISOString();
const now = new Date(nowISO);
const sunrise = new Date(d?.daily?.sunrise?.[0] || now);
const sunset = new Date(d?.daily?.sunset?.[0] || now);
const isNight = now < sunrise || now > sunset;
const nowT = Math.round(d?.current?.temperature_2m ?? NaN);
const rh = d?.current?.relative_humidity_2m ?? null;
const wcode= d?.current?.weather_code ?? 3;
const maxT = Math.round(d?.daily?.temperature_2m_max?.[0] ?? NaN);
const minT = Math.round(d?.daily?.temperature_2m_min?.[0] ?? NaN);
const pop = d?.daily?.precipitation_probability_max?.[0];
if (Number.isFinite(nowT)) set('w-now', nowT);
if (Number.isFinite(maxT)) set('w-max', maxT);
if (Number.isFinite(minT)) set('w-min', minT);
if (typeof pop === 'number') set('w-pop', pop);
if (typeof rh === 'number') set('w-rh', rh);
const meta = codeMap(wcode, isNight);
set('w-icon', meta.emoji); set('w-desc', meta.text);
set('w-time', now.toLocaleTimeString('zh-TW',{hour:'2-digit',minute:'2-digit'}));
}).catch(()=>{
box.querySelector('.weather-body').innerHTML = '<div style="opacity:.85">天氣資料載入失敗,稍後再試</div>';
});
})();
最後一樣,記得載入到header。
1 | |
歷史上的今天
在這邊我們使用維基百科
除了歷史上的今天外你也可以改成顯示在維基上你有興趣的東西
修改側邊欄
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<div class="kira-widget-wrap card-history">
<h3 class="kira-widget-title">
<i class="kirafont icon-time"></i>
歷史上的今天
</h3>
<!-- 只固定內容區高度,外層不要鎖高避免切到標題 -->
<div class="history-card">
<div class="swiper" id="history-container" aria-live="off">
<div class="swiper-wrapper" id="history_container_wrapper"></div>
</div>
<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>
</div>
</div>修改樣式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42:root{
--history-h: 132px; /* 內容區高度:120~160 皆可 */
--history-lines: 3; /* 每則顯示行數 */
}
.kira-right-column .kira-widget-wrap.card-history{ width:100%; }
/* 標題列沿用主題色,這裡只控間距與圖示大小 */
.kira-widget-wrap.card-history .kira-widget-title{
display:flex; align-items:center; gap:.5rem; margin:0 0 12px;
}
.kira-widget-wrap.card-history .kira-widget-title .kirafont{ font-size:1.05em; }
/* 固定內容高度,避免被長文字撐爆;左右留白 14px 看起來不擠 */
.kira-widget-wrap.card-history .history-card{ height:var(--history-h); overflow:hidden; }
.kira-widget-wrap.card-history .swiper{ width:100%; height:var(--history-h); }
#history_container_wrapper{ height:100% !important; }
.kira-widget-wrap.card-history .swiper-slide{
height:var(--history-h); padding:8px 14px; box-sizing:border-box; position:relative;
}
/* 多行顯示 + 截斷(避免爆版);年份微強調 */
.kira-widget-wrap.card-history .history-year{ font-weight:700; letter-spacing:.5px; opacity:.85; }
.kira-widget-wrap.card-history .history-text{
white-space:normal; overflow:hidden; display:-webkit-box;
-webkit-box-orient: vertical; -webkit-line-clamp: var(--history-lines);
line-height:1.45; font-size:.95rem;
}
/* 分隔線用偽元素,不改變高度,翻頁更準 */
.kira-widget-wrap.card-history .swiper-slide::after{
content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
background: var(--card-border, rgba(255,255,255,.15)); pointer-events:none;
}
@media (prefers-color-scheme: light){
.kira-widget-wrap.card-history .swiper-slide::after{ background: rgba(0,0,0,.08); }
}最後是獲取資料的腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/* Wikipedia On-This-Day — 手動翻頁、固定高度版 */
(function () {
const wrap = document.getElementById('history_container_wrapper');
if (!wrap) return;
const lang = 'zh';
const today = new Date();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const feedUrl = `https://api.wikimedia.org/feed/v1/wikipedia/${lang}/onthisday/all/${mm}/${dd}`;
const cacheKey = `HISTORY_TODAY_${lang}_${mm}${dd}`;
const render = (list) => {
wrap.innerHTML = '';
list.forEach(item => {
const slide = document.createElement('div');
slide.className = 'swiper-slide';
slide.innerHTML = `
<div class="history-year">A.D.${item.year}</div>
<div class="history-text">${item.text}</div>`;
if (item.link) slide.addEventListener('click', () => window.open(item.link, '_blank'));
wrap.appendChild(slide);
});
};
const createSwiper = () => {
if (window.__historySwiper__) window.__historySwiper__.destroy(true, true);
window.__historySwiper__ = new Swiper('#history-container', {
direction: 'vertical', effect: 'slide', slidesPerView: 1, spaceBetween: 0,
loop: false, centeredSlides: false, speed: 350, resistanceRatio: 0,
allowTouchMove: true,
mousewheel: { forceToAxis: true, releaseOnEdges: true, sensitivity: .7 },
keyboard: { enabled: true, onlyInViewport: true }
});
};
const run = list => { render(list); createSwiper(); };
// 快取先行
try { const cache = JSON.parse(localStorage.getItem(cacheKey) || 'null'); if (cache?.length) run(cache); } catch{}
// 再抓 API 更新
fetch(feedUrl).then(r=>r.json()).then(data => {
const events = Array.isArray(data?.events) ? data.events : [];
const list = events.slice(0, 15).map(ev => {
const p = (ev.pages && ev.pages[0]) || {};
const link = p?.content_urls?.desktop?.page || p?.content_urls?.mobile?.page || '';
return { year: ev.year, text: ev.text, link };
});
localStorage.setItem(cacheKey, JSON.stringify(list));
run(list);
}).catch(() => run([{ year: '—', text: '載入失敗,請稍後再試', link: '' }]));
})();同樣,別忘了載入到頁面上
1
2
3
4<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<%- css('css/history-today.css') %>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js" defer></script>
<script src="<%- url_for('/js/history-today.js') %>" defer></script>
備註:頁面大小樣式等都是慢慢修改到我喜歡的大小的,這方面有要修改就再自己手動測試吧。
留言區
Kira主題中本身就有寫好的giscus跟gitalk可以直接用了
一開始我也是直接開來用
但使用這兩個需要登入github,所以我想改成不需要登入也能留言的方式,自由度比較高
最後我使用[Twikoo](https://github.com/twikoojs/twikoo)
免費搭建、簡單部屬
留言存放區我使用MongoDB,畢竟使用度不高沒人會留言所以免費額度就夠了
步驟1
- 在 GitHub 建立一個新的 repo,例如:
twikoo-vercel
結構如下:1
2
3
4
5twikoo-vercel/
├─ api/
│ └─ index.js
├─ package.json
└─ vercel.json api/index.js1
2
3
4
5import twikoo from 'twikoo-vercel'
export default async function handler(req, res) {
return twikoo(req, res)
}package.json1
2
3
4
5
6
7
8
9
10
11{
"name": "twikoo-vercel",
"version": "1.0.0",
"type": "module",
"dependencies": {
"twikoo-vercel": "^1.6.18"
},
"engines": {
"node": "18.x"
}
}vercel.json1
2
3
4
5
6{
"version": 2,
"builds": [
{ "src": "api/index.js", "use": "@vercel/node" }
]
}
將這個repo部屬到Vercel
成功後就能拿到api網址
步驟2
建立 MongoDB Atlas Database
- 註冊 MongoDB Atlas
- 建立一個免費的 Cluster。
- 在 Database Access 新增一個使用者:
- 使用者名稱:twikooUser
- 權限:Atlas Admin 或 Read and write to any database
- 在 Network Access 加入 IP 白名單:
- 設定 0.0.0.0/0(允許所有 IP)。
- 這樣 Vercel 才能連上 Atlas
- 複製連線字串(Node.js Driver → 4.0+):
1
mongodb+srv://twikooUser:yourPassword@cluster0.xxxxx.mongodb.net/twikoo?retryWrites=true&w=majority
步驟3
- 到 Vercel → twikoo-vercel → Settings → Environment Variables。
- 新增一個變數:
- Key:MONGODB_URI
- Value:剛剛複製的 MongoDB 連線字串
- 儲存並 Redeploy。
步驟4
- 在主題設定檔
themes/kira/_config.yml新增:1
2
3
4
5
6comments:
use: twikoo
twikoo:
envId: https://twikoo-vercel-xxxx.vercel.app/api
lang: zh-TW - 在主題的
layout/components/comments/新增一個twikoo.ejs:1
2
3
4
5
6
7
8
9<div id="tcomment"></div>
<script src="https://cdn.jsdelivr.net/npm/twikoo/dist/twikoo.all.min.js"></script>
<script>
twikoo.init({
envId: '<%= theme.twikoo.envId %>',
el: '#tcomment',
lang: '<%= theme.twikoo.lang || "zh-TW" %>'
})
</script> - 在post中加入twikoo的選項
1
2
3
4
5<div class="kira-post-footer">
<%- partial('components/comments/gitalk') %>
<%- partial('components/comments/giscus') %>
<%- partial('components/comments/twikoo') %>
</div>
記得存檔,如果之後有更新比較多東西才會再繼續寫。