From 0e7265481f5eb356043c660c324a73e9a2473e31 Mon Sep 17 00:00:00 2001 From: MasonLiu <2857911564@qq.com> Date: Mon, 14 Sep 2026 22:06:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BD=91=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 ++ README.md | 4 +- admin/_visits_lib.php | 18 ++- admin/articles.php | 8 +- admin/content.php | 44 ++++-- admin/exttools.php | 16 ++- admin/login.php | 10 +- admin/nav.php | 10 +- admin/notice.php | 16 ++- admin/tools.php | 14 +- admin/visits.php | 11 +- article/data.php | 70 ---------- assets/css/common.css | 134 +++++++++++++++--- assets/img/logo.jpg | Bin 0 -> 33699 bytes assets/js/common.js | 316 +++++++++++++++++++++++++++++++++++++----- func/_bar.php | 21 --- includes/auth.php | 2 +- includes/db.php | 68 ++++++++- includes/layout.php | 22 --- index.html | 312 +++++++++++++++++++++++++++++++++++++++++ index.php | 196 ++++++++++++++++++++++++-- 操作文档.md | 6 +- 22 files changed, 1071 insertions(+), 238 deletions(-) delete mode 100644 article/data.php create mode 100644 assets/img/logo.jpg delete mode 100644 func/_bar.php create mode 100644 index.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 1100663..1260965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ --- +## 2026-09-14 + +### 优化 + +- 全站清理:移除首页板块悬停浮层的残留样式与脚本、未使用的函数、冗余分支及过时注释(`common.css` / `common.js` / `includes/layout.php` / `admin/login.php` / `includes/auth.php`) +- 删除迁移后遗留的未引用文件:`article/data.php`(文章 JSON 接口,已改为服务端渲染)、`func/_bar.php`(旧 PHP 工具页头部条,工具页已静态化) +- 首页磁贴与板块卡新增依次上浮入场动画;「滚动信息栏」名言点击切换改为淡出淡入;均遵循系统「减少动态效果」设置 +- 优化后台文案:整理「首页顶部组件」模式说明为清单、精简「提示开关」说明 +- 后台体验统一:各内容页(导航 / 内部功能 / 文章 / 外部工具 / 网站 / 公告 / 风控)改用统一的 `.admin-head` 标题区(标题 + 说明 + 操作区 + 分隔线),去除各页零散内联样式 +- 更新说明文档:首页顶部组件模式(现共 10 种)与密码散列方案说明 + ## 2026-09-12 ### 新增 diff --git a/README.md b/README.md index 0a8a056..095bd2b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ - **多库分区**:科普库 / 红队库 / 蓝队库三大板块 + 工具库 + 文章库 - 每个库内分四区:**导航区 · 内部功能区 · 文章区 · 外部工具区** -- 首页顶部组件三种模式(天气 / 访客信息 / 空白精简)可后台切换 -- 快捷站点磁贴,悬停显示备注 +- 首页顶部组件可在后台切换(天气 / 访客信息 / 空白精简 / 渗透测试 / 安全巡检 / 安全态势 / 访问趋势 / 大字时钟 / 公告滚动 / 打字机标语,共 10 种) +- 快捷站点磁贴:圆形图标 + 名称 + 备注一览(悬停亦可查看完整备注) ### 功能区(纯前端,隐私优先) diff --git a/admin/_visits_lib.php b/admin/_visits_lib.php index e6ecc80..cf25c6a 100644 --- a/admin/_visits_lib.php +++ b/admin/_visits_lib.php @@ -36,9 +36,8 @@ function vis_query_access(string $mode, string $ipF, string $q, int $page, int $ $entries = visitors_entries(); $all = []; if ($mode === 'compact') { - // 精简模式:按 IP 聚合(最近访问时间 / IP / 访问次数),首次出现即该 IP 最新一条 + // 精简模式:按 IP 聚合(最近访问时间 / IP / 访问次数),并默认按访问次数从多到少排序 $grp = []; - $order = []; foreach ($entries as $e) { if ($ipF !== '' && stripos($e['ip'], $ipF) === false) { continue; @@ -48,13 +47,18 @@ function vis_query_access(string $mode, string $ipF, string $q, int $page, int $ } if (!isset($grp[$e['ip']])) { $grp[$e['ip']] = ['ip' => $e['ip'], 'last_at' => $e['created_at'], 'cnt' => 0]; - $order[] = $e['ip']; } $grp[$e['ip']]['cnt']++; } - foreach ($order as $ip) { - $all[] = $grp[$ip]; - } + $all = array_values($grp); + // 访问次数多的在前;次数相同按最近访问时间倒序;再相同按 IP 升序 + usort($all, static function (array $a, array $b): int { + if ($a['cnt'] !== $b['cnt']) { + return $b['cnt'] <=> $a['cnt']; + } + $d = strcmp((string) $b['last_at'], (string) $a['last_at']); + return $d !== 0 ? $d : strcmp((string) $a['ip'], (string) $b['ip']); + }); } else { // 完整模式:逐条明细 foreach ($entries as $e) { @@ -117,7 +121,7 @@ function vis_render_access(array $d): string if ($logs) { ?>
- 共命中 ; + 共命中 ; 当前第 / 页,每页 条。
diff --git a/admin/articles.php b/admin/articles.php index 69b5b0a..eee2625 100644 --- a/admin/articles.php +++ b/admin/articles.php @@ -244,8 +244,12 @@ admin_topbar('articles'); ?>
-

文章管理

-

文章库(第五大板块)的文章维护:正文为标准 Markdown,图片统一上传到 data/articles/img 目录。

+
+
+

文章管理

+

文章库(第五大板块)的文章维护:正文为标准 Markdown,图片统一上传到 data/articles/img 目录。

+
+
diff --git a/admin/content.php b/admin/content.php index 4571a03..1ab5253 100644 --- a/admin/content.php +++ b/admin/content.php @@ -162,12 +162,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { break; case 'hero_mode': $mode = (string) ($_POST['hero_mode'] ?? 'weather'); - if (!in_array($mode, ['weather', 'info', 'blank'], true)) { + if (!in_array($mode, ['weather', 'info', 'blank', 'red', 'blue', 'posture', 'trend', 'clock', 'notice', 'slogan'], true)) { $mode = 'weather'; } settings_set('hero_mode', $mode); touch_last_updated(); - $modeText = ['weather' => '天气模式', 'info' => '访客信息模式', 'blank' => '空白精简模式'][$mode]; + $modeText = ['weather' => '天气模式', 'info' => '信息模式', 'blank' => '精简模式', 'red' => '渗透测试', 'blue' => '安全巡检', 'posture' => '安全态势', 'trend' => '访问趋势', 'clock' => '大字时钟', 'notice' => '公告滚动', 'slogan' => '打字机标语'][$mode]; $msgKind = 'ok'; $msgText = '首页顶部组件模式已保存:' . $modeText . '。'; break; @@ -351,8 +351,12 @@ admin_topbar('content'); ?>
-

网站管理

-

对全站展示内容与 logo 进行维护,保存后自动更新首页"上次更新时间"。

+
+
+

网站管理

+

对全站展示内容与 LOGO 进行维护,保存后自动更新首页“上次更新时间”。

+
+
@@ -366,11 +370,9 @@ admin_topbar('content'); - 开启时,前台显示“可切换搜索引擎进行搜索…”等辅助旁白 + 开启时,前台显示搜索栏说明、各分区说明条等辅助旁白
-

前台类似“可切换搜索引擎进行搜索(4 个可用,后台可维护)”、各分区说明条等辅助旁白,以及文案中带后台入口字样的引导句,均带 - st-tip - 标记;关闭后这些文案在全部前台页面统一隐藏,站点导航、搜索、内容等核心功能不受影响。各分区/页面在没有数据时展示的空态占位框(如“该库还没有导航内容”)不受本开关影响,会始终显示以避免空页突兀。

+

上述辅助旁白均带 st-tip 标记,关闭后在全站前台统一隐藏;站点导航、搜索与内容等核心功能不受影响。各分区/页面无数据时的空态占位框(如“该库还没有导航内容”)不受本开关影响,始终显示以避免空页突兀。

@@ -444,15 +446,31 @@ admin_topbar('content');
- - + + + + + + + + +
-

- 天气模式(默认):横幅展示第三方天气条;访客信息模式:横幅显示当前访问者 IP、浏览器、操作系统、内核、屏幕分辨率与系统语言;空白模式:隐藏横幅,仅保留站点信息与时间信息,顶部高度自动缩减。 -

+
diff --git a/admin/exttools.php b/admin/exttools.php index b98e2de..d2aab69 100644 --- a/admin/exttools.php +++ b/admin/exttools.php @@ -133,12 +133,16 @@ admin_topbar('exttools'); ?>
-

外部工具区管理

-

- 链接较多时可先筛选再编辑:按 分区/栏目、启停状态、展示范围、关键词 快速过滤(仅影响当前浏览,不影响保存)。 - 行首“分区 / 栏目”即前台分组标题(如“合集 / 导航”),留空 = 不分组平铺; - 展示范围勾选库,全部不勾选 = 所有库通用。 -

+
+
+

外部工具区管理

+

+ 链接较多时可先筛选再编辑:按 分区/栏目、启停状态、展示范围、关键词 快速过滤(仅影响当前浏览,不影响保存)。 + 行首“分区 / 栏目”即前台分组标题(如“合集 / 导航”),留空 = 不分组平铺; + 展示范围勾选库,全部不勾选 = 所有库通用。 +

+
+
diff --git a/admin/login.php b/admin/login.php index 1b5e4db..43c7e7d 100644 --- a/admin/login.php +++ b/admin/login.php @@ -28,13 +28,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } else { // 轻量防爆破延时(命中黑名单的 IP 在访问本页前即被 access_boot 拦截) usleep(500000); - $risk = risk_login_fail($curIp); - if ($risk[1]) { - $err = '用户名或密码错误。'; - // $err = '登录失败次数过多(本次累计 ' . $risk[0] . ' 次,达到阈值 ' . $risk[2] . ' 次),当前 IP 已被风控自动封禁,全站(含后台登录)访问均被拒绝。请稍后从未被封禁的网络进入后台「风控管理 → IP 黑名单」移除该规则解除,或联系站长处理。'; - } else { - $err = '用户名或密码错误。'; - } + // 累计登录失败次数(达阈值会自动封禁);此处统一返回同一提示,避免暴露账号状态 + risk_login_fail($curIp); + $err = '用户名或密码错误。'; } } diff --git a/admin/nav.php b/admin/nav.php index ace0120..f302a74 100644 --- a/admin/nav.php +++ b/admin/nav.php @@ -156,11 +156,15 @@ admin_topbar('nav'); ?>
-

导航管理

-

管理 的导航块与链接;块内最多 15 个链接(3 小列 × 5 行),超出部分请拆分到新块。

+
+
+

导航管理

+

管理 的导航块与链接;块内最多 15 个链接(3 小列 × 5 行),超出部分请拆分到新块。

+
+
-
+
diff --git a/admin/notice.php b/admin/notice.php index b443094..a66a252 100644 --- a/admin/notice.php +++ b/admin/notice.php @@ -106,12 +106,16 @@ admin_topbar('notice'); ?>
-

更新公告管理

-

- 前台页脚「更新公告」页展示内容:可发布多条公告并保留历史,任意一条均可再次编辑。 - 内容支持超链接,写法 [文字](链接);站内用相对路径(如 /index.php/func/index.php), - 外链用 https://…,也可直接粘贴网址自动识别。换行会被保留。 -

+
+
+

更新公告管理

+

+ 前台页脚「更新公告」页展示内容:可发布多条公告并保留历史,任意一条均可再次编辑。 + 内容支持超链接,写法 [文字](链接);站内用相对路径(如 /index.php/func/index.php), + 外链用 https://…,也可直接粘贴网址自动识别。换行会被保留。 +

+
+
diff --git a/admin/tools.php b/admin/tools.php index a379fda..2438cf6 100644 --- a/admin/tools.php +++ b/admin/tools.php @@ -144,11 +144,15 @@ admin_topbar('tools'); ?>
-

内部功能

-

- 管理功能区首页展示的工具卡片。站内工具页需先在 func/ 下开发对应页面,再到此处登记入口; - 也可登记外部网址作为外链卡片(新窗口打开)。 -

+
+
+

内部功能

+

+ 管理功能区首页展示的工具卡片。站内工具页需先在 func/ 下开发对应页面,再到此处登记入口; + 也可登记外部网址作为外链卡片(新窗口打开)。 +

+
+
diff --git a/admin/visits.php b/admin/visits.php index 4588ad5..58e91da 100644 --- a/admin/visits.php +++ b/admin/visits.php @@ -217,8 +217,13 @@ admin_topbar('visits'); ?>
-

风控管理

-

记录访客对本站的动态请求(含后台),完整模式逐条展示访问明细,精简模式按 IP 汇总次数;黑名单 / 自动风控对所有访问统一生效,命中即返回 403 风控提示页,后台与登录页不例外。

+
+
+

风控管理

+

记录访客对本站的动态请求(含后台),完整模式逐条展示访问明细,精简模式按 IP 汇总次数;黑名单 / 自动风控对所有访问统一生效,命中即返回 403 风控提示页,后台与登录页不例外。 +

+
+
@@ -315,7 +320,7 @@ admin_topbar('visits'); diff --git a/article/data.php b/article/data.php deleted file mode 100644 index 92c52dd..0000000 --- a/article/data.php +++ /dev/null @@ -1,70 +0,0 @@ - false, 'msg' => '缺少文章 id']); - } - $st = $pdo->prepare('SELECT id, part, title, summary, markdown, pinned, created_at, updated_at - FROM articles WHERE id = ? AND enabled = 1 LIMIT 1'); - $st->execute([$id]); - $row = $st->fetch(); - if (!$row) { - json_out(['ok' => false, 'msg' => '文章不存在或已隐藏']); - } - json_out(['ok' => true, 'article' => $row]); -} - -// ---- 默认:list 分组元数据(分区顺序取各分区首篇 id 序;分区内置顶 → sort → id) ---- -$rows = $pdo->query('SELECT id, part, title, summary, pinned, sort, created_at, updated_at - FROM articles WHERE enabled = 1 ORDER BY id ASC')->fetchAll(); -$parts = []; -if ($rows) { - $partOrder = []; - $byPart = []; - foreach ($rows as $r) { - $part = trim((string)$r['part']); - if ($part === '') { - $part = '未分类'; - } - if (!isset($byPart[$part])) { - $byPart[$part] = []; - $partOrder[] = $part; - } - $byPart[$part][] = $r; - } - foreach ($partOrder as $part) { - $items = $byPart[$part]; - usort($items, static function (array $a, array $b): int { - if ((int)$a['pinned'] !== (int)$b['pinned']) { - return (int)$b['pinned'] - (int)$a['pinned']; - } - if ((int)$a['sort'] !== (int)$b['sort']) { - return (int)$a['sort'] <=> (int)$b['sort']; - } - return (int)$a['id'] <=> (int)$b['id']; - }); - $parts[] = ['part' => $part, 'articles' => $items]; - } -} -json_out(['ok' => true, 'parts' => $parts]); diff --git a/assets/css/common.css b/assets/css/common.css index ddbd9e3..c6e0bf8 100644 --- a/assets/css/common.css +++ b/assets/css/common.css @@ -184,6 +184,89 @@ select { width: auto; min-width: 150px; } /* ---------- 首页顶部组件:空白精简模式(hero_mode=blank) ---------- */ .hero-slim { min-height: 0; padding: 16px 0 12px; } +/* ---------- 首页顶部组件:红队/蓝队渗透测试流程模式(hero_mode=red|blue,动态步进) ---------- */ +.ptflow { display: flex; flex-direction: column; gap: 10px; } +.ptflow-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; } +.ptflow-title { font-size: 15px; font-weight: 700; color: #fff; } +.ptflow-title .ptflow-tag { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px; margin-left: 8px; vertical-align: middle; letter-spacing: .5px; } +.ptflow-red .ptflow-title .ptflow-tag { background: rgba(239, 68, 68, .22); color: #fecaca; border: 1px solid rgba(239, 68, 68, .5); } +.ptflow-blue .ptflow-title .ptflow-tag { background: rgba(37, 99, 235, .22); color: #bfdbfe; border: 1px solid rgba(59, 130, 246, .5); } +.ptflow-phase { margin-left: auto; font-size: 12.5px; color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; } +.ptflow-track { display: flex; gap: 8px; margin: 0; padding: 0; list-style: none; position: relative; overflow: hidden; } +.ptflow-track::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, .08) 50%, transparent 100%); animation: ptflowSweep 3.2s linear infinite; } +@keyframes ptflowSweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } } +.ptflow-step { flex: 1 1 0; min-width: 0; position: relative; padding: 9px 10px; border-radius: 10px; background: rgba(10, 22, 44, .32); border: 1px solid rgba(255, 255, 255, .18); transition: transform .35s ease, border-color .35s ease, background .35s ease, box-shadow .35s ease; } +.ptflow-step .ptflow-ic { font-size: 16px; display: block; line-height: 1; } +.ptflow-step b { display: block; color: #fff; font-size: 12.5px; margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.ptflow-step small { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; color: rgba(255, 255, 255, .68); font-size: 11px; line-height: 1.35; margin-top: 2px; } +.ptflow-step.active { transform: translateY(-3px); background: rgba(10, 22, 44, .5); } +.ptflow-red .ptflow-step.active { border-color: #ef4444; box-shadow: 0 8px 22px rgba(239, 68, 68, .35); } +.ptflow-blue .ptflow-step.active { border-color: #3b82f6; box-shadow: 0 8px 22px rgba(59, 130, 246, .35); } +.ptflow-step.done { opacity: .55; } +.ptflow-step.done::after { content: '✓'; position: absolute; top: 6px; right: 8px; font-size: 10px; color: #34d399; } +@media (max-width: 767px) { + .ptflow-track { flex-wrap: wrap; } + .ptflow-step { flex: 1 1 30%; } + .ptflow-phase { margin-left: 0; } +} + +/* ---------- 首页顶部组件:安全态势简报(hero_mode=posture,数字增长) ---------- */ +.js-posture .vb-cell b.js-count { color: #fff; font-variant-numeric: tabular-nums; } + +/* ---------- 首页顶部组件:访问趋势(hero_mode=trend) ---------- */ +.trend-box { display: flex; flex-direction: column; gap: 10px; } +.trend-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; } +.trend-title { font-size: 15px; font-weight: 700; color: #fff; } +.trend-sub { margin-left: auto; font-size: 12.5px; color: rgba(255, 255, 255, .82); } +.trend-sub b { color: #fff; font-variant-numeric: tabular-nums; } +.trend-chart { display: flex; align-items: flex-end; gap: 8px; height: 132px; } +.trend-col { flex: 1 1 0; min-width: 0; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; } +.trend-val { font-size: 12px; color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; margin-bottom: 4px; } +.trend-bar-wrap { flex: 1; width: 100%; display: flex; align-items: flex-end; justify-content: center; } +.trend-bar { display: block; width: 62%; max-width: 34px; height: 0; border-radius: 6px 6px 3px 3px; background: linear-gradient(180deg, #7dd3fc, #38bdf8); box-shadow: 0 0 12px rgba(56, 189, 248, .45); animation: trendGrow .9s cubic-bezier(.2, .8, .2, 1) forwards; } +@keyframes trendGrow { from { height: 0; } to { height: var(--h); } } +.trend-lbl { font-size: 11px; color: rgba(255, 255, 255, .8); margin-top: 5px; font-variant-numeric: tabular-nums; } +.trend-wk { font-size: 10.5px; color: rgba(255, 255, 255, .55); } + +/* ---------- 首页顶部组件:大字时钟(hero_mode=clock) ---------- */ +.clock-box { display: flex; flex-direction: column; align-items: center; gap: 2px; } +.ch-greet { font-size: 13px; letter-spacing: 2px; color: rgba(255, 255, 255, .8); } +.ch-time { font-size: clamp(34px, 8vw, 58px); font-weight: 700; line-height: 1.05; color: #fff; font-variant-numeric: tabular-nums; letter-spacing: 2px; text-shadow: 0 2px 18px rgba(0, 0, 0, .35); } +.ch-date { font-size: 13.5px; color: rgba(255, 255, 255, .85); display: flex; gap: 10px; } +.ch-date .js-ch-week { color: rgba(255, 255, 255, .7); } +.ch-extra { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 3px; flex-wrap: wrap; } +.ch-lunar { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: rgba(255, 255, 255, .85); flex-wrap: wrap; } +.ch-lunar .js-ch-lunar { letter-spacing: .5px; } +.ch-fest { display: none; font-size: 12px; padding: 1px 8px; border-radius: 999px; background: rgba(239, 68, 68, .22); border: 1px solid rgba(239, 68, 68, .5); color: #fecaca; white-space: nowrap; } +.ch-fest.on { display: inline-block; } +.ch-count { display: inline-flex; align-items: baseline; gap: 6px; font-size: 13px; color: rgba(255, 255, 255, .85); flex-wrap: wrap; } +.ch-count .cc-label { color: rgba(255, 255, 255, .72); } +.ch-count .cc-time { font-weight: 700; color: #7dd3fc; font-variant-numeric: tabular-nums; letter-spacing: 1px; } + +/* ---------- 首页顶部组件:公告滚动条(hero_mode=notice) ---------- */ +.ticker-box { display: flex; align-items: center; gap: 10px; background: rgba(10, 22, 44, .32); border: 1px solid rgba(255, 255, 255, .18); border-radius: 10px; padding: 9px 12px; overflow: hidden; } +.tk-label { flex: none; font-size: 12px; font-weight: 700; color: #fff; background: var(--brand); border-radius: 6px; padding: 2px 8px; letter-spacing: 1px; } +.tk-viewport { position: relative; flex: 1; overflow: hidden; white-space: nowrap; } +.tk-track { display: inline-flex; align-items: center; will-change: transform; } +.tk-item { display: inline-flex; align-items: center; gap: 6px; color: #fff; text-decoration: none; font-size: 13.5px; padding: 0 26px; position: relative; } +.tk-item:hover { color: #bae6fd; } +.tk-item::after { content: '·'; position: absolute; right: 0; color: rgba(255, 255, 255, .4); } +.tk-pin { font-size: 10.5px; padding: 1px 6px; border-radius: 999px; background: rgba(239, 68, 68, .25); border: 1px solid rgba(239, 68, 68, .5); color: #fecaca; } +.tk-empty { font-size: 13.5px; color: rgba(255, 255, 255, .75); } +@keyframes tkScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } } + +/* ---------- 首页顶部组件:打字机标语(hero_mode=slogan) ---------- */ +.slogan-box { display: flex; align-items: center; gap: 10px; min-height: 40px; font-size: clamp(16px, 2.6vw, 22px); color: #fff; } +.sg-mark { color: #7dd3fc; font-weight: 700; } +.sg-text { font-weight: 600; letter-spacing: .5px; } +.sg-caret { display: inline-block; width: 9px; height: 1.05em; background: #7dd3fc; margin-left: 2px; transform: translateY(2px); animation: sgBlink 1s steps(1) infinite; } +@keyframes sgBlink { 50% { opacity: 0; } } +@media (max-width: 767px) { + .trend-chart { height: 108px; gap: 5px; } + .trend-wk { display: none; } + .slogan-box { font-size: 16px; } +} + /* ============ 后台风控管理页 ============ */ .vis-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; } .vis-stat { background: var(--bg-2); border: 1px solid var(--line); border-radius: 12px; padding: 12px 16px; } @@ -221,9 +304,12 @@ select { width: auto; min-width: 150px; } .quote-text { font-size: 17px; color: var(--text-2); cursor: pointer; user-select: none; padding: 2px 8px; border-radius: 8px; - transition: color .15s ease, background .15s ease; + transition: color .15s ease, background .15s ease, opacity .14s ease, transform .14s ease; } .quote-text:hover { color: var(--brand); background: var(--hover-bg); } +/* 点击切换名言时先淡出再淡入,避免文字瞬变 */ +.quote-text.quote-fade { opacity: 0; transform: translateY(-3px); } +@media (prefers-reduced-motion: reduce) { .quote-text { transition: color .15s ease, background .15s ease; } } /* ============ 首页四大板块 ============ */ /* 首页板块区:内容放得下时填满视口剩余高度(不出现竖向滚动条),内容超高时自然滚动 */ @@ -253,8 +339,6 @@ select { width: auto; min-width: 150px; } /* 通用图标 (site_icon 输出)在各容器中的尺寸 */ .icon-img { object-fit: contain; vertical-align: middle; } .board-icon img.icon-img { width: 30px; height: 30px; } -.board-section .section-title { text-align: center; font-size: 22px; margin-bottom: 6px; } -.board-section .section-sub { text-align: center; color: var(--text-3); font-size: 14px; margin-bottom: 18px; } .boards { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 18px; } .board-card { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; @@ -275,21 +359,22 @@ select { width: auto; min-width: 150px; } font-size: 13px; color: var(--text-3); line-height: 1.5; max-width: 100%; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } -.func-card .fc-icon img.icon-img { width: 24px; height: 24px; } - -/* 板块展开说明浮层 */ -.board-panel { - position: absolute; top: 6px; bottom: 6px; width: 52%; z-index: 5; - background: var(--card); border: 1px solid var(--brand); border-radius: 16px; - padding: 24px 26px; box-shadow: var(--shadow); pointer-events: none; - opacity: 0; transform: translateY(8px) scale(.98); transition: opacity .18s ease, transform .18s ease; - display: flex; flex-direction: column; justify-content: center; +/* 首页磁贴 / 板块卡入场动画:依次上浮浮现(fill-mode 用 backwards,动画结束后归还 :hover 位移) */ +@keyframes hpRise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } } +.quick-strip .quick-tile, .boards .board-card { animation: hpRise .46s cubic-bezier(.2, .8, .25, 1) backwards; } +.quick-strip > :nth-child(1), .boards > :nth-child(1) { animation-delay: .02s; } +.quick-strip > :nth-child(2), .boards > :nth-child(2) { animation-delay: .07s; } +.quick-strip > :nth-child(3), .boards > :nth-child(3) { animation-delay: .12s; } +.quick-strip > :nth-child(4), .boards > :nth-child(4) { animation-delay: .17s; } +.quick-strip > :nth-child(5), .boards > :nth-child(5) { animation-delay: .22s; } +.quick-strip > :nth-child(6), .boards > :nth-child(6) { animation-delay: .27s; } +.quick-strip > :nth-child(7), .boards > :nth-child(7) { animation-delay: .32s; } +.quick-strip > :nth-child(8), .boards > :nth-child(8) { animation-delay: .37s; } +.quick-strip > :nth-child(n+9), .boards > :nth-child(n+9) { animation-delay: .42s; } +@media (prefers-reduced-motion: reduce) { + .quick-strip .quick-tile, .boards .board-card { animation: none; } } -.board-panel.visible { opacity: 1; transform: none; } -.board-panel.panel-left { left: 0; } -.board-panel.panel-right { right: 0; } -.board-panel-title { font-size: 20px; font-weight: 700; color: var(--brand); margin-bottom: 10px; } -.board-panel-desc { font-size: 15px; color: var(--text-2); } +.func-card .fc-icon img.icon-img { width: 24px; height: 24px; } /* 名言隐藏池 */ .quote-pool { display: none; } @@ -474,6 +559,20 @@ a.nav-btn[data-note]:hover::after { opacity: 1; transform: translateX(-50%) tran .admin-nav .brand .site-name { color: #fff; } .admin-nav a.active { text-decoration: underline; text-underline-offset: 4px; font-weight: 600; } +/* 后台页面标题区(统一各页标题 / 说明 / 操作区) */ +.admin-head { + display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; flex-wrap: wrap; + margin-bottom: 18px; padding-bottom: 12px; border-bottom: 1px solid var(--line); +} +.admin-head .ah-main { min-width: 0; } +.admin-head .ah-title { font-size: 20px; font-weight: 700; margin: 0; } +.admin-head .ah-sub { margin: 6px 0 0; font-size: 13px; color: var(--text-3); line-height: 1.7; } +.admin-head .ah-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +@media (max-width: 600px) { + .admin-head { align-items: flex-start; } + .admin-head .ah-title { font-size: 18px; } +} + .fieldset-card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; margin-bottom: 20px; box-shadow: var(--shadow-sm); } .fieldset-card > .fs-title { font-weight: 700; font-size: 16px; margin: 0 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line); } .form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 18px; } @@ -638,7 +737,6 @@ a.nav-btn[data-note]:hover::after { opacity: 1; transform: translateX(-50%) tran .v-filter input[type=text] { width: 100%; } .quote-inner { flex-wrap: wrap; } .boards { grid-template-columns: repeat(2, 1fr); gap: 12px; min-height: 0; } - .board-panel { width: 100%; } .nav-cols { grid-template-columns: 1fr; gap: 14px; } .nav-area { margin: 18px 12px 30px; } .search-zone { padding-top: 6vh; } diff --git a/assets/img/logo.jpg b/assets/img/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1abc0c33974e74cb4ac178e605dc7965e2605bfa GIT binary patch literal 33699 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
M55NY5a>sn5~DV>_p==@()q4ZAgl)3S>Bp! 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 1 && cur !== -1) { while (idx === cur) idx = (idx + 1) % n; } - quote.textContent = list[idx].textContent; + // 先淡出、换字后再淡入,避免文字瞬间跳变 + swapping = true; + quote.classList.add('quote-fade'); + setTimeout(function () { + quote.textContent = list[idx].textContent; + quote.classList.remove('quote-fade'); + swapping = false; + }, 140); }); }); } - /* ---------- 首页四大板块 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}' }, @@ -756,18 +734,288 @@ } catch (e) { /* 同源限制等:保持默认图标 */ } } + /* ---------- 首页顶部组件:红队 / 蓝队渗透测试流程(hero_mode=red|blue)动态步进 ---------- */ + function initHeroFlows() { + var boxes = $$('.js-ptflow'); + if (!boxes.length) return; + boxes.forEach(function (box) { + var steps = $$('.ptflow-step', box); + if (!steps.length) return; + var phaseEl = $('.js-ptflow-phase', box); + var idx = 0; + function paint() { + steps.forEach(function (s, i) { + s.classList.toggle('active', i === idx); + s.classList.toggle('done', i < idx); + }); + if (phaseEl) { + var cur = steps[idx]; + var name = cur ? (cur.getAttribute('data-phase') || '') : ''; + phaseEl.textContent = '当前阶段:' + name + '(' + (idx + 1) + '/' + steps.length + ')'; + } + idx = (idx + 1) % steps.length; + } + paint(); + setInterval(paint, 1800); + }); + } + + /* ---------- 首页顶部组件:安全态势数字增长(hero_mode=posture) ---------- */ + function initHeroCountUp() { + var els = $$('.js-count'); + if (!els.length) return; + els.forEach(function (el) { + var to = parseInt(el.getAttribute('data-to') || '0', 10); + if (isNaN(to) || to <= 0) { el.textContent = '0'; return; } + var dur = 900, t0 = null; + function step(now) { + if (t0 === null) t0 = now; + var p = Math.min(1, (now - t0) / dur); + var eased = 1 - Math.pow(1 - p, 3); + if (p < 1) { el.textContent = String(Math.round(to * eased)); requestAnimationFrame(step); } + else { el.textContent = String(to); } + } + requestAnimationFrame(step); + }); + } + + /* ---------- 农历 / 节日库(1900-2100,纯前端本地计算,不依赖任何第三方接口) ---------- */ + var LUNAR_INFO = [ + 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, + 0x0d520 + ]; + var LUNAR_GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; + var LUNAR_ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; + var LUNAR_MON = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']; + var LUNAR_D1 = ['日', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; + var LUNAR_D2 = ['初', '十', '廿', '三']; + var LUNAR_FEST = [ + [1, 1, '春节'], [1, 15, '元宵节'], [2, 2, '龙抬头'], [5, 5, '端午节'], [7, 7, '七夕'], + [7, 15, '中元节'], [8, 15, '中秋节'], [9, 9, '重阳节'], [12, 8, '腊八节'], [12, 23, '小年'] + ]; + var SOLAR_FEST = { + '1-1': '元旦', '2-14': '情人节', '3-8': '妇女节', '3-12': '植树节', '4-1': '愚人节', + '5-1': '劳动节', '5-4': '青年节', '6-1': '儿童节', '7-1': '建党节', '8-1': '建军节', + '9-10': '教师节', '10-1': '国庆节', '12-25': '圣诞节' + }; + function lunLeapMonth(y) { return LUNAR_INFO[y - 1900] & 0xf; } + function lunLeapDays(y) { return lunLeapMonth(y) ? ((LUNAR_INFO[y - 1900] & 0x10000) ? 30 : 29) : 0; } + function lunMonthDays(y, m) { return (LUNAR_INFO[y - 1900] & (0x10000 >> m)) ? 30 : 29; } + function lunYearDays(y) { + var i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) { sum += (LUNAR_INFO[y - 1900] & i) ? 1 : 0; } + return sum + lunLeapDays(y); + } + function solar2lunar(date) { + var offset = (Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - Date.UTC(1900, 0, 31)) / 86400000; + var i, temp = 0; + for (i = 1900; i < 2101 && offset > 0; i++) { temp = lunYearDays(i); offset -= temp; } + if (offset < 0) { offset += temp; i--; } + var year = i, leap = lunLeapMonth(i), isLeap = false; + for (i = 1; i < 13 && offset > 0; i++) { + if (leap > 0 && i === (leap + 1) && isLeap === false) { --i; isLeap = true; temp = lunLeapDays(year); } + else { temp = lunMonthDays(year, i); } + if (isLeap === true && i === (leap + 1)) { isLeap = false; } + offset -= temp; + } + if (offset === 0 && leap > 0 && i === leap + 1) { + if (isLeap) { isLeap = false; } else { isLeap = true; --i; } + } + if (offset < 0) { offset += temp; --i; } + return { year: year, month: i, day: offset + 1, isLeap: isLeap }; + } + function lunDayName(d) { + if (d === 10) { return '初十'; } + if (d === 20) { return '二十'; } + if (d === 30) { return '三十'; } + return LUNAR_D2[Math.floor(d / 10)] + LUNAR_D1[d % 10]; + } + function ganzhiYear(y) { return LUNAR_GAN[(y - 4) % 10] + LUNAR_ZHI[(y - 4) % 12]; } + function lunMonthName(m, isLeap) { return (isLeap ? '闰' : '') + (LUNAR_MON[m - 1] || m) + '月'; } + function festivalsOf(date) { + var out = []; + var lu = solar2lunar(date); + for (var i = 0; i < LUNAR_FEST.length; i++) { + if (!lu.isLeap && lu.month === LUNAR_FEST[i][0] && lu.day === LUNAR_FEST[i][1]) { out.push(LUNAR_FEST[i][2]); } + } + if (!lu.isLeap && lu.month === 12 && lu.day === lunMonthDays(lu.year, 12)) { out.push('除夕'); } + var key = (date.getMonth() + 1) + '-' + date.getDate(); + if (SOLAR_FEST[key]) { out.push(SOLAR_FEST[key]); } + var w = date.getDay(), mo = date.getMonth() + 1, dd = date.getDate(); + if (mo === 5 && w === 0 && dd >= 8 && dd <= 14) { out.push('母亲节'); } + if (mo === 6 && w === 0 && dd >= 15 && dd <= 21) { out.push('父亲节'); } + return out; + } + function lunarText(date) { + var lu = solar2lunar(date); + return '农历 ' + ganzhiYear(lu.year) + '年 ' + lunMonthName(lu.month, lu.isLeap) + lunDayName(lu.day); + } + + /* ---------- 首页顶部组件:大字时钟(hero_mode=clock,含农历/节日) ---------- */ + function initHeroClock() { + var boxes = $$('.js-hero-clock'); + if (!boxes.length) return; + var WK = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']; + function pad(n) { return n < 10 ? '0' + n : '' + n; } + function greet(h) { + if (h < 6) return '凌晨好'; + if (h < 9) return '早上好'; + if (h < 12) return '上午好'; + if (h < 14) return '中午好'; + if (h < 18) return '下午好'; + if (h < 23) return '晚上好'; + return '夜深了'; + } + var cacheDay = '', lunarStr = '', festStr = ''; + var nextFestName = '', nextFestTs = 0; + /* 下一个节日:从明天起向后找(含当天已过的),最多一年 */ + function findNextFest(now) { + var y = now.getFullYear(), m = now.getMonth(), d = now.getDate(); + for (var i = 1; i <= 370; i++) { + var dt = new Date(y, m, d + i); + var f = festivalsOf(dt); + if (f.length) { + return { name: f[0], ts: new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), 0, 0, 0).getTime() }; + } + } + return null; + } + function pad2(n) { return n < 10 ? '0' + n : '' + n; } + function fmtCountdown(ms) { + if (ms < 0) ms = 0; + var total = Math.floor(ms / 1000); + var days = Math.floor(total / 86400); + var hh = Math.floor((total % 86400) / 3600); + var mm = Math.floor((total % 3600) / 60); + var ss = total % 60; + var t = pad2(hh) + ':' + pad2(mm) + ':' + pad2(ss); + return days > 0 ? (days + '天 ' + t) : t; + } + /* 每日只需重算一次(农历 / 今日节日 / 下一个节日) */ + function refreshDaily(now) { + var key = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate(); + if (key === cacheDay) return; + cacheDay = key; + lunarStr = lunarText(now); + var todayFest = festivalsOf(now); + festStr = todayFest.length ? todayFest.join(' · ') : ''; + var nf = findNextFest(now); + nextFestName = nf ? nf.name : ''; + nextFestTs = nf ? nf.ts : 0; + } + function tick() { + var d = new Date(); + refreshDaily(d); + var timeStr = pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); + var dateStr = d.getFullYear() + '年' + pad(d.getMonth() + 1) + '月' + pad(d.getDate()) + '日'; + var wk = WK[d.getDay()]; + var gr = greet(d.getHours()); + var left = nextFestTs ? fmtCountdown(nextFestTs - d.getTime()) : '--'; + boxes.forEach(function (box) { + var te = $('.js-ch-time', box), de = $('.js-ch-date', box), + we = $('.js-ch-week', box), ge = $('.js-ch-greet', box), + le = $('.js-ch-lunar', box), fe = $('.js-ch-fest', box), + cl = $('.js-cc-label', box), ct = $('.js-cc-time', box); + if (te) te.textContent = timeStr; + if (de) de.textContent = dateStr; + if (we) we.textContent = wk; + if (ge) ge.textContent = gr; + if (le) le.textContent = lunarStr; + if (fe) { + fe.textContent = festStr; + fe.className = 'ch-fest js-ch-fest' + (festStr ? ' on' : ''); + } + if (cl) cl.textContent = nextFestName ? ('距 ' + nextFestName) : '下一个节日'; + if (ct) ct.textContent = left; + }); + } + tick(); + setInterval(tick, 1000); + } + + /* ---------- 首页顶部组件:公告滚动条(hero_mode=notice) ---------- */ + function initNoticeTicker() { + var track = $('.js-tk-track'); + if (!track) return; + var viewport = $('.js-tk-viewport'); + if (!$$('.tk-item', track).length) return; + var oneSet = track.innerHTML; + var targetW = viewport ? viewport.clientWidth : 0; + var guard = 0; + while (track.scrollWidth < targetW && guard < 20) { + track.innerHTML += oneSet; + guard++; + } + track.innerHTML += track.innerHTML; /* 再补一组,配合 -50% 位移无缝循环 */ + var half = track.scrollWidth / 2; + var dur = Math.max(12, half / 60); /* 约 60px/s */ + track.style.animation = 'tkScroll ' + dur.toFixed(1) + 's linear infinite'; + } + + /* ---------- 首页顶部组件:打字机标语(hero_mode=slogan) ---------- */ + function initHeroSlogan() { + var out = $('.js-sg-text'); + if (!out) return; + var pool = $$('.js-sg-pool span'); + var list = []; + for (var i = 0; i < pool.length; i++) { + var t = (pool[i].textContent || '').trim(); + if (t) list.push(t); + } + if (!list.length) list = ['知识就是力量。']; + var idx = 0, pos = 0, deleting = false; + function run() { + var full = list[idx]; + if (!deleting) { + pos++; + out.textContent = full.slice(0, pos); + if (pos >= full.length) { deleting = true; setTimeout(run, 1800); return; } + setTimeout(run, 90); + } else { + pos--; + out.textContent = full.slice(0, pos); + if (pos <= 0) { deleting = false; idx = (idx + 1) % list.length; setTimeout(run, 360); return; } + setTimeout(run, 38); + } + } + run(); + } + /* ---------- 自动初始化 ---------- */ document.addEventListener('DOMContentLoaded', function () { initSiteIcon(); initTheme(); initClocks(); initQuoteRefresh(); - initBoardHover(); initSearch(); initIconControls(); initCopyButtons(); bindConfirmForms(); initVisitorInfo(); + initHeroFlows(); + initHeroCountUp(); + initHeroClock(); + initNoticeTicker(); + initHeroSlogan(); initLoginDevice(); initVisitsAjax(); initAdminSide(); diff --git a/func/_bar.php b/func/_bar.php deleted file mode 100644 index a7556f5..0000000 --- a/func/_bar.php +++ /dev/null @@ -1,21 +0,0 @@ -'; - echo '
'; - if ($fromNav) { - echo '← 返回上一页'; - } else { - echo '← 返回功能区首页'; - } - echo '' . he($title) . ''; - echo '首页'; - echo '
' . "\n"; -} diff --git a/includes/auth.php b/includes/auth.php index 7345510..90d740a 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -1,7 +1,7 @@ '', // 搜索引擎列表 JSON;空 = 使用默认 4 个 'last_updated' => '', 'hero_bg' => '', // 首页顶部标识栏背景(纯色 / CSS 渐变),空 = 主题默认 - 'hero_mode' => 'weather', // 首页顶部组件模式:weather=天气(默认) / info=访客信息 / blank=空白精简 + 'hero_mode' => 'weather', // 首页顶部组件模式:weather=天气(默认) / info=信息 / blank=精简 / red=渗透测试 / blue=安全巡检 / posture=安全态势 / trend=访问趋势 / clock=大字时钟 / notice=公告滚动 / slogan=打字机标语 'cdn_ranges' => '', // CDN 段库文本;空 = 使用默认内置列表 'article_note' => '', // 文章区首页顶部注要(Markdown 风格,纯文本) ]; @@ -568,6 +568,72 @@ function visitors_entries(): array return array_reverse($out); // 新→旧 } +/** + * 首页顶部组件「安全态势 / 访问趋势」所需的汇总数据(一次遍历访问日志 + 两条轻量查询)。 + * 返回:今日/累计访问、今日/累计独立 IP、拦截次数、黑名单条数、近 N 天每日访问、最近一次成功登录。 + */ +function home_security_stats(int $days = 7): array +{ + $entries = visitors_entries(); + $total = count($entries); + $today = 0; + $allIps = []; + $todayIps = []; + $blockedTotal = 0; + $blockedToday = 0; + $todayStart = date('Y-m-d 00:00:00'); + // 近 N 天日期桶(含今天,由旧到新) + $buckets = []; + $order = []; + for ($i = $days - 1; $i >= 0; $i--) { + $d = date('Y-m-d', strtotime('-' . $i . ' day')); + $buckets[$d] = 0; + $order[] = $d; + } + foreach ($entries as $e) { + $allIps[$e['ip']] = 1; + if ((int) $e['status'] === 403) { + $blockedTotal++; + } + $day = substr((string) $e['created_at'], 0, 10); + if (isset($buckets[$day])) { + $buckets[$day]++; + } + if ((string) $e['created_at'] >= $todayStart) { + $today++; + $todayIps[$e['ip']] = 1; + if ((int) $e['status'] === 403) { + $blockedToday++; + } + } + } + $daily = []; + foreach ($order as $d) { + $daily[] = ['day' => $d, 'cnt' => $buckets[$d]]; + } + $ban = (int) db()->query('SELECT COUNT(*) FROM ip_blacklist')->fetchColumn(); + $lastLogin = null; + $row = db()->query("SELECT username, ip, created_at FROM login_log WHERE status = 'ok' ORDER BY created_at DESC, id DESC LIMIT 1")->fetch(); + if ($row) { + $lastLogin = [ + 'username' => (string) ($row['username'] ?? ''), + 'ip' => (string) ($row['ip'] ?? ''), + 'time' => (string) ($row['created_at'] ?? ''), + ]; + } + return [ + 'total' => $total, + 'today' => $today, + 'ips_total' => count($allIps), + 'ips_today' => count($todayIps), + 'blocked_total' => $blockedTotal, + 'blocked_today' => $blockedToday, + 'ban' => $ban, + 'daily' => $daily, + 'last_login' => $lastLogin, + ]; +} + /** 自动启动:注册访问日志;黑名单对所有请求(前台与后台登录页)统一拦截;自动风控自检仅针对前台 */ function access_boot(): void { diff --git a/includes/layout.php b/includes/layout.php index 0accea3..f368a72 100644 --- a/includes/layout.php +++ b/includes/layout.php @@ -146,28 +146,6 @@ function layout_footer(): void echo '
' . "\n"; } -/** 读取首页展示的名言(quota.txt 随机一行) */ -function home_quote(): string -{ - $file = DATA_DIR . '/quota.txt'; - $lines = []; - if (is_file($file)) { - $raw = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - if (is_array($raw)) { - foreach ($raw as $ln) { - $ln = trim($ln); - if ($ln !== '' && strpos($ln, '#') !== 0) { - $lines[] = $ln; - } - } - } - } - if (!$lines) { - return '知识就是力量。'; - } - return $lines[array_rand($lines)]; -} - /** 首页访客信息(hero 信息模式):IP 与浏览器/系统/内核由服务端解析 UA 提供,屏幕/语言由前端补齐 */ function visitor_brief(): array { diff --git a/index.html b/index.html new file mode 100644 index 0000000..e28e144 --- /dev/null +++ b/index.html @@ -0,0 +1,312 @@ + + + + + + + +加载中… + + + + + +
+ +
+
+
+
+
+
/// SECHOME-BOOT  //  SECURE CHANNEL
+
AUTH: PENDING  //  NODE 0x1F
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + diff --git a/index.php b/index.php index 2016a8a..4c2ea45 100644 --- a/index.php +++ b/index.php @@ -9,9 +9,10 @@ require_once __DIR__ . '/includes/layout.php'; $siteName = setting_get('site_name', 'SecHome'); $siteSlogan = setting_get('site_slogan', ''); $heroBg = setting_get('hero_bg', ''); // 后台自定义顶部背景(纯色 / CSS 渐变) -// 首页顶部组件模式:weather=天气(默认) / info=访客信息 / blank=空白精简 +// 首页顶部组件模式:weather=天气(默认) / info=信息 / blank=精简 / red=渗透测试 / blue=安全巡检 +// / posture=安全态势 / trend=访问趋势 / clock=大字时钟 / notice=公告滚动 / slogan=打字机标语 $heroMode = setting_get('hero_mode', 'weather'); -if (!in_array($heroMode, ['weather', 'info', 'blank'], true)) { +if (!in_array($heroMode, ['weather', 'info', 'blank', 'red', 'blue', 'posture', 'trend', 'clock', 'notice', 'slogan'], true)) { $heroMode = 'weather'; } $visitor = []; @@ -39,6 +40,17 @@ if (is_file($quoteFile)) { } $quoteText = $quotes ? $quotes[array_rand($quotes)] : '知识就是力量。'; +// 安全态势 / 访问趋势:汇总统计数据(仅对应模式时查询,避免无谓开销) +$secStats = null; +if ($heroMode === 'posture' || $heroMode === 'trend') { + $secStats = home_security_stats(7); +} +// 公告滚动:置顶优先、发布时间倒序 +$heroNotices = []; +if ($heroMode === 'notice') { + $heroNotices = db()->query('SELECT title, pinned, created_at FROM notices WHERE enabled = 1 ORDER BY pinned DESC, created_at DESC, id DESC')->fetchAll(); +} + // 四大板块(按后台排序输出) $cats = db()->query('SELECT * FROM categories ORDER BY sort ASC, id ASC')->fetchAll(); // 首页快捷站点(后台配置,新标签页跳转本域名其它网站) @@ -105,23 +117,179 @@ layout_head('');
系统语言--
+ + +
+
+ 红队 · 渗透测试RED TEAM + 当前阶段:侦察(1/8) +
+
    + +
  1. + + + +
  2. + +
+
+ + +
+
+ 蓝队 · 安全巡检BLUE TEAM + 当前阶段:测绘(1/8) +
+
    + +
  1. + + + +
  2. + +
+
+ + +
+
+
今日访问0 +
+
今日独立 IP0
+
累计访问0 +
+
拦截次数0
+
黑名单0 +
+
最近登录
+
+
+ + + $maxCnt) { + $maxCnt = (int) $d['cnt']; + } + } + $weekMap = ['日', '一', '二', '三', '四', '五', '六']; + ?> +
+
+ 近 7 天访问趋势 + 累计 次 · 今日 + +
+
+ 0 ? (int) round($cnt / $maxCnt * 100) : 0; + $ts = (int) strtotime((string) $d['day']); + ?> +
+ + + + +
+ +
+
+ + +
+
--
+
--:--:--
+
----年--月--日
+
+ 农历 -- + 距下一个节日-- +
+
+ + +
+ 公告 + +
+
+ + 置顶 + +
+
+ + 暂无更新公告,可在后台「更新公告管理」中添加 + +
+ + +
+ > + + +
- -
-
- - - + + +
+
+ + + +
+
+ + + +
-
- - - -
-
+
diff --git a/操作文档.md b/操作文档.md index 1ef138a..12328e6 100644 --- a/操作文档.md +++ b/操作文档.md @@ -7,7 +7,7 @@ ### 1. 首页与板块 - 首页是板块选择页:**科普库 / 红队库 / 蓝队库 / 工具库**,另含文章库入口 -- 顶部组件由站长设置,可能是三种之一:天气、访客信息、空白精简 +- 顶部组件由站长设置,共 10 种可选:天气(默认)、访客信息、空白精简、渗透测试、安全巡检、安全态势、访问趋势、大字时钟、公告滚动、打字机标语 - 右下角「☾ / ☀」按钮切换昼夜主题;「↑」回到顶部 ### 2. 库页面(科普 / 红队 / 蓝队) @@ -89,7 +89,7 @@ - **站点 logo**:上传后全局生效(浏览器标签图标与各页面 logo 同步,**含静态工具页**) - **名言库**:编辑 `data/quota.txt` - **底部信息**:版权、ICP、公安备案等 -- **首页顶部组件**:天气(默认)/ 访客信息 / 空白精简 +- **首页顶部组件**:10 种模式可选(天气 / 访客信息 / 空白精简 / 渗透测试 / 安全巡检 / 安全态势 / 访问趋势 / 大字时钟 / 公告滚动 / 打字机标语) - **修改密码**:建议首次登录即修改默认 `admin/admin123` ### 6. 更新公告(notice.php) @@ -158,7 +158,7 @@ assets/js/blowfish.js egoroof-blowfish 2.2.2 (Navicat 密码解密用) assets/js/sm-crypto/sm2.js|sm3.js|sm4.js sm-crypto 0.3.13 ``` -5. **忘记后台密码**:数据库 `users` 表密码为 `md5(base64(明文))`;可将某行改为已知口令的散列后登录,随后在后台重设。 +5. **忘记后台密码**:数据库 `users` 表存储的是密码散列(PHP `password_hash` 强散列)。可将某行临时改为已知口令的旧版散列 `md5(base64(明文))`(系统兼容旧版并会在登录后自动升级)后登录,随后在后台重设。 6. **保存设置后提示去哪里了?** 后台操作提示统一显示为**右下角 Toast**(成功约 3.6 秒、失败约 6 秒自动消失),不会占用页面顶部。 ---