perf(comments): 评论加载提速 + 骨架屏 + 友链弹窗 pjax 修复 + toast 统一 Message.js

优化:
- initArtalk 加节点级幂等守卫(mount.__atkInited,失败回滚),
  消除 artalk.html/mypjax 多入口导致的 4× comments + 4× pv 重复请求
- fetch 包装层对无 body 的 GET/HEAD 去掉多余 content-type,
  消掉 Artalk 对 GET /comments 的 CORS 预检(按完整 URL 缓存,
  每篇文章都重付一次 RTT)。实测评论列表 +1150~1565ms → +626ms

修复:
- 评论列表骨架屏改注入 .atk-list-body(原骨架在 #Comments 内,
  被 Artalk.init 清空),list-loaded/故障/12s 兜底移除
- 友链申请弹窗抽成 modules/friendlink.js 进 page-only bundle
  (原内联脚本在 #pjax-container 外,pjax 后 flApplyOpenModal 未定义)
- toast 统一走本地化 Message.js/Qmsg 适配层(保留 window.Toast/showToast 旧 API)

文档:
- README / 架构总览 更新;新增 评论加载优化方案.md
This commit is contained in:
zqlit committed 2026-10-04 15:00:52 +08:00
1 parent 9e972f2b61
commit 50a906e07c
14 files changed
+1018 -486

No files matched your search

+41 -49
View File
@@ -1,63 +1,55 @@
/**
* Toast — lightweight notification
* Usage: window.Toast.success('msg'), Toast.error('msg'), Toast.warning('msg'), Toast.info('msg')
* window.Toast.show('msg', { type: 'info', duration: 2500 })
* Toast — 统一通知组件(Message.js / Qmsg)
*
* 全站 toast 一律走 Message.js(https://github.com/snwjas/Message.js),
* 库本体已本地化:js/libs/message.min.js + css/libs/message.min.css(随 style.css 打包)。
* 这里只做一层薄适配,保留旧 API,调用点(utils/floating-tools/artalk…)不用改:
* Toast.show(text, { type, duration })
* Toast.success / error / warning / info(text, duration?)
* showToast(text, type) ← 全局快捷方式
*/
(function() {
var container = null;
var counter = 0;
(function () {
if (window.__yingToastReady) return; // PJAX 重复执行防抖
window.__yingToastReady = true;
function ensureContainer() {
if (container) return;
container = document.createElement('div');
container.className = 'ying-toast-container';
document.body.appendChild(container);
var Q = window.Qmsg;
if (!Q) {
// Message.js 没加载出来时兜底:不打断业务,仅降级为无样式 console
var fallback = function (text) {
try { console.log('[toast]', text); } catch (e) {}
};
fallback.success = fallback.error = fallback.warning = fallback.info = fallback;
window.Toast = fallback;
window.showToast = function (text) { fallback(text); };
return;
}
function remove(el, delay) {
setTimeout(function() {
el.classList.remove('ying-toast--visible');
el.addEventListener('transitionend', function onEnd(e) {
if (e.propertyName !== 'opacity') return;
el.removeEventListener('transitionend', onEnd);
if (el.parentNode) el.parentNode.removeChild(el);
});
}, delay);
// 全站统一默认值:最多同时 3 条,自动关闭
try { Q.config({ maxNums: 3, timeout: 2500 }); } catch (e) {}
function opt(duration) {
return duration ? { timeout: duration } : undefined;
}
window.Toast = {
show: function(text, options) {
show: function (text, options) {
options = options || {};
var type = options.type || 'info';
var duration = options.duration || 2200;
ensureContainer();
var el = document.createElement('div');
el.className = 'ying-toast ying-toast--' + type;
el.textContent = text;
el.setAttribute('role', 'status');
el.setAttribute('aria-live', 'polite');
container.appendChild(el);
/* Force layout then animate in */
void el.offsetWidth;
el.classList.add('ying-toast--visible');
remove(el, duration);
if (typeof Q[type] === 'function') return Q[type](text, opt(options.duration));
return Q.info(text, opt(options.duration));
},
success: function (text, duration) { return Q.success(text, opt(duration)); },
error: function (text, duration) { return Q.error(text, opt(duration)); },
warning: function (text, duration) { return Q.warning(text, opt(duration)); },
info: function (text, duration) { return Q.info(text, opt(duration)); },
loading: function (text) { return Q.loading(text); },
closeAll: function () { try { Q.closeAll(); } catch (e) {} }
};
success: function(text, duration) {
this.show(text, { type: 'success', duration: duration });
},
error: function(text, duration) {
this.show(text, { type: 'error', duration: duration });
},
warning: function(text, duration) {
this.show(text, { type: 'warning', duration: duration });
},
info: function(text, duration) {
this.show(text, { type: 'info', duration: duration });
}
// 全局快捷方式(utils.js 原实现迁到这里,单一来源)
window.showToast = function (text, type) {
var t = type || 'info';
if (window.Toast && typeof window.Toast[t] === 'function') window.Toast[t](text);
else window.Toast.info(text);
};
})();