(function () { var currentScript = document.currentScript; var source = currentScript && currentScript.getAttribute("data-notice-src"); if (!source && currentScript && currentScript.src) { source = currentScript.src.replace(/js\/notice-banner\.js(?:\?.*)?$/, "notices.json"); } if (!source) { source = "notices.json"; } var cacheKey = Math.floor(Date.now() / (5 * 60 * 1000)); var joiner = source.indexOf("?") === -1 ? "?" : "&"; fetch(source + joiner + "v=" + cacheKey, { cache: "no-store" }) .then(function (response) { if (!response.ok) { throw new Error("Notice source unavailable"); } return response.json(); }) .then(function (payload) { var notice = selectNotice(payload); if (notice) { renderNotice(notice); } }) .catch(function () { // The page should stay quiet when the optional notice source is missing. }); function selectNotice(payload) { var notices = Array.isArray(payload) ? payload : payload && payload.notices; if (!Array.isArray(notices)) { return null; } var now = Date.now(); var path = normalizePath(window.location.pathname); return notices.find(function (notice) { if (!notice || notice.enabled === false) { return false; } if (notice.startsAt && Date.parse(notice.startsAt) > now) { return false; } if (notice.endsAt && Date.parse(notice.endsAt) < now) { return false; } if (notice.pages && notice.pages.length) { return notice.pages.some(function (page) { return pathMatches(path, normalizePath(page)); }); } return true; }); } function renderNotice(notice) { var dismissedId = window.localStorage.getItem("dismissedNoticeId"); var noticeId = String(notice.id || ""); if (notice.dismissible !== false && noticeId && dismissedId === noticeId) { return; } var body = document.body; var originalPadding = window.getComputedStyle(body).paddingTop || "0px"; body.style.setProperty("--notice-body-pad-top", originalPadding); var banner = document.createElement("div"); banner.className = "notice-banner"; banner.setAttribute("role", "status"); banner.setAttribute("aria-live", "polite"); banner.dataset.level = notice.level || "info"; var inner = document.createElement("div"); inner.className = "notice-banner__inner"; var badge = document.createElement("span"); badge.className = "notice-banner__badge"; badge.textContent = notice.badge || levelLabel(notice.level); var content = document.createElement("div"); content.className = "notice-banner__content"; if (notice.title) { var title = document.createElement("span"); title.className = "notice-banner__title"; title.textContent = notice.title; content.appendChild(title); } var message = document.createElement("span"); message.className = "notice-banner__message"; message.textContent = notice.message || ""; content.appendChild(message); inner.appendChild(badge); inner.appendChild(content); if (notice.url) { var link = document.createElement("a"); link.className = "notice-banner__link"; link.href = resolveUrl(notice.url, source); link.textContent = notice.linkText || "查看详情"; inner.appendChild(link); } var close = document.createElement("button"); close.className = "notice-banner__close"; close.type = "button"; close.setAttribute("aria-label", "关闭通知"); close.textContent = "×"; close.addEventListener("click", function () { if (notice.dismissible !== false && noticeId) { window.localStorage.setItem("dismissedNoticeId", noticeId); } banner.remove(); body.classList.remove("has-notice-banner"); body.style.removeProperty("--notice-banner-height"); }); inner.appendChild(close); banner.appendChild(inner); body.insertBefore(banner, body.firstChild); requestAnimationFrame(function () { banner.classList.add("is-visible"); body.style.setProperty("--notice-banner-height", banner.offsetHeight + "px"); body.classList.add("has-notice-banner"); }); } function levelLabel(level) { var labels = { warning: "提醒", error: "紧急", success: "完成" }; return labels[level] || "通知"; } function normalizePath(path) { return String(path || "").replace(/\\/g, "/").replace(/^.*?:\/\/[^/]+/, ""); } function resolveUrl(url, base) { try { return new URL(url, new URL(base, window.location.href)).href; } catch (error) { return url; } } function pathMatches(path, pattern) { if (!pattern || pattern === "*") { return true; } if (pattern.charAt(pattern.length - 1) === "*") { return path.indexOf(pattern.slice(0, -1)) === 0; } return path === pattern || path.endsWith(pattern); } })();