按反馈逐条处理: ① 界面美化 - 前台重做:品牌区(logo + 站名 + 副标题)、胶囊式引擎切换、圆角卡片与 悬停浮起、聚焦光晕、合集标签页淡入动画;版心改为 shell 变量 (--pad-x = max(gutter, (100% - wrap)/2)),与顶栏左右边缘严格对齐。 - 后台:表格窄屏可横向滚动、状态胶囊、自定义徽章、图标预览框。 ② 自动更新图标质量差 + 允许自定义图标 - 抓取改为阶梯择优:manifest 512/192 → apple-touch-icon → <link rel=icon> (按 sizes 降序,SVG 视为最高清)→ 第三方聚合服务(favicon.yandex.net 实测 100% 可用;icon.horse 可达时最高 256px)→ 站点自身 /favicon.ico。 - 逐个候选实测像素,达到 120px 即停;候选上限 8 个 + 10s 总预算,避免超时。 - 用 GD 只缩不放,统一存成 128×128 PNG;识别并丢弃第三方服务的 「灰底字母」占位块。 - 新增 sites.icon_custom 列(带幂等迁移):可上传图标或填图标地址; 一旦自定义,「全部更新」只刷新名称、跳过图标。 ③ 后台引擎「可见/不可见」不好用 - 小图标换成内联 SVG(原来用 emoji,跨字体显示成方块)。 - 切换改为 AJAX 原地生效 + 状态胶囊同步,不再点一下就跳页; 所有切换/移动表单都带 p 参数,无 JS 时也能正确回到当前页。 - 修掉一个隐蔽的成因:本站此前完全不发 Cache-Control/Expires/ETag, 浏览器只能自行猜测缓存策略,导致「改完刷新还是旧的」。现在所有动态页 统一发 no-store/no-cache。 ④ deploy/ 与 data/ 误入版本库 - 两目录整目录忽略,部署脚本移出仓库(存 _tooling/),已 git rm --cached。 (历史提交里仍含这些文件,如需彻底抹除需 git filter-repo,待确认。) 附带修复(线上实测驱动) - 服务器侧页面缓存:宝塔「网站加速」Apache(mod_lua) 版是一层基于 memcached 的 30 秒整页缓存,且**读不到 FastCGI 返回的 Cache-Control**,所以 no-store 挡不住它;唯一信号是请求 cookie X_CACHE=1。已在根 .htaccess 用 RequestHeader 在请求侧注入该标记(实测任何访客均 X-Cache: MISS), PHP 侧另发一颗 cookie 作为 Nginx 环境的兜底(BYPASS_PAGE_CACHE 开关)。 - 静态资源改为「长缓存 + 版本号」:asset()/icon_src() 拼 mtime+size, assets/.htaccess 与根 .htaccess 给出 max-age=31536000 immutable。 - 修 absolutize() 未还原 JSON 转义 \/ 导致部分图标 URL 抓不下来的问题。 - 修部署脚本 md5sum 在 Git Bash 下输出 "hash *path" 导致 38 项假「缺失文件」。
96 lines
2.8 KiB
PHP
96 lines
2.8 KiB
PHP
<?php
|
|
/**
|
|
* 简航 SimpleNav —— 后台认证与通用工具
|
|
*/
|
|
declare(strict_types=1);
|
|
require_once dirname(__DIR__) . '/functions.php';
|
|
|
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
session_start();
|
|
}
|
|
|
|
function admin_count(): int
|
|
{
|
|
return (int)db()->query('SELECT COUNT(*) FROM users')->fetchColumn();
|
|
}
|
|
|
|
function current_user(): ?array
|
|
{
|
|
if (empty($_SESSION['uid'])) {
|
|
return null;
|
|
}
|
|
$st = db()->prepare('SELECT id, username FROM users WHERE id=?');
|
|
$st->execute([$_SESSION['uid']]);
|
|
$u = $st->fetch();
|
|
return $u ?: null;
|
|
}
|
|
|
|
function require_login(): void
|
|
{
|
|
if (!current_user()) {
|
|
header('Location: ' . url('login.php'));
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function csrf_token(): string
|
|
{
|
|
if (empty($_SESSION['csrf'])) {
|
|
$_SESSION['csrf'] = bin2hex(random_bytes(16));
|
|
}
|
|
return $_SESSION['csrf'];
|
|
}
|
|
|
|
function csrf_field(): string
|
|
{
|
|
return '<input type="hidden" name="csrf" value="' . e(csrf_token()) . '">';
|
|
}
|
|
|
|
function csrf_check(): void
|
|
{
|
|
$t = isset($_POST['csrf']) ? (string)$_POST['csrf'] : '';
|
|
if (!hash_equals(csrf_token(), $t)) {
|
|
http_response_code(419);
|
|
exit('CSRF 校验失败,请刷新页面重试。');
|
|
}
|
|
}
|
|
|
|
/** 统一的后台页面外壳 */
|
|
function admin_layout(string $active, string $title, string $body): void
|
|
{
|
|
$nav = [
|
|
'sites' => ['网站管理', 'admin/'],
|
|
'groups' => ['板块管理', 'admin/?p=groups'],
|
|
'engines' => ['搜索引擎', 'admin/?p=engines'],
|
|
'settings' => ['系统设置', 'admin/?p=settings'],
|
|
];
|
|
?><!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= e($title) ?> · <?= e(site_name()) ?> 后台</title>
|
|
<link rel="icon" href="<?= e(asset('favicon.ico')) ?>" sizes="any">
|
|
<link rel="stylesheet" href="<?= e(asset('assets/css/style.css')) ?>">
|
|
<link rel="stylesheet" href="<?= e(asset('assets/css/admin.css')) ?>"><?php if (setting('primary_color', '#2F6BF7') !== '#2F6BF7'): ?>
|
|
<style>:root{--primary:<?= e(setting('primary_color', '#2F6BF7')) ?>}</style><?php endif; ?>
|
|
</head>
|
|
<body class="admin">
|
|
<header class="topbar">
|
|
<div class="brand"><a href="<?= e(url()) ?>"><?= e(site_name()) ?></a> <span>后台</span></div>
|
|
<nav class="topnav">
|
|
<?php foreach ($nav as $k => $v): ?>
|
|
<a href="<?= e(url($v[1])) ?>" class="<?= $k === $active ? 'active' : '' ?>"><?= e($v[0]) ?></a>
|
|
<?php endforeach; ?>
|
|
</nav>
|
|
<div class="topright">
|
|
<span class="who"><?= e(current_user()['username'] ?? '') ?></span>
|
|
<a class="btn ghost" href="<?= e(url('logout.php')) ?>">退出</a>
|
|
</div>
|
|
</header>
|
|
<main class="wrap"><?= $body ?></main>
|
|
<script src="<?= e(asset('assets/js/admin.js')) ?>"></script>
|
|
</body>
|
|
</html><?php
|
|
}
|