增加修改个人信息和修改密码菜单。
This commit is contained in:
@@ -180,21 +180,19 @@ func UpdateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if req.Nickname != "" {
|
||||
updates["nickname"] = req.Nickname
|
||||
}
|
||||
if req.Email != "" {
|
||||
updates["email"] = req.Email
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updates["avatar"] = req.Avatar
|
||||
updates := map[string]interface{}{
|
||||
"nickname": req.Nickname,
|
||||
"email": req.Email,
|
||||
"avatar": req.Avatar,
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&user).Updates(updates).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"})
|
||||
return
|
||||
}
|
||||
user.Nickname = req.Nickname
|
||||
user.Email = req.Email
|
||||
user.Avatar = req.Avatar
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": user})
|
||||
}
|
||||
|
||||
@@ -65,9 +65,7 @@ func setupAuthenticatedAPIRoutes(api *gin.RouterGroup) {
|
||||
auth.GET("/dashboard/stats", backend.GetDashboardStats)
|
||||
// 当前用户信息
|
||||
auth.GET("/auth/me", backend.GetCurrentUser)
|
||||
//TODO: 修改用户信息接口,在admin/index.html中添加修改用户信息表单
|
||||
auth.PUT("/auth/me", backend.UpdateUser)
|
||||
//TODO: 修改密码接口,在admin/index.html中添加修改密码表单
|
||||
auth.PUT("/auth/password", backend.ChangePassword)
|
||||
auth.POST("/auth/logout", backend.Logout)
|
||||
|
||||
|
||||
@@ -33,6 +33,45 @@ body {
|
||||
.admin-header #username {
|
||||
margin-left: auto;
|
||||
}
|
||||
.user-menu-wrapper {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
}
|
||||
.user-menu-wrapper #username {
|
||||
margin-left: 0;
|
||||
}
|
||||
.user-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
right: 0;
|
||||
z-index: 1100;
|
||||
display: none;
|
||||
min-width: 140px;
|
||||
padding: 4px 0;
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 12px rgba(0,0,0,0.16);
|
||||
}
|
||||
.user-menu-wrapper:hover .user-menu,
|
||||
.user-menu-wrapper:focus-within .user-menu {
|
||||
display: block;
|
||||
}
|
||||
.user-menu button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 9px 14px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #333;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
.user-menu button:hover {
|
||||
background: #f5f5f5;
|
||||
color: #1890ff;
|
||||
}
|
||||
.admin-header span {
|
||||
background: transparent;
|
||||
padding: 5px 12px;
|
||||
|
||||
@@ -108,6 +108,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
bindForm('modal-form', handleModalSubmit);
|
||||
bindForm('user-form', handleUserSubmit);
|
||||
bindForm('settings-form', handleSettingsSubmit);
|
||||
bindForm('profile-form', handleProfileSubmit);
|
||||
bindForm('password-form', handlePasswordSubmit);
|
||||
|
||||
// 封面图上传
|
||||
const coverFileInput = document.getElementById('post-cover-file');
|
||||
@@ -122,6 +124,86 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// 显示个人信息弹窗
|
||||
function showProfileModal() {
|
||||
document.getElementById('profile-nickname').value = currentUser?.nickname || '';
|
||||
document.getElementById('profile-email').value = currentUser?.email || '';
|
||||
document.getElementById('profile-avatar').value = currentUser?.avatar || '';
|
||||
document.getElementById('profile-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hideProfileModal() {
|
||||
document.getElementById('profile-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 保存个人信息
|
||||
async function handleProfileSubmit(e) {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
nickname: document.getElementById('profile-nickname').value.trim(),
|
||||
email: document.getElementById('profile-email').value.trim(),
|
||||
avatar: document.getElementById('profile-avatar').value.trim()
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/auth/me`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
alert(data.error || '保存失败');
|
||||
return;
|
||||
}
|
||||
currentUser = Object.assign({}, currentUser, body, data.data || {});
|
||||
document.getElementById('username').textContent = currentUser.nickname || currentUser.username;
|
||||
hideProfileModal();
|
||||
alert('个人信息已保存');
|
||||
} catch (err) {
|
||||
alert('保存失败: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示修改密码弹窗
|
||||
function showPasswordModal() {
|
||||
document.getElementById('password-form').reset();
|
||||
document.getElementById('password-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hidePasswordModal() {
|
||||
document.getElementById('password-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
async function handlePasswordSubmit(e) {
|
||||
e.preventDefault();
|
||||
const oldPassword = document.getElementById('old-password').value;
|
||||
const newPassword = document.getElementById('new-password').value;
|
||||
const confirmPassword = document.getElementById('confirm-password').value;
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('两次输入的新密码不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/auth/password`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify({ old_password: oldPassword, new_password: newPassword })
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
alert(data.error || '修改密码失败');
|
||||
return;
|
||||
}
|
||||
hidePasswordModal();
|
||||
alert(data.message || '密码修改成功');
|
||||
} catch (err) {
|
||||
alert('修改密码失败: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 安全绑定表单提交(元素不存在时忽略)
|
||||
function bindForm(formId, handler) {
|
||||
const form = document.getElementById(formId);
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
<body>
|
||||
<div class="admin-header">
|
||||
<h2>GoBlog</h2>
|
||||
<span id="username">{{.DisplayName}}</span>
|
||||
<div class="user-menu-wrapper">
|
||||
<span id="username" tabindex="0" aria-haspopup="true">{{.DisplayName}}</span>
|
||||
<div class="user-menu" role="menu">
|
||||
<button type="button" onclick="showProfileModal()">修改个人信息</button>
|
||||
<button type="button" onclick="showPasswordModal()">修改密码</button>
|
||||
</div>
|
||||
</div>
|
||||
<span id="logout-btn">退出</span>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +54,8 @@
|
||||
{{if or (index .PageSet "categories") (index .PageSet "tags") (index .PageSet "pages")}}{{template "admin_modal_common" .}}{{end}}
|
||||
{{if index .PageSet "users"}}{{template "admin_modal_user" .}}{{end}}
|
||||
{{if index .PageSet "themes"}}{{template "admin_modal_theme" .}}{{end}}
|
||||
{{template "admin_modal_profile" .}}
|
||||
{{template "admin_modal_password" .}}
|
||||
|
||||
<script>window.__currentUser = {{.CurrentUser }}</script>
|
||||
<script src="/static/js/admin.js?v=21"></script>
|
||||
@@ -55,6 +63,58 @@
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "admin_modal_profile"}}
|
||||
<div id="profile-modal" class="modal hidden">
|
||||
<div class="modal-content">
|
||||
<h3>修改个人信息</h3>
|
||||
<form id="profile-form">
|
||||
<div class="form-group">
|
||||
<label for="profile-nickname">昵称</label>
|
||||
<input type="text" id="profile-nickname" maxlength="100">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="profile-email">邮箱</label>
|
||||
<input type="email" id="profile-email" maxlength="100" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="profile-avatar">头像地址</label>
|
||||
<input type="url" id="profile-avatar" maxlength="255" placeholder="https://">
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn-secondary" onclick="hideProfileModal()">取消</button>
|
||||
<button type="submit" class="btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "admin_modal_password"}}
|
||||
<div id="password-modal" class="modal hidden">
|
||||
<div class="modal-content">
|
||||
<h3>修改密码</h3>
|
||||
<form id="password-form">
|
||||
<div class="form-group">
|
||||
<label for="old-password">旧密码</label>
|
||||
<input type="password" id="old-password" required autocomplete="current-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new-password">新密码</label>
|
||||
<input type="password" id="new-password" required minlength="6" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm-password">确认新密码</label>
|
||||
<input type="password" id="confirm-password" required minlength="6" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn-secondary" onclick="hidePasswordModal()">取消</button>
|
||||
<button type="submit" class="btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "admin_page_dashboard"}}
|
||||
<!-- 仪表盘 -->
|
||||
<div id="dashboard-page" class="page">
|
||||
|
||||
Reference in New Issue
Block a user