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:
parent
f6f4744e0b
commit
23d94cf0ab
@ -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: {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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秒
|
||||
|
||||
@ -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() {
|
||||
// 后处理钩子:将来可扩展为发送到远程日志服务
|
||||
// 当前只保留接口,不实现具体逻辑
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user