105 lines
3.6 KiB
JavaScript
105 lines
3.6 KiB
JavaScript
(function () {
|
||
// 通用全屏 loading(拉鲁猫吉祥物 + 转圈)。
|
||
// 样式复用 common/theme.css 的 .hy-loading 系列类:新页面按仓库约定必须引入 theme.css,
|
||
// 组件自身只负责 DOM 注入和显隐状态,不重复维护一份样式副本避免两处漂移。
|
||
// 吉祥物图取 theme.css 默认值(lalu 紫猫动画 webp);其他 app 主题可在
|
||
// [data-hy-app-code='...'] 下覆盖 .hy-loading__mascot 的 background 换皮。
|
||
var LOADING_ID = 'hy-global-loading';
|
||
// show/hide 采用引用计数:并发请求各自 show/hide 时,最后一个 hide 才真正关闭遮罩,
|
||
// 避免先完成的请求把还在加载的请求的遮罩提前关掉。
|
||
var visibleCount = 0;
|
||
|
||
function ensureNode() {
|
||
var node = document.getElementById(LOADING_ID);
|
||
if (node) return node;
|
||
node = document.createElement('div');
|
||
node.id = LOADING_ID;
|
||
node.className = 'hy-loading';
|
||
node.setAttribute('role', 'status');
|
||
node.setAttribute('aria-live', 'polite');
|
||
node.setAttribute('aria-hidden', 'true');
|
||
|
||
var mascot = document.createElement('div');
|
||
mascot.className = 'hy-loading__mascot';
|
||
mascot.setAttribute('aria-hidden', 'true');
|
||
|
||
var spinner = document.createElement('div');
|
||
spinner.className = 'hy-loading__spinner';
|
||
spinner.setAttribute('aria-hidden', 'true');
|
||
|
||
var text = document.createElement('div');
|
||
text.className = 'hy-loading__text';
|
||
text.hidden = true;
|
||
|
||
node.appendChild(mascot);
|
||
node.appendChild(spinner);
|
||
node.appendChild(text);
|
||
document.body.appendChild(node);
|
||
return node;
|
||
}
|
||
|
||
function render() {
|
||
var node = ensureNode();
|
||
var active = visibleCount > 0;
|
||
node.classList.toggle('is-active', active);
|
||
node.setAttribute('aria-hidden', active ? 'false' : 'true');
|
||
}
|
||
|
||
function setText(node, message) {
|
||
var text = node.querySelector('.hy-loading__text');
|
||
if (!text) return;
|
||
var value = String(message || '').trim();
|
||
// 文案由调用方传入已本地化的字符串;组件不内置多语言字典(common/i18n.js 归页面消费)。
|
||
text.textContent = value;
|
||
text.hidden = !value;
|
||
}
|
||
|
||
function show(options) {
|
||
if (!document.body) {
|
||
// 页面脚本可能在 body 就绪前调用;挂到 DOMContentLoaded 重放,保持调用方无需关心时序。
|
||
document.addEventListener(
|
||
'DOMContentLoaded',
|
||
function () {
|
||
show(options);
|
||
},
|
||
{ once: true }
|
||
);
|
||
return;
|
||
}
|
||
visibleCount += 1;
|
||
var node = ensureNode();
|
||
setText(node, options && options.text);
|
||
render();
|
||
}
|
||
|
||
function hide(force) {
|
||
if (force) {
|
||
visibleCount = 0;
|
||
} else if (visibleCount > 0) {
|
||
visibleCount -= 1;
|
||
}
|
||
if (!document.getElementById(LOADING_ID)) return;
|
||
render();
|
||
}
|
||
|
||
function isVisible() {
|
||
return visibleCount > 0;
|
||
}
|
||
|
||
window.HyAppLoading = {
|
||
show: show,
|
||
hide: hide,
|
||
isVisible: isVisible,
|
||
};
|
||
|
||
// 与 HyAppToast 的 hyapp:toast 对齐的事件桥:原生壳或其他脚本可通过事件控制遮罩。
|
||
window.addEventListener('hyapp:loading', function (event) {
|
||
var detail = event.detail || {};
|
||
if (detail.visible === false) {
|
||
hide(detail.force === true);
|
||
return;
|
||
}
|
||
show(detail);
|
||
});
|
||
})();
|