feat(admin): 站点信息 + 注册开关可后台设置,模板动态渲染

新增 4 项可配置站点设置(DB持久化+内存缓存,即时生效):
- site.brand          品牌名(页面标题/页脚/管理面板,默认 MetaLab)
- site.framework      框架名(页脚标识,默认 MetaZone)
- site.copyright_start 版权起始年(页脚,默认 2024)
- registration.enabled 注册开关(关闭后禁止新用户注册,默认 true)

实现方式:
- InjectSiteInfo 中间件:全局注入 Brand/Framework/CopyrightStart 到 gin.Context
- BuildPageData 读取 context,所有 SSR 页面自动获取站点信息
- AuthService.Register 最先检查 IsRegistrationEnabled()
- 管理后台新增「基本设置」+「注册设置」区块,文本输入手动保存 + Toggle 即时生效
This commit is contained in:
2026-05-27 15:12:28 +08:00
parent 7bc2dbd970
commit 0b948cf70f
16 changed files with 216 additions and 28 deletions

View File

@ -1,43 +1,94 @@
/* =====================================================
MetaLab 管理面板 - 站点设置
单一职责:加载/保存站点设置,开关即时生效
单一职责:加载/保存站点设置,开关即时生效,文本输入手动保存
===================================================== */
(function() {
'use strict';
const TOGGLE_KEYS = [
{ id: 'registration-enabled', key: 'registration.enabled' },
{ id: 'audit-enabled', key: 'audit.enabled' },
{ id: 'audit-username', key: 'audit.username_audit' },
{ id: 'audit-avatar', key: 'audit.avatar_audit' },
{ id: 'audit-bio', key: 'audit.bio_audit' }
];
// 已有专属 UI 的设置 key不在"其他设置"中重复显示
var DEDICATED_KEYS = {
'site.brand': true, 'site.framework': true, 'site.copyright_start': true,
'registration.enabled': true,
'audit.enabled': true, 'audit.username_audit': true, 'audit.avatar_audit': true, 'audit.bio_audit': true
};
// --- 初始加载 ---
async function loadSettings() {
try {
const res = await api.get('/api/admin/site-settings');
var res = await api.get('/api/admin/site-settings');
if (!res.success) return;
// 审核开关
const audit = res.data.audit || {};
// 所有开关
var audit = res.data.audit || {};
var allSettings = res.data.all || {};
TOGGLE_KEYS.forEach(function(t) {
var el = document.getElementById(t.id);
if (el) {
if (!el) return;
if (t.id === 'registration-enabled') {
el.checked = allSettings['registration.enabled'] !== 'false';
} else {
el.checked = audit[t.key.split('.')[1]] === true;
el.addEventListener('change', function() {
updateBoolSetting(t.key, el.checked, el);
});
}
el.addEventListener('change', function() {
updateBoolSetting(t.key, el.checked, el);
});
});
// 通用设置
renderGeneralSettings(res.data.all || {});
// 文本输入保存按钮
bindSaveButtons();
// 其他设置过滤已有专属UI的key
renderOtherSettings(allSettings);
} catch (e) {
showToast('加载设置失败', 'error');
}
}
// --- 文本输入保存 ---
function bindSaveButtons() {
var buttons = document.querySelectorAll('.btn-save');
buttons.forEach(function(btn) {
btn.addEventListener('click', function() {
var key = btn.getAttribute('data-key');
var inputId = btn.getAttribute('data-input');
var input = document.getElementById(inputId);
if (!key || !input) return;
updateTextSetting(key, input.value, btn);
});
});
}
async function updateTextSetting(key, value, btn) {
btn.disabled = true;
btn.textContent = '...';
try {
var res = await api.put('/api/admin/site-settings', {
key: key,
value: value
});
if (res.success) {
showToast('设置已更新', 'success');
} else {
showToast(res.message || '保存失败', 'error');
}
} catch (e) {
showToast('保存失败', 'error');
} finally {
btn.disabled = false;
btn.textContent = '保存';
}
}
// --- 更新布尔设置 ---
async function updateBoolSetting(key, value, toggle) {
toggle.disabled = true;
@ -60,16 +111,19 @@
}
}
// --- 渲染通用设置 ---
function renderGeneralSettings(all) {
// --- 渲染其他设置过滤已有专属UI的key ---
function renderOtherSettings(all) {
var container = document.getElementById('general-settings');
var keys = Object.keys(all);
if (keys.length === 0) {
var filtered = [];
Object.keys(all).forEach(function(k) {
if (!DEDICATED_KEYS[k]) filtered.push(k);
});
if (filtered.length === 0) {
container.innerHTML = '<div class="settings-empty">暂无其他设置</div>';
return;
}
container.innerHTML = '';
keys.forEach(function(key) {
filtered.forEach(function(key) {
var val = all[key] || '';
var item = document.createElement('div');
item.className = 'setting-item';