| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794 |
- <?php require_once __DIR__ . '/../init.php'; ?><!doctype html>
- <html lang="zh-CN">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width,initial-scale=1" />
- <meta name="color-scheme" content="light dark" />
- <title>BCJD 导航</title>
- <style>
- :root{
- --bg: #0b1020;
- --panel: rgba(255,255,255,.06);
- --panel-2: rgba(255,255,255,.10);
- --text: rgba(255,255,255,.92);
- --muted: rgba(255,255,255,.65);
- --border: rgba(255,255,255,.12);
- --shadow: 0 10px 30px rgba(0,0,0,.35);
- --accent: #6ea8ff;
- --accent2: #7cf7d4;
- --danger: #ff6b6b;
- --ok: #3ddc97;
- --max: 1120px;
- --radius: 18px;
- --radius-sm: 12px;
- --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "PingFang SC", "Noto Sans CJK SC", "Microsoft YaHei", Arial, sans-serif;
- }
- html[data-theme="light"]{
- --bg: #f6f7fb;
- --panel: rgba(0,0,0,.04);
- --panel-2: rgba(0,0,0,.06);
- --text: rgba(0,0,0,.88);
- --muted: rgba(0,0,0,.58);
- --border: rgba(0,0,0,.10);
- --shadow: 0 10px 30px rgba(0,0,0,.12);
- --accent: #245bdb;
- --accent2: #0aa37f;
- --danger: #d64545;
- --ok: #0a7f5f;
- }
- @media (prefers-color-scheme: light){
- html:not([data-theme="dark"]){
- --bg: #f6f7fb;
- --panel: rgba(0,0,0,.04);
- --panel-2: rgba(0,0,0,.06);
- --text: rgba(0,0,0,.88);
- --muted: rgba(0,0,0,.58);
- --border: rgba(0,0,0,.10);
- --shadow: 0 10px 30px rgba(0,0,0,.12);
- --accent: #245bdb;
- --accent2: #0aa37f;
- --danger: #d64545;
- --ok: #0a7f5f;
- }
- }
- *{ box-sizing:border-box; }
- html{ scroll-behavior:smooth; }
- body{
- margin:0;
- min-height:100vh;
- font-family: var(--sans);
- color: var(--text);
- background:
- radial-gradient(1000px 520px at 12% 12%, rgba(110,168,255,.18), transparent 55%),
- radial-gradient(900px 460px at 88% 20%, rgba(124,247,212,.14), transparent 55%),
- radial-gradient(860px 520px at 35% 100%, rgba(255,107,107,.08), transparent 55%),
- var(--bg);
- }
- a{ color:inherit; text-decoration:none; }
- .wrap{
- max-width: var(--max);
- margin: 0 auto;
- padding: 30px 18px 44px;
- }
- header{
- display:flex;
- justify-content:space-between;
- align-items:flex-start;
- gap:18px;
- margin-bottom: 18px;
- }
- .brand{
- display:flex;
- flex-direction:column;
- gap:6px;
- }
- .brand h1{
- margin:0;
- font-size: 24px;
- font-weight: 800;
- letter-spacing:.2px;
- }
- .brand .sub{
- font-size:13px;
- color: var(--muted);
- line-height:1.45;
- }
- .toolbar{
- display:flex;
- align-items:center;
- justify-content:flex-end;
- gap:10px;
- flex-wrap:wrap;
- }
- .pill,
- button{
- height: 42px;
- display:inline-flex;
- align-items:center;
- justify-content:center;
- border-radius:999px;
- border:1px solid var(--border);
- background: var(--panel);
- box-shadow: var(--shadow);
- backdrop-filter: blur(10px);
- color: var(--text);
- }
- .pill{
- padding: 0 14px;
- font-family: var(--mono);
- white-space:nowrap;
- }
- button{
- padding: 0 14px;
- cursor:pointer;
- transition: transform .06s ease, background .15s ease, border-color .15s ease;
- font-family: var(--sans);
- font-size:13px;
- }
- button:hover{
- background: var(--panel-2);
- border-color: rgba(110,168,255,.28);
- }
- button:active{ transform: translateY(1px); }
- .search{
- display:flex;
- align-items:center;
- gap:10px;
- min-width: min(560px, 100%);
- margin-bottom: 22px;
- padding: 13px 14px;
- border-radius: 999px;
- border: 1px solid var(--border);
- background: var(--panel);
- backdrop-filter: blur(10px);
- box-shadow: var(--shadow);
- }
- .search input{
- width:100%;
- border:0;
- outline:none;
- background: transparent;
- color: var(--text);
- font-size:14px;
- font-family: var(--sans);
- }
- .search input::placeholder{ color: var(--muted); }
- .search kbd{
- padding: 3px 8px;
- border-radius: 8px;
- border: 1px solid var(--border);
- background: var(--panel-2);
- color: var(--muted);
- font-size:12px;
- font-family: var(--mono);
- }
- .board{
- display:flex;
- flex-direction:column;
- gap: 22px;
- }
- .section-block{
- display:flex;
- flex-direction:column;
- gap: 12px;
- }
- .section{
- display:flex;
- align-items:flex-end;
- justify-content:space-between;
- gap:10px;
- padding: 0 2px;
- }
- .section h2{
- margin:0;
- font-size: 13px;
- font-weight: 800;
- letter-spacing: .9px;
- text-transform: uppercase;
- color: var(--muted);
- }
- .section .hint{
- font-size: 12px;
- color: var(--muted);
- }
- .grid{
- display:grid;
- grid-template-columns: repeat(12, 1fr);
- gap: 14px;
- }
- .card{
- grid-column: span 4;
- min-height: 138px;
- padding: 14px 14px 12px;
- border-radius: var(--radius);
- border: 1px solid var(--border);
- background:
- linear-gradient(180deg, rgba(255,255,255,.06), transparent 26%),
- linear-gradient(180deg, var(--panel), transparent 120%);
- box-shadow: var(--shadow);
- backdrop-filter: blur(10px);
- display:flex;
- flex-direction:column;
- gap:10px;
- position:relative;
- overflow:hidden;
- transition: transform .10s ease, border-color .15s ease, background .15s ease;
- }
- .card::before{
- content:"";
- position:absolute;
- top:0; left:0; right:0;
- height: 1px;
- background: linear-gradient(90deg, transparent, rgba(255,255,255,.32), transparent);
- opacity:.75;
- pointer-events:none;
- }
- .card:hover{
- transform: translateY(-2px);
- border-color: rgba(110,168,255,.35);
- background:
- linear-gradient(180deg, rgba(255,255,255,.08), transparent 26%),
- linear-gradient(180deg, var(--panel-2), transparent 130%);
- }
- .top{
- display:flex;
- align-items:flex-start;
- justify-content:space-between;
- gap:10px;
- }
- .title{
- display:flex;
- align-items:center;
- gap:10px;
- min-width:0;
- }
- .icon{
- width:40px;
- height:40px;
- flex:0 0 auto;
- display:grid;
- place-items:center;
- border-radius: 13px;
- border: 1px solid var(--border);
- background:
- radial-gradient(18px 18px at 30% 30%, rgba(124,247,212,.35), transparent 60%),
- radial-gradient(18px 18px at 70% 70%, rgba(110,168,255,.32), transparent 60%),
- rgba(255,255,255,.06);
- }
- .icon svg{
- width:20px;
- height:20px;
- opacity:.92;
- }
- .name{
- font-size:15px;
- font-weight:780;
- white-space:nowrap;
- overflow:hidden;
- text-overflow:ellipsis;
- }
- .meta{
- margin-top:2px;
- color: var(--muted);
- font-size:11px;
- font-family: var(--mono);
- white-space:nowrap;
- overflow:hidden;
- text-overflow:ellipsis;
- }
- .tags{
- display:flex;
- flex-wrap:wrap;
- gap:8px;
- }
- .tag{
- padding: 4px 8px;
- border-radius:999px;
- border:1px solid var(--border);
- background: var(--panel-2);
- color: var(--muted);
- font-size:11px;
- line-height:1;
- }
- .tag.ok{
- color: var(--ok);
- border-color: rgba(61,220,151,.35);
- background: rgba(61,220,151,.10);
- }
- .tag.warn{
- color: var(--danger);
- border-color: rgba(255,107,107,.35);
- background: rgba(255,107,107,.08);
- }
- .desc{
- color: var(--muted);
- font-size:13px;
- line-height:1.4;
- display:-webkit-box;
- -webkit-line-clamp:2;
- -webkit-box-orient:vertical;
- overflow:hidden;
- min-height: 36px;
- }
- .actions{
- margin-top:auto;
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap:8px;
- }
- .go{
- display:inline-flex;
- align-items:center;
- gap:8px;
- padding: 9px 12px;
- border-radius: var(--radius-sm);
- border: 1px solid rgba(110,168,255,.35);
- background: rgba(110,168,255,.14);
- font-size:13px;
- font-weight:730;
- }
- .go:hover{ background: rgba(110,168,255,.20); }
- .small{
- max-width:48%;
- white-space:nowrap;
- overflow:hidden;
- text-overflow:ellipsis;
- text-align:right;
- color: var(--muted);
- font-size:12px;
- font-family: var(--mono);
- }
- .empty{
- display:none;
- padding: 18px 16px;
- border-radius: 16px;
- border: 1px dashed var(--border);
- background: var(--panel);
- color: var(--muted);
- text-align:center;
- box-shadow: var(--shadow);
- }
- footer{
- margin-top: 24px;
- padding-top: 14px;
- border-top: 1px solid var(--border);
- display:flex;
- justify-content:space-between;
- gap:12px;
- flex-wrap:wrap;
- color: var(--muted);
- font-size:12px;
- }
- .mono{ font-family: var(--mono); }
- @media (max-width: 980px){
- .card{ grid-column: span 6; }
- .search{ min-width: 100%; }
- }
- @media (max-width: 640px){
- header{
- flex-direction:column;
- align-items:stretch;
- }
- .toolbar{
- justify-content:flex-start;
- }
- .card{ grid-column: span 12; }
- .small{ display:none; }
- }
- /* ===== 登录模态框 ===== */
- .modal-overlay{
- display:none;
- position:fixed;
- inset:0;
- background:rgba(0,0,0,.55);
- backdrop-filter:blur(4px);
- z-index:1000;
- align-items:center;
- justify-content:center;
- }
- .modal-overlay.active{ display:flex; }
- .modal-box{
- width:380px;
- max-width:92vw;
- padding:32px 28px 28px;
- border-radius:var(--radius);
- border:1px solid var(--border);
- background:var(--bg);
- box-shadow:0 20px 60px rgba(0,0,0,.5);
- }
- .modal-box h2{
- margin:0 0 6px;
- font-size:20px;
- font-weight:800;
- }
- .modal-box p{
- margin:0 0 20px;
- color:var(--muted);
- font-size:14px;
- }
- .modal-box label{
- display:block;
- font-size:13px;
- font-weight:600;
- margin-bottom:6px;
- color:var(--muted);
- }
- .modal-box input[type="password"]{
- width:100%;
- height:44px;
- padding:0 14px;
- border-radius:999px;
- border:1px solid var(--border);
- background:var(--panel);
- color:var(--text);
- font-size:15px;
- font-family:var(--sans);
- outline:none;
- transition:border-color .15s;
- }
- .modal-box input[type="password"]:focus{
- border-color:var(--accent);
- }
- .modal-box .btn-row{
- display:flex;
- gap:10px;
- margin-top:20px;
- }
- .modal-box .btn-row button{
- flex:1;
- height:44px;
- font-size:14px;
- font-weight:600;
- }
- .modal-box .btn-primary{
- background:var(--accent);
- border-color:var(--accent);
- color:#fff;
- }
- .modal-box .btn-primary:hover{
- opacity:.88;
- border-color:var(--accent);
- }
- .modal-box .modal-error{
- display:none;
- margin-top:12px;
- padding:10px 14px;
- border-radius:var(--radius-sm);
- background:rgba(255,107,107,.12);
- border:1px solid rgba(255,107,107,.3);
- color:var(--danger);
- font-size:13px;
- }
- /* ===== 私有链接标识 ===== */
- .card.private{
- border-left:3px solid var(--danger);
- }
- </style>
- </head>
- <body>
- <div class="wrap">
- <header>
- <div class="brand">
- <h1>BCJD 服务导航</h1>
- <div class="sub">统一入口 · 快速跳转 · 显示时区:<span class="mono">UTC+8</span></div>
- </div>
- <div class="toolbar">
- <div class="pill" id="clock">--:--:-- (UTC+8)</div>
- <button id="themeBtn" title="切换主题">主题:跟随系统</button>
- <button id="loginBtn" title="登录">登录</button>
- <button id="adminBtn" title="管理" style="display:none">管理</button>
- </div>
- </header>
- <div class="search" role="search" aria-label="Search">
- <span class="mono" style="opacity:.7">/</span>
- <input id="q" placeholder="搜索服务…(回车打开第一个)" autocomplete="off" />
- <kbd>Enter</kbd>
- </div>
- <!-- 加载状态 -->
- <div id="loading" style="text-align:center;padding:40px;color:var(--muted);font-size:14px">加载中…</div>
- <div class="board" id="board" style="display:none">
- <!-- 由 JS 动态渲染 -->
- <div class="empty" id="emptyState">没有找到匹配的服务。</div>
- </div>
- <footer>
- <div>提示:按 <span class="mono">/</span> 聚焦搜索;按 <span class="mono">Enter</span> 打开第一个结果;按 <span class="mono">Esc</span> 清空搜索。</div>
- <div class="mono">Updated (UTC+8): <span id="ts">--</span></div>
- </footer>
- </div>
- <!-- ===== 登录模态框 ===== -->
- <div class="modal-overlay" id="loginModal">
- <div class="modal-box">
- <h2>登录</h2>
- <p>请输入站点密码以解锁全部链接。</p>
- <label for="loginPwd">密码</label>
- <input type="password" id="loginPwd" placeholder="输入密码" autocomplete="current-password" />
- <div class="btn-row">
- <button id="loginCancel">取消</button>
- <button id="loginConfirm" class="btn-primary">登录</button>
- </div>
- <div class="modal-error" id="loginError"></div>
- </div>
- </div>
- <script>
- (function(){
- 'use strict';
- const $ = (s) => document.querySelector(s);
- const $$ = (s) => [...document.querySelectorAll(s)];
- const board = $("#board");
- const loading = $("#loading");
- const emptyState = $("#emptyState");
- const q = $("#q");
- const loginBtn = $("#loginBtn");
- const adminBtn = $("#adminBtn");
- const loginModal = $("#loginModal");
- const loginPwd = $("#loginPwd");
- const loginError = $("#loginError");
- const loginCancel = $("#loginCancel");
- const loginConfirm = $("#loginConfirm");
- let loggedIn = false;
- let allGroups = [];
- // ===== 时钟 =====
- const TZ = "Asia/Shanghai";
- const fmtClock = new Intl.DateTimeFormat("zh-CN", {
- timeZone: TZ, hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
- });
- function formatYMDHMS_UTC8(d){
- const parts = new Intl.DateTimeFormat("zh-CN", {
- timeZone: TZ, year: "numeric", month: "2-digit", day: "2-digit",
- hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false,
- }).formatToParts(d);
- const get = (t) => parts.find(p => p.type === t)?.value || "";
- return `${get("year")}-${get("month")}-${get("day")} ${get("hour")}:${get("minute")}:${get("second")}`;
- }
- function tick(){
- const d = new Date();
- $("#clock").textContent = `${fmtClock.format(d)} (UTC+8)`;
- $("#ts").textContent = formatYMDHMS_UTC8(d);
- }
- tick(); setInterval(tick, 1000);
- // ===== 主题切换 =====
- const themeBtn = $("#themeBtn");
- const root = document.documentElement;
- function getThemeLabel(){
- const cur = root.getAttribute("data-theme");
- if(cur === "dark") return "主题:深色";
- if(cur === "light") return "主题:浅色";
- return "主题:跟随系统";
- }
- function refreshThemeLabel(){ themeBtn.textContent = getThemeLabel(); }
- function setTheme(mode){
- if(!mode){ root.removeAttribute("data-theme"); localStorage.removeItem("theme"); refreshThemeLabel(); return; }
- root.setAttribute("data-theme", mode); localStorage.setItem("theme", mode); refreshThemeLabel();
- }
- const saved = localStorage.getItem("theme");
- if(saved === "dark" || saved === "light") setTheme(saved); else refreshThemeLabel();
- themeBtn.addEventListener("click", () => {
- const cur = root.getAttribute("data-theme");
- if(cur === "dark") setTheme("light"); else if(cur === "light") setTheme(null); else setTheme("dark");
- });
- // ===== API 请求 =====
- async function apiGet(action){
- const r = await fetch(`api.php?action=${action}`);
- if(!r.ok) { const e = await r.json(); throw new Error(e.error || '请求失败'); }
- return r.json();
- }
- async function apiPost(action, data){
- const fd = new URLSearchParams();
- for(const [k,v] of Object.entries(data)) fd.append(k, v);
- const r = await fetch(`api.php?action=${action}`, {
- method: 'POST',
- headers: {'Content-Type':'application/x-www-form-urlencoded'},
- body: fd.toString(),
- });
- if(!r.ok) { const e = await r.json(); throw new Error(e.error || '请求失败'); }
- return r.json();
- }
- // ===== 渲染链接 =====
- function renderLinks(groups){
- board.innerHTML = '';
- for(const g of groups){
- if(!g.links || g.links.length === 0) continue;
- const section = document.createElement('section');
- section.className = 'section-block';
- section.dataset.group = g.name;
- const header = document.createElement('div');
- header.className = 'section';
- header.innerHTML = `<h2>${esc(g.label || g.name)}</h2><div class="hint">${g.hint || ''}</div>`;
- const grid = document.createElement('div');
- grid.className = 'grid';
- for(const link of g.links){
- const card = document.createElement('a');
- card.className = 'card' + (link.is_private ? ' private' : '');
- card.href = link.url;
- card.target = '_blank';
- card.rel = 'noopener';
- card.dataset.group = g.name;
- card.dataset.keywords = (link.keywords || '') + ' ' + link.title + ' ' + (link.description || '');
- const tagClass = link.tag_class ? `tag ${link.tag_class}` : 'tag';
- card.innerHTML = `
- <div class="top">
- <div class="title">
- <div class="icon" aria-hidden="true">${link.icon_svg || ''}</div>
- <div style="min-width:0">
- <div class="name">${esc(link.title)}</div>
- <div class="meta">${esc(link.meta_domain)}</div>
- </div>
- </div>
- <div class="tags">
- ${link.tag_label ? `<span class="${tagClass}">${esc(link.tag_label)}</span>` : ''}
- ${link.is_private ? '<span class="tag" style="color:var(--danger);border-color:rgba(255,107,107,.3);background:rgba(255,107,107,.08)">🔒 私有</span>' : ''}
- </div>
- </div>
- <div class="desc">${esc(link.description)}</div>
- <div class="actions">
- <span class="go">打开 →</span>
- <span class="small">${link.url.replace(/^https?:\/\//,'').split('/')[0]}</span>
- </div>
- `;
- grid.appendChild(card);
- }
- section.appendChild(header);
- section.appendChild(grid);
- board.appendChild(section);
- }
- board.appendChild(emptyState);
- applyFilter();
- }
- function esc(s){ return (s || '').replace(/[&<>"']/g, function(m){
- return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[m];
- });}
- // ===== 获取并渲染链接 =====
- async function loadLinks(){
- loading.style.display = '';
- board.style.display = 'none';
- try {
- const data = await apiGet('get_links');
- loggedIn = data.logged_in;
- allGroups = data.groups || [];
- renderLinks(allGroups);
- loading.style.display = 'none';
- board.style.display = '';
- loginBtn.textContent = loggedIn ? '已登录' : '登录';
- adminBtn.style.display = loggedIn ? '' : 'none';
- } catch(e){
- loading.textContent = '加载失败:' + e.message;
- }
- }
- // ===== 搜索 =====
- function norm(s){ return (s || "").toLowerCase().trim(); }
- function visibleCards(){ return $$(".card").filter(c => c.style.display !== "none"); }
- function updateGroups(){
- for(const group of $$(".section-block")){
- const key = group.dataset.group;
- const count = $$(".card").filter(c => c.dataset.group === key && c.style.display !== "none").length;
- group.style.display = count > 0 ? "" : "none";
- }
- }
- function applyFilter(){
- const v = norm(q.value);
- for(const c of $$(".card")){
- const hay = norm((c.dataset.keywords || "") + " " + c.textContent);
- c.style.display = (!v || hay.includes(v)) ? "" : "none";
- }
- updateGroups();
- emptyState.style.display = visibleCards().length ? "none" : "block";
- }
- q.addEventListener("input", applyFilter);
- q.addEventListener("keydown", (e) => {
- if(e.key === "Enter"){ const first = visibleCards()[0]; if(first) first.click(); }
- if(e.key === "Escape"){ q.value = ""; applyFilter(); q.blur(); }
- });
- document.addEventListener("keydown", (e) => {
- if(e.key === "/" && document.activeElement !== q){ e.preventDefault(); q.focus(); }
- });
- // ===== 登录逻辑 =====
- function showLoginModal(){
- loginModal.classList.add('active'); loginPwd.value = '';
- loginError.style.display = 'none'; loginPwd.focus();
- }
- function hideLoginModal(){ loginModal.classList.remove('active'); }
- loginBtn.addEventListener('click', () => {
- if(loggedIn){
- if(!confirm('确定要退出登录吗?')) return;
- apiPost('logout', {}).then(() => { loggedIn = false; loadLinks(); }).catch(e => alert(e.message));
- } else {
- showLoginModal();
- }
- });
- function doLogin(){
- const pwd = loginPwd.value;
- if(!pwd){ loginError.textContent = '请输入密码'; loginError.style.display = ''; return; }
- loginConfirm.disabled = true; loginConfirm.textContent = '登录中…';
- apiPost('login', {password: pwd}).then(data => {
- if(data.success){ hideLoginModal(); loadLinks(); }
- else { loginError.textContent = data.error || '密码错误'; loginError.style.display = ''; loginConfirm.disabled = false; loginConfirm.textContent = '登录'; }
- }).catch(e => {
- loginError.textContent = e.message; loginError.style.display = '';
- loginConfirm.disabled = false; loginConfirm.textContent = '登录';
- });
- }
- loginConfirm.addEventListener('click', doLogin);
- loginPwd.addEventListener('keydown', (e) => { if(e.key === 'Enter') doLogin(); });
- loginCancel.addEventListener('click', hideLoginModal);
- loginModal.addEventListener('click', (e) => { if(e.target === loginModal) hideLoginModal(); });
- // ===== 管理面板 =====
- adminBtn.addEventListener('click', () => { window.location.href = 'admin.php'; });
- // ===== 启动 =====
- loadLinks();
- })();
- </script>
- </body>
- </html>
|