字体切全量、评论徽章优先级、友链自助申请卡片、友圈头像兜底
Deploy to Production / pre-check (push) Successful in 1m27s
Deploy to Production / build (push) Successful in 4m49s
Deploy to Production / deploy-edgeone (push) Successful in 3m50s
Deploy to Production / finalize (push) Successful in 21s
Deploy to Production / notify-failure (push) Skipped

- 字体:zql 从 subset(776K) 切换到全量(1.2MB woff2),subset 覆盖
  不足导致大量字回退系统字体,视觉不一致;全量一次加载彻底解决
- 评论徽章统一 comment-rendered 注入,优先级互斥:友链 > 友圈 >
  官职(后端 rank 字段),官方系统徽章(博主)独立显示
- 留言页新增友链自助申请入口卡片 + 弹窗表单(名称/链接/订阅/邮箱
  必填),提交进 api.200181.xyz 待审队列,审核结果邮件通知申请人
- 友圈头像:favicon 抓取失败时兜底第三方 favicon 服务
This commit is contained in:
QunLIn committed 2026-10-03 12:13:45 +08:00
1 parent e12da91314
commit 5fcbd2a7b6
4 files changed
+136 -82

No files matched your search

+4 -2
View File
@@ -1,6 +1,8 @@
@font-face {
font-family: 'zql';
src: url('/font/zql-v2-subset.woff2') format('woff2'),
url('/font/zql-v2-subset.woff') format('woff');
/* 2026-10-03 从 subset 切换到全量字体(1.2MB woff2):subset 覆盖不足,
很多字回退系统字体导致视觉不一致。全量仅比子集大 ~460KB,一次加载永久解决。 */
src: url('/font/zql-v2.woff2') format('woff2'),
url('/font/zql-v2.woff') format('woff');
font-display: swap;
}
+45 -79
View File
@@ -550,7 +550,6 @@ window.initArtalk = function() {
setTimeout(saveConf, 5000);
// ── Badge system: 友链 / 友圈 / 评论等级 ──
var badgesInjected = false;
var normalizeHost = function(h) {
return (h || '').replace(/^www\./, '');
@@ -621,108 +620,75 @@ var getPageKey = function() {
return window.artalkConfig ? (window.artalkConfig.pageKey || window.location.pathname) : window.location.pathname;
};
var processBadges = function() {
if (!linkMap && !feedMap) return;
// 友链 / 友圈徽章:数据来自 window.friendLinks / window.friendFeeds
// (评论等级官职徽章已迁到 comment-rendered 处理,见下方)
document.querySelectorAll('.atk-comment-wrap').forEach(function(wrap) {
var nickEl = wrap.querySelector('.atk-nick');
var avatarEl = wrap.querySelector('.atk-avatar');
if (!nickEl) return;
var nickText = (nickEl.textContent || '').trim();
var nickLower = nickText.toLowerCase();
var nickLink = nickEl.querySelector('a[href]');
var commentHost = null;
if (nickLink) {
try { commentHost = normalizeHost(new URL(nickLink.getAttribute('href')).hostname); } catch(e) {}
}
// 1. 友链 badge (green gradient) — from /api/links?all=1
if (linkMap && !nickEl.querySelector('.atk-link-badge')) {
var linkMatch = null;
if (commentHost && linkMap.hosts.has(commentHost)) {
linkMatch = linkMap.hosts.get(commentHost);
} else if (linkMap.names.has(nickLower)) {
linkMatch = linkMap.names.get(nickLower);
}
if (linkMatch) {
var lb = document.createElement('span');
lb.className = 'atk-link-badge';
lb.textContent = '友链';
lb.title = '友链: ' + linkMatch;
nickEl.appendChild(lb);
}
}
// 2. 友圈 badge (gold gradient) — from /api/feeds
if (feedMap && !nickEl.querySelector('.atk-feed-badge')) {
var feedMatch = null;
if (commentHost && feedMap.hosts.has(commentHost)) {
feedMatch = feedMap.hosts.get(commentHost);
} else if (feedMap.names.has(nickLower)) {
feedMatch = feedMap.names.get(nickLower);
}
if (feedMatch) {
var fb = document.createElement('span');
fb.className = 'atk-feed-badge';
fb.textContent = '友圈';
fb.title = '友圈: ' + feedMatch;
nickEl.appendChild(fb);
}
}
// 3. 评论等级徽章已由后端下发(badge_name/badge_color 官方槽渲染),不再本地注入
});
// ── 评论区徽章统一注入(comment-rendered 每条触发)──
// 优先级:官方系统徽章(博主等,Artalk 自渲染)独立显示;
// 主题徽章互斥取最高一级:友链 > 友圈 > 评论官职。
// 数据源:window.friendLinks/friendFeeds(友链、友圈)+ 后端 rank_* 字段(官职)。
var ensureBadgeMaps = function() {
if (!linkMap && window.friendLinks) linkMap = buildMap(window.friendLinks);
if (!feedMap && window.friendFeeds) feedMap = buildMap(window.friendFeeds);
return !!(linkMap || feedMap);
};
var injectAllBadges = function() {
if (badgesInjected) return;
badgesInjected = true;
var matchBadge = function(map, host, nickLower) {
if (!map) return null;
if (host && map.hosts.has(host)) return map.hosts.get(host);
if (map.names.has(nickLower)) return map.names.get(nickLower);
return null;
};
if (window.friendLinks) linkMap = buildMap(window.friendLinks);
if (window.friendFeeds) feedMap = buildMap(window.friendFeeds);
if (linkMap || feedMap) {
processBadges();
}
var injectBadge = function(nickEl, cls, text, title, color, bg) {
if (nickEl.querySelector('.' + cls)) return;
var b = document.createElement('span');
b.className = cls;
b.textContent = text;
if (title) b.title = title;
if (color) b.style.color = color;
if (bg) b.style.backgroundColor = bg;
nickEl.appendChild(b);
};
// 评论等级官职徽章——后端按全站真实评论数计算,随评论 API 下发
// rank_name/rank_title/rank_count/rank_color/rank_bg(不占用官方 badge 槽,
// 官方徽章如「博主」保留,官职徽章接在其后面)。配色沿用主题原版。
artalk.on('comment-rendered', function(node) {
try {
var data = null;
if (node && typeof node.getData === 'function') data = node.getData();
else if (node && node.comment && typeof node.comment.getData === 'function') data = node.comment.getData();
else data = node;
if (!data || !data.rank_name) return;
if (!data) return;
var el = null;
if (node && node.$el) el = node.$el;
else if (node && typeof node.getEl === 'function') el = node.getEl();
if (!el || !el.querySelector) return;
var nickEl = el.querySelector('.atk-nick');
if (!nickEl || nickEl.querySelector('.atk-rank-badge')) return;
var rb = document.createElement('span');
rb.className = 'atk-rank-badge';
rb.textContent = data.rank_name;
if (!nickEl) return;
if (!ensureBadgeMaps()) return;
var nickLower = (data.nick || '').toLowerCase();
var host = null;
try { if (data.link) host = normalizeHost(new URL(data.link).hostname); } catch (e) {}
// 优先级:友链 > 友圈 > 官职(互斥,只注入一枚主题徽章)
var linkMatch = matchBadge(linkMap, host, nickLower);
if (linkMatch) {
injectBadge(nickEl, 'atk-link-badge', '友链', '友链: ' + linkMatch);
return;
}
var feedMatch = matchBadge(feedMap, host, nickLower);
if (feedMatch) {
injectBadge(nickEl, 'atk-feed-badge', '友圈', '友圈: ' + feedMatch);
return;
}
if (data.rank_name && !nickEl.querySelector('.atk-rank-badge')) {
var tip = data.rank_title || data.rank_name;
if (data.rank_count != null) tip += ' (' + data.rank_count + '条评论)';
rb.title = tip;
rb.style.color = data.rank_color || '';
rb.style.backgroundColor = data.rank_bg || '';
nickEl.appendChild(rb);
injectBadge(nickEl, 'atk-rank-badge', data.rank_name, tip, data.rank_color || '', data.rank_bg || '');
}
} catch (e) { /* 徽章注入失败不影响评论 */ }
});
artalk.on('list-loaded', function() {
// Badge injection
badgesInjected = false;
setTimeout(injectAllBadges, 300);
// Add staggered animation to the comment list
var list = document.querySelector('.atk-list');
if (list) {
+86
View File
@@ -11,6 +11,18 @@
- 图标:{{ .Site.Params.message_info.icon | default .Site.Params.author.avatar }}
</div>
</div>
<!-- 友链自助申请入口(卡片样式与文章外链卡片一致) -->
{{ $rssBase := site.Params.rssapi.base | default "https://api.200181.xyz" }}
<a class="link-card" id="flApplyOpen" href="javascript:void(0)" style="margin:14px 0;text-decoration:none">
<img class="link-card-icon" src="/favicon.ico" alt="" loading="lazy"
onerror="this.src='{{ site.Params.author.avatar | default "/favicon.ico" }}';this.onerror=null;">
<span class="link-card-body">
<span class="link-card-title">友链自助申请</span>
<span style="font-size:12px;color:var(--at-color-meta,#8a94a6)">想和我交换友链?点这里填写申请,审核结果会邮件通知你</span>
</span>
</a>
<hr class="uk-divider-icon">
<div class="comments-area">
<!-- 评论区内容 -->
@@ -19,4 +31,78 @@
</div>
</div>
<!-- 友链申请弹窗 -->
<div id="flApplyMask" hidden style="position:fixed;inset:0;z-index:9999;background:rgba(20,32,55,.45);display:flex;align-items:center;justify-content:center;padding:16px">
<div style="background:#fff;color:#253244;border-radius:10px;max-width:420px;width:100%;padding:22px;box-shadow:0 12px 32px rgba(20,32,55,.2);max-height:90vh;overflow:auto">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">
<b style="font-size:16px">友链自助申请</b>
<span id="flApplyClose" style="cursor:pointer;color:#8a94a6;font-size:20px;line-height:1" title="关闭">×</span>
</div>
<label style="display:block;margin-bottom:10px"><span style="display:block;font-size:12px;color:#8a94a6;margin-bottom:4px">站点名称 *</span>
<input id="flName" style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #e7e9ee;border-radius:6px;font-size:14px" placeholder="我的小站"></label>
<label style="display:block;margin-bottom:10px"><span style="display:block;font-size:12px;color:#8a94a6;margin-bottom:4px">站点链接 *</span>
<input id="flUrl" style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #e7e9ee;border-radius:6px;font-size:14px" placeholder="https://example.com"></label>
<label style="display:block;margin-bottom:10px"><span style="display:block;font-size:12px;color:#8a94a6;margin-bottom:4px">订阅地址(RSS/Atom)*</span>
<input id="flFeed" style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #e7e9ee;border-radius:6px;font-size:14px" placeholder="https://example.com/feed"></label>
<label style="display:block;margin-bottom:10px"><span style="display:block;font-size:12px;color:#8a94a6;margin-bottom:4px">邮箱 *(接收审核结果)</span>
<input id="flEmail" type="email" style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #e7e9ee;border-radius:6px;font-size:14px" placeholder="you@example.com"></label>
<label style="display:block;margin-bottom:14px"><span style="display:block;font-size:12px;color:#8a94a6;margin-bottom:4px">站点简介(选填)</span>
<textarea id="flDesc" rows="2" style="width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid #e7e9ee;border-radius:6px;font-size:14px;resize:vertical"></textarea></label>
<div id="flMsg" hidden style="font-size:13px;margin-bottom:10px"></div>
<button id="flSubmit" class="uk-button uk-button-primary" style="width:100%;background:#2f9e63;border:none;color:#fff;padding:10px;border-radius:6px;font-size:14px;cursor:pointer">提交申请</button>
</div>
</div>
<script>
(function () {
var BASE = '{{ $rssBase }}';
var open = document.getElementById('flApplyOpen');
var mask = document.getElementById('flApplyMask');
if (!open || !mask) return;
function close() { mask.hidden = true; }
open.addEventListener('click', function () {
mask.hidden = false;
var msg = document.getElementById('flMsg');
msg.hidden = true;
});
document.getElementById('flApplyClose').addEventListener('click', close);
mask.addEventListener('click', function (e) { if (e.target === mask) close(); });
document.getElementById('flSubmit').addEventListener('click', function () {
var btn = this, msg = document.getElementById('flMsg');
var payload = {
name: document.getElementById('flName').value.trim(),
url: document.getElementById('flUrl').value.trim(),
feed: document.getElementById('flFeed').value.trim(),
email: document.getElementById('flEmail').value.trim(),
description: document.getElementById('flDesc').value.trim()
};
if (!payload.name || !payload.url || !payload.feed || !payload.email) {
msg.hidden = false; msg.style.color = '#d75a4a'; msg.textContent = '请填写全部必填项'; return;
}
btn.disabled = true; btn.textContent = '提交中…';
msg.hidden = true;
fetch(BASE + '/api/link-apply', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}).then(function (r) { return r.json().then(function (d) { return { code: r.status, d: d }; }); })
.then(function (res) {
if (res.code === 200 && res.d.ok) {
msg.hidden = false; msg.style.color = '#2f9e63'; msg.textContent = res.d.message || '申请已提交';
btn.textContent = '已提交 ✓';
setTimeout(close, 2200);
} else {
msg.hidden = false; msg.style.color = '#d75a4a'; msg.textContent = res.d.error || '提交失败,请稍后再试';
btn.disabled = false; btn.textContent = '提交申请';
}
})
.catch(function () {
msg.hidden = false; msg.style.color = '#d75a4a'; msg.textContent = '网络异常,请稍后再试';
btn.disabled = false; btn.textContent = '提交申请';
});
});
})();
</script>
{{ end }}
+1 -1
View File
@@ -76,7 +76,7 @@
})();
</script>
<!-- Preload Fonts -->
<link rel="preload" href="{{ "font/zql-v2-subset.woff2" | relURL }}" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{{ "font/zql-v2.woff2" | relURL }}" as="font" type="font/woff2" crossorigin>
<!-- remixicon 不预加载,避免与 CSS 中带 query 参数的 URL 不一致导致警告 -->
{{ $echartsHead := resources.Get "js/libs/echarts.min.js" | resources.Fingerprint }}