后台新增「搜索与链接的打开方式」,并让站点链接跟随设置

原先的设置项只作用于顶部搜索框,文案也写作「搜索结果新窗口打开」;而站点格
与合集里的链接 target 是写死的 _blank —— 于是切到「当前页面打开」时会出现
半生效:搜索留在本页,点网站却仍然弹新标签。

- index.php:把打开方式收敛成一个 open_target(),搜索框与所有站点格共用同一
  来源,任何一处都不再写死 target;页面输出 data-open="blank|self" 便于断言与排查。
- admin/index.php:后台由含义含糊的复选框改为明确的二选一(新标签页 / 当前页面),
  并补一段说明它管哪两处、不影响后台管理界面。
- admin/actions.php:保存走白名单回退 —— '0' 之外(含字段缺失、被篡改成怪值)
  一律回到 '1'(新标签页)。默认值与改造前的旧行为一致,老库不受影响。
- 当前页面模式下不再输出无意义的 rel="noopener"。

验证:php -l 4 个文件通过;e2e 113/113(新增 20 项,并做了反向验证——把链接
改回写死 _blank 后,相关断言精确失败 4 条);线上部署 0 FAIL(双版本语法检查、
62 条静态断言、38 文件哈希逐字节一致、HTTP 冒烟、真机上切换两种模式并已复原)。
This commit is contained in:
2026-09-28 19:28:57 +08:00
parent 273074218c
commit 47b45cfa23
4 changed files with 35 additions and 5 deletions
+4 -1
View File
@@ -213,12 +213,15 @@ try {
$src = in_array($srcRaw, ['auto', 'baidu', 'bing', 'so360'], true) ? $srcRaw : 'baidu'; $src = in_array($srcRaw, ['auto', 'baidu', 'bing', 'so360'], true) ? $srcRaw : 'baidu';
// 合集区显示方式:flat = 全部平铺;其余一律回到 tabs(标签页折叠) // 合集区显示方式:flat = 全部平铺;其余一律回到 tabs(标签页折叠)
$coll = (string)($_POST['collections_layout'] ?? 'tabs') === 'flat' ? 'flat' : 'tabs'; $coll = (string)($_POST['collections_layout'] ?? 'tabs') === 'flat' ? 'flat' : 'tabs';
// 打开方式:'0' = 当前页面;其余(含字段缺失、被篡改成怪值)一律回到 '1'(新标签页)
// ——默认必须等于改造前的旧行为,这样老库里已有的值上线后不会突然变样。
$open = (string)($_POST['open_blank'] ?? '1') === '0' ? '0' : '1';
$map = [ $map = [
'site_name' => trim((string)($_POST['site_name'] ?? '')), 'site_name' => trim((string)($_POST['site_name'] ?? '')),
'subtitle' => trim((string)($_POST['subtitle'] ?? '')), 'subtitle' => trim((string)($_POST['subtitle'] ?? '')),
'default_engine' => preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['default_engine'] ?? '')), 'default_engine' => preg_replace('/[^a-z0-9\-_]/i', '', (string)($_POST['default_engine'] ?? '')),
'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7', 'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7',
'open_blank' => isset($_POST['open_blank']) ? '1' : '0', 'open_blank' => $open,
'icon_services' => isset($_POST['icon_services']) ? '1' : '0', 'icon_services' => isset($_POST['icon_services']) ? '1' : '0',
'suggest_source' => $src, 'suggest_source' => $src,
'collections_layout' => $coll, 'collections_layout' => $coll,
+13 -1
View File
@@ -335,7 +335,19 @@ else: // settings ?>
某个板块是进「合集」还是做成普通网格板块,由「板块 → 展示方式」决定。 某个板块是进「合集」还是做成普通网格板块,由「板块 → 展示方式」决定。
</p> </p>
<label>主题色<input type="text" name="primary_color" value="<?= e(setting('primary_color', '#2F6BF7')) ?>"></label> <label>主题色<input type="text" name="primary_color" value="<?= e(setting('primary_color', '#2F6BF7')) ?>"></label>
<label class="chk"><input type="checkbox" name="open_blank" <?= setting('open_blank', '1') === '1' ? 'checked' : '' ?>> 搜索结果新窗口打开</label> <label>搜索与链接的打开方式
<?php $ob = setting('open_blank', '1'); ?>
<select name="open_blank">
<option value="1" <?= $ob === '0' ? '' : 'selected' ?>>新标签页打开(推荐)</option>
<option value="0" <?= $ob === '0' ? 'selected' : '' ?>>当前页面打开</option>
</select>
</label>
<p class="hint span2">
同时管两处:<b>顶部搜索</b>(提交后到哪儿看结果)和<b>所有网站格 / 合集链接</b>。
「新标签页打开」(默认):点完当前导航页还在,方便连着开好几个;
「当前页面打开」:在同一个标签里跳走,适合把本站当浏览器起始页、不想堆一堆标签的场景。
<b>不影响</b>后台管理界面里的链接。
</p>
<label class="chk"><input type="checkbox" name="icon_services" <?= setting('icon_services', '1') === '1' ? 'checked' : '' ?>> 联网搜索高清图标(第三方聚合服务)</label> <label class="chk"><input type="checkbox" name="icon_services" <?= setting('icon_services', '1') === '1' ? 'checked' : '' ?>> 联网搜索高清图标(第三方聚合服务)</label>
<p class="hint span2">勾上后,当站点自身没有提供高清图标时,会依次尝试 icon.horse、Yandex 图标服务;关掉则只用站点自己声明的图标。</p> <p class="hint span2">勾上后,当站点自身没有提供高清图标时,会依次尝试 icon.horse、Yandex 图标服务;关掉则只用站点自己声明的图标。</p>
<div class="span2 form-actions"><button class="btn primary">保存设置</button></div> <div class="span2 form-actions"><button class="btn primary">保存设置</button></div>
+2
View File
@@ -104,6 +104,8 @@ function seed_settings(PDO $pdo): void
'subtitle' => '个人网站导航', 'subtitle' => '个人网站导航',
'default_engine' => 'bing', 'default_engine' => 'bing',
'primary_color' => '#2F6BF7', 'primary_color' => '#2F6BF7',
// '1' = 顶部搜索框与所有站点/合集链接都在**新标签页**打开;'0' = 都在**当前页面**打开。
// 默认 '1' 与引入该设置前的旧行为一致;老库若已有这一行不会被覆盖(INSERT OR IGNORE)。
'open_blank' => '1', 'open_blank' => '1',
]; ];
$stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)'); $stmt = $pdo->prepare('INSERT OR IGNORE INTO settings (k, v) VALUES (?, ?)');
+16 -3
View File
@@ -21,6 +21,17 @@ $engines = $pdo->query("SELECT * FROM engines WHERE is_active=1 ORDER BY sort
// flat = 全部平铺 —— 每个合集各占一块,一眼看全、不用点 // flat = 全部平铺 —— 每个合集各占一块,一眼看全、不用点
$collLayout = setting('collections_layout', 'tabs') === 'flat' ? 'flat' : 'tabs'; $collLayout = setting('collections_layout', 'tabs') === 'flat' ? 'flat' : 'tabs';
/** 站点链接与顶部搜索框的打开方式(后台「设置 → 打开方式」)
* 返回 `_blank`(新标签页,默认,等同改造前的旧行为)或 `_self`(当前页面)。
* ⚠️ 搜索框与站点格必须共用这一个来源,不要在任何一处写死 target。 */
function open_target(): string
{
return setting('open_blank', '1') === '1' ? '_blank' : '_self';
}
$openTarget = open_target();
$openMode = $openTarget === '_blank' ? 'blank' : 'self';
// 默认引擎 // 默认引擎
$defSlug = setting('default_engine', 'bing'); $defSlug = setting('default_engine', 'bing');
$default = null; $default = null;
@@ -48,9 +59,11 @@ function site_tile(array $s): void
{ {
$label = site_label($s); $label = site_label($s);
$src = icon_src($s['icon']); $src = icon_src($s['icon']);
// 打开方式与顶部搜索框同源(见 open_target()):新标签页 or 当前页面
$tgt = open_target();
?> ?>
<li class="site"> <li class="site">
<a href="<?= e($s['url']) ?>" target="_blank" rel="noopener" title="<?= e($s['name'] ?: $label) ?>"> <a href="<?= e($s['url']) ?>" target="<?= e($tgt) ?>"<?= $tgt === '_blank' ? ' rel="noopener"' : '' ?> title="<?= e($s['name'] ?: $label) ?>">
<?php if ($src): ?> <?php if ($src): ?>
<img class="icon" src="<?= e($src) ?>" alt="" loading="lazy"> <img class="icon" src="<?= e($src) ?>" alt="" loading="lazy">
<?php else: ?> <?php else: ?>
@@ -80,7 +93,7 @@ function site_tile(array $s): void
<style>:root { --primary: <?= e($primary) ?>; }</style> <style>:root { --primary: <?= e($primary) ?>; }</style>
<?php endif; ?> <?php endif; ?>
</head> </head>
<body> <body data-open="<?= e($openMode) ?>">
<div class="container"> <div class="container">
<!-- 顶部品牌区 --> <!-- 顶部品牌区 -->
@@ -108,7 +121,7 @@ function site_tile(array $s): void
<?php endif; ?> <?php endif; ?>
</div> </div>
<form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= setting('open_blank', '1') === '1' ? '_blank' : '_self' ?>" rel="noopener" <form id="search-form" action="<?= e($default['search_url'] ?? 'https://cn.bing.com/search') ?>" method="get" target="<?= e($openTarget) ?>"<?= $openTarget === '_blank' ? ' rel="noopener"' : '' ?>
data-slug="<?= e($default['slug'] ?? '') ?>"> data-slug="<?= e($default['slug'] ?? '') ?>">
<input type="text" id="search-text" name="<?= e($default['param'] ?? 'q') ?>" <input type="text" id="search-text" name="<?= e($default['param'] ?? 'q') ?>"
placeholder="<?= e(($default['name'] ?? '搜索') . '搜索') ?>" autocomplete="off"> placeholder="<?= e(($default['name'] ?? '搜索') . '搜索') ?>" autocomplete="off">