mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Fix a large number of bugs
This commit is contained in:
@@ -1,237 +1,340 @@
|
||||
{{ define "profile.html" }}
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header">个人资料</div>
|
||||
<div class="layui-card-body">
|
||||
<form class="layui-form" id="accountForm" lay-filter="accountForm" onsubmit="return false">
|
||||
<!-- 按照要求纵向排序:用户名、旧密码、新密码、确认新密码 -->
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="username" placeholder="请输入用户名(不修改可留空或保持不变)" autocomplete="off" class="layui-input" />
|
||||
<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>
|
||||
</ul>
|
||||
<div class="layui-tab-content">
|
||||
<!-- 修改密码模块 -->
|
||||
<div class="layui-tab-item layui-show">
|
||||
<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="passwordForm" lay-filter="passwordForm" onsubmit="return false">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-old-password">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="password" name="old_password" placeholder="请输入当前密码" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-new-password">新的密码</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="password" name="new_password" placeholder="请输入新密码(至少6位)" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">确认密码</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="password" name="confirm_password" placeholder="请再次输入新密码" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<!-- 修改用户名模块 -->
|
||||
<div class="layui-tab-item">
|
||||
<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="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 class="layui-form-item">
|
||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-username">新用户名</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="text" name="new_username" placeholder="请输入新用户名" autocomplete="off" class="layui-input"
|
||||
lay-verify="required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label" style="cursor: pointer;" data-tips="user-old-password">当前密码</label>
|
||||
<div class="layui-input-block">
|
||||
<div class="layui-input-wrap">
|
||||
<input type="password" name="password" placeholder="请输入当前密码以确认身份" autocomplete="off"
|
||||
class="layui-input" lay-verify="required" lay-affix="eye" />
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</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">
|
||||
</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">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="submitAccount">保存更改</button>
|
||||
<!-- 将原先 type="reset" 改为自定义按钮,避免浏览器重置成初始空值 -->
|
||||
<button type="button" id="btnReset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||
(() => {
|
||||
// 等待layui加载完成
|
||||
function waitForLayui(callback) {
|
||||
if (typeof layui !== 'undefined') {
|
||||
callback();
|
||||
} else {
|
||||
setTimeout(() => waitForLayui(callback), 100);
|
||||
}
|
||||
}
|
||||
|
||||
waitForLayui(() => {
|
||||
layui.use(['form', 'layer'], () => {
|
||||
const form = layui.form
|
||||
const layer = layui.layer
|
||||
|
||||
// 记录初始用户名,用于判断是否需要更新
|
||||
let initialUsername = ''
|
||||
// 缓存最近一次加载到表单中的资料,用于“重置”恢复
|
||||
let lastProfile = null
|
||||
|
||||
// 加载个人资料:填充用户名
|
||||
// 返回:无;副作用:设置 initialUsername、lastProfile 与表单值
|
||||
const loadProfile = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/profile/info', {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '加载失败')
|
||||
const payload = data.data || {}
|
||||
initialUsername = payload.username || ''
|
||||
|
||||
const display = { ...payload }
|
||||
lastProfile = display
|
||||
form.val('accountForm', display)
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
|
||||
}
|
||||
<script>
|
||||
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
|
||||
(() => {
|
||||
// 工具方法:将数值角色转为中文标签
|
||||
const roleToText = (role) => {
|
||||
const r = typeof role === 'string' ? parseInt(role, 10) : role
|
||||
return r === 0 ? '管理员' : '子账号'
|
||||
}
|
||||
|
||||
// 校验密码表单:当任一密码字段填写时,要求三个字段均填写且有效
|
||||
// 返回:{ ok: boolean, msg?: string }
|
||||
const validatePassword = (fields) => {
|
||||
const oldPwd = (fields.old_password || '').trim()
|
||||
const newPwd = (fields.new_password || '').trim()
|
||||
const confirmPwd = (fields.confirm_password || '').trim()
|
||||
const anyFilled = !!(oldPwd || newPwd || confirmPwd)
|
||||
if (!anyFilled) return { ok: true }
|
||||
if (!oldPwd || !newPwd || !confirmPwd) return { ok: false, msg: '请完整填写旧密码/新密码/确认新密码' }
|
||||
if (newPwd.length < 6) return { ok: false, msg: '新密码长度不能少于6位' }
|
||||
if (newPwd !== confirmPwd) return { ok: false, msg: '两次输入的新密码不一致' }
|
||||
if (oldPwd === newPwd) return { ok: false, msg: '新密码不能与旧密码相同' }
|
||||
return { ok: true }
|
||||
// 格式化时间
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
// 更新用户名:传输 username 与 old_password(当仅修改用户名时必须提供当前密码;同时修改密码时沿用同一 old_password)
|
||||
// 返回:Promise<void>
|
||||
const updateUsername = async (username, oldPassword) => {
|
||||
const payload = { username }
|
||||
if (oldPassword) payload.old_password = oldPassword
|
||||
const res = await fetch('/admin/api/profile/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '保存资料失败')
|
||||
}
|
||||
|
||||
// 更新密码:仅传输旧/新/确认三个字段
|
||||
// 返回:Promise<any> 后端响应数据,用于可能的重定向处理
|
||||
const updatePassword = async (fields) => {
|
||||
const payload = {
|
||||
old_password: fields.old_password,
|
||||
new_password: fields.new_password,
|
||||
confirm_password: fields.confirm_password
|
||||
}
|
||||
const res = await fetch('/admin/api/profile/password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
const data = await res.json()
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改密码失败')
|
||||
return data
|
||||
}
|
||||
|
||||
// 提交综合更新:
|
||||
// 规则:
|
||||
// - 用户名:仅当与 initialUsername 不同且非空时更新
|
||||
// - 密码:当任一密码字段填写时,要求完整校验并更新;若均未填则不更新
|
||||
// - 若两者均无改动,则提示“未修改任何内容”
|
||||
form.on('submit(submitAccount)', async (obj) => {
|
||||
const fields = obj.field
|
||||
const desiredUsername = (fields.username || '').trim()
|
||||
const needUpdateUsername = desiredUsername && desiredUsername !== initialUsername
|
||||
|
||||
// 判定密码相关输入:
|
||||
// - wantChangePassword:输入了新密码或确认密码,视为尝试修改密码(将要求三个字段都填写)
|
||||
// - onlyOldProvided:仅输入了旧密码,用于支持“仅修改用户名需要当前密码”的场景
|
||||
const hasOld = !!(fields.old_password && fields.old_password.trim())
|
||||
const hasNewOrConfirm = !!((fields.new_password && fields.new_password.trim()) || (fields.confirm_password && fields.confirm_password.trim()))
|
||||
const wantChangePassword = hasNewOrConfirm
|
||||
const onlyOldProvided = hasOld && !hasNewOrConfirm
|
||||
|
||||
if (!needUpdateUsername && !wantChangePassword) {
|
||||
layer.msg('未修改任何内容', { icon: 0 })
|
||||
return false
|
||||
}
|
||||
|
||||
// 修改密码场景:需进行严格校验(旧/新/确认均必填)
|
||||
if (wantChangePassword) {
|
||||
const pwdCheck = validatePassword(fields)
|
||||
if (!pwdCheck.ok) {
|
||||
layer.msg(pwdCheck.msg, { icon: 2 })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 仅修改用户名:要求输入当前密码
|
||||
if (needUpdateUsername && !wantChangePassword && !hasOld) {
|
||||
layer.msg('修改用户名需要输入当前密码', { icon: 2 })
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 始终先更新用户名,再更新密码(避免改密后跳转导致无法继续)
|
||||
if (needUpdateUsername) {
|
||||
await updateUsername(desiredUsername, hasOld ? fields.old_password : '')
|
||||
initialUsername = desiredUsername
|
||||
}
|
||||
|
||||
if (wantChangePassword) {
|
||||
await updatePassword(fields)
|
||||
layer.msg('密码修改成功', { icon: 1 })
|
||||
// 清空密码框
|
||||
form.val('accountForm', {
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
})
|
||||
} else {
|
||||
// 未修改密码,仅修改资料
|
||||
await loadProfile()
|
||||
layer.msg('保存成功', { icon: 1 })
|
||||
}
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '保存失败', { 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)
|
||||
})
|
||||
|
||||
// 绑定“重置”按钮:将表单恢复为最近一次加载到表单中的资料
|
||||
// 逻辑:
|
||||
// - 如有 lastProfile,直接回填;
|
||||
// - 回填时同时清空三个密码字段;
|
||||
// - 如暂无缓存(极小概率),则重新请求资料
|
||||
const bindReset = () => {
|
||||
const btn = document.getElementById('btnReset')
|
||||
if (!btn) return
|
||||
btn.addEventListener('click', () => {
|
||||
if (lastProfile) {
|
||||
form.val('accountForm', { ...lastProfile, old_password: '', new_password: '', confirm_password: '' })
|
||||
layer.msg('已恢复为当前资料', { icon: 1 })
|
||||
} else {
|
||||
loadProfile()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 在确保 Layui 可用后再执行页面逻辑
|
||||
ensureLayui().then(() => {
|
||||
layui.use(['form', 'layer', 'element'], () => {
|
||||
const form = layui.form
|
||||
const layer = layui.layer
|
||||
const element = layui.element
|
||||
|
||||
// 初始化加载
|
||||
bindReset()
|
||||
loadProfile()
|
||||
})
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
{{ end }}
|
||||
// 全局变量
|
||||
let currentUsername = null
|
||||
|
||||
// 获取当前用户名
|
||||
const getCurrentUsername = async () => {
|
||||
try {
|
||||
const res = await fetch('/admin/api/profile/info')
|
||||
let data
|
||||
try {
|
||||
const text = await res.text()
|
||||
data = JSON.parse(text)
|
||||
} catch (e) {
|
||||
throw new Error('服务器响应格式错误')
|
||||
}
|
||||
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '获取用户信息失败')
|
||||
|
||||
currentUsername = data.data.username
|
||||
// 填充用户名修改表单的当前用户名
|
||||
form.val('usernameForm', { current_username: currentUsername })
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '获取用户信息失败', { icon: 2 })
|
||||
}
|
||||
}
|
||||
|
||||
// 修改密码模块
|
||||
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/profile/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
|
||||
})
|
||||
})
|
||||
|
||||
let data
|
||||
try {
|
||||
const text = await res.text()
|
||||
data = JSON.parse(text)
|
||||
} catch (e) {
|
||||
throw new Error('服务器响应格式错误')
|
||||
}
|
||||
|
||||
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 === currentUsername) {
|
||||
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/profile/update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: fields.new_username,
|
||||
old_password: fields.password
|
||||
})
|
||||
})
|
||||
|
||||
let data
|
||||
try {
|
||||
const text = await res.text()
|
||||
data = JSON.parse(text)
|
||||
} catch (e) {
|
||||
throw new Error('服务器响应格式错误')
|
||||
}
|
||||
|
||||
const ok = (data.success === true) || (data.code === 0)
|
||||
if (!ok) throw new Error(data.message || data.msg || '修改用户名失败')
|
||||
|
||||
layer.msg('用户名修改成功', { icon: 1 })
|
||||
|
||||
// 重新获取当前用户名
|
||||
await getCurrentUsername()
|
||||
|
||||
// 清空表单(不显示重置提示)
|
||||
form.val('usernameForm', {
|
||||
new_username: '',
|
||||
password: '',
|
||||
current_username: currentUsername || ''
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
layer.msg(e.message || '修改用户名失败', { icon: 2 })
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
form.val('usernameForm', {
|
||||
new_username: '',
|
||||
password: '',
|
||||
current_username: currentUsername || ''
|
||||
})
|
||||
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)
|
||||
|
||||
// 初始化加载
|
||||
getCurrentUsername()
|
||||
})
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
</section>
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user