/* common.js —— 全站通用交互 * 功能:昼/夜主题切换、实时时钟、随机名言刷新、四大板块 hover 浮层、 * 搜索栏(引擎下拉/清除/回车搜索)、复制到剪贴板、DOM 工具函数 */ (function (global) { 'use strict'; /* ---------- 工具 ---------- */ function $(sel, root) { return (root || document).querySelector(sel); } function $$(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); } /* ---------- 主题(昼/夜) ---------- */ var THEME_KEY = 'hp-theme'; function currentTheme() { var saved = null; try { saved = localStorage.getItem(THEME_KEY); } catch (e) { /* ignore */ } if (saved === 'light' || saved === 'dark') return saved; // 未设置:跟随系统 return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light'; } function applyTheme(t, persist) { document.documentElement.setAttribute('data-theme', t); if (persist) { try { localStorage.setItem(THEME_KEY, t); } catch (e) { /* ignore */ } } var icons = $$('.js-theme-icon'); icons.forEach(function (el) { el.textContent = (t === 'dark') ? '☀' : '☾'; }); var btn = $('.js-theme-btn'); if (btn) btn.title = (t === 'dark') ? '切换到白天模式' : '切换到黑夜模式'; } function initTheme() { applyTheme(currentTheme(), false); $$('.js-theme-btn').forEach(function (btn) { btn.addEventListener('click', function () { var next = (document.documentElement.getAttribute('data-theme') === 'dark') ? 'light' : 'dark'; applyTheme(next, true); }); }); if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () { // 用户手动选过则不跟随系统变化 try { if (!localStorage.getItem(THEME_KEY)) applyTheme(currentTheme(), false); } catch (e) { applyTheme(currentTheme(), false); } }); } } /* ---------- 实时时钟 ---------- */ function pad(n) { return (n < 10 ? '0' : '') + n; } function formatTime(d) { return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); } function initClocks() { function tick() { $$('.js-clock').forEach(function (el) { el.textContent = formatTime(new Date()); }); } tick(); setInterval(tick, 1000); } /* ---------- 名言条:点击格言本身切换 ---------- */ function initQuoteRefresh() { $$('.js-quote').forEach(function (quote) { quote.addEventListener('click', function () { var list = $$('.js-quote-pool span', document); // 服务端把所有名言放入隐藏池 if (!list.length) return; // 定位当前展示文本在池中的位置,避免换到下一条仍是同一条 var currentText = quote.textContent; var cur = -1; for (var i = 0; i < list.length; i++) { if (list[i].textContent === currentText) { cur = i; break; } } var n = list.length; var idx = Math.floor(Math.random() * n); if (n > 1 && cur !== -1) { while (idx === cur) idx = (idx + 1) % n; } quote.textContent = list[idx].textContent; }); }); } /* ---------- 首页四大板块 hover 展开说明 ---------- */ function initBoardHover() { var section = $('.js-boards'); if (!section) return; var cards = $$('.js-board-card', section); var overlay = $('.js-board-panel', section); if (!cards.length || !overlay) return; var titleEl = $('.js-board-panel-title', overlay); var descEl = $('.js-board-panel-desc', overlay); var bodyEl = section; function showPanel(card) { var name = card.getAttribute('data-name') || ''; var desc = card.getAttribute('data-desc') || ''; var idx = cards.indexOf(card); if (titleEl) titleEl.textContent = name; if (descEl) descEl.textContent = desc; overlay.classList.remove('panel-left', 'panel-right'); // 第 0 / 1 块吸附左侧边缘,第 2 / 3 块吸附右侧边缘(实际板块位置不变) if (idx === 0 || idx === 1) overlay.classList.add('panel-left'); else overlay.classList.add('panel-right'); overlay.classList.add('visible'); } cards.forEach(function (card) { card.addEventListener('mouseenter', function () { showPanel(card); }); card.addEventListener('click', function () { /* 点击由 包裹,这里无额外处理 */ }); }); section.addEventListener('mouseleave', function () { overlay.classList.remove('visible'); }); } /* ---------- 搜索栏 ---------- */ var DEFAULT_ENGINES = [ { key: 'bing', name: 'Bing', icon: 'B', url: 'https://www.bing.com/search?q={kw}' }, { key: 'baidu', name: '百度', icon: '度', url: 'https://www.baidu.com/s?wd={kw}' }, { key: 'github', name: 'GitHub', icon: 'G', url: 'https://github.com/search?q={kw}' }, { key: 'google', name: 'Google', icon: 'G', url: 'https://www.google.com/search?q={kw}' } ]; function initSearch() { var box = $('.js-search'); if (!box) return; var cfg = null; try { cfg = JSON.parse(box.getAttribute('data-engines') || 'null'); } catch (e) { cfg = null; } var engines = (cfg && cfg.length) ? cfg : DEFAULT_ENGINES; var curKey = box.getAttribute('data-engine') || engines[0].key; var cur = engines.filter(function (e) { return e.key === curKey; })[0] || engines[0]; var input = $('.js-search-input', box); var clearBtn = $('.js-search-clear', box); var engineBtn = $('.js-engine-btn', box); var engineName = $('.js-engine-name', box); var menu = $('.js-engine-menu', box); function setEngine(e) { cur = e; // 按钮区域只显示图标(全称在展开下拉列表时展示) if (engineName) engineName.textContent = e.icon || ''; if (engineBtn) engineBtn.title = '搜索引擎:' + e.name; box.setAttribute('data-engine', e.key); hideMenu(); } function showMenu() { if (!menu) return; if (!menu.children.length) { engines.forEach(function (e) { var li = document.createElement('li'); li.textContent = e.icon + ' ' + e.name; li.className = (e.key === cur.key) ? 'active' : ''; li.addEventListener('click', function () { setEngine(e); }); menu.appendChild(li); }); } menu.classList.add('open'); } function hideMenu() { if (menu) menu.classList.remove('open'); } function updateClear() { if (clearBtn) clearBtn.style.visibility = (input && input.value) ? 'visible' : 'hidden'; } function doSearch() { var kw = (input ? input.value : '').trim(); if (!kw) { if (input) input.focus(); return; } window.open(cur.url.replace('{kw}', encodeURIComponent(kw)), '_blank', 'noopener'); } if (engineBtn) engineBtn.addEventListener('click', function (ev) { ev.stopPropagation(); if (menu && menu.classList.contains('open')) hideMenu(); else showMenu(); }); if (clearBtn) clearBtn.addEventListener('click', function () { if (input) { input.value = ''; input.focus(); } updateClear(); }); if (input) { input.addEventListener('input', updateClear); input.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') doSearch(); }); } document.addEventListener('click', function () { hideMenu(); }); var goBtn = $('.js-search-go', box); if (goBtn) goBtn.addEventListener('click', doSearch); setEngine(cur); updateClear(); } /* ---------- 后台图标控件:文本 / 图片上传(≤200KB → dataURL) ---------- */ function isIconImage(v) { v = String(v || '').trim(); if (!v) return false; if (/^data:image\//i.test(v) || /^https?:\/\//i.test(v)) return true; if (/\.(png|jpe?g|gif|svg|webp)(\?|#|$)/i.test(v)) return true; return v.charAt(0) === '/' || v.indexOf('./') === 0 || v.indexOf('../') === 0; } function updateIconPreview(ctl) { var input = ctl.querySelector('.icon-input'); var box = ctl.querySelector('.icon-preview'); if (!input || !box) return; var v = input.value.trim(); box.innerHTML = ''; if (!v) return; // 空:显示空心占位框 if (isIconImage(v)) { var img = document.createElement('img'); img.src = v; img.alt = ''; img.loading = 'lazy'; box.appendChild(img); } else { box.textContent = v; } } function initIconControls() { // 点击“上传”→ 触发隐藏 file document.addEventListener('click', function (ev) { var btn = ev.target && ev.target.closest ? ev.target.closest('.icon-upload') : null; if (!btn) return; var ctl = btn.closest('.icon-ctl'); var file = ctl ? ctl.querySelector('.icon-file') : null; if (file) file.click(); }); // 选择图片 → 校验并转 dataURL 写入同控件文本框 document.addEventListener('change', function (ev) { var file = ev.target; if (!file || !file.classList || !file.classList.contains('icon-file')) return; var ctl = file.closest('.icon-ctl'); var input = ctl ? ctl.querySelector('.icon-input') : null; var f = file.files && file.files[0]; function reset() { file.value = ''; } if (!f) return; if (f.type.indexOf('image/') !== 0) { alert('请选择图片文件。'); reset(); return; } if (f.size > 200 * 1024) { alert('图片不能超过 200KB。'); reset(); return; } var rd = new FileReader(); rd.onload = function () { if (input) { input.value = rd.result; updateIconPreview(ctl); } reset(); }; rd.onerror = function () { alert('图片读取失败,请重试。'); reset(); }; rd.readAsDataURL(f); }); // 手动输入文本 → 实时预览 document.addEventListener('input', function (ev) { var t = ev.target; if (t && t.classList && t.classList.contains('icon-input')) { updateIconPreview(t.closest('.icon-ctl')); } }); // 初始化页面既有控件预览 $$('.icon-ctl').forEach(updateIconPreview); } /* ---------- 复制 ---------- */ function copyText(text, btnEl) { function done(ok) { if (!btnEl) return; var old = btnEl.textContent; btnEl.textContent = ok ? '✓ 已复制' : '复制失败'; setTimeout(function () { btnEl.textContent = old; }, 1500); } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(function () { done(true); }, function () { fallbackCopy(text, done); }); } else { fallbackCopy(text, done); } } function fallbackCopy(text, done) { var ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;opacity:0;'; document.body.appendChild(ta); ta.select(); try { done(document.execCommand('copy')); } catch (e) { done(false); } document.body.removeChild(ta); } function initCopyButtons() { document.addEventListener('click', function (ev) { var btn = ev.target.closest ? ev.target.closest('.js-copy') : null; if (!btn) return; var text = btn.getAttribute('data-copy'); if (text === null || text === undefined) { var target = $(btn.getAttribute('data-target') || ''); text = target ? target.value : ''; } copyText(text, btn); }); } /* ---------- 站点风格确认弹窗(回车确认 / Esc 取消 / 点击遮罩取消) ---------- */ function escHtml(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function siteConfirm(message, opts) { opts = opts || {}; return new Promise(function (resolve) { var danger = opts.danger !== false; var okText = opts.okText || (danger ? '删除' : '确认'); var overlay = document.createElement('div'); overlay.className = 'sc-overlay'; overlay.innerHTML = '
' + '
' + escHtml(opts.title || '操作确认') + '
' + '
' + escHtml(message || '') + '
' + '
' + '' + '' + '
'; var okBtn = overlay.querySelector('.sc-ok'); var cancelBtn = overlay.querySelector('.sc-cancel'); var finished = false; function close(val) { if (finished) return; finished = true; document.removeEventListener('keydown', onKey, true); if (overlay.parentNode) overlay.parentNode.removeChild(overlay); resolve(val); } function onKey(ev) { if (ev.key === 'Escape') { ev.preventDefault(); close(false); } else if (ev.key === 'Enter') { ev.preventDefault(); close(true); } } okBtn.addEventListener('click', function () { close(true); }); cancelBtn.addEventListener('click', function () { close(false); }); overlay.addEventListener('mousedown', function (ev) { if (ev.target === overlay) close(false); }); document.addEventListener('keydown', onKey, true); document.body.appendChild(overlay); okBtn.focus(); }); } // 统一入口:旧浏览器/异常时回落原生 confirm(仍然以 Promise 风格使用) function askConfirm(message, opts) { return siteConfirm(message, opts); } // 表单:带 data-confirm="提示文案" 的表单提交前弹窗确认 function bindConfirmForms() { document.addEventListener('submit', function (ev) { var f = ev.target; if (!f || !f.getAttribute || !f.hasAttribute('data-confirm')) return; ev.preventDefault(); siteConfirm(f.getAttribute('data-confirm')).then(function (ok) { if (ok) { f.submit(); } else { hpClearScrollY(); } }); }); } /* ---------- 同页 POST 提交后保持滚动位置(整页刷新不再跳回顶部) ---------- */ var HP_SCROLL_KEY = 'hp-scroll-pos'; function hpSamePagePost(f) { if (!f || !f.method) return false; if (!/post/i.test(f.method)) return false; if (f.target === '_blank') return false; var a = (f.getAttribute('action') || '').trim(); if (a === '') return true; // 无 action = 提交当前页 try { var cur = new URL(location.href.split('#')[0]); var u = new URL(a, cur.href); return u.origin === cur.origin && u.pathname === cur.pathname; } catch (e) { return false; } } function hpSaveScrollY() { try { var y = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; sessionStorage.setItem(HP_SCROLL_KEY, JSON.stringify({ p: location.pathname + location.search, y: y })); } catch (e) { /* ignore */ } } function hpClearScrollY() { try { sessionStorage.removeItem(HP_SCROLL_KEY); } catch (e) { /* ignore */ } } var hpScrollTarget = -1; function hpRestoreScrollY() { if (hpScrollTarget >= 0) { window.scrollTo(0, hpScrollTarget); return; } try { var raw = sessionStorage.getItem(HP_SCROLL_KEY); if (!raw) return; sessionStorage.removeItem(HP_SCROLL_KEY); var d = JSON.parse(raw); if (d && d.p === (location.pathname + location.search) && typeof d.y === 'number' && d.y > 0) { hpScrollTarget = d.y; window.scrollTo(0, hpScrollTarget); } } catch (e) { hpClearScrollY(); } } // 捕获阶段:同页 POST 提交前保存(按回车等也会触发 submit 事件) document.addEventListener('submit', function (ev) { if (hpSamePagePost(ev.target)) hpSaveScrollY(); }, true); // 捕获阶段:点击提交按钮时也保存(bindConfirmForms 确认后走 f.submit() 不触发 submit 事件) document.addEventListener('click', function (ev) { var t = ev.target; var btn = (t && t.closest) ? t.closest('button[type="submit"], input[type="submit"], button:not([type])') : null; if (!btn) return; var f = btn.form || null; if (hpSamePagePost(f)) hpSaveScrollY(); }, true); // 资源加载完成后再定位一次,防止图片等异步资源改变高度造成轻微偏移 window.addEventListener('load', function () { if (hpScrollTarget >= 0) { window.scrollTo(0, hpScrollTarget); hpScrollTarget = -1; } }); /* ---------- 首页访客信息(hero 信息模式:补齐屏幕分辨率 / 系统语言) ---------- */ function initVisitorInfo() { var box = $('.js-visitor-box'); if (!box) return; var langEl = $('.js-vlang', box); if (langEl) { var lang = ''; try { lang = navigator.language || (navigator.languages && navigator.languages[0]) || ''; } catch (e) { /* ignore */ } langEl.textContent = lang || '--'; } var scrEl = $('.js-vscreen', box); if (scrEl) { var w = 0, h = 0, dpr = 1; try { w = window.screen.width || 0; h = window.screen.height || 0; dpr = window.devicePixelRatio || 1; } catch (e) { /* ignore */ } if (w && h) { var ratio = Math.round(dpr * 100); scrEl.textContent = (ratio !== 100) ? (w + ' × ' + h + '(' + ratio + '%)') : (w + ' × ' + h); } } } /* ---------- 返回顶部(右下角按钮;滚动后出现,回到顶部即隐藏) ---------- */ function initToTop() { var btn = document.querySelector('.js-to-top'); if (!btn) return; var doc = document.scrollingElement || document.documentElement; var containers = []; var ticking = false; function currentY() { var y = window.pageYOffset || (doc && doc.scrollTop) || 0; for (var i = 0; i < containers.length; i++) { var t = containers[i].scrollTop || 0; if (t > y) y = t; } return y; } function apply() { ticking = false; btn.classList.toggle('show', currentY() > 180); } function schedule() { if (ticking) return; ticking = true; if (window.requestAnimationFrame) window.requestAnimationFrame(apply); else setTimeout(apply, 30); } // 捕获阶段监听:主文档与任意内滚容器的滚动都能收到,避免漏触发 document.addEventListener('scroll', schedule, true); window.addEventListener('resize', schedule, { passive: true }); // 采集内滚容器:DOM 就绪与资源加载后各测一次(布局完成后才能正确判断是否溢出) function collect() { var list = document.querySelectorAll('main, section, article, div, ul, ol'); Array.prototype.forEach.call(list, function (el) { if (el === btn || containers.indexOf(el) >= 0) return; if (el.closest('.admin-side, .hp-toasts, .art-topbar')) return; // 侧栏/弹窗/顶栏自身滚动不应触发 if (el.scrollHeight <= el.clientHeight + 40) return; var cs; try { cs = window.getComputedStyle(el); } catch (e) { return; } if (cs.position === 'fixed' || cs.position === 'sticky') return; if (cs.overflowY === 'auto' || cs.overflowY === 'scroll') containers.push(el); }); apply(); } collect(); if (document.readyState !== 'complete') window.addEventListener('load', collect); btn.addEventListener('click', function () { btn.classList.remove('show'); // 乐观隐藏,避免到顶后仍残留 for (var i = 0; i < containers.length; i++) { if (containers[i].scrollTop > 0) containers[i].scrollTop = 0; } if (doc && doc.scrollTop > 0) { try { doc.scrollTo({ top: 0, behavior: 'smooth' }); } catch (e) { doc.scrollTop = 0; } } else { try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch (e) { window.scrollTo(0, 0); } } schedule(); }); schedule(); } /* ---------- 后台侧边栏:收缩 / 移动端抽屉 / 本页功能锚点 ---------- */ function initAdminSide() { var side = document.querySelector('.admin-side'); if (!side) return; var body = document.body; var mq = window.matchMedia ? window.matchMedia('(max-width: 900px)') : null; function isMobile() { return mq ? mq.matches : window.innerWidth <= 900; } // 收缩(桌面)/ 抽屉开关(小屏) Array.prototype.forEach.call(document.querySelectorAll('.js-admin-side-toggle'), function (btn) { btn.addEventListener('click', function () { if (isMobile()) { body.classList.toggle('hp-side-open'); } else { var collapsed = body.classList.toggle('hp-side-collapsed'); try { localStorage.setItem('hp-admin-side-collapsed', collapsed ? '1' : '0'); } catch (e) { /* ignore */ } } }); }); var mask = document.getElementById('adminSideMask'); if (mask) mask.addEventListener('click', function () { body.classList.remove('hp-side-open'); }); window.addEventListener('resize', function () { if (!isMobile()) body.classList.remove('hp-side-open'); }); Array.prototype.forEach.call(side.querySelectorAll('a'), function (a) { a.addEventListener('click', function () { if (isMobile()) body.classList.remove('hp-side-open'); }); }); // 采集当前页“本页功能”:fieldset-card 的标题 var secWrap = document.getElementById('adminSideSections'); var secList = document.getElementById('adminSideSectionList'); if (!secWrap || !secList) return; var items = []; Array.prototype.forEach.call(document.querySelectorAll('.fieldset-card'), function (card) { var t = card.querySelector('.fs-title'); if (!t) return; var clone = t.cloneNode(true); Array.prototype.forEach.call(clone.querySelectorAll('.tip'), function (n) { if (n.parentNode) n.parentNode.removeChild(n); }); var text = (clone.textContent || '').replace(/\s+/g, ' ').trim(); if (text === '') return; items.push({ el: card, text: text }); }); if (items.length < 1) return; var idx = 0; items.forEach(function (it) { idx++; if (!it.el.id) it.el.id = 'hp-sec-' + idx; var a = document.createElement('a'); a.className = 'as-sub'; a.href = '#' + it.el.id; a.textContent = it.text; a.title = it.text; a.addEventListener('click', function (ev) { ev.preventDefault(); try { it.el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) { it.el.scrollIntoView(); } }); secList.appendChild(a); }); secWrap.hidden = false; // 高亮当前可视区块 var subs = secList.querySelectorAll('.as-sub'); function spy() { var best = -1, bestTop = -Infinity; for (var i = 0; i < items.length; i++) { var top = items[i].el.getBoundingClientRect().top - 120; if (top <= 0 && top > bestTop) { bestTop = top; best = i; } } for (var j = 0; j < subs.length; j++) subs[j].classList.toggle('active', j === best); } var st = false; function onScroll() { if (st) return; st = true; if (window.requestAnimationFrame) window.requestAnimationFrame(function () { st = false; spy(); }); else { st = false; spy(); } } document.addEventListener('scroll', onScroll, true); window.addEventListener('resize', onScroll); spy(); } /* ---------- 登录页:采集设备信息(系统 / 机型 / 屏幕等,能拿到什么就带什么)随表单一并提交 ---------- */ function initLoginDevice() { var field = document.getElementById('devField'); if (!field) return; function baseInfo() { var info = {}; try { info.ua = navigator.userAgent || ''; info.platform = navigator.platform || ''; info.lang = navigator.language || ''; info.screen = (window.screen && screen.width && screen.height) ? (screen.width + '×' + screen.height + '@' + (window.devicePixelRatio || 1)) : ''; info.touch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); } catch (e) { /* ignore */ } return info; } function fill() { try { field.value = JSON.stringify(baseInfo()); } catch (e) { /* ignore */ } } fill(); // Chromium 系 UA-CH:可拿到机型(model)、系统版本、架构等高熵值 try { if (navigator.userAgentData && navigator.userAgentData.getHighEntropyValues) { navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion', 'architecture', 'model', 'uaFullVersion']) .then(function (h) { var info = baseInfo(); info.uch = h || {}; field.value = JSON.stringify(info); }) .catch(function () { /* ignore */ }); } } catch (e) { /* ignore */ } } /* ---------- 风控管理页:筛选 / 翻页局部刷新(不整页跳转、不回到顶部) ---------- */ function initVisitsAjax() { var boxes = { access: document.getElementById('visResults'), loginfail: document.getElementById('lfResults') }; if (!boxes.access && !boxes.loginfail) return; function serialize(form) { var parts = []; var els = form.elements; for (var i = 0; i < els.length; i++) { var el = els[i]; if (!el.name || el.disabled) continue; var t = (el.type || '').toLowerCase(); if ((t === 'checkbox' || t === 'radio') && !el.checked) continue; parts.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value)); } return parts.join('&'); } function load(section, query, pushUrl) { var box = boxes[section]; if (!box) return; box.classList.add('vis-loading'); var xhr = new XMLHttpRequest(); try { xhr.open('GET', 'visits_data.php?section=' + encodeURIComponent(section) + (query ? ('&' + query) : ''), true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; box.classList.remove('vis-loading'); if (xhr.status === 200) { box.innerHTML = xhr.responseText; if (pushUrl && history.replaceState) { history.replaceState(null, '', 'visits.php' + (query ? ('?' + query) : '')); } } else { box.innerHTML = '
加载失败(可能登录状态已失效),请刷新页面后重试。
'; } }; xhr.send(); } catch (e) { box.classList.remove('vis-loading'); } } // 筛选表单(事件委托,局部刷新后依然有效) document.addEventListener('submit', function (ev) { var form = ev.target; if (!form || !form.getAttribute || !form.hasAttribute('data-vis-form')) return; if (!/get/i.test(form.method || '')) return; ev.preventDefault(); load(form.getAttribute('data-vis-form'), serialize(form), true); }, true); // 分页链接(委托到 document,避开局部刷新后监听丢失) document.addEventListener('click', function (ev) { var a = (ev.target && ev.target.closest) ? ev.target.closest('a[data-vis-page]') : null; if (!a) return; ev.preventDefault(); load(a.getAttribute('data-vis-section') || 'access', a.getAttribute('data-vis-page') || '', true); }, true); } /* ---------- 功能区静态工具页:库卡片 ?from=nav 进入时,返回按钮改为“返回上一页” ---------- */ function initFuncBack() { var el = document.querySelector('.js-fb-back'); if (!el) return; if (!/(?:^|[?&])from=nav(?:&|$)/.test(location.search)) return; el.textContent = '← 返回上一页'; el.title = '返回上一页(回到来源库首页)'; el.addEventListener('click', function (ev) { // 有浏览历史时返回来源页;否则保留默认 href(功能区首页) if (window.history.length > 1) { ev.preventDefault(); window.history.back(); } }); } /* ---------- 右下角 Toast 通知(后台整页刷新后的成功/失败提示使用,不影响页面布局) ---------- */ function hpToast(kind, text, dur) { if (text === '' || text === null || typeof text === 'undefined') return; kind = (kind === 'ok' || kind === 'err') ? kind : 'ok'; var wrap = document.querySelector('.hp-toasts'); if (!wrap) { wrap = document.createElement('div'); wrap.className = 'hp-toasts'; document.body.appendChild(wrap); } var box = document.createElement('div'); box.className = 'hp-toast ' + kind; var ic = document.createElement('span'); ic.className = 'ht-ic'; ic.textContent = kind === 'ok' ? '✓' : '!'; var tx = document.createElement('div'); tx.className = 'ht-tx'; tx.textContent = String(text); var x = document.createElement('button'); x.type = 'button'; x.className = 'ht-x'; x.setAttribute('aria-label', '关闭提示'); x.textContent = '×'; function close() { if (!box.parentNode) return; box.classList.add('hide'); setTimeout(function () { if (box.parentNode) box.parentNode.removeChild(box); if (wrap.parentNode && !wrap.children.length) wrap.parentNode.removeChild(wrap); }, 240); } x.addEventListener('click', close); box.appendChild(ic); box.appendChild(tx); box.appendChild(x); wrap.appendChild(box); var delay = (typeof dur === 'number' && dur > 0) ? dur : (kind === 'err' ? 6000 : 3600); setTimeout(close, delay); } /* 由 PHP 注入的整页操作提示(window.hpToastMsg = {kind, text})自动弹出 */ function hpShowPageToast() { var m = window.hpToastMsg; if (!m || typeof m !== 'object') return; hpToast(m.kind, m.text); } /* ---------- 静态工具页 favicon 跟随后台站点 logo(api/site.php 探测,异常静默回退默认) ---------- */ function initSiteIcon() { // 仅纯静态 .html 页需要(PHP 页面 favicon 已由 layout_head 服务端输出) if (!/\.html$/i.test(location.pathname)) return; var iconEl = document.querySelector('link[rel~="icon"]'); if (!iconEl) return; var abs = ''; try { abs = new URL(iconEl.getAttribute('href') || '', location.href).href; } catch (e) { return; } var marker = '/assets/img/logo.svg'; var pos = abs.indexOf(marker); if (pos < 0) return; // 非默认图标(已自定义)则无需处理 var root = abs.slice(0, pos + 1); var xhr = new XMLHttpRequest(); try { xhr.open('GET', root + 'api/site.php', true); xhr.timeout = 4000; xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; try { var d = JSON.parse(xhr.responseText || '{}'); if (d && typeof d.logo === 'string' && d.logo !== '') { iconEl.href = root + d.logo; } } catch (e) { /* 接口异常:保持默认图标 */ } }; xhr.onerror = function () { /* file:// 或网络异常:保持默认图标 */ }; xhr.send(); } catch (e) { /* 同源限制等:保持默认图标 */ } } /* ---------- 自动初始化 ---------- */ document.addEventListener('DOMContentLoaded', function () { initSiteIcon(); initTheme(); initClocks(); initQuoteRefresh(); initBoardHover(); initSearch(); initIconControls(); initCopyButtons(); bindConfirmForms(); initVisitorInfo(); initLoginDevice(); initVisitsAjax(); initAdminSide(); initToTop(); initFuncBack(); hpShowPageToast(); }); global.HomePage = { $: $, $$: $$, copyText: copyText, applyTheme: applyTheme, currentTheme: currentTheme, confirm: siteConfirm, ask: askConfirm, toast: hpToast }; // 底部脚本执行时 DOM 已解析完毕:在首帧绘制前后尽早恢复滚动位置,避免“先停在顶部再跳回”的大幅跳动 hpRestoreScrollY(); })(window);