mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 10:42:45 +08:00
Enhance user authentication and authentication
Fix the modification of personal information Fix the formatted page template
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -38,59 +38,54 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
// 仪表盘统计脚本(采用箭头函数与中文注释)
|
||||
layui.use(['layer', 'util'], function(){
|
||||
const layer = layui.layer;
|
||||
const util = layui.util;
|
||||
const $ = layui.$;
|
||||
// 仪表盘统计脚本(采用箭头函数与中文注释)
|
||||
layui.use(['layer', 'util'], function () {
|
||||
const layer = layui.layer;
|
||||
const util = layui.util;
|
||||
const $ = layui.$;
|
||||
|
||||
// 全局引用:ECharts CDN 地址
|
||||
const echartsCdn = 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js';
|
||||
// 全局引用:ECharts CDN 地址
|
||||
const echartsCdn = 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js';
|
||||
|
||||
// 工具函数:加载 ECharts 库(若已加载则直接回调)
|
||||
// 功能:通过全局的 loadScript 方法按需加载图表库,避免重复加载
|
||||
const ensureECharts = (cb) => {
|
||||
if (window.echarts) { cb && cb(); return; }
|
||||
if (typeof loadScript === 'function') {
|
||||
loadScript(echartsCdn, () => cb && cb());
|
||||
} else {
|
||||
// 兜底:直接插入 <script>
|
||||
const s = document.createElement('script');
|
||||
s.src = echartsCdn;
|
||||
s.onload = () => cb && cb();
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 函数:刷新基本信息和运行状态
|
||||
// 说明:请求后台获取最新的系统信息并更新页面显示
|
||||
const refreshSystemInfo = () => {
|
||||
$.get('/admin/api/system/info', (res) => {
|
||||
if (res && res.code === 0 && res.data) {
|
||||
const data = res.data;
|
||||
// 更新运行时长
|
||||
if (data.uptime) {
|
||||
$('.system-info-item').each(function() {
|
||||
const label = $(this).find('.system-info-label').text();
|
||||
if (label === '运行时长') {
|
||||
$(this).find('.system-info-value').text(data.uptime);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 工具函数:加载 ECharts 库(若已加载则直接回调)
|
||||
// 功能:通过全局的 loadScript 方法按需加载图表库,避免重复加载
|
||||
const ensureECharts = (cb) => {
|
||||
if (window.echarts) { cb && cb(); return; }
|
||||
if (typeof loadScript === 'function') {
|
||||
loadScript(echartsCdn, () => cb && cb());
|
||||
} else {
|
||||
// 兜底:直接插入 <script>
|
||||
const s = document.createElement('script');
|
||||
s.src = echartsCdn;
|
||||
s.onload = () => cb && cb();
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
}).fail(() => {
|
||||
console.log('获取系统信息失败');
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// 立即刷新一次系统信息
|
||||
refreshSystemInfo();
|
||||
});
|
||||
// 函数:刷新基本信息和运行状态
|
||||
// 说明:请求后台获取最新的系统信息并更新页面显示
|
||||
const refreshSystemInfo = () => {
|
||||
$.get('/admin/api/system/info', (res) => {
|
||||
if (res && res.code === 0 && res.data) {
|
||||
const data = res.data;
|
||||
// 更新运行时长
|
||||
if (data.uptime) {
|
||||
$('.system-info-item').each(function () {
|
||||
const label = $(this).find('.system-info-label').text();
|
||||
if (label === '运行时长') {
|
||||
$(this).find('.system-info-value').text(data.uptime);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).fail(() => {
|
||||
console.log('获取系统信息失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 立即刷新一次系统信息
|
||||
refreshSystemInfo();
|
||||
});
|
||||
</script>
|
||||
{{ end }}
|
||||
@@ -1,73 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{{ .Title }} - {{ .SystemName }}</title>
|
||||
<!-- 站点图标 -->
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||
<script type="module" src="./static/lib/include.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-layout layui-layout-admin" id="app">
|
||||
<div class="layui-header">
|
||||
<!-- 头部区域(可配合layui 已有的水平导航) -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<!-- 移动端显示 -->
|
||||
<li class="layui-nav-item layui-show-xs-inline-block" lay-header-event="menuLeft">
|
||||
<i class="layui-icon layui-icon-spread-left"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<!-- 刷新页面按钮 -->
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="refresh-btn" style="background-color: unset" title="刷新页面">
|
||||
<i class="layui-icon layui-icon-refresh-3" style="font-size: 20px"></i>
|
||||
</a>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{{ .Title }} - {{ .SystemName }}</title>
|
||||
<!-- 站点图标 -->
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||
<script type="module" src="./static/lib/include.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="layui-layout layui-layout-admin" id="app">
|
||||
<div class="layui-header">
|
||||
<!-- 头部区域(可配合layui 已有的水平导航) -->
|
||||
<ul class="layui-nav layui-layout-left">
|
||||
<!-- 移动端显示 -->
|
||||
<li class="layui-nav-item layui-show-xs-inline-block" lay-header-event="menuLeft">
|
||||
<i class="layui-icon layui-icon-spread-left"></i>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
<!-- 刷新页面按钮 -->
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="refresh-btn" style="background-color: unset" title="刷新页面">
|
||||
<i class="layui-icon layui-icon-refresh-3" style="font-size: 20px"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<i id="change-theme" class="layui-icon layui-icon-theme" style="font-size: 20px"></i>
|
||||
</li>
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="logout-btn" style="background-color: unset" title="退出登录">
|
||||
<i class="layui-icon layui-icon-logout" style="font-size: 20px"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layui-side layui-bg-black">
|
||||
<div class="layui-side-scroll">
|
||||
<!-- 左侧导航区域 -->
|
||||
<div class="layui-logo layui-bg-black logo-enhanced">{{ .SystemName }}</div>
|
||||
<ul class="layui-nav layui-nav-tree" lay-shrink="all" lay-unselect lay-filter="nav-side" id="ws-nav-side">
|
||||
<li class="layui-nav-item">
|
||||
<a class="" href="javascript:;">系统管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="dashboard" href="javascript:;">仪表盘</a></dd>
|
||||
<dd><a data-path="user" href="javascript:;">个人资料</a></dd>
|
||||
<dd><a data-path="settings" href="javascript:;">系统设置</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<i id="change-theme" class="layui-icon layui-icon-theme" style="font-size: 20px"></i>
|
||||
</li>
|
||||
<li class="layui-nav-item" lay-unselect>
|
||||
<a href="javascript:;" id="logout-btn" style="background-color: unset" title="退出登录">
|
||||
<i class="layui-icon layui-icon-logout" style="font-size: 20px"></i>
|
||||
</a>
|
||||
<a href="javascript:;">应用管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="apps" href="javascript:;">应用列表</a></dd>
|
||||
<dd><a data-path="apis" href="javascript:;">接口列表</a></dd>
|
||||
<dd><a data-path="variables" href="javascript:;">变量列表</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layui-side layui-bg-black">
|
||||
<div class="layui-side-scroll">
|
||||
<!-- 左侧导航区域 -->
|
||||
<div class="layui-logo layui-bg-black logo-enhanced">{{ .SystemName }}</div>
|
||||
<ul class="layui-nav layui-nav-tree" lay-shrink="all" lay-unselect lay-filter="nav-side" id="ws-nav-side">
|
||||
<li class="layui-nav-item">
|
||||
<a class="" href="javascript:;">系统管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="dashboard" href="javascript:;">仪表盘</a></dd>
|
||||
<dd><a data-path="user" href="javascript:;">个人资料</a></dd>
|
||||
<dd><a data-path="settings" href="javascript:;">系统设置</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:;">应用管理</a>
|
||||
<dl class="layui-nav-child">
|
||||
<dd><a data-path="apps" href="javascript:;">应用列表</a></dd>
|
||||
<dd><a data-path="apis" href="javascript:;">接口列表</a></dd>
|
||||
<dd><a data-path="variables" href="javascript:;">变量列表</a></dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-body">
|
||||
<!-- 内容主体区域 -->
|
||||
<wc-include id="router-view" allow-scripts></wc-include>
|
||||
</div>
|
||||
<div class="layui-footer">{{ .FooterText }}</div>
|
||||
</div>
|
||||
<script type="module" src="./static/js/admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<div class="layui-body">
|
||||
<!-- 内容主体区域 -->
|
||||
<wc-include id="router-view" allow-scripts></wc-include>
|
||||
</div>
|
||||
<div class="layui-footer">{{ .FooterText }}</div>
|
||||
</div>
|
||||
<script type="module" src="./static/js/admin.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
{{/* 管理员登录页面模板:使用layui构建的登录界面 */}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
@@ -21,6 +22,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.demo-login-container {
|
||||
width: 400px;
|
||||
margin: 21px auto 0;
|
||||
@@ -29,42 +31,48 @@
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
margin: 8px 0 0;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
|
||||
/* 调整表单项间距 */
|
||||
.login-form .layui-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* 最后一个表单项不需要底部边距 */
|
||||
.login-form .layui-form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.demo-login-other .layui-icon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
top: 2px;
|
||||
font-size: 26px;
|
||||
}
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
top: 2px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
@@ -72,7 +80,7 @@
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
|
||||
/* 响应式设计 - 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.demo-login-container {
|
||||
@@ -80,22 +88,25 @@
|
||||
margin: 10px auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
padding: 25px 15px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 25px 15px;
|
||||
}
|
||||
|
||||
|
||||
/* 移动端表单项间距调整 */
|
||||
.login-form .layui-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.demo-login-container {
|
||||
width: 95%;
|
||||
@@ -105,16 +116,19 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
|
||||
|
||||
/* 小屏幕表单项间距调整 */
|
||||
.login-form .layui-form-item {
|
||||
margin-bottom: 16px;
|
||||
@@ -126,6 +140,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form class="layui-form">
|
||||
<div class="demo-login-container">
|
||||
@@ -133,26 +148,31 @@
|
||||
<h1>{{ .SystemName }}</h1>
|
||||
<p>管理员登录</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="login-form">
|
||||
<!-- CSRF令牌隐藏字段 -->
|
||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}" id="csrf-token">
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-username"></i>
|
||||
</div>
|
||||
<input type="text" name="username" value="" lay-verify="required" placeholder="用户名" lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||
<input type="text" name="username" value="" lay-verify="required" placeholder="用户名"
|
||||
lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-wrap">
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-password"></i>
|
||||
</div>
|
||||
<input type="password" name="password" value="" lay-verify="required" placeholder="密 码" lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye">
|
||||
<input type="password" name="password" value="" lay-verify="required" placeholder="密 码"
|
||||
lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs7">
|
||||
@@ -160,22 +180,26 @@
|
||||
<div class="layui-input-prefix">
|
||||
<i class="layui-icon layui-icon-vercode"></i>
|
||||
</div>
|
||||
<input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码" lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||
<input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码"
|
||||
lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs5">
|
||||
<div style="margin-left: 5px; text-align: right;">
|
||||
<img id="captcha-img" src="/admin/captcha" onclick="this.src='/admin/captcha?t='+ new Date().getTime();" style="cursor: pointer; height: 38px; border-radius: 4px; width: 100%;" title="点击刷新验证码">
|
||||
<img id="captcha-img" src="/admin/captcha"
|
||||
onclick="this.src='/admin/captcha?t='+ new Date().getTime();"
|
||||
style="cursor: pointer; height: 38px; border-radius: 4px; width: 100%;"
|
||||
title="点击刷新验证码">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="layui-form-item">
|
||||
<button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">立即登录</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="login-footer">
|
||||
<p>{{ .FooterText }}</p>
|
||||
</div>
|
||||
@@ -185,58 +209,63 @@
|
||||
<!-- 请勿在项目正式环境中引用该 layui.js 地址 -->
|
||||
<script src="//unpkg.com/layui@2.12.1/dist/layui.js"></script>
|
||||
<script>
|
||||
layui.use(function(){
|
||||
layui.use(function () {
|
||||
var form = layui.form;
|
||||
var layer = layui.layer;
|
||||
|
||||
|
||||
// 登录提交回调:向 /admin/login 发送请求,并依据 code===0 判断成功与否
|
||||
form.on('submit(demo-login)', function(data){
|
||||
form.on('submit(demo-login)', function (data) {
|
||||
var loadIndex = layer.load(1, {
|
||||
shade: [0.1, '#fff']
|
||||
});
|
||||
|
||||
|
||||
// 获取CSRF令牌
|
||||
var csrfToken = document.getElementById('csrf-token').value;
|
||||
|
||||
// 发送登录请求
|
||||
fetch('/admin/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrfToken
|
||||
},
|
||||
body: JSON.stringify(data.field)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
layer.close(loadIndex);
|
||||
|
||||
// 根据统一接口:code === 0 表示成功
|
||||
const isOk = result && result.code === 0;
|
||||
if (isOk) {
|
||||
layer.msg('登录成功', {
|
||||
icon: 1,
|
||||
time: 1500
|
||||
}, function(){
|
||||
const redirect = (result.data && result.data.redirect) || '/admin';
|
||||
window.location.href = redirect;
|
||||
});
|
||||
} else {
|
||||
const msg = (result && (result.msg || result.message)) || '登录失败,请检查用户名和密码';
|
||||
layer.msg(msg, {icon: 2});
|
||||
|
||||
// 登录失败时刷新验证码
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
layer.close(loadIndex);
|
||||
|
||||
// 根据统一接口:code === 0 表示成功
|
||||
const isOk = result && result.code === 0;
|
||||
if (isOk) {
|
||||
layer.msg('登录成功', {
|
||||
icon: 1,
|
||||
time: 1500
|
||||
}, function () {
|
||||
const redirect = (result.data && result.data.redirect) || '/admin';
|
||||
window.location.href = redirect;
|
||||
});
|
||||
} else {
|
||||
const msg = (result && (result.msg || result.message)) || '登录失败,请检查用户名和密码';
|
||||
layer.msg(msg, { icon: 2 });
|
||||
|
||||
// 登录失败时刷新验证码
|
||||
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('登录错误:', error);
|
||||
layer.msg('网络错误,请稍后重试', { icon: 2 });
|
||||
|
||||
// 网络错误时也刷新验证码
|
||||
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
layer.close(loadIndex);
|
||||
console.error('登录错误:', error);
|
||||
layer.msg('网络错误,请稍后重试', {icon: 2});
|
||||
|
||||
// 网络错误时也刷新验证码
|
||||
document.getElementById('captcha-img').src = '/admin/captcha?t=' + new Date().getTime();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return false; // 阻止表单跳转
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,7 +1,6 @@
|
||||
{{ define "settings.html" }}
|
||||
<section>
|
||||
<h2>系统设置</h2>
|
||||
|
||||
<!-- 基本信息设置 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">基本信息设置</div>
|
||||
@@ -34,7 +33,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 系统配置设置 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">系统配置</div>
|
||||
@@ -61,7 +60,8 @@
|
||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="session-timeout">会话超时</label>
|
||||
<div class="layui-input-block">
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<input type="number" name="session_timeout" placeholder="3600" min="300" max="86400" class="layui-input" style="width: 120px;" />
|
||||
<input type="number" name="session_timeout" placeholder="3600" min="300" max="86400" class="layui-input"
|
||||
style="width: 120px;" />
|
||||
<span class="layui-form-mid">秒(300-86400秒)</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 页脚与备案信息 -->
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">页脚与备案</div>
|
||||
@@ -103,13 +103,14 @@
|
||||
<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" />
|
||||
<input type="url" name="psb_record_link" placeholder="http://www.beian.gov.cn/portal/registerSystemInfo"
|
||||
class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="layui-form-item" style="margin-top: 24px;">
|
||||
<div class="layui-input-block">
|
||||
@@ -129,159 +130,159 @@
|
||||
}
|
||||
}
|
||||
|
||||
waitForLayui(function() {
|
||||
layui.use(['jquery', 'form', 'layer', 'util'], function() {
|
||||
waitForLayui(function () {
|
||||
layui.use(['jquery', 'form', 'layer', 'util'], function () {
|
||||
const { $, form, layer, util } = layui;
|
||||
|
||||
// 缓存上次加载的设置值,用于“重置”恢复
|
||||
let originalSettings = {};
|
||||
// 缓存上次加载的设置值,用于“重置”恢复
|
||||
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 数据回填到各表单控件
|
||||
* - 文本/文本域/下拉:直接赋值
|
||||
* - 开关:根据 "1"/"0" 置为选中/未选中
|
||||
*/
|
||||
const fillForms = (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 maintenanceChecked = (settings.maintenance_mode || '0') === '1';
|
||||
$('[name="maintenance_mode"]').prop('checked', maintenanceChecked);
|
||||
$('[name="default_user_role"]').val(settings.default_user_role || '1');
|
||||
$('[name="session_timeout"]').val(settings.session_timeout || '3600');
|
||||
|
||||
// 页脚与备案
|
||||
$('[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 || '');
|
||||
|
||||
// 渲染 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('#footerForm'),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“保存所有设置”点击
|
||||
* - 二次确认后提交
|
||||
* - 显示加载中,防重复提交
|
||||
* - 成功后提示并刷新缓存的 originalSettings
|
||||
*/
|
||||
const handleSaveAll = () => {
|
||||
const payload = collectAllSettings();
|
||||
layer.confirm('确认保存所有设置?', { icon: 3, title: '提示' }, (idx) => {
|
||||
layer.close(idx);
|
||||
const btn = $('#saveAllBtn');
|
||||
btn.prop('disabled', true).addClass('layui-btn-disabled');
|
||||
const loadIdx = layer.load(2, { content: '正在保存...' });
|
||||
|
||||
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 || '保存成功', { icon: 1, time: 1000 });
|
||||
originalSettings = { ...payload };
|
||||
} else {
|
||||
layer.msg(res.msg || '保存失败', { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('保存设置失败:', err);
|
||||
layer.msg('网络错误,保存失败', { icon: 2 });
|
||||
})
|
||||
.finally(() => {
|
||||
layer.close(loadIdx);
|
||||
btn.prop('disabled', false).removeClass('layui-btn-disabled');
|
||||
/**
|
||||
* 加载后台所有设置并回填到三个表单
|
||||
* - 从 /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 });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“重置”点击
|
||||
* - 恢复为上次加载的 originalSettings
|
||||
*/
|
||||
const handleReset = () => {
|
||||
fillForms(originalSettings);
|
||||
layer.msg('已恢复到上次加载的值', { icon: 1, time: 800 });
|
||||
};
|
||||
/**
|
||||
* 将 settings 数据回填到各表单控件
|
||||
* - 文本/文本域/下拉:直接赋值
|
||||
* - 开关:根据 "1"/"0" 置为选中/未选中
|
||||
*/
|
||||
const fillForms = (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 || '');
|
||||
|
||||
// 事件绑定
|
||||
$('#saveAllBtn').off('click').on('click', handleSaveAll);
|
||||
$('#resetBtn').off('click').on('click', handleReset);
|
||||
// 系统配置
|
||||
const maintenanceChecked = (settings.maintenance_mode || '0') === '1';
|
||||
$('[name="maintenance_mode"]').prop('checked', maintenanceChecked);
|
||||
$('[name="default_user_role"]').val(settings.default_user_role || '1');
|
||||
$('[name="session_timeout"]').val(settings.session_timeout || '3600');
|
||||
|
||||
// 初始化:加载设置
|
||||
loadSettings();
|
||||
// 页脚与备案
|
||||
$('[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 || '');
|
||||
|
||||
// 渲染 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('#footerForm'),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“保存所有设置”点击
|
||||
* - 二次确认后提交
|
||||
* - 显示加载中,防重复提交
|
||||
* - 成功后提示并刷新缓存的 originalSettings
|
||||
*/
|
||||
const handleSaveAll = () => {
|
||||
const payload = collectAllSettings();
|
||||
layer.confirm('确认保存所有设置?', { icon: 3, title: '提示' }, (idx) => {
|
||||
layer.close(idx);
|
||||
const btn = $('#saveAllBtn');
|
||||
btn.prop('disabled', true).addClass('layui-btn-disabled');
|
||||
const loadIdx = layer.load(2, { content: '正在保存...' });
|
||||
|
||||
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 || '保存成功', { icon: 1, time: 1000 });
|
||||
originalSettings = { ...payload };
|
||||
} else {
|
||||
layer.msg(res.msg || '保存失败', { icon: 2 });
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('保存设置失败:', err);
|
||||
layer.msg('网络错误,保存失败', { icon: 2 });
|
||||
})
|
||||
.finally(() => {
|
||||
layer.close(loadIdx);
|
||||
btn.prop('disabled', false).removeClass('layui-btn-disabled');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理“重置”点击
|
||||
* - 恢复为上次加载的 originalSettings
|
||||
*/
|
||||
const handleReset = () => {
|
||||
fillForms(originalSettings);
|
||||
layer.msg('已恢复到上次加载的值', { icon: 1, time: 800 });
|
||||
};
|
||||
|
||||
// 事件绑定
|
||||
$('#saveAllBtn').off('click').on('click', handleSaveAll);
|
||||
$('#resetBtn').off('click').on('click', handleReset);
|
||||
|
||||
// 初始化:加载设置
|
||||
loadSettings();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,472 +1,357 @@
|
||||
{{ define "user.html" }}
|
||||
<style>
|
||||
/* 基础模块样式 */
|
||||
.user-module {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-module .layui-card-header {
|
||||
font-weight: 600;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.module-tabs {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.module-tabs .layui-tab-title li {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.readonly-field {
|
||||
cursor: not-allowed !important;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* 浅色模式样式 */
|
||||
:root {
|
||||
--user-card-header-bg: #f8f9fa;
|
||||
--user-card-header-color: #333;
|
||||
--user-readonly-bg: #f5f5f5;
|
||||
--user-readonly-color: #666;
|
||||
--user-card-bg: #ffffff;
|
||||
--user-card-border: #e6e6e6;
|
||||
--user-input-bg: #ffffff;
|
||||
--user-input-border: #d9d9d9;
|
||||
--user-input-color: #333;
|
||||
}
|
||||
|
||||
/* 深色模式样式 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--user-card-header-bg: #2f2f2f;
|
||||
--user-card-header-color: #e6e6e6;
|
||||
--user-readonly-bg: #3a3a3a;
|
||||
--user-readonly-color: #999;
|
||||
--user-card-bg: #1f1f1f;
|
||||
--user-card-border: #404040;
|
||||
--user-input-bg: #2a2a2a;
|
||||
--user-input-border: #404040;
|
||||
--user-input-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手动深色模式类 */
|
||||
.dark {
|
||||
--user-card-header-bg: #2f2f2f;
|
||||
--user-card-header-color: #e6e6e6;
|
||||
--user-readonly-bg: #3a3a3a;
|
||||
--user-readonly-color: #999;
|
||||
--user-card-bg: #1f1f1f;
|
||||
--user-card-border: #404040;
|
||||
--user-input-bg: #2a2a2a;
|
||||
--user-input-border: #404040;
|
||||
--user-input-color: #e6e6e6;
|
||||
}
|
||||
|
||||
/* 应用CSS变量到元素 */
|
||||
.user-module .layui-card-header {
|
||||
background-color: var(--user-card-header-bg) !important;
|
||||
color: var(--user-card-header-color) !important;
|
||||
}
|
||||
|
||||
.readonly-field {
|
||||
background-color: var(--user-readonly-bg) !important;
|
||||
color: var(--user-readonly-color) !important;
|
||||
}
|
||||
|
||||
.user-module .layui-card {
|
||||
background-color: var(--user-card-bg);
|
||||
border-color: var(--user-card-border);
|
||||
}
|
||||
|
||||
.user-module .layui-input {
|
||||
background-color: var(--user-input-bg);
|
||||
border-color: var(--user-input-border);
|
||||
color: var(--user-input-color);
|
||||
}
|
||||
|
||||
/* 确保表单元素在深色模式下的可读性 */
|
||||
.user-module .layui-form-label {
|
||||
color: var(--user-card-header-color);
|
||||
}
|
||||
|
||||
/* 按钮在深色模式下的样式调整 */
|
||||
.user-module .layui-btn-primary {
|
||||
background-color: var(--user-input-bg);
|
||||
border-color: var(--user-input-border);
|
||||
color: var(--user-input-color);
|
||||
}
|
||||
|
||||
.user-module .layui-btn-primary:hover {
|
||||
background-color: var(--user-readonly-bg);
|
||||
}
|
||||
|
||||
/* 标签页在深色模式下的样式 */
|
||||
.module-tabs .layui-tab-title {
|
||||
border-bottom-color: var(--user-card-border);
|
||||
}
|
||||
|
||||
.module-tabs .layui-tab-title li {
|
||||
color: var(--user-input-color);
|
||||
}
|
||||
|
||||
.module-tabs .layui-tab-title .layui-this {
|
||||
color: var(--lay-color-primary, #1e9fff);
|
||||
}
|
||||
|
||||
/* 图标颜色适配 */
|
||||
.user-module .layui-icon {
|
||||
color: var(--user-card-header-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="layui-tab layui-tab-brief module-tabs" lay-filter="userTabs">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">个人资料</li>
|
||||
<li>修改密码</li>
|
||||
<li>修改用户名</li>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<!-- 个人资料模块 -->
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div class="layui-card user-module">
|
||||
<div class="layui-card-header">
|
||||
<i class="layui-icon layui-icon-user"></i> 个人资料
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="profileForm" lay-filter="profileForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">UUID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="uuid" disabled readonly class="layui-input readonly-field" style="font-family: monospace; font-size: 12px;" />
|
||||
<section>
|
||||
<h2>个人资料</h2>
|
||||
<div class="layui-tab layui-tab-brief" lay-filter="userTabs" style="margin-top: 16px;">
|
||||
<ul class="layui-tab-title">
|
||||
<li class="layui-this">个人资料</li>
|
||||
<li>修改密码</li>
|
||||
<li>修改用户名</li>d
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<!-- 个人资料模块 -->
|
||||
<div class="layui-tab-item layui-show">
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">个人资料</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="profileForm" lay-filter="profileForm">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">UUID</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="uuid" disabled readonly class="layui-input readonly-field"
|
||||
style="font-family: monospace; font-size: 12px;" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户组</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="role" disabled readonly class="layui-input readonly-field" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户组</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="role" disabled readonly class="layui-input readonly-field" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" disabled readonly class="layui-input readonly-field" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" disabled readonly class="layui-input readonly-field" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">创建时间</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="created_at" disabled readonly class="layui-input readonly-field" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">创建时间</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="created_at" disabled readonly class="layui-input readonly-field" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改密码模块 -->
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-card user-module">
|
||||
<div class="layui-card-header">
|
||||
<i class="layui-icon layui-icon-password"></i> 修改密码
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
<!-- 修改密码模块 -->
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">修改密码</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">新密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">新密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">确认密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">确认密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitPassword">
|
||||
<i class="layui-icon layui-icon-ok"></i> 修改密码
|
||||
</button>
|
||||
<button type="button" id="resetPasswordBtn" class="layui-btn layui-btn-primary">
|
||||
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||
</button>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitPassword">
|
||||
<i class="layui-icon layui-icon-ok"></i> 修改密码
|
||||
</button>
|
||||
<button type="button" id="resetPasswordBtn" class="layui-btn layui-btn-primary">
|
||||
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改用户名模块 -->
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-card user-module">
|
||||
<div class="layui-card-header">
|
||||
<i class="layui-icon layui-icon-edit"></i> 修改用户名
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="usernameForm" lay-filter="usernameForm" onsubmit="return false">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="current_username" disabled readonly class="layui-input readonly-field" />
|
||||
<!-- 修改用户名模块 -->
|
||||
<div class="layui-tab-item">
|
||||
<div class="layui-card" style="margin-top: 16px;">
|
||||
<div class="layui-card-header">修改用户名</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="usernameForm" lay-filter="usernameForm" onsubmit="return false">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="current_username" disabled readonly class="layui-input readonly-field" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">新用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">新用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input"
|
||||
lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off" class="layui-input" lay-verify="required" />
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitUsername">
|
||||
<i class="layui-icon layui-icon-ok"></i> 修改用户名
|
||||
</button>
|
||||
<button type="button" id="resetUsernameBtn" class="layui-btn layui-btn-primary">
|
||||
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||
</button>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitUsername">
|
||||
<i class="layui-icon layui-icon-ok"></i> 修改用户名
|
||||
</button>
|
||||
<button type="button" id="resetUsernameBtn" class="layui-btn layui-btn-primary">
|
||||
<i class="layui-icon layui-icon-refresh"></i> 重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||
(() => {
|
||||
// 工具方法:将数值角色转为中文标签
|
||||
const roleToText = (role) => {
|
||||
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||
return r === 0 ? '管理员' : '普通成员'
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 如果未加载 layui,则按需加载
|
||||
const ensureLayui = () => new Promise((resolve) => {
|
||||
if (window.layui) return resolve(window.layui)
|
||||
const css = document.createElement('link')
|
||||
css.rel = 'stylesheet'
|
||||
css.href = 'https://unpkg.com/layui@2.10.1/dist/css/layui.css'
|
||||
document.head.appendChild(css)
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://unpkg.com/layui@2.10.1/dist/layui.js'
|
||||
script.onload = () => resolve(window.layui)
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
// 在确保 Layui 可用后再执行页面逻辑
|
||||
ensureLayui().then(() => {
|
||||
layui.use(['form', 'layer', 'element'], () => {
|
||||
const form = layui.form
|
||||
const layer = layui.layer
|
||||
const element = layui.element
|
||||
|
||||
// 全局变量
|
||||
let userProfile = null
|
||||
|
||||
// 加载个人资料
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/profile')
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '加载失败')
|
||||
|
||||
userProfile = data.data || {}
|
||||
|
||||
// 填充个人资料表单
|
||||
const profileData = {
|
||||
...userProfile,
|
||||
role: roleToText(userProfile.role),
|
||||
created_at: formatTime(userProfile.created_at)
|
||||
}
|
||||
form.val('profileForm', profileData)
|
||||
|
||||
// 填充用户名修改表单的当前用户名
|
||||
form.val('usernameForm', { current_username: userProfile.username })
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
|
||||
}
|
||||
<script>
|
||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||
(() => {
|
||||
// 工具方法:将数值角色转为中文标签
|
||||
const roleToText = (role) => {
|
||||
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||
return r === 0 ? '管理员' : '普通成员'
|
||||
}
|
||||
|
||||
// 修改密码模块
|
||||
const PasswordModule = {
|
||||
validate: (fields) => {
|
||||
const { old_password, new_password, confirm_password } = fields
|
||||
|
||||
if (!old_password || !new_password || !confirm_password) {
|
||||
return { ok: false, msg: '请填写完整的密码信息' }
|
||||
}
|
||||
|
||||
if (new_password.length < 6) {
|
||||
return { ok: false, msg: '新密码长度不能少于6位' }
|
||||
}
|
||||
|
||||
if (new_password !== confirm_password) {
|
||||
return { ok: false, msg: '两次输入的新密码不一致' }
|
||||
}
|
||||
|
||||
if (old_password === new_password) {
|
||||
return { ok: false, msg: '新密码不能与当前密码相同' }
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
},
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
submit: async (fields) => {
|
||||
const validation = PasswordModule.validate(fields)
|
||||
if (!validation.ok) {
|
||||
layer.msg(validation.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
// 如果未加载 layui,则按需加载
|
||||
const ensureLayui = () => new Promise((resolve) => {
|
||||
if (window.layui) return resolve(window.layui)
|
||||
const css = document.createElement('link')
|
||||
css.rel = 'stylesheet'
|
||||
css.href = 'https://unpkg.com/layui@2.10.1/dist/css/layui.css'
|
||||
document.head.appendChild(css)
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://unpkg.com/layui@2.10.1/dist/layui.js'
|
||||
script.onload = () => resolve(window.layui)
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
old_password: fields.old_password,
|
||||
new_password: fields.new_password,
|
||||
confirm_password: fields.confirm_password
|
||||
})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
||||
|
||||
// 检查是否需要跳转
|
||||
if (data.data?.redirect) {
|
||||
layer.msg('密码修改成功,即将跳转到登录页', { icon: 1, time: 1500 }, () => {
|
||||
window.location.href = data.data.redirect
|
||||
})
|
||||
} else {
|
||||
// 密码修改成功,不跳转,重置表单
|
||||
layer.msg('密码修改成功', { icon: 1 })
|
||||
document.getElementById('passwordForm').reset()
|
||||
// 在确保 Layui 可用后再执行页面逻辑
|
||||
ensureLayui().then(() => {
|
||||
layui.use(['form', 'layer', 'element'], () => {
|
||||
const form = layui.form
|
||||
const layer = layui.layer
|
||||
const element = layui.element
|
||||
|
||||
// 全局变量
|
||||
let userProfile = null
|
||||
|
||||
// 加载个人资料
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/profile')
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '加载失败')
|
||||
|
||||
userProfile = data.data || {}
|
||||
|
||||
// 填充个人资料表单
|
||||
const profileData = {
|
||||
...userProfile,
|
||||
role: roleToText(userProfile.role),
|
||||
created_at: formatTime(userProfile.created_at)
|
||||
}
|
||||
form.val('profileForm', profileData)
|
||||
|
||||
// 填充用户名修改表单的当前用户名
|
||||
form.val('usernameForm', { current_username: userProfile.username })
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '修改密码失败', { icon: 2 })
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
document.getElementById('passwordForm').reset()
|
||||
layer.msg('表单已重置', { icon: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
// 修改用户名模块
|
||||
const UsernameModule = {
|
||||
validate: (fields) => {
|
||||
const { new_username, password } = fields
|
||||
|
||||
if (!new_username || !password) {
|
||||
return { ok: false, msg: '请填写新用户名和当前密码' }
|
||||
}
|
||||
|
||||
if (new_username === userProfile?.username) {
|
||||
return { ok: false, msg: '新用户名不能与当前用户名相同' }
|
||||
}
|
||||
|
||||
if (new_username.length < 3) {
|
||||
return { ok: false, msg: '用户名长度不能少于3位' }
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
},
|
||||
|
||||
submit: async (fields) => {
|
||||
const validation = UsernameModule.validate(fields)
|
||||
if (!validation.ok) {
|
||||
layer.msg(validation.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/profile/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: fields.new_username,
|
||||
old_password: fields.password
|
||||
// 修改密码模块
|
||||
const PasswordModule = {
|
||||
validate: (fields) => {
|
||||
const { old_password, new_password, confirm_password } = fields
|
||||
|
||||
if (!old_password || !new_password || !confirm_password) {
|
||||
return { ok: false, msg: '请填写完整的密码信息' }
|
||||
}
|
||||
|
||||
if (new_password.length < 6) {
|
||||
return { ok: false, msg: '新密码长度不能少于6位' }
|
||||
}
|
||||
|
||||
if (new_password !== confirm_password) {
|
||||
return { ok: false, msg: '两次输入的新密码不一致' }
|
||||
}
|
||||
|
||||
if (old_password === new_password) {
|
||||
return { ok: false, msg: '新密码不能与当前密码相同' }
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
},
|
||||
|
||||
submit: async (fields) => {
|
||||
const validation = PasswordModule.validate(fields)
|
||||
if (!validation.ok) {
|
||||
layer.msg(validation.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/password', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
old_password: fields.old_password,
|
||||
new_password: fields.new_password,
|
||||
confirm_password: fields.confirm_password
|
||||
})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
||||
|
||||
// 检查是否需要跳转
|
||||
if (data.data?.redirect) {
|
||||
layer.msg('密码修改成功,即将跳转到登录页', { icon: 1, time: 1500 }, () => {
|
||||
window.location.href = data.data.redirect
|
||||
})
|
||||
} else {
|
||||
// 密码修改成功,不跳转,重置表单
|
||||
layer.msg('密码修改成功', { icon: 1 })
|
||||
document.getElementById('passwordForm').reset()
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '修改密码失败', { icon: 2 })
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
document.getElementById('passwordForm').reset()
|
||||
layer.msg('表单已重置', { icon: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
// 修改用户名模块
|
||||
const UsernameModule = {
|
||||
validate: (fields) => {
|
||||
const { new_username, password } = fields
|
||||
|
||||
if (!new_username || !password) {
|
||||
return { ok: false, msg: '请填写新用户名和当前密码' }
|
||||
}
|
||||
|
||||
if (new_username === userProfile?.username) {
|
||||
return { ok: false, msg: '新用户名不能与当前用户名相同' }
|
||||
}
|
||||
|
||||
if (new_username.length < 3) {
|
||||
return { ok: false, msg: '用户名长度不能少于3位' }
|
||||
}
|
||||
|
||||
return { ok: true }
|
||||
},
|
||||
|
||||
submit: async (fields) => {
|
||||
const validation = UsernameModule.validate(fields)
|
||||
if (!validation.ok) {
|
||||
layer.msg(validation.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/admin/api/user/profile/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: fields.new_username,
|
||||
old_password: fields.password
|
||||
})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改用户名失败')
|
||||
|
||||
layer.msg('用户名修改成功', { icon: 1 })
|
||||
|
||||
// 重新加载个人资料
|
||||
await loadProfile()
|
||||
|
||||
// 清空表单(不显示重置提示)
|
||||
form.val('usernameForm', {
|
||||
new_username: '',
|
||||
password: '',
|
||||
current_username: userProfile?.username || ''
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
form.val('usernameForm', {
|
||||
new_username: '',
|
||||
password: '',
|
||||
current_username: userProfile?.username || ''
|
||||
})
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改用户名失败')
|
||||
|
||||
layer.msg('用户名修改成功', { icon: 1 })
|
||||
|
||||
// 重新加载个人资料
|
||||
await loadProfile()
|
||||
|
||||
// 清空表单
|
||||
UsernameModule.reset()
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
||||
layer.msg('表单已重置', { icon: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
form.val('usernameForm', {
|
||||
new_username: '',
|
||||
password: '',
|
||||
current_username: userProfile?.username || ''
|
||||
// 绑定表单提交事件
|
||||
form.on('submit(submitPassword)', (obj) => {
|
||||
return PasswordModule.submit(obj.field)
|
||||
})
|
||||
layer.msg('表单已重置', { icon: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定表单提交事件
|
||||
form.on('submit(submitPassword)', (obj) => {
|
||||
return PasswordModule.submit(obj.field)
|
||||
form.on('submit(submitUsername)', (obj) => {
|
||||
return UsernameModule.submit(obj.field)
|
||||
})
|
||||
|
||||
// 绑定重置按钮
|
||||
document.getElementById('resetPasswordBtn')?.addEventListener('click', PasswordModule.reset)
|
||||
document.getElementById('resetUsernameBtn')?.addEventListener('click', UsernameModule.reset)
|
||||
|
||||
// 初始化加载
|
||||
loadProfile()
|
||||
})
|
||||
})
|
||||
|
||||
form.on('submit(submitUsername)', (obj) => {
|
||||
return UsernameModule.submit(obj.field)
|
||||
})
|
||||
|
||||
// 绑定重置按钮
|
||||
document.getElementById('resetPasswordBtn')?.addEventListener('click', PasswordModule.reset)
|
||||
document.getElementById('resetUsernameBtn')?.addEventListener('click', UsernameModule.reset)
|
||||
|
||||
// 初始化加载
|
||||
loadProfile()
|
||||
})
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
})()
|
||||
</script>
|
||||
</section>
|
||||
{{ end }}
|
||||
@@ -3,7 +3,8 @@
|
||||
<h2>变量管理</h2>
|
||||
<div class="layui-btn-container" style="margin:12px 0">
|
||||
<button class="layui-btn" id="btnAddVariable"><i class="layui-icon layui-icon-add-1"></i> 新增变量</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteVariables"><i class="layui-icon layui-icon-delete"></i> 批量删除</button>
|
||||
<button class="layui-btn layui-btn-danger" id="btnBatchDeleteVariables"><i class="layui-icon layui-icon-delete"></i>
|
||||
批量删除</button>
|
||||
</div>
|
||||
|
||||
<div class="layui-card" style="margin-top:12px">
|
||||
@@ -62,7 +63,8 @@
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">变量别名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="alias" lay-verify="required|alias" placeholder="请输入变量别名(英文开头,只能包含数字和英文字母)" autocomplete="off" class="layui-input" />
|
||||
<input type="text" name="alias" lay-verify="required|alias" placeholder="请输入变量别名(英文开头,只能包含数字和英文字母)"
|
||||
autocomplete="off" class="layui-input" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
@@ -90,47 +92,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
waitForLayui(function() {
|
||||
layui.use(['table', 'form', 'layer', 'element'], function() {
|
||||
const table = layui.table;
|
||||
const form = layui.form;
|
||||
const layer = layui.layer;
|
||||
const $ = layui.$;
|
||||
waitForLayui(function () {
|
||||
layui.use(['table', 'form', 'layer', 'element'], function () {
|
||||
const table = layui.table;
|
||||
const form = layui.form;
|
||||
const layer = layui.layer;
|
||||
const $ = layui.$;
|
||||
|
||||
// 自定义验证规则
|
||||
form.verify({
|
||||
alias: function(value) {
|
||||
if (!value) return '别名不能为空';
|
||||
// 检查是否以英文字母开头,且只包含数字和英文字母
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(value)) {
|
||||
return '别名必须以英文字母开头,只能包含数字和英文字母';
|
||||
// 自定义验证规则
|
||||
form.verify({
|
||||
alias: function (value) {
|
||||
if (!value) return '别名不能为空';
|
||||
// 检查是否以英文字母开头,且只包含数字和英文字母
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(value)) {
|
||||
return '别名必须以英文字母开头,只能包含数字和英文字母';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 格式化时间函数
|
||||
function formatDateTime(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
// 格式化时间函数
|
||||
function formatDateTime(dateStr) {
|
||||
if (!dateStr) return '-';
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
|
||||
// 加载应用列表到下拉框
|
||||
function loadApps() {
|
||||
$.ajax({
|
||||
url: '/admin/variable/apps',
|
||||
type: 'GET',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
if (res.code === 0 && res.data) {
|
||||
let options = '<option value="">请选择应用</option>';
|
||||
res.data.forEach(function(app) {
|
||||
res.data.forEach(function (app) {
|
||||
options += '<option value="' + app.uuid + '">' + app.name + '</option>';
|
||||
});
|
||||
$('select[name="app_uuid"]').html(options);
|
||||
form.render('select');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
layer.msg('加载应用列表失败', {icon: 2});
|
||||
error: function () {
|
||||
layer.msg('加载应用列表失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -143,7 +145,7 @@
|
||||
elem: '#variablesTable',
|
||||
id: 'variablesTable',
|
||||
url: '/admin/variable/list',
|
||||
parseData: function(res) {
|
||||
parseData: function (res) {
|
||||
return {
|
||||
code: res.code,
|
||||
msg: res.msg || '',
|
||||
@@ -160,20 +162,20 @@
|
||||
limit: 20,
|
||||
limits: [10, 20, 50, 100],
|
||||
loading: true,
|
||||
done: function(res, curr, count) {
|
||||
done: function (res, curr, count) {
|
||||
// 表格渲染完成后的回调
|
||||
},
|
||||
cols: [[
|
||||
{type: 'checkbox', width: 50},
|
||||
{field: 'id', title: 'ID', width: 80, sort: true},
|
||||
{field: 'app_name', title: '应用名称', minWidth: 120},
|
||||
{field: 'number', title: '变量编号', width: 180},
|
||||
{field: 'alias', title: '变量别名', minWidth: 150},
|
||||
{ type: 'checkbox', width: 50 },
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'app_name', title: '应用名称', minWidth: 120 },
|
||||
{ field: 'number', title: '变量编号', width: 180 },
|
||||
{ field: 'alias', title: '变量别名', minWidth: 150 },
|
||||
{
|
||||
field: 'data',
|
||||
title: '变量数据',
|
||||
field: 'data',
|
||||
title: '变量数据',
|
||||
minWidth: 200,
|
||||
templet: function(d) {
|
||||
templet: function (d) {
|
||||
// 限制显示长度,避免内容过长影响布局
|
||||
if (d.data && d.data.length > 50) {
|
||||
return '<span title="' + d.data + '">' + d.data.substring(0, 50) + '...</span>';
|
||||
@@ -182,10 +184,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
minWidth: 150,
|
||||
templet: function(d) {
|
||||
templet: function (d) {
|
||||
// 限制显示长度,避免内容过长影响布局
|
||||
if (d.remark && d.remark.length > 30) {
|
||||
return '<span title="' + d.remark + '">' + d.remark.substring(0, 30) + '...</span>';
|
||||
@@ -194,32 +196,32 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
title: '创建时间',
|
||||
field: 'created_at',
|
||||
title: '创建时间',
|
||||
width: 180,
|
||||
templet: function(d) {
|
||||
templet: function (d) {
|
||||
return formatDateTime(d.created_at);
|
||||
}
|
||||
},
|
||||
{title: '操作', width: 180, align: 'center', toolbar: '#tpl-variables-ops', fixed: 'right'}
|
||||
{ title: '操作', width: 180, align: 'center', toolbar: '#tpl-variables-ops', fixed: 'right' }
|
||||
]]
|
||||
});
|
||||
|
||||
// 监听应用选择变化
|
||||
form.on('select(appSelect)', function(data) {
|
||||
variablesTable.reload({
|
||||
where: {
|
||||
app_uuid: data.value,
|
||||
search: $('input[name="search"]').val()
|
||||
},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
form.on('select(appSelect)', function (data) {
|
||||
variablesTable.reload({
|
||||
where: {
|
||||
app_uuid: data.value,
|
||||
search: $('input[name="search"]').val()
|
||||
},
|
||||
page: {
|
||||
curr: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 搜索功能
|
||||
$('#btnSearchVariables').on('click', function() {
|
||||
$('#btnSearchVariables').on('click', function () {
|
||||
variablesTable.reload({
|
||||
where: {
|
||||
app_uuid: $('select[name="app_uuid"]').val(),
|
||||
@@ -232,7 +234,7 @@
|
||||
});
|
||||
|
||||
// 重置搜索
|
||||
$('#btnResetVariables').on('click', function() {
|
||||
$('#btnResetVariables').on('click', function () {
|
||||
$('#variableFilterForm')[0].reset();
|
||||
form.render();
|
||||
variablesTable.reload({
|
||||
@@ -244,72 +246,72 @@
|
||||
});
|
||||
|
||||
// 新增变量
|
||||
$('#btnAddVariable').on('click', function() {
|
||||
$('#btnAddVariable').on('click', function () {
|
||||
console.log('新增变量按钮被点击');
|
||||
$('#variableForm')[0].reset();
|
||||
$('input[name="id"]').val('');
|
||||
|
||||
|
||||
// 重新加载应用列表到表单中
|
||||
loadApps();
|
||||
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '新增变量',
|
||||
content: $('#variableFormLayer'),
|
||||
area: ['500px', '460px'],
|
||||
btn: ['创建', '取消'],
|
||||
yes: function(index, layero) {
|
||||
yes: function (index, layero) {
|
||||
// 手动收集表单数据
|
||||
var formData = {};
|
||||
$('#variableForm').find('input, select, textarea').each(function() {
|
||||
$('#variableForm').find('input, select, textarea').each(function () {
|
||||
var $this = $(this);
|
||||
var name = $this.attr('name');
|
||||
if (name && name !== 'id') {
|
||||
formData[name] = $this.val();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
console.log('新增变量 - 收集到的表单数据:', formData);
|
||||
|
||||
|
||||
// 验证必填字段
|
||||
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
||||
layer.msg('应用UUID不能为空', {icon: 2});
|
||||
layer.msg('应用UUID不能为空', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (!formData.alias || formData.alias.trim() === '') {
|
||||
layer.msg('请输入变量别名', {icon: 2});
|
||||
layer.msg('请输入变量别名', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (!formData.data || formData.data.trim() === '') {
|
||||
layer.msg('请输入变量数据', {icon: 2});
|
||||
layer.msg('请输入变量数据', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('新增变量 - 发送的JSON数据:', JSON.stringify(formData));
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/variable/create',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(formData),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
layer.close(index);
|
||||
variablesTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
||||
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
||||
error: function (xhr) {
|
||||
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
},
|
||||
btn2: function(index) {
|
||||
btn2: function (index) {
|
||||
layer.close(index);
|
||||
},
|
||||
success: function() {
|
||||
success: function () {
|
||||
form.render();
|
||||
},
|
||||
shadeClose: false
|
||||
@@ -317,32 +319,32 @@
|
||||
});
|
||||
|
||||
// 批量删除
|
||||
$('#btnBatchDeleteVariables').on('click', function() {
|
||||
$('#btnBatchDeleteVariables').on('click', function () {
|
||||
const checkStatus = table.checkStatus('variablesTable');
|
||||
const data = checkStatus.data;
|
||||
|
||||
|
||||
if (data.length === 0) {
|
||||
layer.msg('请选择要删除的变量', {icon: 2});
|
||||
layer.msg('请选择要删除的变量', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
layer.confirm('确定删除选中的 ' + data.length + ' 个变量吗?', {icon: 3, title: '提示'}, function(index) {
|
||||
|
||||
layer.confirm('确定删除选中的 ' + data.length + ' 个变量吗?', { icon: 3, title: '提示' }, function (index) {
|
||||
const ids = data.map(item => item.id);
|
||||
$.ajax({
|
||||
url: '/admin/variable/batch_delete',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({ids: ids}),
|
||||
data: JSON.stringify({ ids: ids }),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
variablesTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '批量删除失败', {icon: 2});
|
||||
layer.msg(res.msg || '批量删除失败', { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '批量删除失败', {icon: 2});
|
||||
error: function (xhr) {
|
||||
layer.msg(xhr.responseText || '批量删除失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
@@ -350,107 +352,107 @@
|
||||
});
|
||||
|
||||
// 表格工具栏事件
|
||||
table.on('tool(variablesTableFilter)', function(obj) {
|
||||
table.on('tool(variablesTableFilter)', function (obj) {
|
||||
const data = obj.data;
|
||||
|
||||
|
||||
if (obj.event === 'edit') {
|
||||
// 编辑
|
||||
console.log('编辑按钮被点击', data);
|
||||
$('#variableForm')[0].reset();
|
||||
$('input[name="uuid"]').val(data.uuid);
|
||||
|
||||
|
||||
// 重新加载应用列表,然后设置选中值
|
||||
loadApps();
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
$('select[name="app_uuid"]').val(data.app_uuid);
|
||||
form.render('select');
|
||||
}, 100);
|
||||
|
||||
|
||||
$('input[name="alias"]').val(data.alias);
|
||||
$('textarea[name="data"]').val(data.data);
|
||||
$('textarea[name="remark"]').val(data.remark);
|
||||
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: '编辑变量',
|
||||
content: $('#variableFormLayer'),
|
||||
area: ['500px', '460px'],
|
||||
btn: ['保存', '取消'],
|
||||
yes: function(index, layero) {
|
||||
yes: function (index, layero) {
|
||||
// 手动收集表单数据
|
||||
var formData = {};
|
||||
$('#variableForm').find('input, select, textarea').each(function() {
|
||||
$('#variableForm').find('input, select, textarea').each(function () {
|
||||
var $this = $(this);
|
||||
var name = $this.attr('name');
|
||||
if (name && name !== 'id') {
|
||||
formData[name] = $this.val();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
console.log('编辑变量 - 收集到的表单数据:', formData);
|
||||
|
||||
|
||||
// 验证必填字段
|
||||
if (!formData.app_uuid || formData.app_uuid.trim() === '') {
|
||||
layer.msg('应用UUID不能为空', {icon: 2});
|
||||
layer.msg('应用UUID不能为空', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (!formData.alias || formData.alias.trim() === '') {
|
||||
layer.msg('请输入变量别名', {icon: 2});
|
||||
layer.msg('请输入变量别名', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
if (!formData.data || formData.data.trim() === '') {
|
||||
layer.msg('请输入变量数据', {icon: 2});
|
||||
layer.msg('请输入变量数据', { icon: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('编辑变量 - 发送的JSON数据:', JSON.stringify(formData));
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: '/admin/variable/update',
|
||||
type: 'POST',
|
||||
data: JSON.stringify(formData),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
layer.close(index);
|
||||
variablesTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '操作失败', {icon: 2});
|
||||
layer.msg(res.msg || '操作失败', { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '操作失败', {icon: 2});
|
||||
error: function (xhr) {
|
||||
layer.msg(xhr.responseText || '操作失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
},
|
||||
btn2: function(index) {
|
||||
btn2: function (index) {
|
||||
layer.close(index);
|
||||
},
|
||||
success: function() {
|
||||
success: function () {
|
||||
form.render();
|
||||
},
|
||||
shadeClose: false
|
||||
});
|
||||
|
||||
|
||||
} else if (obj.event === 'del') {
|
||||
// 删除
|
||||
layer.confirm('确定删除该变量吗?', {icon: 3, title: '提示'}, function(index) {
|
||||
layer.confirm('确定删除该变量吗?', { icon: 3, title: '提示' }, function (index) {
|
||||
$.ajax({
|
||||
url: '/admin/variable/delete',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({id: data.id}),
|
||||
data: JSON.stringify({ id: data.id }),
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
success: function (res) {
|
||||
if (res.code === 0) {
|
||||
layer.msg(res.msg, {icon: 1});
|
||||
layer.msg(res.msg, { icon: 1 });
|
||||
variablesTable.reload();
|
||||
} else {
|
||||
layer.msg(res.msg || '删除失败', {icon: 2});
|
||||
layer.msg(res.msg || '删除失败', { icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
layer.msg(xhr.responseText || '删除失败', {icon: 2});
|
||||
error: function (xhr) {
|
||||
layer.msg(xhr.responseText || '删除失败', { icon: 2 });
|
||||
}
|
||||
});
|
||||
layer.close(index);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
|
||||
<head>
|
||||
<title>{{.SystemName}} - 生活就像愤怒的小鸟,失败后总有几只猪在笑。</title>
|
||||
<!-- 站 点 协 议 -->
|
||||
@@ -17,9 +18,10 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="bookmark" href="/favicon.ico" />
|
||||
<!-- 样 式 文 件 -->
|
||||
<link rel="stylesheet" href="//lib.baomitu.com/layui/2.8.17/css/layui.css"/>
|
||||
<link rel="stylesheet" href="//lib.baomitu.com/layui/2.8.17/css/layui.css" />
|
||||
<style>
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@@ -69,6 +71,7 @@
|
||||
from {
|
||||
text-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
|
||||
}
|
||||
|
||||
to {
|
||||
text-shadow: 0 0 30px rgba(0, 212, 255, 0.8), 0 0 40px rgba(0, 212, 255, 0.6);
|
||||
}
|
||||
@@ -79,7 +82,7 @@
|
||||
border: 2px solid rgba(0, 212, 255, 0.3);
|
||||
border-radius: 15px;
|
||||
padding: 30px 25px;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
@@ -100,8 +103,13 @@
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.box-form .layui-form-item {
|
||||
@@ -119,8 +127,15 @@
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.info-text {
|
||||
@@ -141,11 +156,11 @@
|
||||
font-size: 14px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
||||
.body_beian {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
|
||||
.body_beian a {
|
||||
color: rgba(0, 212, 255, 0.8);
|
||||
text-decoration: none;
|
||||
@@ -174,193 +189,197 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 代 码 结 构 -->
|
||||
<div class="layui-container">
|
||||
<canvas id="canvas"></canvas>
|
||||
<div class="body-background body_box">
|
||||
<div class="layui-form box-form body_box">
|
||||
<div class="layui-form-item logo-title">
|
||||
<h1><strong>系统提醒</strong></h1>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="layui-form-item">
|
||||
<div class="warning-text">🚫 未授权,拒绝访问</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="info-text">💬 如有问题,请联系网站管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body_footer">{{.FooterText}}</div>
|
||||
{{if or .ICPRecord .PSBRecord}}<div class="body_beian">{{if .ICPRecord}}<a href="{{.ICPRecordLink}}" target="_blank">{{.ICPRecord}}</a>{{end}}{{if and .ICPRecord .PSBRecord}} {{end}}{{if .PSBRecord}}<a href="{{.PSBRecordLink}}" target="_blank">{{.PSBRecord}}</a>{{end}}</div>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 资 源 引 入 -->
|
||||
<script src="//lib.baomitu.com/jquery/3.6.4/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<script>
|
||||
// 获取canvas元素和绘图上下文
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 设置canvas尺寸为全屏
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 粒子类
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// 重置粒子位置和属性
|
||||
reset() {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.vx = (Math.random() - 0.5) * 2;
|
||||
this.vy = (Math.random() - 0.5) * 2;
|
||||
this.size = Math.random() * 3 + 1;
|
||||
this.opacity = Math.random() * 0.8 + 0.2;
|
||||
this.color = this.getRandomColor();
|
||||
}
|
||||
|
||||
// 获取随机颜色
|
||||
getRandomColor() {
|
||||
const colors = [
|
||||
'#00FF00', '#0080FF', '#FF0080', '#FFFF00',
|
||||
'#FF8000', '#8000FF', '#00FFFF', '#FF4000'
|
||||
];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// 更新粒子位置
|
||||
update() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// 边界检测,粒子超出边界时重置
|
||||
if (this.x < 0 || this.x > canvas.width ||
|
||||
this.y < 0 || this.y > canvas.height) {
|
||||
<body>
|
||||
<!-- 代 码 结 构 -->
|
||||
<div class="layui-container">
|
||||
<canvas id="canvas"></canvas>
|
||||
<div class="body-background body_box">
|
||||
<div class="layui-form box-form body_box">
|
||||
<div class="layui-form-item logo-title">
|
||||
<h1><strong>系统提醒</strong></h1>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="layui-form-item">
|
||||
<div class="warning-text">🚫 未授权,拒绝访问</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="info-text">💬 如有问题,请联系网站管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body_footer">{{.FooterText}}</div>
|
||||
{{if or .ICPRecord .PSBRecord}}<div class="body_beian">{{if .ICPRecord}}<a href="{{.ICPRecordLink}}"
|
||||
target="_blank">{{.ICPRecord}}</a>{{end}}{{if and .ICPRecord .PSBRecord}} {{end}}{{if .PSBRecord}}<a
|
||||
href="{{.PSBRecordLink}}" target="_blank">{{.PSBRecord}}</a>{{end}}</div>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 资 源 引 入 -->
|
||||
<script src="//lib.baomitu.com/jquery/3.6.4/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<script>
|
||||
// 获取canvas元素和绘图上下文
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 设置canvas尺寸为全屏
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 粒子类
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// 随机改变透明度
|
||||
this.opacity += (Math.random() - 0.5) * 0.02;
|
||||
this.opacity = Math.max(0.1, Math.min(1, this.opacity));
|
||||
}
|
||||
|
||||
// 绘制粒子
|
||||
draw() {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// 创建粒子数组
|
||||
const particles = [];
|
||||
const particleCount = 150;
|
||||
|
||||
// 初始化粒子
|
||||
const initParticles = () => {
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
};
|
||||
|
||||
// 绘制连线
|
||||
const drawConnections = () => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果距离小于100像素,绘制连线
|
||||
if (distance < 100) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = (100 - distance) / 100 * 0.3;
|
||||
ctx.strokeStyle = '#00FF00';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
// 重置粒子位置和属性
|
||||
reset() {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.vx = (Math.random() - 0.5) * 2;
|
||||
this.vy = (Math.random() - 0.5) * 2;
|
||||
this.size = Math.random() * 3 + 1;
|
||||
this.opacity = Math.random() * 0.8 + 0.2;
|
||||
this.color = this.getRandomColor();
|
||||
}
|
||||
|
||||
// 获取随机颜色
|
||||
getRandomColor() {
|
||||
const colors = [
|
||||
'#00FF00', '#0080FF', '#FF0080', '#FFFF00',
|
||||
'#FF8000', '#8000FF', '#00FFFF', '#FF4000'
|
||||
];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
// 更新粒子位置
|
||||
update() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// 边界检测,粒子超出边界时重置
|
||||
if (this.x < 0 || this.x > canvas.width ||
|
||||
this.y < 0 || this.y > canvas.height) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
// 随机改变透明度
|
||||
this.opacity += (Math.random() - 0.5) * 0.02;
|
||||
this.opacity = Math.max(0.1, Math.min(1, this.opacity));
|
||||
}
|
||||
|
||||
// 绘制粒子
|
||||
draw() {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
// 清除画布,使用半透明黑色创建拖尾效果
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 更新和绘制所有粒子
|
||||
particles.forEach(particle => {
|
||||
particle.update();
|
||||
particle.draw();
|
||||
});
|
||||
|
||||
// 绘制粒子间的连线
|
||||
drawConnections();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// 鼠标交互效果
|
||||
const addMouseInteraction = () => {
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
|
||||
// 鼠标附近的粒子会被吸引
|
||||
|
||||
// 创建粒子数组
|
||||
const particles = [];
|
||||
const particleCount = 150;
|
||||
|
||||
// 初始化粒子
|
||||
const initParticles = () => {
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
};
|
||||
|
||||
// 绘制连线
|
||||
const drawConnections = () => {
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 如果距离小于100像素,绘制连线
|
||||
if (distance < 100) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = (100 - distance) / 100 * 0.3;
|
||||
ctx.strokeStyle = '#00FF00';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
// 清除画布,使用半透明黑色创建拖尾效果
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 更新和绘制所有粒子
|
||||
particles.forEach(particle => {
|
||||
const dx = mouseX - particle.x;
|
||||
const dy = mouseY - particle.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 150) {
|
||||
particle.vx += dx * 0.0001;
|
||||
particle.vy += dy * 0.0001;
|
||||
particle.update();
|
||||
particle.draw();
|
||||
});
|
||||
|
||||
// 绘制粒子间的连线
|
||||
drawConnections();
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// 鼠标交互效果
|
||||
const addMouseInteraction = () => {
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
mouseX = e.clientX;
|
||||
mouseY = e.clientY;
|
||||
|
||||
// 鼠标附近的粒子会被吸引
|
||||
particles.forEach(particle => {
|
||||
const dx = mouseX - particle.x;
|
||||
const dy = mouseY - particle.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 150) {
|
||||
particle.vx += dx * 0.0001;
|
||||
particle.vy += dy * 0.0001;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 点击时添加新粒子
|
||||
canvas.addEventListener('click', (e) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const newParticle = new Particle();
|
||||
newParticle.x = e.clientX + (Math.random() - 0.5) * 20;
|
||||
newParticle.y = e.clientY + (Math.random() - 0.5) * 20;
|
||||
particles.push(newParticle);
|
||||
}
|
||||
|
||||
// 限制粒子数量
|
||||
if (particles.length > particleCount + 50) {
|
||||
particles.splice(0, 5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 点击时添加新粒子
|
||||
canvas.addEventListener('click', (e) => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const newParticle = new Particle();
|
||||
newParticle.x = e.clientX + (Math.random() - 0.5) * 20;
|
||||
newParticle.y = e.clientY + (Math.random() - 0.5) * 20;
|
||||
particles.push(newParticle);
|
||||
}
|
||||
|
||||
// 限制粒子数量
|
||||
if (particles.length > particleCount + 50) {
|
||||
particles.splice(0, 5);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 启动粒子系统
|
||||
initParticles();
|
||||
addMouseInteraction();
|
||||
animate();
|
||||
</script>
|
||||
};
|
||||
|
||||
// 启动粒子系统
|
||||
initParticles();
|
||||
addMouseInteraction();
|
||||
animate();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user