Files
SimpleNav/suggest.php
T
laoyang 853e4dd7c6 feat: 图标策略升级 + 引擎显隐就地切换 + 前后台一致性;界面美化
按反馈逐条处理:

① 界面美化
- 前台重做:品牌区(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 项假「缺失文件」。
2026-09-28 16:43:10 +08:00

117 lines
3.9 KiB
PHP

<?php
/**
* 简航 SimpleNav —— 搜索联想代理
* 浏览器直接调用各搜索引擎的联想接口会有跨域/JSONP 限制,这里由服务端代理并统一输出 JSON。
* 用法:suggest.php?slug=bing&q=关键词
*/
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
header('Content-Type: application/json; charset=utf-8');
send_no_cache();
$slug = isset($_GET['slug']) ? preg_replace('/[^a-z0-9\-_]/i', '', (string)$_GET['slug']) : '';
$q = isset($_GET['q']) ? trim((string)$_GET['q']) : '';
if ($q === '') {
echo json_encode(['q' => '', 'items' => []]);
exit;
}
$items = [];
try {
$st = db()->prepare('SELECT * FROM engines WHERE slug=? AND is_active=1 LIMIT 1');
$st->execute([$slug]);
$engine = $st->fetch();
if ($engine && $engine['suggest_url'] !== '') {
$u = str_replace('{q}', rawurlencode($q), $engine['suggest_url']);
$resp = http_get($u, 100000, 5);
if ($resp !== null) {
$items = parse_suggest($engine['suggest_type'], ensure_utf8($resp['body']), $q);
}
}
} catch (Throwable $e) {
$items = [];
}
// 去重、限长、裁剪
$items = array_values(array_unique(array_filter(array_map('trim', $items))));
$items = array_slice($items, 0, 10);
echo json_encode(['q' => $q, 'items' => $items], JSON_UNESCAPED_UNICODE);
exit;
/** 按引擎类型解析联想结果 */
function parse_suggest(string $type, string $body, string $q): array
{
$body = trim($body);
$out = [];
switch ($type) {
case 'bing_osjson':
case 'google_firefox':
// ["query", ["s1","s2",...]]
$j = json_decode($body, true);
if (is_array($j) && isset($j[1]) && is_array($j[1])) {
$out = $j[1];
}
break;
case 'baidu_sugrec':
// {"q":"x","g":[{"type":"..","q":".."}], ...}
$j = json_decode($body, true);
if (is_array($j)) {
if (!empty($j['g']) && is_array($j['g'])) {
foreach ($j['g'] as $it) { if (isset($it['q'])) $out[] = $it['q']; }
} elseif (!empty($j['s']) && is_array($j['s'])) {
$out = $j['s'];
}
}
break;
case 'so360':
// {"s":[".."]} 或 {"result":[{"word":".."}]}
$j = json_decode($body, true);
if (is_array($j)) {
if (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; }
elseif (!empty($j['result']) && is_array($j['result'])) {
foreach ($j['result'] as $it) { if (isset($it['word'])) $out[] = $it['word']; }
}
}
break;
case 'sogou_ajaj':
// {"query":"x","items":["a","b"]}(可能是 GBK,已在上面转码)
$j = json_decode($body, true);
if (is_array($j) && !empty($j['items']) && is_array($j['items'])) {
$out = $j['items'];
} else {
// 兜底:正则抓引号字符串
if (preg_match_all('#"([^"]{1,40})"#u', $body, $m)) {
$out = array_slice($m[1], 1); // 跳过 query 自身
}
}
break;
case 'ddg':
// [{"phrase":".."},...]
$j = json_decode($body, true);
if (is_array($j)) {
foreach ($j as $it) {
if (is_array($it) && isset($it['phrase'])) { $out[] = $it['phrase']; }
elseif (is_string($it)) { $out[] = $it; }
}
}
break;
default:
// 通用兜底:尝试 JSON,再退化正则
$j = json_decode($body, true);
if (is_array($j)) {
if (isset($j[1]) && is_array($j[1])) { $out = $j[1]; }
elseif (!empty($j['s']) && is_array($j['s'])) { $out = $j['s']; }
}
}
return $out;
}