Files
SimpleNav/manifest.php
T
laoyang 92eb9d90be 后台上传站点图标:首页品牌图 + favicon + PWA 图标一起换
需求:后台新增设置,允许管理员上传自己的图标;上传后首页品牌图标与浏览器
标签页 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 无溢出、线上直连桌面+移动端几何全绿、
线上诊断页走通「上传→前台全换→复原」。
2026-09-28 22:28:23 +08:00

56 lines
2.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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);