271 lines
6.3 KiB
Vue
271 lines
6.3 KiB
Vue
<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>
|
||
|