feat: 实现用户退出登录功能并完善日志系统用户信息

- 头部用户菜单添加下拉框和退出登录选项
- 退出登录功能集成认证Store和路由跳转
- WebSocket连接动态获取当前登录用户信息
- 日志记录系统添加用户名和用户ID字段
- 移除vite-plugin-vue-devtools避免开发环境干扰

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-19 09:50:22 +08:00
parent f6f4744e0b
commit 23d94cf0ab
5 changed files with 123 additions and 10 deletions

View File

@ -2,13 +2,11 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {

View File

@ -57,10 +57,18 @@
<i class="fas fa-bell"></i>
<span class="badge">3</span>
</div>
<div class="user-menu">
<div class="user-menu" @click="toggleUserDropdown">
<img src="@/assets/images/adminlogo.png" alt="用户头像" class="user-avatar">
<span class="username">管理员</span>
<i class="fas fa-chevron-down"></i>
<i class="fas fa-chevron-down" :class="{ 'rotated': showUserDropdown }"></i>
<!-- 用户下拉菜单 -->
<div v-show="showUserDropdown" class="user-dropdown">
<div class="dropdown-item" @click.stop="handleLogout">
<i class="fas fa-sign-out-alt"></i>
<span>退出登录</span>
</div>
</div>
</div>
<div class="system-settings">
<i class="fas fa-cog"></i>
@ -70,8 +78,10 @@
</template>
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useCADStore } from '@/stores/cad'
import { useAuthStore } from '@/stores/auth'
import { PAGE_TYPES, CAD_REQUIRED_PAGES } from '@/config/pages'
// Props
@ -87,6 +97,13 @@ const emit = defineEmits(['page-change'])
// Store
const cadStore = useCADStore()
const authStore = useAuthStore()
// Router
const router = useRouter()
//
const showUserDropdown = ref(false)
// CAD
const isCADConnected = computed(() => {
@ -102,6 +119,17 @@ const handleNavClick = (page) => {
emit('page-change', page)
}
//
const toggleUserDropdown = () => {
showUserDropdown.value = !showUserDropdown.value
}
// 退
const handleLogout = () => {
authStore.logout()
router.push('/login')
}
</script>
<style scoped>
@ -237,6 +265,7 @@ const handleNavClick = (page) => {
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.user-menu:hover {
@ -258,6 +287,56 @@ const handleNavClick = (page) => {
.user-menu i {
color: var(--color-text-secondary);
font-size: 12px;
transition: transform 0.3s ease;
}
.user-menu i.rotated {
transform: rotate(180deg);
}
.user-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background: var(--color-bg-primary);
border: 1px solid var(--color-border-primary);
border-radius: 8px;
box-shadow: 0 4px 20px var(--color-shadow-black-1);
min-width: 140px;
z-index: 1000;
}
.dropdown-item {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
color: var(--color-text-primary);
cursor: pointer;
transition: background-color 0.3s ease;
font-size: 14px;
}
.dropdown-item:hover {
background: var(--color-white-rgb-1);
}
.dropdown-item:first-child {
border-radius: 8px 8px 0 0;
}
.dropdown-item:last-child {
border-radius: 0 0 8px 8px;
}
.dropdown-item:only-child {
border-radius: 8px;
}
.dropdown-item i {
font-size: 14px;
color: var(--color-text-secondary);
}
.system-settings {

View File

@ -11,7 +11,7 @@ const API_CONFIG = {
BASE_URL: 'ws://localhost:8000',
ENDPOINT: '/api/v1/ws/connect',
CLIENT_ID: 'web_client',
USER_ID: 'admin',
USER_ID: null, // 动态设置从auth store获取
RECONNECT_ATTEMPTS: 5,
RECONNECT_DELAY: 1000, // 1秒
HEARTBEAT_INTERVAL: 30000 // 30秒

View File

@ -8,6 +8,9 @@ import websocketService from './websocketService'
// 日志记录系统
class ApiLogger {
static log(method, url, requestData, response, duration) {
// 获取当前用户信息
const currentUser = this.getCurrentUser()
const logEntry = {
timestamp: new Date().toISOString(),
method: method.toUpperCase(),
@ -16,13 +19,25 @@ class ApiLogger {
status: response.ok ? 'success' : 'error',
statusCode: response.status,
duration: `${duration}ms`,
responseSize: response.headers?.get('content-length') || 'unknown'
responseSize: response.headers?.get('content-length') || 'unknown',
user: currentUser?.username || 'anonymous' // 添加用户信息
}
// 为将来扩展预留:可以发送到日志服务
this.sendToLogService(logEntry)
}
// 获取当前登录用户信息
static getCurrentUser() {
try {
const userKey = 'user' // 与auth.js中的tokenConfig.userKey保持一致
const savedUser = localStorage.getItem(userKey)
return savedUser ? JSON.parse(savedUser) : null
} catch {
return null
}
}
static sendToLogService() {
// 后处理钩子:将来可扩展为发送到远程日志服务
// 当前只保留接口,不实现具体逻辑

View File

@ -35,8 +35,23 @@ class WebSocketService {
// 获取WebSocket连接URL
getConnectionUrl() {
const { BASE_URL, ENDPOINT, CLIENT_ID, USER_ID } = this.config
return `${BASE_URL}${ENDPOINT}?client_id=${CLIENT_ID}&user_id=${USER_ID}`
const { BASE_URL, ENDPOINT, CLIENT_ID } = this.config
// 动态获取当前用户信息
const currentUser = this.getCurrentUser()
const userId = currentUser?.username || 'anonymous'
return `${BASE_URL}${ENDPOINT}?client_id=${CLIENT_ID}&user_id=${userId}`
}
// 获取当前登录用户信息
getCurrentUser() {
try {
// 从localStorage读取用户信息
const userKey = 'user' // 与auth.js中的tokenConfig.userKey保持一致
const savedUser = localStorage.getItem(userKey)
return savedUser ? JSON.parse(savedUser) : null
} catch {
return null
}
}
// 连接WebSocket
@ -241,6 +256,10 @@ class WebSocketService {
return false
}
// 获取当前用户信息
const currentUser = this.getCurrentUser()
const username = currentUser?.username || 'anonymous'
const logData = {
type: 'log_operation',
operation: operation.substring(0, 100), // 限制长度
@ -249,7 +268,9 @@ class WebSocketService {
target_object: options.target_object || '',
status: options.status || 'success',
duration: options.duration || 0,
operation_category: options.operation_category || '软件控制'
operation_category: options.operation_category || '软件控制',
user_name: username, // 添加用户名信息
user_id: username // 添加用户ID信息
}
return this.send(logData)