1
This commit is contained in:
270
app/components/UserMenu.vue
Normal file
270
app/components/UserMenu.vue
Normal file
@@ -0,0 +1,270 @@
|
||||
<template>
|
||||
<div class="user-menu">
|
||||
<div class="user-info" @click="toggleMenu">
|
||||
<span class="user-name">{{ userName || '未登录' }}</span>
|
||||
<span class="dropdown-icon">▼</span>
|
||||
</div>
|
||||
<div v-if="menuVisible" class="user-menu-dropdown">
|
||||
<div class="menu-item" @click="showUserInfo">
|
||||
<span class="menu-icon">👤</span>
|
||||
<span class="menu-text">个人信息</span>
|
||||
</div>
|
||||
<div class="menu-item" @click="showSettings">
|
||||
<span class="menu-icon">⚙️</span>
|
||||
<span class="menu-text">设置</span>
|
||||
</div>
|
||||
<div class="menu-divider"></div>
|
||||
<div class="menu-item" @click="handleLogout">
|
||||
<span class="menu-icon">🚪</span>
|
||||
<span class="menu-text">退出登录</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 个人信息对话框 -->
|
||||
<UserInfoDialog
|
||||
:visible="showUserInfoDialog"
|
||||
:user-info="userInfo"
|
||||
@close="showUserInfoDialog = false"
|
||||
/>
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<SettingsDialog
|
||||
:visible="showSettingsDialog"
|
||||
@close="showSettingsDialog = false"
|
||||
@save="handleSettingsSave"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import UserInfoDialog from './UserInfoDialog.vue';
|
||||
import SettingsDialog from './SettingsDialog.vue';
|
||||
import logMixin from '../mixins/logMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'UserMenu',
|
||||
mixins: [logMixin],
|
||||
components: {
|
||||
UserInfoDialog,
|
||||
SettingsDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuVisible: false,
|
||||
showUserInfoDialog: false,
|
||||
showSettingsDialog: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState('auth', ['isLoggedIn', 'userName', 'snCode', 'deviceId', 'remainingDays', 'phone']),
|
||||
...mapState('mqtt', ['isConnected']),
|
||||
userInfo() {
|
||||
return {
|
||||
userName: this.userName || '',
|
||||
phone: this.phone || '',
|
||||
snCode: this.snCode || '',
|
||||
deviceId: this.deviceId || '',
|
||||
remainingDays: this.remainingDays
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleMenu() {
|
||||
this.menuVisible = !this.menuVisible;
|
||||
},
|
||||
showUserInfo() {
|
||||
this.menuVisible = false;
|
||||
this.showUserInfoDialog = true;
|
||||
},
|
||||
showSettings() {
|
||||
this.menuVisible = false;
|
||||
this.showSettingsDialog = true;
|
||||
},
|
||||
async handleLogout() {
|
||||
this.menuVisible = false;
|
||||
|
||||
if (!this.isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.addLog('info', '正在注销登录...');
|
||||
|
||||
// 断开MQTT连接
|
||||
if (this.isConnected && window.electronAPI && window.electronAPI.mqtt) {
|
||||
try {
|
||||
await window.electronAPI.invoke('mqtt:disconnect');
|
||||
} catch (error) {
|
||||
console.error('断开MQTT连接失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 调用后端退出接口(如果可用)
|
||||
if (window.electronAPI && window.electronAPI.invoke) {
|
||||
try {
|
||||
await window.electronAPI.invoke('auth:logout');
|
||||
} catch (error) {
|
||||
console.error('调用退出接口失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 store 清理认证状态(无论接口是否成功都要执行)
|
||||
if (this.$store) {
|
||||
this.$store.dispatch('auth/logout');
|
||||
|
||||
|
||||
// 清空平台状态
|
||||
this.$store.commit('platform/SET_PLATFORM_LOGIN_STATUS', {
|
||||
status: '-',
|
||||
color: '#FF9800',
|
||||
isLoggedIn: false
|
||||
});
|
||||
|
||||
// 停止任务更新定时器
|
||||
const taskTimer = this.$store.state.task.taskUpdateTimer;
|
||||
if (taskTimer) {
|
||||
clearInterval(taskTimer);
|
||||
this.$store.dispatch('task/setTaskUpdateTimer', null);
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到登录页面
|
||||
if (this.$router) {
|
||||
this.$router.push('/login').catch(err => {
|
||||
// 忽略重复导航错误
|
||||
if (err.name !== 'NavigationDuplicated') {
|
||||
console.error('跳转登录页失败:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.addLog('success', '注销登录成功');
|
||||
} catch (error) {
|
||||
console.error('退出登录异常:', error);
|
||||
|
||||
// 即使出错,也强制清空状态并跳转
|
||||
if (this.$store) {
|
||||
this.$store.dispatch('auth/logout');
|
||||
}
|
||||
|
||||
if (this.$router) {
|
||||
this.$router.push('/login').catch(() => {});
|
||||
}
|
||||
|
||||
this.addLog('error', `注销登录异常: ${error.message}`);
|
||||
}
|
||||
},
|
||||
handleSettingsSave(settings) {
|
||||
// 设置已保存,可以在这里处理额外的逻辑
|
||||
console.log('设置已保存:', settings);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 点击外部关闭菜单
|
||||
this.handleClickOutside = (e) => {
|
||||
if (this.$el && !this.$el.contains(e.target)) {
|
||||
this.menuVisible = false;
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 清理事件监听器
|
||||
if (this.handleClickOutside) {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-menu {
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 15px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
.user-info:hover & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.user-menu-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
min-width: 160px;
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-divider {
|
||||
height: 1px;
|
||||
background: #e0e0e0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user