feat(user-settings): add user settings modal with username and password update

Implement user settings functionality in TopBar component with a modal dialog
that allows users to update their username and password. The modal includes
form validation and error handling, with proper password confirmation checks.
This commit is contained in:
2026-03-31 21:52:31 +08:00
parent 11245a5b7f
commit dcc2d06c8f
2 changed files with 207 additions and 8 deletions

View File

@@ -52,6 +52,8 @@ export const userApi = {
api.post('/userMgr/remove', { targetUserID }), api.post('/userMgr/remove', { targetUserID }),
listUsers: () => listUsers: () =>
api.get('/userMgr/list'), api.get('/userMgr/list'),
modifyUser: (userID, username, passwd) =>
api.post(`/userMgr/modify`, { userID, username, passwd }),
modifyType: (userID, newType) => modifyType: (userID, newType) =>
api.post(`/userMgr/modifyType`, { userID, newType: newType.toLowerCase() || 'visitor' }) api.post(`/userMgr/modifyType`, { userID, newType: newType.toLowerCase() || 'visitor' })
}; };

View File

@@ -28,7 +28,7 @@
<span class="username">{{ username }}</span> <span class="username">{{ username }}</span>
</div> </div>
<div class="user-menu" v-show="menuVisible" @click.stop> <div class="user-menu" v-show="menuVisible" @click.stop>
<button class="menu-item" @click="handleSettings"> <button class="menu-item" @click="showEditUser = true">
<i class="fas fa-user-cog"></i> <i class="fas fa-user-cog"></i>
<span>User Settings</span> <span>User Settings</span>
</button> </button>
@@ -38,14 +38,49 @@
</button> </button>
</div> </div>
</div> </div>
<div v-if="showEditUser" class="modal-overlay" @click="closeEditUserDialog">
<div class="modal" @click.stop>
<h3>User Settings</h3>
<form @submit.prevent="handleEditUserSubmit" class="form">
<div class="form-group">
<label>Username</label>
<input
type="text"
v-model="editFormData.newUsername"
required
>
</div>
<div class="form-group">
<label>New Password</label>
<input
type="password"
v-model="editFormData.newPasswd"
>
</div>
<div class="form-group">
<label>Confirm Password</label>
<input
type="password"
v-model="editFormData.confirmPasswd"
>
</div>
<div class="form-actions">
<button type="button" class="cancel-btn" @click="closeEditUserDialog">Cancel</button>
<button type="submit" class="submit-btn" :disabled="loading">
{{ loading ? 'Processing...' : 'Submit' }}
</button>
</div>
</form>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { ref, onMounted, onBeforeUnmount } from 'vue'; import { ref, onMounted, onBeforeUnmount } from 'vue';
import { systemApi, authApi } from '../api/index.js'; import { systemApi, authApi, userApi } from '../api/index.js';
import { getCookie, setCookie, deleteCookie as removeCookie } from '../utils/functions.js'; import { getCookie, setCookie, deleteCookie as removeCookie, showNotification } from '../utils/functions.js';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
export default { export default {
@@ -57,7 +92,15 @@ export default {
const softwareInfo = ref(null); const softwareInfo = ref(null);
const username = ref(getCookie('username') || 'User'); const username = ref(getCookie('username') || 'User');
const menuVisible = ref(false); const menuVisible = ref(false);
const showEditUser = ref(false);
const userInfoRef = ref(null); const userInfoRef = ref(null);
const loading = ref(false);
const userID = ref(parseInt(getCookie('user-id') || '0'));
const editFormData = ref({
newUsername: '',
newPasswd: '',
confirmPasswd: ''
});
const router = useRouter(); const router = useRouter();
const checkStatus = async () => { const checkStatus = async () => {
@@ -86,9 +129,61 @@ export default {
menuVisible.value = !menuVisible.value; menuVisible.value = !menuVisible.value;
}; };
const handleSettings = () => { const handleEditUser = () => {
menuVisible.value = false; showEditUser.value = true;
alert('User settings feature coming soon!'); };
const handleEditUserSubmit = async () => {
loading.value = true;
try {
const userIDValue = userID.value;
const newUsername = editFormData.value.newUsername;
const newPasswd = editFormData.value.newPasswd;
const confirmPasswd = editFormData.value.confirmPasswd;
if (!newUsername.trim()) {
showNotification('Username cannot be empty', 'error');
loading.value = false;
return;
}
if (newPasswd || confirmPasswd) {
if (newPasswd !== confirmPasswd) {
showNotification('Passwords do not match', 'error');
loading.value = false;
return;
}
}
let response;
if (newPasswd && newPasswd === confirmPasswd) {
response = await userApi.modifyUser(userIDValue, newUsername, newPasswd);
} else {
response = await userApi.modifyUser(userIDValue, newUsername, '');
}
if (response.success) {
showNotification('User information updated successfully', 'success');
username.value = newUsername;
setCookie('username', newUsername);
closeEditUserDialog();
} else {
showNotification(response.message || 'Failed to update user', 'error');
}
} catch (error) {
showNotification(error.message || 'Failed to update user', 'error');
} finally {
loading.value = false;
}
};
const closeEditUserDialog = () => {
showEditUser.value = false;
editFormData.value = {
newUsername: '',
newPasswd: '',
confirmPasswd: ''
};
}; };
const handleLogout = async () => { const handleLogout = async () => {
@@ -130,9 +225,14 @@ export default {
handleSearch, handleSearch,
menuVisible, menuVisible,
toggleMenu, toggleMenu,
handleSettings, handleEditUser,
showEditUser,
handleEditUserSubmit,
closeEditUserDialog,
handleLogout, handleLogout,
userInfoRef userInfoRef,
loading,
editFormData
}; };
} }
}; };
@@ -319,4 +419,101 @@ export default {
width: 20px; width: 20px;
text-align: center; text-align: center;
} }
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--card-bg);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal h3 {
font-size: 20px;
color: var(--text-color);
margin: 0 0 20px 0;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color);
}
.form-group input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 14px;
background: var(--bg-color);
color: var(--text-color);
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: var(--primary-color);
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.cancel-btn {
padding: 10px 20px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: transparent;
color: var(--text-color);
cursor: pointer;
transition: all 0.3s;
}
.cancel-btn:hover {
background: var(--hover-bg);
border-color: var(--primary-color);
}
.submit-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
background: var(--primary-color);
color: white;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn:hover:not(:disabled) {
background: var(--primary-hover);
}
.submit-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style> </style>