需求:后台新增设置,允许管理员上传自己的图标;上传后首页品牌图标与浏览器 标签页 favicon(以及「添加到手机主屏」图标)一起换成它。 要点: - 一张上传图服务端居中裁剪成正方形,生成 16/32/48/64/180/192/512 + maskable 192/512 整套尺寸,并手工拼装多帧 favicon.ico。 - 单一来源 site_icon_url() 统一取址;site_icon_head() 被首页/登录页/后台 三处共用;PWA manifest 改由 manifest.php 动态输出、maskable 也跟随 —— 三处防线共同杜绝「首页换了、标签页还是旧的」这类半生效。 - 目录可写则落静态文件(最快);不可写的保守环境整套压进设置、由 site-icon.php 按需输出,功能不因权限限制整块不可用。 - 与「网站列表里每个网站自己的图标」是两个维度:目录与设置键都分开, 清理时互不误伤。 顺手修掉两个真问题: - login.php / admin/_auth.php 各留了一行写死的 favicon,与新的 site_icon_head() 并存 → head 里两条 rel="icon",是「上传了却没换」的 温床。已删掉写死那两行。 - 自定义 favicon.ico 原先只塞 16+32 两帧,而内置是 16/32/48/64 四帧 —— 换图标反而退步。已补齐 icon-64 并统一为 4 帧。 - setting() 的请求内缓存导致「同请求写后再读」拿到旧值(真实坑): settings_cache() 支持强制重载 + settings_reload(),写入后立即失效。 验证:单测 44/44(含只读目录动态回退)、HTTP 56/56、e2e 174/0、 反向验证 5 变体全部被抓住(含「写死残留」与「ico 只塞两帧」两个新哨兵)、 真机几何 360/390/768/1440 无溢出、线上直连桌面+移动端几何全绿、 线上诊断页走通「上传→前台全换→复原」。
56 lines
2.4 KiB
PHP
56 lines
2.4 KiB
PHP
<?php
|
||
/**
|
||
* 简航 SimpleNav —— 动态输出 PWA manifest(site.webmanifest)
|
||
*
|
||
* 为什么不再用静态 site.webmanifest:
|
||
* 用户的「添加到手机主屏」读的是这里的 icons。图标一旦可自定义,静态文件就会
|
||
* 一直指向内置的那几张 → 出现半生效(标签页 favicon 换了、主屏图标还是旧的)。
|
||
* 顺带把 name / theme_color 也接上后台设置,不再与页面上显示的值打架。
|
||
*
|
||
* 静态 site.webmanifest 保留作回退(未被 Web 服务器路由到这里时仍可用)。
|
||
*/
|
||
declare(strict_types=1);
|
||
require_once __DIR__ . '/functions.php';
|
||
|
||
header('Content-Type: application/manifest+json; charset=utf-8');
|
||
// manifest 只随设置变化,短缓存即可(与图标文件的长缓存策略不同)
|
||
header('Cache-Control: no-cache, must-revalidate');
|
||
|
||
$name = site_name();
|
||
$short = mb_substr($name, 0, 12);
|
||
|
||
// 图标地址全部走 site_icon_url() 这个单一来源:有自定义就用自定义,没有就用内置。
|
||
// 内置那几张保持原样(相对路径),保证「没上传」时的行为与改造前完全一致。
|
||
$custom = site_icon_key() !== '';
|
||
$icon = static function (string $n, int $size, string $fallback) use ($custom): ?array {
|
||
if ($custom) {
|
||
$u = site_icon_url($n, $size);
|
||
if ($u !== '') {
|
||
return ['src' => $u, 'sizes' => $size . 'x' . $size, 'type' => 'image/png'];
|
||
}
|
||
}
|
||
return ['src' => $fallback, 'sizes' => $size . 'x' . $size, 'type' => 'image/png'];
|
||
};
|
||
|
||
$icons = [];
|
||
$i192 = $icon('icon-192', 192, 'assets/icons/icon-192.png');
|
||
$i512 = $icon('icon-512', 512, 'assets/icons/icon-512.png');
|
||
$m192 = $icon('icon-192-maskable', 192, 'assets/icons/icon-192-maskable.png');
|
||
$m512 = $icon('icon-512-maskable', 512, 'assets/icons/icon-512-maskable.png');
|
||
if ($i192) { $icons[] = $i192; }
|
||
if ($i512) { $icons[] = $i512; }
|
||
if ($m192) { $m192['purpose'] = 'maskable'; $icons[] = $m192; }
|
||
if ($m512) { $m512['purpose'] = 'maskable'; $icons[] = $m512; }
|
||
|
||
$out = [
|
||
'name' => $name,
|
||
'short_name' => $short,
|
||
'start_url' => './',
|
||
'scope' => './',
|
||
'display' => 'standalone',
|
||
'background_color' => '#d8d9da',
|
||
'theme_color' => setting('primary_color', '#2F6BF7'),
|
||
'icons' => $icons,
|
||
];
|
||
|
||
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); |