多软件区分:邮件按软件换色+图标+软件名;补提交回执邮件

用户要求:多软件并存时,用「颜色 + 图标 + 名字」三重区分,
邮件也要跟着所属软件变,一眼能看出是哪款软件的来信。

1. software 表加 icon / download_url 字段(幂等迁移)
   - icon 存单个字符(emoji / 汉字 / 字母),不存图片路径:
     前端与邮件用同一个字符渲染,天然一致,不用再管一套上传与权限
   - 没配 icon 时取名首字兜底,任何地方都不出现空图标
   - 后台软件编辑加图标快选(12 个常用 emoji)

2. 邮件配色改为跟随软件(fb_mail_brand 取 fb_software_colors)
   - 头部色条、按钮、引用块色条、徽标全部跟着软件走
   - 邮件头部改为「图标色块 + 软件名」,下面再小字标站点名
   - 主题行统一加【软件名】前缀,不点开就能分辨

3. 新增 receipt 事件:提交后立即给提问人发「已收到、待审核、请耐心等待」
   - 独立开关 notify.receipt_enabled(与「回复通知」是两件事)
   - 新增 7 处调用前的静默 return 也一并写日志

4. reply 邮件补上下文:原提问摘要 + 本次回复,像博客评论通知
5. status=resolved 文案改为「已完成并上线」,配了下载地址时给下载按钮

验证:多软件配色/图标/前缀全部通过(含反向验证),XSS 转义通过,
空图标兜底通过;页面顶栏、页头、选择器图标渲染正确。
This commit is contained in:
2026-11-03 08:00:50 +08:00
parent e43f2ce67a
commit 738c6c09ff
12 changed files with 451 additions and 52 deletions
+32
View File
@@ -772,12 +772,42 @@
}
}
/* ---------- 图标快选(后台软件编辑) ---------- */
function bindIconPicker() {
var boxes = document.querySelectorAll('.icon-presets');
for (var i = 0; i < boxes.length; i++) {
(function (box) {
// 图标输入框是「图标快选」的前一个兄弟里的 input[name=icon]
var field = box.parentNode;
var input = field ? field.querySelector('input[name=icon]') : null;
if (!input) { return; }
var btns = box.querySelectorAll('.icon-preset');
function syncOn(v) {
for (var k = 0; k < btns.length; k++) {
var same = (btns[k].getAttribute('data-icon') || '') === v;
if (same) { btns[k].classList.add('on'); } else { btns[k].classList.remove('on'); }
}
}
for (var j = 0; j < btns.length; j++) {
btns[j].addEventListener('click', function () {
input.value = this.getAttribute('data-icon') || '';
syncOn(input.value);
});
}
input.addEventListener('input', function () { syncOn(this.value); });
syncOn(input.value);
})(boxes[i]);
}
}
/* ---------- 邮件测试发送(后台设置页) ---------- */
function bindMailTest() {
var btn = document.getElementById('mailTestBtn');
if (!btn) { return; }
var out = document.getElementById('mailTestOut');
var to = document.getElementById('mailTestTo');
var swSel = document.getElementById('mailTestSw');
btn.addEventListener('click', function () {
var addr = to ? (to.value || '').trim() : '';
if (!addr) {
@@ -793,6 +823,7 @@
fd.append('csrf', btn.getAttribute('data-csrf') || '');
fd.append('act', 'mail_test');
fd.append('to', addr);
if (swSel && swSel.value) { fd.append('sw', swSel.value); }
fetch('admin.php', { method: 'POST', body: fd, credentials: 'same-origin' })
.then(function (r) { return r.text(); })
@@ -821,6 +852,7 @@
bindAdminToggle();
bindWontfixPanel();
bindColorPicker();
bindIconPicker();
bindMailTest();
bindInlineValidate(); // ★ 必须在 bindNoDoubleSubmit 之前
bindNoDoubleSubmit();