Files
NetworkAuth/web/template/admin/user.html
2025-10-24 00:09:45 +08:00

256 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{ define "user.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">
<!-- 按照要求纵向排序ID、角色、用户名、旧密码、新密码、确认新密码 -->
<div class="layui-form-item">
<label class="layui-form-label">ID</label>
<div class="layui-input-block">
<input type="text" name="id" disabled readonly class="layui-input" />
</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" />
</div>
</div>
<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" />
</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>
</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>
<script>
// 使用自执行函数创建局部作用域,避免与其他页面脚本发生全局命名冲突
(() => {
// 工具方法:将数值角色转为中文标签
// 0 => 管理员1 => 普通成员
const roleToText = (role) => {
// 将可能的字符串数值转为数字
const r = typeof role === 'string' ? parseInt(role, 10) : role
return r === 0 ? '管理员' : '普通成员'
}
// 如果未加载 layui则按需加载兼容用户直接访问片段页 /admin/user
// 说明:当 window.layui 不存在时,动态引入 Layui 的 CSS 和 JS加载完成后再执行页面逻辑
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'], () => {
const form = layui.form
const layer = layui.layer
// 记录初始用户名,用于判断是否需要更新
let initialUsername = ''
// 缓存最近一次加载到表单中的资料,用于“重置”恢复
let lastProfile = null
// 加载个人资料填充ID/用户名/角色(角色显示中文标签并禁用)
// 返回:无;副作用:设置 initialUsername、lastProfile 与表单值
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 || '加载失败')
const payload = data.data || {}
initialUsername = payload.username || ''
// 将角色转换为中文展示,并缓存为最近一次加载的“默认值”
const display = { ...payload, role: roleToText(payload.role) }
lastProfile = display
form.val('accountForm', display)
} catch (e) {
layer.msg(e.message || '加载个人资料失败', { icon: 2 })
}
}
// 校验密码表单:当任一密码字段填写时,要求三个字段均填写且有效
// 返回:{ 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 }
}
// 更新用户名:传输 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/user/profile/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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/user/password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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) {
const pwdResp = await updatePassword(fields)
// 修改密码后通常需要重新登录,优先使用后端返回的 redirect否则默认登录页
const redirect = pwdResp && pwdResp.data && pwdResp.data.redirect ? pwdResp.data.redirect : '/admin/login'
layer.msg('密码修改成功,即将跳转到登录页', { icon: 1, time: 1200 }, () => {
window.location.href = redirect
})
} else {
// 未修改密码,仅修改资料
await loadProfile()
layer.msg('保存成功', { icon: 1 })
}
} catch (e) {
layer.msg(e.message || '保存失败', { icon: 2 })
}
return false
})
// 绑定“重置”按钮:将表单恢复为最近一次加载到表单中的资料
// 逻辑:
// - 如有 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()
}
})
}
// 初始化加载
bindReset()
loadProfile()
})
})
})()
</script>
{{ end }}