添加notice功能

This commit is contained in:
2026-05-01 08:25:29 +08:00
Unverified
parent 3185abfef4
commit bad707fce1
16 changed files with 799 additions and 6 deletions
+2
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tech-query.css">
<link rel="stylesheet" href="../css/notice-banner.css">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
</head>
<body>
@@ -205,5 +206,6 @@
loadData();
</script>
<script src="https://cdn.jsdmirror.cn/gh/bishshi/wechat-detect@main/wechat-detect.js"></script>
<script src="../js/notice-banner.js"></script>
</body>
</html>
+2
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tech-query.css">
<link rel="stylesheet" href="../css/notice-banner.css">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="https://unpkg.com/html5-qrcode"></script>
</head>
@@ -337,5 +338,6 @@
initializeSearchTermFromUrl();
</script>
<script src="https://cdn.jsdmirror.cn/gh/bishshi/wechat-detect@main/wechat-detect.js"></script>
<script src="../js/notice-banner.js"></script>
</body>
</html>
+136
View File
@@ -0,0 +1,136 @@
.notice-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 9999;
display: none;
justify-content: center;
padding: 10px 14px;
color: #102033;
font-family: "Noto Sans SC", "Microsoft YaHei", Arial, sans-serif;
background: linear-gradient(90deg, #fff7d6, #dff7ff);
border-bottom: 1px solid rgba(16, 32, 51, 0.14);
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
}
.notice-banner.is-visible {
display: flex;
}
.notice-banner__inner {
width: min(100%, 1120px);
display: grid;
grid-template-columns: auto 1fr auto auto;
align-items: center;
gap: 10px;
}
.notice-banner__badge {
min-width: 40px;
padding: 4px 9px;
border-radius: 999px;
color: #ffffff;
background: #2563eb;
font-size: 12px;
font-weight: 700;
text-align: center;
line-height: 1.2;
}
.notice-banner[data-level="warning"] .notice-banner__badge {
background: #b45309;
}
.notice-banner[data-level="error"] .notice-banner__badge {
background: #be123c;
}
.notice-banner[data-level="success"] .notice-banner__badge {
background: #047857;
}
.notice-banner__content {
min-width: 0;
font-size: 14px;
line-height: 1.45;
}
.notice-banner__title {
margin-right: 6px;
font-weight: 700;
}
.notice-banner__message {
color: rgba(16, 32, 51, 0.84);
}
.notice-banner__link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
color: #ffffff;
background: #075985;
font-size: 14px;
font-weight: 700;
text-decoration: none;
white-space: nowrap;
}
.notice-banner__link:hover {
background: #0369a1;
text-decoration: none;
}
.notice-banner__close {
width: 30px;
height: 30px;
border: 0;
border-radius: 50%;
color: #102033;
background: rgba(16, 32, 51, 0.08);
font-size: 20px;
line-height: 1;
cursor: pointer;
}
.notice-banner__close:hover {
background: rgba(16, 32, 51, 0.14);
}
body.has-notice-banner {
padding-top: calc(var(--notice-body-pad-top, 0px) + var(--notice-banner-height, 0px) + 12px) !important;
}
@media (max-width: 640px) {
.notice-banner {
padding: 9px 10px;
}
.notice-banner__inner {
grid-template-columns: auto 1fr auto;
gap: 8px;
}
.notice-banner__link {
grid-column: 2 / 3;
justify-self: start;
}
.notice-banner__content {
font-size: 13px;
}
}
@media print {
.notice-banner {
display: none !important;
}
body.has-notice-banner {
padding-top: var(--notice-body-pad-top, 0px) !important;
}
}
+301
View File
@@ -0,0 +1,301 @@
:root {
--bg: #07111f;
--panel: rgba(9, 20, 40, 0.78);
--panel-strong: rgba(10, 24, 46, 0.94);
--line: rgba(103, 213, 255, 0.22);
--line-strong: rgba(103, 213, 255, 0.48);
--text: #edf7ff;
--muted: #96b2d1;
--accent: #67d5ff;
--accent-strong: #2de2a6;
--warning: #ffcc66;
--font-display: "Orbitron", "Segoe UI", sans-serif;
--font-body: "Noto Sans SC", "Microsoft YaHei", sans-serif;
}
* {
box-sizing: border-box;
}
html {
color-scheme: dark;
}
body {
margin: 0;
min-height: 100vh;
color: var(--text);
font-family: var(--font-body);
background:
linear-gradient(135deg, rgba(7, 17, 31, 0.96), rgba(10, 28, 54, 0.96)),
repeating-linear-gradient(90deg, rgba(103, 213, 255, 0.055) 0 1px, transparent 1px 72px),
repeating-linear-gradient(0deg, rgba(103, 213, 255, 0.045) 0 1px, transparent 1px 72px);
}
a {
color: inherit;
}
.notice-page {
width: min(calc(100% - 32px), 1120px);
margin: 0 auto;
padding: 28px 0 56px;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 34px;
}
.brand,
.back-link,
.primary-action {
display: inline-flex;
align-items: center;
min-height: 40px;
border-radius: 999px;
text-decoration: none;
}
.brand {
gap: 10px;
color: var(--text);
font-weight: 700;
}
.brand-mark {
display: inline-grid;
place-items: center;
width: 40px;
height: 40px;
border: 1px solid var(--line-strong);
border-radius: 12px;
color: var(--accent);
font-family: var(--font-display);
background: rgba(103, 213, 255, 0.08);
}
.back-link {
padding: 0 16px;
border: 1px solid var(--line);
color: var(--muted);
background: rgba(255, 255, 255, 0.04);
}
.back-link:hover,
.primary-action:hover {
border-color: var(--line-strong);
color: var(--text);
}
.notice-hero {
display: grid;
grid-template-columns: minmax(0, 1fr) 280px;
gap: 22px;
align-items: stretch;
margin-bottom: 22px;
}
.hero-copy,
.status-panel,
.notice-card,
.side-card {
border: 1px solid var(--line);
background: var(--panel);
box-shadow: 0 28px 60px rgba(0, 0, 0, 0.32);
backdrop-filter: blur(18px);
}
.hero-copy {
padding: clamp(28px, 5vw, 54px);
border-radius: 24px;
}
.eyebrow {
display: inline-block;
margin-bottom: 14px;
color: var(--accent);
font-family: var(--font-display);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
margin-bottom: 16px;
font-size: clamp(34px, 6vw, 64px);
line-height: 1.05;
}
.hero-copy p {
max-width: 760px;
margin-bottom: 0;
color: var(--muted);
font-size: 17px;
line-height: 1.8;
}
.status-panel {
display: flex;
min-height: 240px;
flex-direction: column;
justify-content: space-between;
padding: 24px;
border-radius: 24px;
}
.status-badge {
width: max-content;
padding: 7px 12px;
border-radius: 999px;
color: #06111f;
background: var(--warning);
font-size: 13px;
font-weight: 800;
}
.status-code {
color: var(--accent);
font-family: var(--font-display);
font-size: 44px;
font-weight: 700;
}
.status-panel p {
margin-bottom: 0;
color: var(--muted);
}
.notice-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 22px;
align-items: start;
}
.notice-card,
.side-card {
border-radius: 20px;
}
.notice-card {
padding: clamp(24px, 4vw, 40px);
}
.notice-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 22px;
}
.notice-meta span,
.info-label {
color: var(--muted);
font-size: 13px;
}
.notice-meta span {
padding: 6px 10px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255, 255, 255, 0.04);
}
.notice-card h2,
.side-card h2 {
margin-bottom: 12px;
font-size: 24px;
}
.notice-card p,
.side-card p {
color: var(--muted);
line-height: 1.8;
}
.info-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-top: 30px;
}
.info-grid div {
min-height: 92px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 16px;
background: rgba(255, 255, 255, 0.04);
}
.info-grid strong {
display: block;
margin-top: 8px;
font-size: 16px;
}
.side-card {
padding: 24px;
}
code {
color: var(--accent);
font-family: Consolas, "Courier New", monospace;
}
.primary-action {
justify-content: center;
width: 100%;
margin-top: 14px;
padding: 0 18px;
border: 1px solid rgba(45, 226, 166, 0.5);
color: #06111f;
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
font-weight: 800;
}
@media (max-width: 840px) {
.notice-hero,
.notice-layout {
grid-template-columns: 1fr;
}
.status-panel {
min-height: 180px;
}
.info-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.notice-page {
width: min(calc(100% - 20px), 1120px);
padding-top: 16px;
}
.topbar {
align-items: flex-start;
flex-direction: column;
margin-bottom: 22px;
}
.hero-copy,
.status-panel,
.notice-card,
.side-card {
border-radius: 16px;
}
}
+175
View File
@@ -0,0 +1,175 @@
(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);
}
})();
+141
View File
@@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通知详情</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/notice-page.css">
</head>
<body>
<main class="notice-page">
<nav class="topbar" aria-label="页面导航">
<a class="brand" href="../index.html">
<span class="brand-mark">BI</span>
<span>通知中心</span>
</a>
<a class="back-link" href="../index.html">返回首页</a>
</nav>
<section class="notice-hero">
<div class="hero-copy">
<span class="eyebrow">Notice Center</span>
<h1 id="noticeTitle">重要通知</h1>
<p id="noticeSummary">2026 年上半年 4 月数据已完成更新。请按需进入相关查询页面查看最新记录。</p>
</div>
<div class="status-panel" aria-label="通知状态">
<span class="status-badge" id="noticeBadge">通知</span>
<div class="status-code" id="noticeLevel">INFO</div>
<p id="noticeWindow">长期有效</p>
</div>
</section>
<section class="notice-layout">
<article class="notice-card">
<div class="notice-meta">
<span id="noticeDate">2026-05-01</span>
<span id="noticeState">当前生效</span>
</div>
<h2 id="articleTitle">重要通知</h2>
<p id="articleMessage">2026 年上半年 4 月数据已完成更新。请按需进入相关查询页面查看最新记录。</p>
<div class="info-grid">
<div>
<span class="info-label">适用范围</span>
<strong>证书、信函与单据查询</strong>
</div>
<div>
<span class="info-label">提醒类型</span>
<strong>服务公告与使用提示</strong>
</div>
<div>
<span class="info-label">查看方式</span>
<strong>页面顶部通知横幅</strong>
</div>
</div>
</article>
<aside class="side-card">
<h2>温馨提示</h2>
<p>如通知涉及查询范围、开放时间或数据更新,请以本页展示内容为准。完成阅读后,可返回首页继续使用相关查询服务。</p>
<a class="primary-action" href="../index.html">返回查询入口</a>
</aside>
</section>
</main>
<script>
(function () {
fetch("../notices.json?v=" + Math.floor(Date.now() / 300000), { cache: "no-store" })
.then(function (response) {
if (!response.ok) {
throw new Error("notice source unavailable");
}
return response.json();
})
.then(function (payload) {
var notices = Array.isArray(payload) ? payload : payload.notices;
var notice = Array.isArray(notices) && notices.find(function (item) {
return item && item.enabled !== false;
});
if (notice) {
renderNotice(notice);
}
})
.catch(function () {});
function renderNotice(notice) {
setText("noticeTitle", notice.title || "重要通知");
setText("noticeSummary", notice.message || "");
setText("noticeBadge", notice.badge || "通知");
setText("noticeLevel", String(notice.level || "info").toUpperCase());
setText("articleTitle", notice.title || "重要通知");
setText("articleMessage", notice.message || "");
setText("noticeDate", formatDate(notice.startsAt) || "未设置开始时间");
setText("noticeWindow", formatWindow(notice.startsAt, notice.endsAt));
}
function setText(id, text) {
var node = document.getElementById(id);
if (node) {
node.textContent = text;
}
}
function formatDate(value) {
if (!value) {
return "";
}
var date = new Date(value);
if (Number.isNaN(date.getTime())) {
return "";
}
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit"
});
}
function formatWindow(start, end) {
var startText = formatDate(start);
var endText = formatDate(end);
if (startText && endText) {
return startText + " 至 " + endText;
}
if (startText) {
return startText + " 起生效";
}
return "长期有效";
}
})();
</script>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
{
"notices": [
{
"id": "site-notice-2026-05-01",
"enabled": true,
"level": "info",
"badge": "通知",
"title": "重要通知",
"message": "2026 年上半年 4 月数据已完成更新。请按需进入相关查询页面查看最新记录。",
"linkText": "查看详情",
"url": "notice/index.html",
"dismissible": true,
"startsAt": "2026-05-01T00:00:00+08:00",
"endsAt": ""
}
]
}
+3 -1
View File
@@ -6,6 +6,7 @@
<title>多彩场景信函样例</title>
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/notice-banner.css">
</head>
<body>
@@ -31,5 +32,6 @@
</div>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+3 -1
View File
@@ -6,6 +6,7 @@
<title>多彩场景信函样例</title>
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/notice-banner.css">
</head>
<body>
@@ -31,5 +32,6 @@
</div>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+3 -1
View File
@@ -6,6 +6,7 @@
<title>多彩场景信函样例</title>
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/notice-banner.css">
</head>
<body>
@@ -31,5 +32,6 @@
</div>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+3 -1
View File
@@ -6,6 +6,7 @@
<title>多彩场景信函样例</title>
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/notice-banner.css">
</head>
<body>
@@ -31,5 +32,6 @@
</div>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+3 -1
View File
@@ -6,6 +6,7 @@
<title>多彩场景信函样例</title>
<link href="https://fonts.googleapis.com/css2?family=Zhi+Mang+Xing&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
<link rel="stylesheet" href="../css/notice-banner.css">
</head>
<body>
@@ -31,5 +32,6 @@
</div>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+2
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tech-query.css">
<link rel="stylesheet" href="../css/notice-banner.css">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
</head>
<body>
@@ -132,5 +133,6 @@
document.addEventListener('DOMContentLoaded', loadTicketDetail);
</script>
<script src="https://cdn.jsdmirror.cn/gh/bishshi/wechat-detect@main/wechat-detect.js"></script>
<script src="../js/notice-banner.js"></script>
</body>
</html>
+2
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tech-query.css">
<link rel="stylesheet" href="../css/notice-banner.css">
<script src="https://cdn.jsdmirror.cn/gh/bishshi/wechat-detect@main/wechat-detect.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="https://unpkg.com/html5-qrcode"></script>
@@ -223,5 +224,6 @@
document.getElementById('searchBtn').addEventListener('click', handleSearch);
initializeSearchTermFromUrl();
</script>
<script src="../js/notice-banner.js"></script>
</body>
</html>
+4 -1
View File
@@ -4,6 +4,8 @@
<meta charset="UTF-8">
<title>打印单据</title>
<link rel="stylesheet" href="../css/notice-banner.css">
<style>
body {
font-family: Arial, "Microsoft YaHei";
@@ -171,5 +173,6 @@ async function load() {
load();
</script>
<script src="https://cdn.jsdmirror.cn/gh/bishshi/wechat-detect@main/wechat-detect.js"></script>
<script src="../js/notice-banner.js"></script>
</body>
</html>
</html>
+2
View File
@@ -8,6 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;600;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/tech-query.css">
<link rel="stylesheet" href="../css/notice-banner.css">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
</head>
<body>
@@ -152,5 +153,6 @@
document.getElementById('searchBtn').addEventListener('click', handleSearch);
</script>
<script src="../js/notice-banner.js"></script>
</body>
</html>