mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 添加管理员账户信息修改功能
- 新增右上角用户菜单,包含修改账户信息和退出登录选项 - 实现修改用户名和密码功能,支持独立修改或同时修改 - 添加密码强度验证(最少8位)和确认密码验证 - 修改后自动退出登录,确保安全性 - 同步更新Redis和data/init.json文件,保持数据一致性 - 优化用户体验,提供实时反馈和错误提示 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -186,6 +186,20 @@ const app = createApp({
|
||||
callbackUrl: ''
|
||||
},
|
||||
|
||||
// 用户菜单和账户修改相关
|
||||
userMenuOpen: false,
|
||||
currentUser: {
|
||||
username: ''
|
||||
},
|
||||
showChangePasswordModal: false,
|
||||
changePasswordLoading: false,
|
||||
changePasswordForm: {
|
||||
newUsername: '',
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@@ -202,6 +216,15 @@ const app = createApp({
|
||||
// 初始化防抖函数
|
||||
this.setTrendPeriod = this.debounce(this._setTrendPeriod, 300);
|
||||
|
||||
// 添加全局点击事件监听器,用于关闭用户菜单
|
||||
document.addEventListener('click', (event) => {
|
||||
// 检查点击是否在用户菜单外部
|
||||
const userMenuButton = event.target.closest('.relative');
|
||||
if (!userMenuButton || !userMenuButton.querySelector('button[\@click*="userMenuOpen"]')) {
|
||||
this.userMenuOpen = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.authToken) {
|
||||
this.isLoggedIn = true;
|
||||
|
||||
@@ -784,6 +807,10 @@ const app = createApp({
|
||||
this.authToken = data.token;
|
||||
localStorage.setItem('authToken', this.authToken);
|
||||
this.isLoggedIn = true;
|
||||
|
||||
// 记录当前用户名
|
||||
this.currentUser.username = this.loginForm.username;
|
||||
|
||||
this.loadDashboard();
|
||||
} else {
|
||||
this.loginError = data.message;
|
||||
@@ -796,6 +823,75 @@ const app = createApp({
|
||||
}
|
||||
},
|
||||
|
||||
// 用户菜单相关方法
|
||||
openChangePasswordModal() {
|
||||
this.userMenuOpen = false;
|
||||
this.showChangePasswordModal = true;
|
||||
},
|
||||
|
||||
closeChangePasswordModal() {
|
||||
this.showChangePasswordModal = false;
|
||||
this.changePasswordForm = {
|
||||
newUsername: '',
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
},
|
||||
|
||||
async changePassword() {
|
||||
// 验证表单
|
||||
if (this.changePasswordForm.newPassword !== this.changePasswordForm.confirmPassword) {
|
||||
this.showToast('新密码和确认密码不一致', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.changePasswordForm.newPassword.length < 8) {
|
||||
this.showToast('新密码长度至少8位', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
this.changePasswordLoading = true;
|
||||
try {
|
||||
const response = await fetch('/web/auth/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + this.authToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
newUsername: this.changePasswordForm.newUsername || undefined,
|
||||
currentPassword: this.changePasswordForm.currentPassword,
|
||||
newPassword: this.changePasswordForm.newPassword
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
this.showToast('账户信息修改成功,即将退出登录', 'success');
|
||||
this.closeChangePasswordModal();
|
||||
|
||||
// 将新的用户名更新到本地状态
|
||||
if (this.changePasswordForm.newUsername) {
|
||||
this.currentUser.username = this.changePasswordForm.newUsername;
|
||||
}
|
||||
|
||||
// 延迟2秒后自动退出登录
|
||||
setTimeout(() => {
|
||||
this.logout();
|
||||
}, 2000);
|
||||
} else {
|
||||
this.showToast(result.message || '修改失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
this.showToast('网络错误,请稍后再试', 'error');
|
||||
} finally {
|
||||
this.changePasswordLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async logout() {
|
||||
if (this.authToken) {
|
||||
try {
|
||||
|
||||
@@ -83,12 +83,42 @@
|
||||
<p class="text-gray-600 text-sm leading-tight mt-0.5">管理后台</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="logout"
|
||||
class="btn btn-danger px-6 py-3 flex items-center gap-2"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt"></i>退出登录
|
||||
</button>
|
||||
<!-- 用户菜单 -->
|
||||
<div class="relative">
|
||||
<button
|
||||
@click="userMenuOpen = !userMenuOpen"
|
||||
class="btn btn-primary px-4 py-3 flex items-center gap-2 relative"
|
||||
>
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<span>{{ currentUser.username || 'Admin' }}</span>
|
||||
<i class="fas fa-chevron-down text-xs transition-transform duration-200" :class="{ 'rotate-180': userMenuOpen }"></i>
|
||||
</button>
|
||||
|
||||
<!-- 悬浮菜单 -->
|
||||
<div
|
||||
v-if="userMenuOpen"
|
||||
class="absolute right-0 top-full mt-2 w-48 bg-white rounded-xl shadow-xl border border-gray-200 py-2 z-50"
|
||||
@click.stop
|
||||
>
|
||||
<button
|
||||
@click="openChangePasswordModal"
|
||||
class="w-full px-4 py-3 text-left text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-3"
|
||||
>
|
||||
<i class="fas fa-key text-blue-500"></i>
|
||||
<span>修改账户信息</span>
|
||||
</button>
|
||||
|
||||
<hr class="my-2 border-gray-200">
|
||||
|
||||
<button
|
||||
@click="logout"
|
||||
class="w-full px-4 py-3 text-left text-red-600 hover:bg-red-50 transition-colors flex items-center gap-3"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
<span>退出登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2385,6 +2415,103 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改账户信息模态框 -->
|
||||
<div v-if="showChangePasswordModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||||
<div class="modal-content w-full max-w-md p-8 mx-auto">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center">
|
||||
<i class="fas fa-key text-white"></i>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900">修改账户信息</h3>
|
||||
</div>
|
||||
<button
|
||||
@click="closeChangePasswordModal"
|
||||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="changePassword" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">当前用户名</label>
|
||||
<input
|
||||
:value="currentUser.username || 'Admin'"
|
||||
type="text"
|
||||
disabled
|
||||
class="form-input w-full bg-gray-100 cursor-not-allowed"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">当前用户名,输入新用户名以修改</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">新用户名</label>
|
||||
<input
|
||||
v-model="changePasswordForm.newUsername"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="输入新用户名(留空保持不变)"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">留空表示不修改用户名</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">当前密码</label>
|
||||
<input
|
||||
v-model="changePasswordForm.currentPassword"
|
||||
type="password"
|
||||
required
|
||||
class="form-input w-full"
|
||||
placeholder="请输入当前密码"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">新密码</label>
|
||||
<input
|
||||
v-model="changePasswordForm.newPassword"
|
||||
type="password"
|
||||
required
|
||||
class="form-input w-full"
|
||||
placeholder="请输入新密码"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-2">密码长度至少8位</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">确认新密码</label>
|
||||
<input
|
||||
v-model="changePasswordForm.confirmPassword"
|
||||
type="password"
|
||||
required
|
||||
class="form-input w-full"
|
||||
placeholder="请再次输入新密码"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="closeChangePasswordModal"
|
||||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="changePasswordLoading"
|
||||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||||
>
|
||||
<div v-if="changePasswordLoading" class="loading-spinner mr-2"></div>
|
||||
<i v-else class="fas fa-save mr-2"></i>
|
||||
{{ changePasswordLoading ? '保存中...' : '保存修改' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast 通知组件 -->
|
||||
<div v-for="(toast, index) in toasts" :key="toast.id"
|
||||
:class="['toast rounded-2xl p-4 shadow-2xl backdrop-blur-sm',
|
||||
|
||||
Reference in New Issue
Block a user