feat: 完成WebSocket日志系统接入和信息面板状态同步

- 在API客户端添加WebSocket日志记录钩子,所有CAD操作自动记录到后台
- 修复信息面板软件状态字段名(is_running vs status)和分页逻辑错误
- CAD连接成功时自动同步更新信息面板软件状态显示
- WebSocket连接时主动获取历史日志,实现完整的日志显示功能
- 清理调试输出,优化用户体验

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-17 18:54:45 +08:00
parent 4d02c503ef
commit a27e9aaec1
4 changed files with 83 additions and 37 deletions

View File

@ -142,6 +142,8 @@ const handleOperation = async () => {
- ✅ Creo专属页面框架模型分析、导出工具
- ✅ 通用格式转换页面框架
- ✅ 智能薄壳化分析接口startShellAnalysis
- ✅ WebSocket日志系统完整接入
- ✅ 信息面板实时日志显示和软件状态同步
## 页面添加标准流程
@ -153,13 +155,29 @@ const handleOperation = async () => {
- 添加页面切换处理方法
4. **组件间通信** - 使用emit事件不用router.push
## 日志系统实现总结
### ✅ 完成内容
1. **API钩子集成** - 所有CAD操作自动通过WebSocket记录日志到后台
2. **实时状态同步** - CAD连接成功后自动更新信息面板软件状态
3. **字段名修复** - 修复了后台返回`is_running`字段与前端`status`字段不匹配的问题
4. **分页逻辑修复** - 修复了翻页时重复调用后台接口导致的分页错乱
5. **日志实时显示** - 信息面板实时接收并按时间排序显示操作日志
### 🔧 关键修改
- **apiClient.js**: 添加WebSocket日志记录钩子和CAD连接成功时的状态同步
- **InfoManagementPanel.vue**: 修复软件状态字段名和分页逻辑
- **websocketService.js**: 连接时主动获取历史日志,清理调试输出
### 💡 数据流
`CAD操作``API调用``后处理钩子``WebSocket记录``后台存储``实时推送``信息面板显示`
## 待开发功能
- Creo模型分析、导出工具具体功能实现
- 通用格式转换具体功能实现
- Revit和PDMS集成
- 3D模型查看器
- 实时日志系统
---

View File

@ -63,21 +63,21 @@
</div>
<div class="software-details">
<div class="software-name-backend">{{ software.name || software.id }}</div>
<div class="software-status" :class="{ running: isRunning(software.status) }">
{{ getStatusText(software.status) }}
<div class="software-status" :class="{ running: software.is_running }">
{{ software.is_running ? '运行中' : '已停止' }}
</div>
</div>
</div>
<div class="software-actions">
<button
v-if="!isRunning(software.status)"
v-if="!software.is_running"
class="action-btn start"
@click="startSoftware(software.id)"
>
<i class="fas fa-play"></i>启动
</button>
<button
v-if="isRunning(software.status)"
v-if="software.is_running"
class="action-btn stop"
@click="stopSoftware(software.id)"
>
@ -86,7 +86,7 @@
<button
class="action-btn restart"
@click="restartSoftware(software.id)"
:disabled="!isRunning(software.status)"
:disabled="!software.is_running"
>
<i class="fas fa-redo"></i>重启
</button>
@ -134,13 +134,20 @@
class="log-item"
>
<div class="log-header">
<div class="log-operation">{{ log.operation }}</div>
<div class="log-operation">
<i class="fas fa-cogs" v-if="log.operation_category === 'CAD操作'"></i>
<i class="fas fa-desktop" v-else-if="log.operation_category === '软件控制'"></i>
<i class="fas fa-gear" v-else></i>
{{ log.operation }}
</div>
<div class="log-time">{{ formatTime(log.timestamp) }}</div>
</div>
<div class="log-details" v-if="log.details">{{ log.details }}</div>
<div class="log-meta">
<span class="log-category" v-if="log.operation_category">{{ log.operation_category }}</span>
<span class="log-user" v-if="log.user_id">{{ log.user_id }}</span>
<span class="log-status" :class="log.status">{{ getLogStatusText(log.status) }}</span>
<span class="log-duration" v-if="log.duration">{{ log.duration }}ms</span>
</div>
</div>
</div>
@ -236,8 +243,8 @@ const operationFilter = ref('')
const currentPage = ref(1)
const logsPerPage = 10
//
const filteredLogs = computed(() => {
//
const allFilteredLogs = computed(() => {
let filtered = logs.value
if (logTypeFilter.value) {
@ -252,14 +259,19 @@ const filteredLogs = computed(() => {
)
}
return filtered
})
//
const filteredLogs = computed(() => {
const start = (currentPage.value - 1) * logsPerPage
const end = start + logsPerPage
return filtered.slice(start, end)
return allFilteredLogs.value.slice(start, end)
})
//
const totalPages = computed(() => {
return Math.ceil(logs.value.length / logsPerPage)
return Math.ceil(allFilteredLogs.value.length / logsPerPage)
})
// WebSocket
@ -283,9 +295,10 @@ const initListeners = () => {
//
const onLogUpdate = (logData) => {
if (Array.isArray(logData)) {
logs.value = logData
//
logs.value = [...logData].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
} else if (logData.logs) {
logs.value = logData.logs
logs.value = [...logData.logs].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
}
}
websocketService.on('onLogUpdate', onLogUpdate)
@ -433,14 +446,12 @@ const formatTime = (timestamp) => {
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
refreshLogs()
}
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
refreshLogs()
}
}
</script>

View File

@ -2,7 +2,9 @@
// 包含拦截器、日志记录和后处理能力
import { getDefaultRequestConfig, getNotificationConfig } from '@/config/cad'
import cadConfig from '@/config/cad'
import { ElNotification, ElLoading } from 'element-plus'
import websocketService from './websocketService'
// 日志记录系统
class ApiLogger {
@ -59,6 +61,7 @@ class ApiClient {
// 设置通知处理器
setupNotificationHandler() {
// 通知处理器
PostProcessManager.addHandler(async (response, context) => {
// 默认显示通知,可通过配置关闭
if (context.showNotification !== false && context.operationContext) {
@ -84,6 +87,35 @@ class ApiClient {
}
}
})
// WebSocket日志记录处理器
PostProcessManager.addHandler(async (response, context) => {
if (context.operationContext) {
const { software, operation } = context.operationContext
const status = response.ok ? 'success' : 'failed'
const details = response.ok ?
`${software} ${operation}执行成功` :
`${software} ${operation}执行失败: ${response.statusText || '未知错误'}`
// 发送到WebSocket后台记录
websocketService.logOperation(
`${software} - ${operation}`,
details,
{
action_type: 'execute',
target_object: context.url,
status: status,
duration: context.duration || 0,
operation_category: 'CAD操作'
}
)
// CAD连接成功时同步更新信息面板软件状态
if (response.ok && operation === '连接测试') {
websocketService.getSoftwareList()
}
}
})
}
// 核心请求方法

View File

@ -31,7 +31,6 @@ class WebSocketService {
onOperationTypesUpdate: []
}
console.log('WebSocketService 初始化完成')
}
// 获取WebSocket连接URL
@ -47,12 +46,10 @@ class WebSocketService {
}
const url = this.getConnectionUrl()
console.log('开始连接统一管理后台:', url)
this.setState('CONNECTING')
this.ws = new WebSocket(url)
this.ws.onopen = (event) => {
console.log('统一管理后台连接成功')
this.setState('CONNECTED')
this.reconnectAttempts = 0
this.startHeartbeat()
@ -63,13 +60,13 @@ class WebSocketService {
this.getSoftwareList()
this.getLogStats()
this.getOperationTypes()
this.queryLogs()
}, 500)
}
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data)
console.log('收到管理后台消息:', message)
this.handleMessage(message)
this.emit('onMessage', message)
} catch (error) {
@ -78,7 +75,6 @@ class WebSocketService {
}
this.ws.onclose = (event) => {
console.log('统一管理后台连接关闭:', event)
this.setState('DISCONNECTED')
this.stopHeartbeat()
this.emit('onClose', event)
@ -86,7 +82,6 @@ class WebSocketService {
// 自动重连
if (!event.wasClean && this.reconnectAttempts < this.config.RECONNECT_ATTEMPTS) {
const delay = this.config.RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts)
console.log(`统一管理后台将在 ${delay}ms 后重连,第 ${this.reconnectAttempts + 1} 次尝试`)
setTimeout(() => {
this.reconnectAttempts++
this.connect()
@ -95,7 +90,6 @@ class WebSocketService {
}
this.ws.onerror = (event) => {
console.error('统一管理后台连接错误:', event)
this.setState('ERROR')
this.emit('onError', event)
}
@ -103,7 +97,6 @@ class WebSocketService {
// 断开连接
disconnect() {
console.log('主动断开统一管理后台连接')
if (this.ws) {
this.ws.close(1000, 'Client disconnect')
this.ws = null
@ -116,11 +109,9 @@ class WebSocketService {
send(message) {
if (this.connectionState === 'CONNECTED' && this.ws) {
const jsonMessage = JSON.stringify(message)
console.log('发送管理后台消息:', message)
this.ws.send(jsonMessage)
return true
} else {
console.warn('统一管理后台未连接,消息发送失败:', message)
return false
}
}
@ -132,13 +123,17 @@ class WebSocketService {
this.handleInfoMessage(message)
break
case 'heartbeat':
console.log('收到心跳响应')
break
case 'software_list_update':
this.handleSoftwareListUpdate(message)
break
case 'log_recorded':
console.log('操作日志已记录:', message.data)
break
case 'software_started':
case 'software_stopped':
case 'software_restarted':
// 软件状态变更后自动刷新软件列表
this.getSoftwareList()
break
case 'error':
console.error('管理后台错误:', message.message)
@ -181,7 +176,6 @@ class WebSocketService {
handleSoftwareListUpdate(message) {
if (message.data && message.data.software_list) {
this.softwareList = message.data.software_list
console.log('软件列表已更新:', this.softwareList)
this.emit('onSoftwareUpdate', this.softwareList)
}
}
@ -208,7 +202,6 @@ class WebSocketService {
return false
}
console.log('启动软件:', softwareId)
return this.send({
type: 'start_software',
software_id: softwareId
@ -222,7 +215,6 @@ class WebSocketService {
return false
}
console.log('停止软件:', softwareId)
return this.send({
type: 'stop_software',
software_id: softwareId
@ -236,7 +228,6 @@ class WebSocketService {
return false
}
console.log('重启软件:', softwareId)
return this.send({
type: 'restart_software',
software_id: softwareId
@ -261,7 +252,6 @@ class WebSocketService {
operation_category: options.operation_category || '软件控制'
}
console.log('记录操作日志:', logData)
return this.send(logData)
}
@ -281,7 +271,6 @@ class WebSocketService {
if (filters.start_time) queryData.start_time = filters.start_time
if (filters.end_time) queryData.end_time = filters.end_time
console.log('查询操作日志:', queryData)
return this.send(queryData)
}
@ -300,19 +289,16 @@ class WebSocketService {
// API方法 - 获取日志统计信息
getLogStats() {
console.log('获取日志统计信息')
return this.send({ type: 'get_log_stats' })
}
// API方法 - 清理过期日志
cleanupLogs() {
console.log('清理过期日志')
return this.send({ type: 'cleanup_logs' })
}
// API方法 - 获取操作类型列表
getOperationTypes() {
console.log('获取操作类型列表')
return this.send({ type: 'get_operation_types' })
}
@ -337,7 +323,6 @@ class WebSocketService {
if (this.connectionState !== newState) {
const oldState = this.connectionState
this.connectionState = newState
console.log(`统一管理后台状态变化: ${oldState} -> ${newState}`)
this.emit('onStateChange', { oldState, newState })
}
}