Files
FeedLite/lib/layout.php
T
laoyang 2ef119f7cb 标题字号折中;软件选择移入右上角汉堡菜单
用户反馈三点:
1. 顶栏图标太大(56px)
2. 顶栏文字反而比内容区还小 —— 主次颠倒
3. 不要再整页显示「选择软件:」,改成右上角三横线按钮,点开下拉选

字号标准(用户给的基准):
  「选择软件」标签 13.5px,比它大两号 ≈ 17px。
  → 顶栏 17px 字 / 30px 图标;内容区 15px 字 / 24px 图标。
  这样 17 > 15,层级正确。

   之前两轮都搞错了:
   - 先给顶栏 46px 图标 + 22px 字:图标过大
   - 再给 56px + 26px:更大
   - 而且内容区当时是 30px,比顶栏还大,视觉上「上面比下面小」

软件选择改造:
- 顶栏右侧新增汉堡按钮(三横线,展开变×)
- 抽屉面板放在 header 内、正文之上,默认 hidden
- 列出软件:图标 + 名字 + 当前项标记
- index.php 删除整行「选择软件:」+ chips
- new.php 表单内也删除那排 chip(同一选择不该出现两次)
- 交互:点击展开/再点收起/点外部关闭/Esc 关闭
- 宽屏抽屉横向排列,窄屏纵向

验证(CDP 驱动真实浏览器):
汉堡按钮存在、初始收起、点击展开、aria 同步、软件条目与当前标记齐全、
再点收起、点外部关闭 —— 全部 PASS;
顶栏字号 17px > 内容区 15px,页面无裸露 ?>;
全部路由 200;窄屏 15px/13px 层级仍正确。
2026-11-03 08:51:20 +08:00

219 lines
9.8 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
/**
* 页面外壳(头部 / 底部)
*/
if (!defined('FB_ROOT')) { http_response_code(403); exit('forbidden'); }
/**
* 输出页面头部
* @param string $title 页面标题
* @param array $opt ['robots' => 'noindex', 'desc' => '...']
*/
function fb_layout_head($title, array $opt = []) {
$site = (string)fb_cfg('site_name');
$robots = isset($opt['robots']) ? (string)$opt['robots'] : 'index,follow';
$full = $title === '' ? $site : ($title . ' · ' . $site);
/* ---- 按软件换主题色 ----
* 通过 $opt['software'] 传入软件行;全站主题只靠 --brand / --brand-600 /
* --brand-soft 三个变量,覆写它们即可,不用为每个软件写一套 CSS。
* 没传软件(如后台、安装页)就用主题默认色,保持中性。 */
$swColors = null;
if (!empty($opt['software'])) {
list($cMain, $cDark, $cSoft) = fb_software_colors($opt['software']);
if ($cMain !== '') { $swColors = [$cMain, $cDark, $cSoft]; }
}
$themeColor = $swColors ? $swColors[0] : '#0057B8';
?><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="<?php echo e($themeColor); ?>">
<meta name="robots" content="<?php echo e($robots); ?>">
<title><?php echo e($full); ?></title>
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon-16x16.png">
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon.png">
<link rel="manifest" href="assets/site.webmanifest">
<link rel="stylesheet" href="assets/style.css?v=<?php echo (int)FB_ASSET_V; ?>">
<?php if ($swColors): ?>
<?php /* 按软件覆写主题色:只改这三个变量,其它样式自动跟着变 */ ?>
<style>:root{
--brand:<?php echo e($swColors[0]); ?>;
--brand-600:<?php echo e($swColors[1]); ?>;
--brand-soft:<?php echo e($swColors[2]); ?>;
}</style>
<?php endif; ?>
</head>
<body>
<header class="topbar">
<div class="wrap">
<?php
/* 顶栏品牌位:当前在哪个软件下,就显示「图标 + XX 反馈系统」。
* 这是页面唯一的主标题 —— 多软件共用一套反馈系统,
* 用户要一眼看清「我现在是在给哪个软件的反馈系统提问题」。
* 内容区不再重复一遍同样的文字,只保留「图标 + 软件名」。
*
* 字号刻意比「选择软件」那个标签大一号左右(17px),
* 不再压过内容区标题 —— 之前给到 26px 反而比下面的字还小,
* 视觉上主次颠倒。 */
$curSw = isset($opt['software']) && is_array($opt['software']) ? $opt['software'] : null;
$swList = fb_softwares(true);
?>
<?php if ($curSw): ?>
<a class="brand-mini" href="./" title="<?php echo e(fb_software_site_title($curSw)); ?>">
<?php echo fb_software_badge($curSw, 'brand', fb_software_site_title($curSw)); ?>
</a>
<?php else: ?>
<a class="brand-mini" href="./" title="<?php echo e($site); ?>">
<span class="logo-mark" role="img" aria-label="轻反馈图标"></span>
<span class="brand-text"><?php echo e($site); ?></span>
</a>
<?php endif; ?>
<div class="spacer"></div>
<a class="navlink hide-sm" href="./">全部问题</a>
<?php if (fb_admin_logged_in()): ?><a class="navlink hide-sm" href="admin.php">管理后台</a><?php endif; ?>
<a class="btn-mini hide-sm" href="new.php">我要反馈</a>
<?php if (count($swList) > 1): ?>
<?php /* 软件切换收进右上角汉堡菜单:
* 原来平铺一行「选择软件:」+ 若干 chip,占掉一整行,
* 而且软件一多就把列表往下推。收进抽屉后页面更干净,
* 移动端也不用单独适配那排 chip。 */ ?>
<button type="button" class="sw-toggle" id="swToggle"
aria-label="切换软件" aria-expanded="false" aria-controls="swPanel">
<span></span><span></span><span></span>
</button>
<?php endif; ?>
</div>
<?php if (count($swList) > 1): ?>
<div class="sw-panel" id="swPanel" hidden>
<div class="wrap">
<div class="sw-panel-hd">选择软件</div>
<div class="sw-panel-list">
<?php foreach ($swList as $s): ?>
<?php
$on = $curSw && (int)$s['id'] === (int)$curSw['id'];
$href = fb_qs(['sw' => (string)$s['slug'], 'cat' => null, 'page' => null]);
if ($href === '') { $href = '?sw=' . urlencode((string)$s['slug']); }
?>
<a class="sw-item<?php echo $on ? ' on' : ''; ?>" href="<?php echo e($href); ?>">
<?php echo fb_software_badge($s, 'md'); ?>
<?php if ($on): ?><span class="sw-item-cur">当前</span><?php endif; ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
</header>
<main class="wrap">
<?php
}
/** 输出页面底部 */
function fb_layout_foot() {
// 页脚两件事:① 常驻「管理后台」入口(省得每次手敲 admin.php);② 可关闭的 FeedLite 版权句。
// 版权句里的 FeedLite 指向开源仓库,地址可后台改;关掉开关则整句不渲染,只留管理后台链接。
$showCredit = (bool)fb_cfg('footer.show_credit', true);
$creditUrl = trim((string)fb_cfg('footer.credit_url', ''));
if ($creditUrl === '') { $creditUrl = fb_credit_url_default(); }
// 只放行 http/https:防止把 javascript:/data: 之类写进页脚链接(后台设置项,同样要防)
if (!preg_match('~^https?://~i', $creditUrl)) { $creditUrl = fb_credit_url_default(); }
?>
<footer class="foot">
<div class="wrap">
<p class="muted">本站仅保存你提交的昵称、邮箱与内容,用于问题跟进;页面不会公开展示邮箱与手机号。</p>
<p class="muted foot-line">
<?php if ($showCredit): ?><span><?php echo e((string)fb_cfg('site_name', '轻反馈')); ?> · 由 <a href="<?php echo e($creditUrl); ?>" target="_blank" rel="noopener">FeedLite</a> 驱动</span><?php endif; ?>
<?php if ($showCredit): ?><span class="dot">·</span><?php endif; ?>
<a href="admin.php">管理后台</a>
</p>
</div>
</footer>
<script src="assets/app.js?v=<?php echo (int)FB_ASSET_V; ?>"></script>
</body>
</html><?php
}
/** 渲染提示条 */
function fb_flash_box($msg, $type = 'error') {
if ($msg === '' || $msg === null) { return; }
echo '<div class="flash ' . e($type) . '">' . e($msg) . '</div>';
}
/**
* 渲染附件上传控件(图片 / 视频;上传经服务器中转落地,local/OSS 前端同一路径)
* 需置于 <form> 内部:会输出一个 name="attachments" 的隐藏字段,
* 由 assets/app.js 在文件上传成功后回填 JSON 描述。
*
* @param array $existing 校验失败时已上传的附件描述(避免用户重复上传)
*/
function fb_uploaders(array $existing = []) {
$kinds = [];
if (fb_upload_kind_on('image')) { $kinds[] = 'image'; }
if (fb_upload_kind_on('video')) { $kinds[] = 'video'; }
if (!$kinds) { return; }
$init = [];
foreach ($existing as $it) {
$kind = isset($it['kind']) ? (string)$it['kind'] : 'image';
$key = isset($it['key']) ? (string)$it['key'] : '';
if ($key === '') { continue; }
$init[] = [
'kind' => $kind,
'key' => $key,
'size' => isset($it['size']) ? (int)$it['size'] : 0,
'name' => isset($it['name']) ? (string)$it['name'] : '',
'mime' => isset($it['mime']) ? (string)$it['mime'] : '',
'w' => isset($it['width']) ? (int)$it['width'] : 0,
'h' => isset($it['height']) ? (int)$it['height'] : 0,
'url' => fb_attach_url($key),
'thumb' => $kind === 'image' ? fb_attach_thumb_url($key, 360) : '',
];
}
?>
<div class="uploader-box"
data-uploader-root
data-csrf="<?php echo e(fb_csrf_token()); ?>"
data-sign-url="upload.php"
data-init="<?php echo e(json_encode($init, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); ?>">
<?php foreach ($kinds as $kind):
$isImg = ($kind === 'image');
$maxN = (int)fb_upload_cfg($kind . '_max_per_post', 1);
$maxB = (int)fb_upload_cfg($kind . '_max_bytes', 0);
$accept = implode(',', (array)fb_upload_cfg($kind . '_mimes', []));
$label = $isImg ? '图片' : '视频';
// 0 表示不限制,文案别写成「最多 0 张」
$cntTxt = $maxN > 0 ? ('最多 ' . $maxN . ($isImg ? ' 张' : ' 个')) : '数量不限';
$sizeTxt = $maxB > 0
? ('单' . ($isImg ? '张' : '个') . '不超过 ' . fb_size_h($maxB))
: '大小不限';
$desc = $cntTxt . ',' . $sizeTxt . ($isImg ? ';大图会自动压缩后上传' : '');
?>
<div class="uploader" data-uploader data-kind="<?php echo e($kind); ?>"
data-max-count="<?php echo $maxN; ?>"
data-max-bytes="<?php echo $maxB; ?>"
data-max-side="<?php echo (int)fb_upload_cfg('image_max_side', 1600); ?>"
data-accept="<?php echo e($accept); ?>"
data-label="<?php echo e($label); ?>">
<div class="up-head">
<span class="up-title"><?php echo e($label); ?> <i class="hint"><?php echo e($desc); ?></i></span>
<button type="button" class="btn up-pick">选择<?php echo e($label); ?></button>
<input type="file" class="up-file" accept="<?php echo e($accept); ?>"
<?php echo $isImg ? 'multiple' : ''; ?> tabindex="-1">
</div>
<div class="up-list"></div>
</div>
<?php endforeach; ?>
<input type="hidden" name="attachments" class="att-field" value="">
</div>
<?php
}