RAM 角色自动探测 + 邮件配置界面 + 每个软件独立主题色 (#1 #2)

## 1. ECS RAM 角色改为自动探测
原先要用户去阿里云控制台查「实例 RAM 角色名」再手填,纯属多余 ——
机器自己知道答案。

新增 fb_oss_detect_instance_role():读元数据
GET /latest/meta-data/ram/security-credentials/(末尾带斜杠)拿到角色名列表。
- 兼容单行 / 多行 / JSON 数组三种返回
- 未配置角色时 fb_oss_instance_creds() 自动探测,不再报「未配置」
- 后台改成只读展示「已绑定:xxx」,手填入口收进「高级」折叠里
- 保存时留空 = 自动探测后写入,不存空值
- 探测失败给出可操作原因(没绑角色 / 不在 ECS 上 / 元数据被禁用)

## 2. 邮件配置界面(原先完全缺失)
lib/mailer.php 有完整 SMTP 实现,但后台一个输入框都没有,
只能改 config.php —— 用户装完根本找不到地方配。

根因有两层:
- fb_setting_keys() 白名单里没有任何 smtp.* / notify.* 键,
  就算加了表单,fb_set_setting() 也会把值全部丢弃
- 设置页只有「站点信息」「附件存储」两个区块

补上:白名单 15 个键 + 后台「邮件通知」区块
- SMTP 服务器/端口/加密方式(SSL/STARTTLS/不加密)
- 登录账号/密码(留空不修改,避免每次重填)
- 发件地址/显示名、超时、SSL 校验
- 通知规则:回复通知、每次回复都通知、新反馈通知管理员、发信冷却
- 「发送测试邮件」按钮,AJAX 发完就地显示成功或失败原因,
  不用保存再靠猜

## 3. 每个软件一个主题色
需求:从短链接进不同软件,颜色不同,一眼区分。

- software 表加 color 字段(幂等迁移)
- 全站配色靠 --brand / --brand-600 / --brand-soft 三个变量,
  按软件覆写这三个即可,不用为每个软件写一套 CSS
- fb_color_variants() 由主色推出深一档(×0.85)与浅一档(往白混 88%)
- 未配色的软件按 id 从 8 色调色板取默认色,保证同站不撞色
- 后台:颜色选择器(原生取色 + 8 个预设 + 用默认)
- 前台:index / new / thread 都按当前软件注入主题色,
  meta theme-color 也跟着变

验证:三功能专项 30/30;新旧回归全过
(全新安装 33/33、老基线 25/25、附件 22/22、安装向导 60/60、约束检查 20/20)
This commit is contained in:
2026-11-01 16:39:09 +08:00
parent c19e475e85
commit f1a81cbaa1
12 changed files with 498 additions and 12 deletions
+20 -1
View File
@@ -13,12 +13,23 @@ 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="#0057B8">
<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">
@@ -28,6 +39,14 @@ function fb_layout_head($title, array $opt = []) {
<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">