Files
FeedLite/lib/bootstrap.php
T
laoyang 9b971a5b92 下拉框改为自绘列表;修复新建软件缺排序字段
用户两个反馈:
① 「排序那个怎么删了?排序还得留着呀」
② 「所有下拉框拉下来之后全是灰色,不好看,重新设计」

一、排序字段(是误解,但确实有缺陷)

排序字段没有被删,一直在 if ($esw) 分支里渲染。
用户看到"没有",是因为他打开的是「添加软件」——
新建时 $esw 为空,整块不渲染,所以确实没有这一项。
这是真实缺陷:新建软件时无法指定排序。

修法:
- 把排序字段移出 if ($esw),新建/编辑都显示
- 新建时默认值 = 当前最大 sort_order + 1(排到最后)
- 后端 sw_add 改为优先用表单值(用 isset 判断,
  用户明确填 0 是合法值——排最前面,不能被当成"没填")

与「下载地址」不同:排序是真实会被用到的字段
(前台软件列表、顶栏三横线菜单都按它排),不能删。

二、下拉框重设计

为什么必须自绘:原生 select 的展开列表由浏览器绘制,
CSS 只能改 option 的底色,改不了圆角/行高/hover 高亮,
各平台样式还不一样(Windows 是灰方框)。用户说的"全是灰色"就是这个。

但不重写 HTML:全站 16 个 select,逐个改容易漏,
也会破坏 onchange 提交这类内联逻辑。

做法:原生 select 保留(视觉隐藏,仍参与提交与 change 事件),
JS 在旁生成按钮 + 浮层列表。
- 浮层用 position:fixed 挂 body,避免被 overflow:hidden 裁(踩过)
- 位置自动避让视口,下方不够翻上方
- 支持键盘:上下键 Home End Enter Esc
- 选中项带对勾
- 无 JS 时原生 select 仍可用

配色踩坑:选中项最初用 var(--brand-soft),而该变量会被软件主题色覆盖 ——
同步日历主题色 #ff3f44 时 --brand-soft 成了 #ffe8e9(粉红),
下拉里一片粉很难看。下拉是工具性控件,不该跟软件换色,
改用中性色:#eef1f5 选中 / #f1f3f6 hover,四个软件下完全一致。

验证(真实页面实测):
- 2 个 select 均被增强,原生已隐藏
- 面板白底 rgb(255,255,255)、圆角 11px、双层柔和阴影、7 个列表项
- 选中项 rgb(238,241,245)、hover rgb(241,243,246)、带对勾
- 四个软件(#0057b8/#ff3f44/#a556f7/#3571fb)下选中项底色完全相同,
  不再随主题色变粉
- 键盘下方向键三次 → 高亮「处理中」,底色正确
- 选择后 selectedIndex/value 同步、面板自动关闭、change 事件已触发
- 浮层 fixed 定位、完整可见、不被裁切
- 排序字段 3 处(软件/分类/状态)齐全;5 个路由 200
2026-11-04 19:27:01 +08:00

146 lines
6.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
/**
* 启动引导:加载配置、注册自动加载、开启会话、初始化数据库。
* 所有页面(index/thread/new/api)都以此为入口。
*/
if (!defined('FB_ROOT')) {
define('FB_ROOT', dirname(__DIR__));
// 静态资源版本号:改 assets/ 下的 css/js 后把这个数字 +1,
// 让浏览器与 CDN 缓存失效。全站只此一处,别再往页面里写死数字。
if (!defined('FB_ASSET_V')) { define('FB_ASSET_V', 49); }
}
/* ------------------------------------------------------------------
* 错误显示策略必须最先设置:
* 任何被打印到页面的 Warning/Deprecated 都会让响应头提前发出,
* 从而令随后的 session_start() 失败(表现为「会话已过期」、CSRF 全部失效)。
* 所以这里在 require 任何库文件之前就把 display_errors 关掉。
* ------------------------------------------------------------------ */
@ini_set('display_errors', '0');
@ini_set('log_errors', '1');
error_reporting(E_ALL);
// 兜底:捕获致命错误时也不要向页面输出堆栈(生产环境)
if (!defined('FB_BOOTSTRAPPED')) {
define('FB_BOOTSTRAPPED', 1);
}
// 后台改设置后自增,用来让 fb_cfg 的进程内缓存失效
if (!isset($GLOBALS['__fb_cfg_stamp'])) {
$GLOBALS['__fb_cfg_stamp'] = 0;
}
if (!function_exists('fb_cfg')) {
/**
* 只读【配置文件】的值(config.php → config.example.php),
* **不叠加 settings 表、绝不触达数据库**。
*
* 为什么必须单独有这么一条通道:正常的 fb_cfg() 会去读 settings 表,
* 而读表要先 fb_db() —— 那一步会就地建出一个空数据库文件。
* 「是否已安装」的判定恰恰以「数据库文件在不在」为依据,
* 所以判定必须走这条不碰库的路径,否则第一秒就把安装态自己造出来了。
*/
function fb_cfg_file() {
static $file = null;
static $stamp = -1;
$now = isset($GLOBALS['__fb_cfg_stamp']) ? (int)$GLOBALS['__fb_cfg_stamp'] : 0;
if ($file === null || $stamp !== $now) {
$local = FB_ROOT . '/config.php';
$example = FB_ROOT . '/config.example.php';
if (is_file($local)) { $file = (array)(require $local); }
elseif (is_file($example)) { $file = (array)(require $example); }
else { $file = []; }
$stamp = $now;
}
return $file;
}
/**
* 读取配置(点分路径,如 fb_cfg('smtp.host'))
* 取值顺序:settings 表(后台改过的)> config.php(本地配置)> config.example.php(出厂默认)> $default
*
* config.php 不参与版本管理(见 .gitignore),新部署时可从 config.example.php 复制一份;
* 若不存在则直接以 config.example.php 为默认值运行,保证 clone 下来即可跑。
*/
function fb_cfg($path = null, $default = null) {
static $merged = null;
static $stamp = -1;
$now = isset($GLOBALS['__fb_cfg_stamp']) ? (int)$GLOBALS['__fb_cfg_stamp'] : 0;
if ($merged === null || $stamp !== $now) {
$cfg = fb_cfg_file();
// 后台设置以嵌套数组形式叠加在文件配置之上(只覆盖同名键)
$tree = function_exists('fb_setting_tree') ? fb_setting_tree() : [];
$merged = is_array($tree) && $tree ? array_replace_recursive($cfg, $tree) : $cfg;
$stamp = $now;
}
if ($path === null || $path === '') { return $merged; }
$cur = $merged;
foreach (explode('.', (string)$path) as $k) {
if (is_array($cur) && array_key_exists($k, $cur)) {
$cur = $cur[$k];
} else {
return $default;
}
}
return $cur;
}
}
/** 让 fb_cfg() 的进程内缓存失效(改了磁盘上的 config.php 后必须调用) */
function fb_cfg_flush() {
$GLOBALS['__fb_cfg_stamp'] = ((int)$GLOBALS['__fb_cfg_stamp']) + 1;
}
require_once FB_ROOT . '/lib/util.php';
require_once FB_ROOT . '/lib/db.php';
require_once FB_ROOT . '/lib/oss.php';
require_once FB_ROOT . '/lib/attach.php';
require_once FB_ROOT . '/lib/captcha.php';
require_once FB_ROOT . '/lib/mailer.php';
require_once FB_ROOT . '/lib/layout.php';
/* ------------------------------------------------------------------
* 安装引导:数据库还没生成(data/ 下没数据)就说明是全新部署,
* 一律先送去 install.php 填配置、建库、设口令,装完才进系统。
* 判据只看「数据库文件在不在」——绝不能在这里调 fb_db(),
* 因为那会就地建出一个空库、把安装态自己"装"出来,之后就再也进不了向导。
* ------------------------------------------------------------------ */
if (!defined('FB_SKIP_INSTALL_REDIRECT') && !fb_installed()) {
if (PHP_SAPI === 'cli') {
fwrite(STDERR, "FeedLite 尚未安装:请先在浏览器中打开本目录下的 install.php 完成安装。\n");
exit(1);
}
fb_redirect('install.php');
}
// 会话(CLI 下不启用)
if (PHP_SAPI !== 'cli' && session_status() !== PHP_SESSION_ACTIVE) {
$p = session_get_cookie_params();
$https = (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off');
@session_set_cookie_params([
'lifetime' => 0,
'path' => !empty($p['path']) ? $p['path'] : '/',
'domain' => '',
'secure' => $https,
'httponly' => true,
'samesite' => 'Lax',
]);
@session_start();
}
// 初始化数据库(建表)。
// ★ 只在【已安装】时做:未安装就建库,等于第一秒把这个状态自己抹掉,
// 安装向导(install.php)随后就会被判定成「已经装好了」而拒绝工作。
// install.php 在真正提交安装时会自己显式调用 fb_db()。
if (fb_installed()) {
fb_db();
// 顺手确保 uploads/ 目录与「禁止直读」规则在位且为最新版(local / oss 都需要)。
// ⚠️ 为什么放在这里:老站点上早已存在的旧版 .htaccess(曾允许直读)不会自己更新,
// 而部署账号通常对 uploads/ 无写权限 —— 只有 PHP(目录属主)能改。
// 函数内部只在「文件缺失或版本标记过期」时才写盘,命中后每次请求几乎零开销。
if (function_exists('fb_local_ensure_dir')) {
fb_local_ensure_dir();
}
}