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:
@ -45,6 +45,19 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* --- 文本输入框 + 保存按钮 --- */
|
||||
.setting-input-wrap {
|
||||
display: flex; gap: 8px; align-items: center; flex-shrink: 0;
|
||||
}
|
||||
.setting-input {
|
||||
padding: 6px 10px; border: 1px solid var(--border);
|
||||
border-radius: 4px; font-size: 13px; width: 200px;
|
||||
outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.setting-input:focus { border-color: var(--primary); }
|
||||
.btn-save { width: 56px; text-align: center; }
|
||||
.btn-save:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* --- 空状态 --- */
|
||||
.settings-empty {
|
||||
text-align: center; padding: 28px 20px;
|
||||
|
||||
@ -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';
|
||||
|
||||
Reference in New Issue
Block a user