修复:跨 tab 保存互相清空配置;补齐全流程邮件通知与统一模板 (#2)
## 一、用户报「我明明配过 SMTP,怎么全丢了、信也发不出」
**没有重置数据库**(installed_at 仍是 11-01 15:14,oss.*/site_name/admin.username 全在)。
真因是我上一轮「邮件拆独立 tab」挖的坑:站点设置与邮件通知**共用 act=settings**,
而保存函数把 smtp 字段无条件写库:
foreach ($smtpText as $k) {
fb_set_setting($k, trim((string)$post($k, \x27\x27))); // 没提交 → 空串 → 覆盖
}
在站点设置页保存附件设置时,该表单没有 smtp_* 字段,$post() 取到空串,
于是 host/user/from 被清成空。smtp.pass 因为单独做了「留空=不修改」而幸存,
所以现场是 pass 有值、其余全空 —— 这个组合本身就是 bug 的指纹。
修法:新增 $has() 判断「本次请求是否真的提交了该字段」,没提交就跳过。
按 tab 分两组保护:
- $isSiteForm:站点信息/页脚/附件/OSS,逐字段 has() 双保险
- $isMailForm:SMTP/通知开关,整段 + 逐字段
checkbox 尤其要保护:未提交会被当成「取消勾选」,
在站点设置页存一次就会把「有人回复时通知」全关掉。
## 二、notify.admin_new_thread 是假的(用户说「提交了反馈收不到邮件」)
这个设置项:白名单里有、后台有勾选框,**但没有任何代码实现它**。
grep 只命中三处(白名单/保存/界面),没有一处真正发信。
已在 new.php 提交成功后实现:查新帖 → 取所属软件名 → fb_notify_event(\x27new\x27)。
## 三、补齐全部事件,统一入口
此前只有 reply 和 close 两个事件,且两套模板风格不一致。
新增 fb_notify_event() 统一入口,处理「开关→收件人→节流→发信→日志」。
覆盖 8 类事件:reply / status / approve / hide / close / new。
- status 按状态给不同文案(待处理/处理中/已解决/关闭/不予考虑),
「不予考虑」语气克制、必须带理由并留申诉入口
- approve / hide 原先完全不发信,现在补上
- 状态没变化时不发信(不打扰用户)
## 四、邮件模板重做
抽出 fb_mail_layout() 统一外壳,所有事件共用:
- table + 内联样式(Outlook/QQ邮箱/Gmail 兼容)
- 600px 卡片、顶部主色条、圆角徽标、字段表、引用块、callout、CTA 按钮
- 响应式(已验 375px 无溢出、无裁切)
- preheader 摘要、纯文本兜底链接、收件人提示
## 五、后台状态按钮(用户要求)
问题详情页顶部一排按钮,点一下立刻生效并自动发信:
「通过并公开」「处理中」「已解决」「不予考虑」「撤下」
- 当前状态对应的按钮置灰
- 「不予考虑」展开内联面板填理由(不用 window.prompt)
- 新增 setstatus 动作;「通过并公开」会同时置可见
## 六、notify_log 加 reason 列
原表只有 reply_id,无法区分事件类型。加 reason 并幂等迁移;
老库 reply_id 是 NOT NULL,非回复事件用 0 表示。
## 验证
邮件专项 73/73:
- 8 类事件模板全部完整 HTML、table 布局、徽标、页脚、无 script
- 8 个主题互不相同;状态徽标文案正确
- 总开关关闭时 admin_new_thread 仍可独立生效
- 节流只作用于 reply/status,close 不受影响
- SMTP 未启用时留失败日志(含 reason 与原因)
- C1-C5:站点页保存后 smtp.host/user/notify.enabled 均未被清空;
邮件页保存后 oss.bucket 未被清空
反向验证:完整回退两层保护后 C1/C2/C3 全部 FAIL
(host 从 smtp.qiye.aliyun.com 变成空、notify.enabled 从 1 变 0),
证明测试能真正抓到该 bug,不是假通过。
回归:全新安装 33/33、老基线 25/25、附件 22/22。
This commit is contained in:
@@ -81,6 +81,41 @@ main.wrap{padding-top:24px; padding-bottom:40px; min-height:60vh}
|
||||
.page-head h1{margin-bottom:4px}
|
||||
.page-head .muted{font-size:14px; margin:0}
|
||||
|
||||
/* ---------- 后台处理按钮栏 ---------- */
|
||||
.act-bar{
|
||||
margin:0 0 18px; padding:14px 16px;
|
||||
background:var(--card); border:1px solid var(--line); border-radius:12px;
|
||||
}
|
||||
.act-bar-title{
|
||||
font-size:12.5px; font-weight:700; letter-spacing:.5px;
|
||||
color:var(--muted); text-transform:uppercase; margin-bottom:10px;
|
||||
}
|
||||
.act-bar-body{display:flex; flex-wrap:wrap; gap:8px; align-items:center}
|
||||
.act-inline{display:inline}
|
||||
.act-btn{white-space:nowrap}
|
||||
.act-btn.is-cur{
|
||||
opacity:.55; cursor:default;
|
||||
box-shadow:none;
|
||||
}
|
||||
.act-btn.ok{border-color:#b7dfc2; color:#1e7b3c}
|
||||
.act-btn.ok:hover:not(:disabled){background:#f0f9f2}
|
||||
.act-btn.warn{border-color:#f0dfae; color:#8a6a15}
|
||||
.act-btn.warn:hover:not(:disabled){background:#fff9ec}
|
||||
.act-state.on{
|
||||
display:inline-block; padding:6px 12px; border-radius:99px;
|
||||
font-size:12.5px; font-weight:600;
|
||||
background:#f0f9f2; color:#1e7b3c; border:1px solid #b7dfc2;
|
||||
}
|
||||
.act-hint{
|
||||
margin-left:auto; font-size:12.5px; color:var(--muted);
|
||||
}
|
||||
@media (max-width:560px){
|
||||
.act-hint{margin-left:0; flex-basis:100%}
|
||||
}
|
||||
.act-note{
|
||||
margin-top:14px; padding-top:14px; border-top:1px dashed var(--line);
|
||||
}
|
||||
|
||||
/* ---------- 主题色选择器 ---------- */
|
||||
.sw-dot{
|
||||
display:inline-block; width:10px; height:10px; border-radius:50%;
|
||||
|
||||
Reference in New Issue
Block a user