Files
NetworkAuth/web/template/admin/settings.html
2026-03-18 21:51:17 +08:00

509 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{ define "settings.html" }}
<section>
<h2>系统设置</h2>
<!-- 系统配置设置 -->
<div class="layui-panel" style="margin-top: 16px;">
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">安全配置</h3>
<div style="padding: 20px;">
<form class="layui-form" id="systemForm">
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="maintenance-mode">维护模式</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; justify-content: flex-start; gap: 10px;">
<input type="checkbox" name="maintenance_mode" lay-skin="switch" lay-text="开启|关闭" title="开启|关闭">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="encryption-key">加密密钥</label>
<div class="layui-input-block">
<div style="display: flex; gap: 10px;">
<input type="text" name="encryption_key" placeholder="请输入数据加密密钥" class="layui-input" readonly>
<button type="button" class="layui-btn layui-btn-primary" id="generateEncBtn">生成</button>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="jwt-secret">JWT密钥</label>
<div class="layui-input-block">
<div style="display: flex; gap: 10px;">
<input type="text" name="jwt_secret" placeholder="请输入JWT签名密钥" class="layui-input" readonly>
<button type="button" class="layui-btn layui-btn-primary" id="generateJwtBtn">生成</button>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="jwt-refresh">JWT刷新</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; gap: 10px;">
<input type="number" name="jwt_refresh" placeholder="6" min="1" lay-affix="number" class="layui-input"
style="width: 120px;" />
<span class="layui-form-mid">小时至少1小时</span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="jwt-expire">JWT有效期</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; gap: 10px;">
<input type="number" name="jwt_expire" placeholder="24" min="1" lay-affix="number" class="layui-input"
style="width: 120px;" />
<span class="layui-form-mid">小时至少1小时</span>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="save_system">保存安全配置</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm reset-btn" data-type="system">重置</button>
</div>
</div>
</form>
</div>
</div>
<!-- Cookie 设置 -->
<div class="layui-panel" style="margin-top: 16px;">
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">Cookie 设置</h3>
<div style="padding: 20px;">
<form class="layui-form" id="cookieForm">
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="cookie-secure">Secure</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; justify-content: flex-start; gap: 10px;">
<input type="checkbox" name="cookie_secure" lay-skin="switch" lay-text="开启|关闭" title="开启|关闭">
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="cookie-samesite">Same</label>
<div class="layui-input-block">
<select name="cookie_same_site">
<option value="Strict">Strict</option>
<option value="Lax">Lax</option>
<option value="None">None</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="cookie-domain">Domain</label>
<div class="layui-input-block">
<input type="text" name="cookie_domain" placeholder="留空则默认为当前域名" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="cookie-max-age">MaxAge</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; gap: 10px;">
<input type="number" name="cookie_max_age" placeholder="86400" min="0" lay-affix="number" class="layui-input"
style="width: 120px;" />
<span class="layui-form-mid"></span>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="save_cookie">保存Cookie设置</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm reset-btn" data-type="cookie">重置</button>
</div>
</div>
</form>
</div>
</div>
<!-- 日志清理设置 -->
<div class="layui-panel" style="margin-top: 16px;">
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">日志清理设置</h3>
<div style="padding: 20px;">
<form class="layui-form" id="logCleanupForm">
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="login-log-cleanup">登录日志</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; gap: 10px;">
<span class="layui-form-mid">保留</span>
<input type="number" name="login_log_cleanup_days" placeholder="30" min="0" lay-affix="number" class="layui-input" style="width: 80px;" />
<span class="layui-form-mid">天,且保留最近</span>
<input type="number" name="login_log_cleanup_limit" placeholder="10000" min="0" lay-affix="number" class="layui-input" style="width: 100px;" />
<span class="layui-form-mid">0为不限制</span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="operation-log-cleanup">操作日志</label>
<div class="layui-input-block">
<div style="display: flex; align-items: center; gap: 10px;">
<span class="layui-form-mid">保留</span>
<input type="number" name="operation_log_cleanup_days" placeholder="30" min="0" lay-affix="number" class="layui-input" style="width: 80px;" />
<span class="layui-form-mid">天,且保留最近</span>
<input type="number" name="operation_log_cleanup_limit" placeholder="10000" min="0" lay-affix="number" class="layui-input" style="width: 100px;" />
<span class="layui-form-mid">0为不限制</span>
</div>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="save_cleanup">保存清理策略</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm reset-btn" data-type="cleanup">重置</button>
</div>
</div>
</form>
</div>
</div>
<!-- 基本信息设置 -->
<div class="layui-panel" style="margin-top: 16px;">
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">基本信息设置</h3>
<div style="padding: 20px;">
<form class="layui-form" id="basicForm">
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="site-title">站点标题</label>
<div class="layui-input-block">
<input type="text" name="site_title" lay-verify="required" placeholder="请输入站点标题" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="site-keywords">关键词</label>
<div class="layui-input-block">
<input type="text" name="site_keywords" placeholder="请输入站点关键词,多个关键词用逗号分隔" class="layui-input" />
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label" style="cursor: pointer;" data-tips="site-description">站点描述</label>
<div class="layui-input-block">
<textarea name="site_description" placeholder="请输入站点描述" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="site-logo">站点Logo</label>
<div class="layui-input-block">
<input type="text" name="site_logo" placeholder="/assets/logo.svg" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="save_basic">保存基本信息</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm reset-btn" data-type="basic">重置</button>
</div>
</div>
</form>
</div>
</div>
<!-- 页脚与备案信息 -->
<div class="layui-panel" style="margin-top: 16px;">
<h3 style="margin: 0; padding: 15px 20px; border-bottom: 1px solid var(--lay-color-border-2); padding-bottom: 10px; margin-bottom: 15px;">页脚与备案</h3>
<div style="padding: 20px;">
<form class="layui-form" id="footerForm">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label" style="cursor: pointer;" data-tips="footer-text">页脚文本</label>
<div class="layui-input-block">
<textarea name="footer_text" placeholder="© 2025 凌动技术 保留所有权利" class="layui-textarea"></textarea>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="icp-record">ICP备案</label>
<div class="layui-input-block">
<input type="text" name="icp_record" placeholder="京ICP备12345678号" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="icp-record-link">备案链接</label>
<div class="layui-input-block">
<input type="url" name="icp_record_link" placeholder="https://beian.miit.gov.cn" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="psb-record">公安备案</label>
<div class="layui-input-block">
<input type="text" name="psb_record" placeholder="京公网安备11010802012345号" class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label" style="cursor: pointer;" data-tips="psb-record-link">备案链接</label>
<div class="layui-input-block">
<input type="url" name="psb_record_link" placeholder="http://www.beian.gov.cn/portal/registerSystemInfo"
class="layui-input" />
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="button" class="layui-btn layui-btn-sm" lay-submit lay-filter="save_footer">保存页脚备案</button>
<button type="button" class="layui-btn layui-btn-primary layui-btn-sm reset-btn" data-type="footer">重置</button>
</div>
</div>
</form>
</div>
</div>
</section>
<script>
// 等待layui加载完成
function waitForLayui(callback) {
if (typeof layui !== 'undefined') {
callback();
} else {
setTimeout(() => waitForLayui(callback), 100);
}
}
waitForLayui(function () {
layui.use(['jquery', 'form', 'layer', 'util'], function () {
const { $, form, layer, util } = layui;
// 缓存上次加载的设置值,用于“重置”恢复
let originalSettings = {};
/**
* 加载后台所有设置并回填到三个表单
* - 从 /admin/api/settings 获取 name:value 映射
* - 处理开关型字段maintenance_mode
* - 渲染 layui 组件
*/
const loadSettings = async () => {
try {
const res = await fetch('/admin/api/settings', {
method: 'GET',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await res.json();
if (data.code !== 0) {
layer.msg(data.msg || '加载设置失败', { icon: 2 });
return;
}
originalSettings = data.data || {};
fillForms(originalSettings);
} catch (err) {
console.error('获取设置失败:', err);
layer.msg('网络错误,无法加载设置', { icon: 2 });
}
};
/**
* 将 settings 数据回填到各表单控件
* - 拆分为独立的填充函数,便于局部重置
*/
const fillSystem = (settings) => {
const maintenanceChecked = (settings.maintenance_mode || '0') === '1';
$('[name="maintenance_mode"]').prop('checked', maintenanceChecked);
$('[name="jwt_secret"]').val(settings.jwt_secret || '');
$('[name="encryption_key"]').val(settings.encryption_key || '');
$('[name="jwt_refresh"]').val(settings.jwt_refresh || '6');
$('[name="jwt_expire"]').val(settings.jwt_expire || '24');
};
const fillCookie = (settings) => {
const cookieSecureChecked = (settings.cookie_secure || 'true') === 'true' || settings.cookie_secure === '1';
$('[name="cookie_secure"]').prop('checked', cookieSecureChecked);
$('[name="cookie_same_site"]').val(settings.cookie_same_site || 'Lax');
$('[name="cookie_domain"]').val(settings.cookie_domain || '');
$('[name="cookie_max_age"]').val(settings.cookie_max_age || '86400');
};
const fillCleanup = (settings) => {
$('[name="login_log_cleanup_days"]').val(settings.login_log_cleanup_days || '30');
$('[name="login_log_cleanup_limit"]').val(settings.login_log_cleanup_limit || '10000');
$('[name="operation_log_cleanup_days"]').val(settings.operation_log_cleanup_days || '30');
$('[name="operation_log_cleanup_limit"]').val(settings.operation_log_cleanup_limit || '10000');
};
const fillBasic = (settings) => {
$('[name="site_title"]').val(settings.site_title || '');
$('[name="site_keywords"]').val(settings.site_keywords || '');
$('[name="site_description"]').val(settings.site_description || '');
$('[name="site_logo"]').val(settings.site_logo || '');
};
const fillFooter = (settings) => {
$('[name="footer_text"]').val(settings.footer_text || '');
$('[name="icp_record"]').val(settings.icp_record || '');
$('[name="icp_record_link"]').val(settings.icp_record_link || '');
$('[name="psb_record"]').val(settings.psb_record || '');
$('[name="psb_record_link"]').val(settings.psb_record_link || '');
};
const fillForms = (settings = {}) => {
fillBasic(settings);
fillSystem(settings);
fillCookie(settings);
fillCleanup(settings);
fillFooter(settings);
// 渲染 layui 组件
form.render();
};
/**
* 收集某个表单下所有可用控件的值
* - 统一将 checkbox 转为 "1"/"0"
* - 其他控件转为字符串,避免后端类型不一致
*/
const collectForm = (selector) => {
const obj = {};
const $form = $(selector);
$form.find('input, textarea, select').each(function () {
const $el = $(this);
const name = $el.attr('name');
if (!name) return; // 无 name 不纳入
const type = ($el.attr('type') || '').toLowerCase();
let value = '';
if (type === 'checkbox') {
value = $el.prop('checked') ? '1' : '0';
} else {
value = ($el.val() ?? '').toString();
}
obj[name] = value;
});
return obj;
};
/**
* 汇总三个表单的字段为一个扁平对象
*/
const collectAllSettings = () => {
return {
...collectForm('#basicForm'),
...collectForm('#systemForm'),
...collectForm('#cookieForm'),
...collectForm('#footerForm'),
...collectForm('#logCleanupForm'),
};
};
/**
* 提交设置到后端
* @param {Object} payload - 要保存的设置对象
* @param {HTMLElement} btnElem - 触发保存的按钮元素(用于禁用/恢复)
* @param {String} successMsg - 成功提示信息
*/
const submitSettings = (payload, btnElem, successMsg = '保存成功') => {
const $btn = $(btnElem);
$btn.prop('disabled', true).addClass('layui-btn-disabled');
const loadIdx = layer.msg('正在保存...', {
icon: 16,
time: 0,
shade: 0.1
});
fetch('/admin/api/settings/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(payload)
})
.then(resp => resp.json())
.then(res => {
if (res.code === 0) {
layer.msg(res.msg || successMsg, { icon: 1, time: 1000 });
// 更新本地缓存,合并新保存的设置
originalSettings = { ...originalSettings, ...payload };
} else {
layer.msg(res.msg || '保存失败', { icon: 2 });
}
})
.catch(err => {
console.error('保存设置失败:', err);
var msg = '网络错误,保存失败';
if (err.response && err.response.data && err.response.data.msg) {
msg = err.response.data.msg;
} else if (err.message) {
msg = err.message;
}
layer.msg(msg, { icon: 2 });
})
.finally(() => {
layer.close(loadIdx);
$btn.prop('disabled', false).removeClass('layui-btn-disabled');
});
};
/**
* 绑定各个分块的保存按钮
*/
form.on('submit(save_system)', function(data){
submitSettings(collectForm('#systemForm'), data.elem, '安全配置已保存');
return false;
});
form.on('submit(save_cookie)', function(data){
submitSettings(collectForm('#cookieForm'), data.elem, 'Cookie设置已保存');
return false;
});
form.on('submit(save_cleanup)', function(data){
submitSettings(collectForm('#logCleanupForm'), data.elem, '清理策略已保存');
return false;
});
form.on('submit(save_basic)', function(data){
submitSettings(collectForm('#basicForm'), data.elem, '基本信息已保存');
return false;
});
form.on('submit(save_footer)', function(data){
submitSettings(collectForm('#footerForm'), data.elem, '页脚备案已保存');
return false;
});
/**
* 处理各个分块的重置按钮
*/
$(document).on('click', '.reset-btn', function() {
const type = $(this).data('type');
switch (type) {
case 'system':
fillSystem(originalSettings);
break;
case 'cookie':
fillCookie(originalSettings);
break;
case 'cleanup':
fillCleanup(originalSettings);
break;
case 'basic':
fillBasic(originalSettings);
break;
case 'footer':
fillFooter(originalSettings);
break;
}
form.render();
layer.msg('已恢复该部分默认值', { icon: 1, time: 800 });
});
/**
* 生成安全密钥
*/
const generateKey = async (type) => {
try {
const loadIdx = layer.load(2);
const res = await fetch(`/admin/api/settings/generate_key?type=${type}`, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const data = await res.json();
layer.close(loadIdx);
if (data.code === 0) {
if (type === 'jwt') {
$('[name="jwt_secret"]').val(data.data.key);
} else if (type === 'encryption') {
$('[name="encryption_key"]').val(data.data.key);
}
layer.msg('生成成功', { icon: 1 });
} else {
layer.msg(data.msg || '生成失败', { icon: 2 });
}
} catch (err) {
console.error('生成密钥失败:', err);
layer.closeAll('loading');
layer.msg('网络错误', { icon: 2 });
}
};
$('#generateJwtBtn').on('click', () => generateKey('jwt'));
$('#generateEncBtn').on('click', () => generateKey('encryption'));
// 初始化:加载设置
loadSettings();
});
});
</script>
{{ end }}