feat(TopBar): add user settings & logout btn and finish logout function

refactor(auth): rename login-token to token for consistency

Update all token references from 'login-token' to 'token' across the application. Also enhance TopBar with user menu functionality including logout and settings options.

- Standardize token cookie name for better maintainability
- Add dropdown menu to TopBar with logout functionality
- Implement click-outside behavior for menu
- Improve TopBar styling and user interaction
This commit is contained in:
2026-03-10 19:05:56 +08:00
parent 491c74e231
commit f103858fe0
4 changed files with 120 additions and 10 deletions

View File

@@ -8,7 +8,7 @@ const api = axios.create({
api.interceptors.request.use(
config => {
const token = getCookie('login-token');
const token = getCookie('token');
if (token) {
config.headers['X-Token'] = token;
}

View File

@@ -22,18 +22,31 @@
<div class="version-info" v-if="softwareInfo">
<span>v{{ softwareInfo.Version }}</span>
</div>
<div class="user-info">
<div class="avatar">{{ username.charAt(0).toUpperCase() }}</div>
<span class="username">{{ username }}</span>
<div class="user-profile" @click.stop="toggleMenu">
<div class="user-info" ref="userInfoRef">
<div class="avatar">{{ username.charAt(0).toUpperCase() }}</div>
<span class="username">{{ username }}</span>
</div>
<div class="user-menu" v-show="menuVisible" @click.stop>
<button class="menu-item" @click="handleSettings">
<i class="fas fa-user-cog"></i>
<span>User Settings</span>
</button>
<button class="menu-item" @click="handleLogout">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import { systemApi } from '../api/index.js';
import { getCookie } from '../utils/functions.js';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { systemApi, authApi } from '../api/index.js';
import { getCookie, setCookie, deleteCookie as removeCookie } from '../utils/functions.js';
import { useRouter } from 'vue-router';
export default {
name: 'TopBar',
@@ -43,6 +56,9 @@ export default {
const isOnline = ref(false);
const softwareInfo = ref(null);
const username = ref(getCookie('username') || 'User');
const menuVisible = ref(false);
const userInfoRef = ref(null);
const router = useRouter();
const checkStatus = async () => {
try {
@@ -66,10 +82,44 @@ export default {
emit('search', searchQuery.value);
};
const toggleMenu = () => {
menuVisible.value = !menuVisible.value;
};
const handleSettings = () => {
menuVisible.value = false;
alert('User settings feature coming soon!');
};
const handleLogout = async () => {
menuVisible.value = false;
try {
await authApi.logout();
} catch (error) {
console.error('Logout API error:', error);
}
removeCookie('token');
removeCookie('username');
removeCookie('user-type');
removeCookie('user-id');
router.push('/login');
};
const closeMenu = (event) => {
if (userInfoRef.value && !userInfoRef.value.contains(event.target)) {
menuVisible.value = false;
}
};
onMounted(() => {
checkStatus();
getSoftwareInfo();
setInterval(checkStatus, 5000);
document.addEventListener('click', closeMenu);
});
onBeforeUnmount(() => {
document.removeEventListener('click', closeMenu);
});
return {
@@ -77,7 +127,12 @@ export default {
isOnline,
softwareInfo,
username,
handleSearch
handleSearch,
menuVisible,
toggleMenu,
handleSettings,
handleLogout,
userInfoRef
};
}
};
@@ -184,11 +239,26 @@ export default {
color: #999;
}
.user-profile {
position: relative;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
padding: 8px 12px;
border-radius: 8px;
transition: background-color 0.2s;
}
.user-info:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.user-profile:hover .user-info {
background-color: rgba(0, 0, 0, 0.05);
}
.avatar {
@@ -202,11 +272,51 @@ export default {
color: white;
font-weight: 600;
font-size: 14px;
flex-shrink: 0;
}
.username {
font-size: 14px;
color: var(--text-color);
font-weight: 500;
white-space: nowrap;
}
.user-menu {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
min-width: 180px;
background: var(--card-bg);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
z-index: 101;
border: 1px solid var(--border-color);
}
.user-menu .menu-item {
width: 100%;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
background: none;
border: none;
cursor: pointer;
font-size: 14px;
color: var(--text-color);
transition: background-color 0.2s;
}
.user-menu .menu-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.user-menu .menu-item i {
font-size: 16px;
width: 20px;
text-align: center;
}
</style>

View File

@@ -65,7 +65,7 @@ const router = createRouter({
});
router.beforeEach((to, from, next) => {
const token = getCookie('login-token');
const token = getCookie('token');
const userType = getCookie('user-type');
if (to.meta.requiresAuth && !token) {

View File

@@ -112,7 +112,7 @@ export default {
};
const handleLoginSuccess = (result) => {
setCookie('login-token', result.data.token);
setCookie('token', result.data.token);
setCookie('user-id', result.data.userID);
setCookie('username', result.data.username);
setCookie('user-type', result.data.type);