2026-09-28 15:48:35 +08:00
<? php
/**
* 简航 SimpleNav —— 后台首页(网站 / 板块 / 搜索引擎 / 设置)
*/
declare ( strict_types = 1 );
require_once __DIR__ . '/_auth.php' ;
require_login ();
2026-09-28 16:43:10 +08:00
send_no_cache ();
2026-09-28 15:48:35 +08:00
$pdo = db ();
$p = isset ( $_GET [ 'p' ]) ? ( string ) $_GET [ 'p' ] : 'sites' ;
if ( ! in_array ( $p , [ 'sites' , 'groups' , 'engines' , 'settings' ], true )) {
$p = 'sites' ;
}
$flash = $_SESSION [ 'flash' ] ?? null ;
unset ( $_SESSION [ 'flash' ]);
$groups = $pdo -> query ( 'SELECT * FROM groups ORDER BY sort_order, id' ) -> fetchAll ();
$engines = $pdo -> query ( 'SELECT * FROM engines ORDER BY sort_order, id' ) -> fetchAll ();
$SUGGEST_TYPES = [ 'bing_osjson' , 'baidu_sugrec' , 'google_firefox' , 'sogou_ajaj' , 'so360' , 'ddg' ];
function flash_html ( ? array $f ) : string
{
if ( ! $f ) return '' ;
$cls = ( $f [ 't' ] ?? 'ok' ) === 'err' ? 'alert err' : 'alert ok' ;
return '<div class="' . $cls . '">' . e ( $f [ 'm' ]) . '</div>' ;
}
2026-09-28 16:43:10 +08:00
/** 眼睛(显示)/ 斜杠眼(隐藏)图标 —— 用内联 SVG,避免 emoji 字体差异导致显示成方块 */
function eye_svg ( bool $on ) : string
2026-09-28 15:48:35 +08:00
{
2026-09-28 16:43:10 +08:00
$slash = $on ? '' : '<path d="M3 3l18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>' ;
return '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="currentColor">'
. '<path d="M12 5c-5.5 0-9.6 4.3-11 7 1.4 2.7 5.5 7 11 7s9.6-4.3 11-7c-1.4-2.7-5.5-7-11-7zm0 12a5 5 0 110-10 5 5 0 010 10z"/>'
. '<circle cx="12" cy="12" r="2.6"/>'
. $slash
. '</svg>' ;
}
/**
* 显隐切换按钮:表单自带 ajax=1,前端原地切换、不跳页;
* 即使浏览器禁用 JS,表单也会正常提交并回到当前列表页(p 已带上)。
*/
function toggle_form ( string $action , int $id , int $state , string $p = '' ) : string
{
$on = $state === 1 ;
return '<form method="post" action="' . e ( url ( 'admin/actions.php' )) . '" class="inline js-toggle" data-id="' . $id . '">'
. csrf_field ()
. '<input type="hidden" name="action" value="' . e ( $action ) . '">'
2026-09-28 15:48:35 +08:00
. '<input type="hidden" name="id" value="' . $id . '">'
2026-09-28 16:43:10 +08:00
. '<input type="hidden" name="p" value="' . e ( $p ) . '">'
. '<input type="hidden" name="ajax" value="1">'
. '<button type="submit" class="ico-btn toggle' . ( $on ? ' on' : '' ) . '"'
. ' data-on="' . ( $on ? '1' : '0' ) . '"'
. ' title="' . ( $on ? '当前:显示中(点击隐藏)' : '当前:已隐藏(点击显示)' ) . '">'
. eye_svg ( $on )
. '</button></form>' ;
2026-09-28 15:48:35 +08:00
}
2026-09-29 09:21:51 +08:00
/** 上/下移按钮(无 JS 回退用;有 JS 时以拖拽为主) */
function move_form ( string $action , int $id , string $dir , string $label , string $p , string $extra = '' ) : string
2026-09-28 15:48:35 +08:00
{
return '<form method="post" action="' . e ( url ( 'admin/actions.php' )) . '" class="inline">'
. csrf_field () . '<input type="hidden" name="action" value="' . e ( $action ) . '">'
. '<input type="hidden" name="id" value="' . $id . '">'
. '<input type="hidden" name="dir" value="' . $dir . '">'
. '<input type="hidden" name="p" value="' . e ( $p ) . '">'
2026-09-29 09:21:51 +08:00
. $extra
2026-09-28 15:48:35 +08:00
. '<button class="ico-btn" title="' . e ( $label ) . '">' . ( $dir === 'up' ? '↑' : '↓' ) . '</button></form>' ;
}
2026-09-29 09:21:51 +08:00
/** 拖拽手柄(纯展示,真实拖拽由 admin.js 绑在整行上) */
function drag_handle () : string
{
return '<span class="drag-handle" title="按住拖动调整顺序" aria-hidden="true">'
. '<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">'
. '<circle cx="9" cy="7" r="1.6"/><circle cx="15" cy="7" r="1.6"/>'
. '<circle cx="9" cy="12" r="1.6"/><circle cx="15" cy="12" r="1.6"/>'
. '<circle cx="9" cy="17" r="1.6"/><circle cx="15" cy="17" r="1.6"/>'
. '</svg></span>' ;
}
2026-09-28 15:48:35 +08:00
ob_start ();
if ( $p === 'sites' ) :
2026-09-29 09:21:51 +08:00
// 一次取全量,再按板块分桶(避免每板块一次查询)
$allRows = $pdo -> query ( 'SELECT s.*, g.name AS gname FROM sites s JOIN groups g ON g.id=s.group_id ORDER BY g.sort_order, g.id, s.sort_order, s.id' ) -> fetchAll ();
$byGroup = [];
foreach ( $allRows as $rr ) {
$byGroup [( int ) $rr [ 'group_id' ]][] = $rr ;
}
2026-09-28 16:43:10 +08:00
$customCount = 0 ;
2026-09-29 09:21:51 +08:00
foreach ( $allRows as $rr ) { if (( int )( $rr [ 'icon_custom' ] ?? 0 ) === 1 ) { $customCount ++ ; } }
2026-09-28 15:48:35 +08:00
?>
<?= flash_html($flash) ?>
<section class="card">
<div class="card-head">
<h2>新增 / 编辑网站</h2>
2026-09-28 16:43:10 +08:00
<button type="button" class="btn ghost" id="btn-update-all" data-url="<?= e(url('admin/update_all.php')) ?>">⟳ 全部更新(名称 + 自动图标)</button>
2026-09-28 15:48:35 +08:00
</div>
2026-09-28 16:43:10 +08:00
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="grid-form" id="site-form" enctype="multipart/form-data">
2026-09-28 15:48:35 +08:00
<?= csrf_field() ?>
<input type="hidden" name="action" value="site_save">
<input type="hidden" name="id" id="f-id" value="0">
<input type="hidden" name="icon" id="f-icon" value="">
<label>板块
<select name="group_id" id="f-group" required>
<?php foreach ($groups as $g): ?>
<option value="<?= (int)$g['id'] ?>"><?= e($g['name']) ?></option>
<?php endforeach; ?>
</select>
</label>
<label class="span2">网址
<span class="row">
<input type="text" name="url" id="f-url" placeholder="https://example.com" required>
<button type="button" class="btn ghost" id="btn-fetch" data-url="<?= e(url('admin/fetch_meta.php')) ?>">自动获取</button>
</span>
</label>
2026-09-28 16:43:10 +08:00
<label>网站名称(自动抓取)
2026-09-28 15:48:35 +08:00
<input type="text" name="name" id="f-name" placeholder="自动抓取">
</label>
<label>简称(页面显示,手动)
<input type="text" name="short_name" id="f-short" placeholder="如:知乎">
</label>
2026-09-28 16:43:10 +08:00
<div class="span2 icon-field">
<div class="icon-field-preview">
<span class="icon-preview" id="f-icon-preview"><span class="ph">无</span></span>
</div>
<div class="icon-field-body">
<div class="icon-field-title">图标</div>
<input type="text" name="icon_url" id="f-icon-url" placeholder="自定义图标图片地址(http/https),留空则用自动获取的">
<div class="row icon-row2">
<input type="file" name="icon_file" id="f-icon-file" accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon,.svg,.ico">
<label class="chk"><input type="checkbox" name="icon_auto" id="f-icon-auto" checked> 允许自动更新图标</label>
</div>
<p class="hint">
优先级:<b>上传文件</b> > <b>自定义地址</b> > 「自动获取」抓到的图。<br>
选择上传或填写地址后会自动取消勾选「允许自动更新图标」,该网站图标此后不再被「全部更新」覆盖;
勾回去即可恢复自动。
</p>
</div>
</div>
2026-09-28 15:48:35 +08:00
<label class="chk"><input type="checkbox" name="is_visible" id="f-visible" checked> 显示</label>
<div class="span2 form-actions">
<button type="submit" class="btn primary">保存</button>
<button type="button" class="btn ghost" id="btn-reset">清空</button>
<span class="hint" id="fetch-hint"></span>
</div>
</form>
</section>
<section class="card">
2026-09-28 16:43:10 +08:00
<div class="card-head">
2026-09-29 09:21:51 +08:00
<h2>网站列表(<?= count($allRows) ?>) </h2>
2026-09-28 16:43:10 +08:00
<span class="hint">其中 <?= $customCount ?> 个使用自定义图标(不会自动更新)</span>
</div>
2026-09-29 09:21:51 +08:00
<p class="hint" style="margin-top:-6px">
按<b>板块</b>分成标签页,每个板块单独调整顺序:<b>按住某一行拖动</b>即可改顺序,松手即保存;
也可以用每行右侧的 ↑ ↓ 微调(不支持拖拽的环境用)。
<b>新添加的网站一律排在所属板块的最后</b>,直到你手动把它拖到别处。
</p>
<?php if (!$allRows): ?>
<p class="hint">还没有任何网站,先在上面的表单里添加一个。</p>
<?php else: ?>
<?php
// 板块标签页:只显示有网站的板块(空板块不占位)
$tabGroups = [];
foreach ($groups as $g) {
$gid = (int)$g['id'];
if (!empty($byGroup[$gid])) { $tabGroups[] = ['g' => $g, 'items' => $byGroup[$gid]]; }
}
?>
<div class="sites-tabs" id="sites-tabs" role="tablist" data-reorder-url="<?= e(url('admin/actions.php')) ?>">
<?php foreach ($tabGroups as $i => $t): $gid = (int)$t['g']['id']; ?>
<button type="button" class="tab<?= $i === 0 ? ' active' : '' ?>" role="tab"
aria-selected="<?= $i === 0 ? 'true' : 'false' ?>"
data-target="sgroup-<?= $gid ?>"><?= e($t['g']['name']) ?>
<span class="tab-count"><?= count($t['items']) ?></span>
</button>
<?php endforeach; ?>
</div>
<div class="sites-panels">
<?php foreach ($tabGroups as $i => $t): $gid = (int)$t['g']['id']; ?>
<div class="sites-panel<?= $i === 0 ? ' active' : '' ?>" id="sgroup-<?= $gid ?>" role="tabpanel"
data-group-id="<?= $gid ?>">
<div class="tbl-wrap"><table class="tbl tbl-sortable">
<thead><tr><th class="th-drag"></th><th>简称(展示)</th><th>名称</th><th>网址</th><th>显示</th><th>微调</th><th>操作</th></tr></thead>
2026-09-29 21:45:16 +08:00
<tbody data-group-id="<?= $gid ?>" data-sortable data-sort-action="site_reorder">
2026-09-29 09:21:51 +08:00
<?php foreach ($t['items'] as $r): $label = $r['short_name'] !== '' ? $r['short_name'] : ($r['name'] !== '' ? $r['name'] : domain_of($r['url'])); $src = icon_src($r['icon']); ?>
<tr draggable="true" data-id="<?= (int)$r['id'] ?>">
<td class="td-drag"><?= drag_handle() ?></td>
<td>
<?php if ($src): ?><img class="mini" src="<?= e($src) ?>" alt=""><?php else: ?><span class="mini letter" style="background:<?= e(letter_color($label)) ?>"><?= e(mb_substr($label, 0, 1)) ?></span><?php endif; ?>
<?php if ((int)($r['icon_custom'] ?? 0) === 1): ?><span class="badge" title="用户自定义图标,不会被自动更新覆盖">自定义</span><?php endif; ?>
<?= e($label) ?>
</td>
<td class="muted"><?= e($r['name']) ?></td>
<td class="muted ell"><a href="<?= e($r['url']) ?>" target="_blank" rel="noopener"><?= e($r['url']) ?></a></td>
<td><?= toggle_form('site_toggle', (int)$r['id'], (int)$r['is_visible'], 'sites') ?></td>
<td class="nowrap"><?= move_form('site_move', (int)$r['id'], 'up', '上移', 'sites', '<input type="hidden" name="reload" value="1"><input type="hidden" name="gid" value="' . $gid . '">') . move_form('site_move', (int)$r['id'], 'down', '下移', 'sites', '<input type="hidden" name="reload" value="1"><input type="hidden" name="gid" value="' . $gid . '">') ?></td>
<td class="nowrap">
<button type="button" class="ico-btn edit-site"
data-id="<?= (int)$r['id'] ?>" data-group="<?= (int)$r['group_id'] ?>"
data-url="<?= e($r['url']) ?>" data-name="<?= e($r['name']) ?>"
data-short="<?= e($r['short_name']) ?>" data-icon="<?= e($r['icon']) ?>"
data-icon-url="<?= e($src) ?>" data-custom="<?= (int)($r['icon_custom'] ?? 0) ?>"
data-visible="<?= (int)$r['is_visible'] ?>" title="编辑">✎</button>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="inline" onsubmit="return confirm('确定删除该网站?');">
<?= csrf_field() ?><input type="hidden" name="action" value="site_delete">
<input type="hidden" name="p" value="sites">
<input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
<button class="ico-btn danger" title="删除">✕</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table></div>
<p class="hint sort-status" data-group-id="<?= $gid ?>"></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
2026-09-28 15:48:35 +08:00
</section>
<?php
elseif ($p === 'groups'): ?>
<?= flash_html($flash) ?>
<section class="card">
<div class="card-head"><h2>新增 / 编辑板块</h2></div>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="grid-form" id="group-form">
<?= csrf_field() ?>
<input type="hidden" name="action" value="group_save">
<input type="hidden" name="id" id="g-id" value="0">
<input type="hidden" name="p" value="groups">
<label>板块名称<input type="text" name="name" id="g-name" required></label>
<label>展示方式
<select name="display" id="g-display">
<option value="grid">普通网格板块(如:常用 / 工具)</option>
<option value="tabs">合集标签页</option>
</select>
</label>
<label class="chk"><input type="checkbox" name="is_visible" id="g-visible" checked> 显示</label>
<div class="span2 form-actions"><button class="btn primary">保存</button><button type="button" class="btn ghost" id="g-reset">清空</button></div>
</form>
</section>
<section class="card">
2026-09-29 21:45:16 +08:00
<div class="card-head"><h2>板块列表(<?= count($groups) ?>) </h2></div>
<p class="hint" style="margin-top:-6px">
板块的先后顺序就是它们在<b>前台出现的顺序</b>: <b>按住某一行拖动</b>即可调整,松手即保存;
也可以用每行右侧的 ↑ ↓ 微调(不支持拖拽的环境用)。
<b>新添加的板块一律排在最后</b>,直到你手动把它拖到别处。
</p>
<div class="tbl-wrap"><table class="tbl tbl-sortable">
<thead><tr><th class="th-drag"></th><th>名称</th><th>展示方式</th><th>网站数</th><th>显示</th><th>微调</th><th>操作</th></tr></thead>
<tbody data-sortable data-sort-action="group_reorder" data-reorder-url="<?= e(url('admin/actions.php')) ?>">
2026-09-28 15:48:35 +08:00
<?php foreach ($groups as $g): $cnt = (int)$pdo->query('SELECT COUNT(*) FROM sites WHERE group_id=' . (int)$g['id'])->fetchColumn(); ?>
2026-09-29 21:45:16 +08:00
<tr draggable="true" data-id="<?= (int)$g['id'] ?>">
<td class="td-drag"><?= drag_handle() ?></td>
2026-09-28 15:48:35 +08:00
<td><?= e($g['name']) ?></td>
<td><?= $g['display'] === 'tabs' ? '合集标签页' : '普通网格' ?></td>
<td><?= $cnt ?></td>
2026-09-28 16:43:10 +08:00
<td><?= toggle_form('group_toggle', (int)$g['id'], (int)$g['is_visible'], 'groups') ?></td>
2026-09-28 15:48:35 +08:00
<td class="nowrap"><?= move_form('group_move', (int)$g['id'], 'up', '上移', 'groups') . move_form('group_move', (int)$g['id'], 'down', '下移', 'groups') ?></td>
<td class="nowrap">
<button type="button" class="ico-btn edit-group" data-id="<?= (int)$g['id'] ?>" data-name="<?= e($g['name']) ?>" data-display="<?= e($g['display']) ?>" data-visible="<?= (int)$g['is_visible'] ?>">✎</button>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="inline" onsubmit="return confirm('删除板块会同时删除其下所有网站,确定?');">
<?= csrf_field() ?><input type="hidden" name="action" value="group_delete"><input type="hidden" name="p" value="groups"><input type="hidden" name="id" value="<?= (int)$g['id'] ?>">
<button class="ico-btn danger">✕</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
2026-09-28 16:43:10 +08:00
</table></div>
2026-09-29 21:45:16 +08:00
<p class="hint sort-status"></p>
2026-09-28 15:48:35 +08:00
</section>
<?php
elseif ($p === 'engines'): ?>
<?= flash_html($flash) ?>
<section class="card">
<div class="card-head"><h2>新增 / 编辑搜索引擎</h2></div>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="grid-form" id="engine-form">
<?= csrf_field() ?>
<input type="hidden" name="action" value="engine_save">
<input type="hidden" name="id" id="e-id" value="0">
<input type="hidden" name="p" value="engines">
<label>名称<input type="text" name="name" id="e-name" required></label>
<label>标识 slug<input type="text" name="slug" id="e-slug" placeholder="bing" required></label>
<label class="span2">搜索地址<input type="text" name="search_url" id="e-search" placeholder="https://cn.bing.com/search" required></label>
<label>参数名<input type="text" name="param" id="e-param" placeholder="q / wd / query"></label>
<label>主题色<input type="text" name="color" id="e-color" placeholder="#60A504"></label>
<label class="span2">联想接口({q} 为关键词占位)<input type="text" name="suggest_url" id="e-sug" placeholder="https://api.bing.com/osjson.aspx?query={q}"></label>
<label>联想解析类型
<select name="suggest_type" id="e-sugtype">
<?php foreach ($SUGGEST_TYPES as $t): ?><option value="<?= e($t) ?>"><?= e($t) ?></option><?php endforeach; ?>
</select>
</label>
2026-09-28 16:43:10 +08:00
<label class="chk"><input type="checkbox" name="is_active" id="e-active"> 启用(显示在首页顶部)</label>
2026-09-28 17:13:10 +08:00
<p class="hint span2">
这两项只在「设置 → 联想词来源」选<b>各搜索引擎自己的接口</b>时才会用到;
默认是统一用百度出建议(搜索本身仍走各自的搜索地址)。
这里填的接口只要通了就会被优先使用,没通就自动回退到百度。
</p>
2026-09-28 15:48:35 +08:00
<div class="span2 form-actions"><button class="btn primary">保存</button><button type="button" class="btn ghost" id="e-reset">清空</button></div>
</form>
</section>
<section class="card">
2026-09-28 16:43:10 +08:00
<div class="card-head">
<h2>搜索引擎列表</h2>
<span class="hint">点最右侧「眼睛」图标即在该行原地切换,不会跳页</span>
</div>
<div class="tbl-wrap"><table class="tbl">
<thead><tr><th>名称</th><th>搜索地址</th><th>联想类型</th><th>前台显示</th><th>排序</th><th>操作</th></tr></thead>
2026-09-28 15:48:35 +08:00
<tbody>
<?php foreach ($engines as $en): ?>
<tr>
<td><span style="color:<?= e($en['color']) ?>">■</span> <?= e($en['name']) ?> <span class="muted">(<?= e($en['slug']) ?>)</span></td>
<td class="muted ell"><?= e($en['search_url']) ?></td>
<td class="muted"><?= e($en['suggest_type']) ?></td>
2026-09-28 16:43:10 +08:00
<td class="nowrap">
<span class="state <?= $en['is_active'] ? 'on' : 'off' ?> js-state" data-on-text="已显示" data-off-text="已隐藏"><?= $en['is_active'] ? '已显示' : '已隐藏' ?></span>
<?= toggle_form('engine_toggle', (int)$en['id'], (int)$en['is_active'], 'engines') ?>
</td>
2026-09-28 15:48:35 +08:00
<td class="nowrap"><?= move_form('engine_move', (int)$en['id'], 'up', '上移', 'engines') . move_form('engine_move', (int)$en['id'], 'down', '下移', 'engines') ?></td>
<td class="nowrap">
<button type="button" class="ico-btn edit-engine"
data-id="<?= (int)$en['id'] ?>" data-name="<?= e($en['name']) ?>" data-slug="<?= e($en['slug']) ?>"
data-search="<?= e($en['search_url']) ?>" data-param="<?= e($en['param']) ?>" data-sug="<?= e($en['suggest_url']) ?>"
data-sugtype="<?= e($en['suggest_type']) ?>" data-color="<?= e($en['color']) ?>" data-active="<?= (int)$en['is_active'] ?>">✎</button>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="inline" onsubmit="return confirm('确定删除该引擎?');">
<?= csrf_field() ?><input type="hidden" name="action" value="engine_delete"><input type="hidden" name="p" value="engines"><input type="hidden" name="id" value="<?= (int)$en['id'] ?>">
<button class="ico-btn danger">✕</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
2026-09-28 16:43:10 +08:00
</table></div>
<p class="hint">提示:联想接口需填写对应平台的地址,解析类型可选 bing_osjson / baidu_sugrec / google_firefox / sogou_ajaj / so360 / ddg。「前台显示」为该引擎是否出现在首页顶部搜索框上方的切换条里。</p>
2026-09-28 15:48:35 +08:00
</section>
<?php
else: // settings ?>
<?= flash_html($flash) ?>
<section class="card">
<div class="card-head"><h2>站点设置</h2></div>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="grid-form">
<?= csrf_field() ?>
<input type="hidden" name="action" value="settings_save">
<label>站点名称<input type="text" name="site_name" value="<?= e(setting('site_name', '简航')) ?>"></label>
2026-09-28 23:38:13 +08:00
<label>副标题<input type="text" name="subtitle" value="<?= e(setting('subtitle')) ?>" placeholder="留空则不显示"></label>
<p class="hint" style="grid-column:1/-1;margin:-6px 0 6px">
副标题显示在首页站点名称下方(也会出现在浏览器标题里)。<b>留空即隐藏</b>,不会退回默认文案。
</p>
2026-09-28 15:48:35 +08:00
<label>默认搜索引擎
<select name="default_engine">
<?php foreach ($engines as $en): ?>
<option value="<?= e($en['slug']) ?>" <?= setting('default_engine') === $en['slug'] ? 'selected' : '' ?>><?= e($en['name']) ?></option>
<?php endforeach; ?>
</select>
</label>
2026-09-28 17:13:10 +08:00
<label>联想词来源
<select name="suggest_source">
<?php
$cur = setting('suggest_source', 'baidu');
$opts = [['baidu', '百度(推荐,最快)'], ['bing', '必应'], ['so360', '360'], ['auto', '各搜索引擎自己的接口']];
$known = array_column($engines, 'slug');
foreach ($opts as $o):
if ($o[0] !== 'auto' && !in_array($o[0], $known, true)) { continue; }
?>
<option value="<?= e($o[0]) ?>" <?= $cur === $o[0] ? 'selected' : '' ?>><?= e($o[1]) ?></option>
<?php endforeach; ?>
</select>
</label>
<p class="hint span2">
搜索时仍然用你选中的那个引擎,这里只决定<b>下拉建议词</b>从哪儿来。
实测(2026-09-28):<b>百度 0.12s、360 0.20s、必应 0.5~1.6s</b> 可用;
<b>搜狗接口已失效</b>(返回 0 字节)、<b>谷歌与 DuckDuckGo 国内连不上</b>。
所以默认「统一用百度」——这样切到任何引擎下拉都有词。选「各搜索引擎自己的接口」则恢复成
各用各的(没恢复好的引擎会自动回退到百度的建议)。
</p>
2026-09-28 20:25:33 +08:00
<label>联想词按键
2026-09-29 08:10:22 +08:00
<?php $sk = setting('suggest_keys', 'both'); ?>
2026-09-28 20:25:33 +08:00
<select name="suggest_keys">
2026-09-29 08:10:22 +08:00
<option value="both" <?= $sk === 'both' ? 'selected' : '' ?>>两者都可以(默认,推荐)</option>
<option value="arrows" <?= $sk === 'arrows' ? 'selected' : '' ?>>只用 ↑ / ↓ 方向键</option>
<option value="punct" <?= $sk === 'punct' ? 'selected' : '' ?>>只用 , 和 .(输入法手感)</option>
2026-09-28 20:25:33 +08:00
</select>
</label>
<p class="hint span2">
2026-09-29 08:10:22 +08:00
决定用哪组键在联想下拉里<b>选词</b>:选中某条后回车才按该词搜索;不选就直接回车 = 用你输入的原文。<br>
<b>推荐「两者都可以」</b>——手不用离开主键区,习惯中文输入法的话按 , . 更顺手,同时也保留方向键。
⚠️ 只要联想下拉<b>打开着</b>,逗号与句号就会当作选词键、不再直接输入 ——
想搜「python, pandas」这类带逗号的词,先按 <b>Esc</b> 关掉下拉再输。<br>
若只留一组键,<b>另一组会完全没反应</b>(不是坏了,是这里的选择),所以除非你明确只想用一组,建议保持默认。
2026-09-28 20:25:33 +08:00
</p>
2026-09-28 17:51:10 +08:00
<label>合集显示方式
<?php $cl = setting('collections_layout', 'tabs'); ?>
<select name="collections_layout">
<option value="tabs" <?= $cl === 'flat' ? '' : 'selected' ?>>标签页折叠(合集多时用)</option>
<option value="flat" <?= $cl === 'flat' ? 'selected' : '' ?>>全部平铺(每组各占一块)</option>
</select>
</label>
<p class="hint span2">
只影响底部<b>「合集」</b>那一区,中间那些普通网格板块(常用 / 工具…)不受影响。
<b>每个合集里条目不多</b>时选「全部平铺」,一眼看全、不用点;
<b>合集很多、条目很杂</b>时选「标签页折叠」,默认只展开当前那一个。
某个板块是进「合集」还是做成普通网格板块,由「板块 → 展示方式」决定。
</p>
2026-09-28 15:48:35 +08:00
<label>主题色<input type="text" name="primary_color" value="<?= e(setting('primary_color', '#2F6BF7')) ?>"></label>
2026-09-28 19:28:57 +08:00
<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>
2026-09-28 16:43:10 +08:00
<label class="chk"><input type="checkbox" name="icon_services" <?= setting('icon_services', '1') === '1' ? 'checked' : '' ?>> 联网搜索高清图标(第三方聚合服务)</label>
<p class="hint span2">勾上后,当站点自身没有提供高清图标时,会依次尝试 icon.horse、Yandex 图标服务;关掉则只用站点自己声明的图标。</p>
2026-09-28 15:48:35 +08:00
<div class="span2 form-actions"><button class="btn primary">保存设置</button></div>
</form>
</section>
2026-09-28 22:28:23 +08:00
<section class="card">
<div class="card-head"><h2>站点图标</h2></div>
<?php
$iconOn = site_icon_mode() === 'custom';
$iconPrev = site_icon_url('logo', 512);
$iconDirW = site_icon_dir_writable();
?>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" enctype="multipart/form-data" class="grid-form">
<?= csrf_field() ?>
<input type="hidden" name="action" value="site_icon_save">
<div class="span2 icon-field">
<div class="icon-field-preview big">
<span class="icon-preview big">
<img src="<?= e($iconPrev !== '' ? $iconPrev : url('assets/img/logo.svg')) ?>" alt="">
</span>
</div>
<div class="icon-field-body">
<div class="icon-field-title">
当前:<?= $iconOn ? '自定义图标' : '内置默认图标' ?>
<span class="pill" data-site-icon="<?= e(site_icon_mode()) ?>">
<?= $iconOn ? '自定义' : '默认' ?>
</span>
</div>
<div class="row icon-row2">
<input type="file" name="site_icon_file" accept="image/png,image/jpeg,image/gif,image/webp">
<button class="btn primary" type="submit">上传并应用</button>
</div>
<p class="hint">
上传后,<b>浏览器标签页图标(favicon) </b>与<b>首页顶部的品牌图标</b>会一起换成它,
「添加到手机主屏」的图标也一并更新。<br>
建议用<b>正方形</b>图片(至少 32× 32,推荐 512×512 以上);非正方形会<b>居中裁剪</b>成正方形。
支持 PNG / JPG / GIF / WebP,单张限 5MB。
</p>
<?php if ($iconOn): ?>
<p class="hint" data-site-icon-files="<?= $iconDirW ? 'static' : 'inline' ?>">
<?= $iconDirW
? '已在 uploads/site/ 生成一整套尺寸(16/32/48/180/192/512 + PWA 自适应版)。'
: '站点目录不可写,整套尺寸已内置在本站数据里,由 site-icon.php 按需输出。' ?>
</p>
<?php endif; ?>
</div>
</div>
</form>
<?php if ($iconOn): ?>
<form method="post" action="<?= e(url('admin/actions.php')) ?>">
<?= csrf_field() ?>
<input type="hidden" name="action" value="site_icon_reset">
<p class="hint">想换回内置图标:<button class="btn ghost" type="submit">恢复默认图标</button></p>
</form>
<?php endif; ?>
<p class="hint">
⚠️ 这与「网站列表里每个网站自己的图标」是<b>两回事</b>:这里是<b>本站自己</b>的图标(一个),
那里是导航格子里<b>每个目标网站</b>的图标(很多个)。改这里不会动到任何一个网站格子。
</p>
</section>
2026-09-28 15:48:35 +08:00
<section class="card">
<div class="card-head"><h2>修改管理员密码</h2></div>
<form method="post" action="<?= e(url('admin/actions.php')) ?>" class="grid-form">
<?= csrf_field() ?>
<input type="hidden" name="action" value="password_save">
<label>原密码<input type="password" name="old_password" autocomplete="current-password" required></label>
<label>新密码<input type="password" name="new_password" autocomplete="new-password" required></label>
<div class="span2 form-actions"><button class="btn primary">修改密码</button></div>
</form>
</section>
<?php
endif;
$body = ob_get_clean();
admin_layout($p, '后台', $body);