feat: 实现WebSocket后台连接和信息面板集成
- 添加WebSocket配置到cad.js配置文件 - 创建websocketService.js实现完整WebSocket服务 - 集成WebSocket到main.js应用启动流程 - 移除InfoManagementPanel.vue所有硬编码数据 - 实现实时软件状态、操作日志、统计信息更新 - 优化面板样式匹配参考项目视觉效果 - 添加渐变按钮样式和深色主题一致性 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
167b3fc6c8
commit
4d02c503ef
@ -9,9 +9,9 @@
|
||||
right: -400px; /* 初始隐藏在右侧 */
|
||||
width: 400px;
|
||||
height: calc(100vh - 60px);
|
||||
background: var(--color-bg-primary);
|
||||
border-left: 1px solid var(--color-border-primary);
|
||||
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1);
|
||||
background: linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 100%);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -28,8 +28,8 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 15px 20px;
|
||||
background: var(--color-bg-primary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
background: #1e1e1e;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
@ -37,13 +37,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--color-text-primary);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.panel-title i {
|
||||
color: var(--color-primary);
|
||||
color: #2a5caa;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@ -59,15 +59,15 @@
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--color-error-rgb-1);
|
||||
color: var(--color-text-error);
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
color: #f44336;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-indicator.connected {
|
||||
background: var(--color-success-rgb-1);
|
||||
color: var(--color-text-success);
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.status-indicator i {
|
||||
@ -77,7 +77,7 @@
|
||||
.panel-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
@ -85,8 +85,8 @@
|
||||
}
|
||||
|
||||
.panel-close:hover {
|
||||
background: var(--color-white-rgb-1);
|
||||
color: var(--color-text-primary);
|
||||
background: #2a2a2a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 面板内容区域 */
|
||||
@ -100,8 +100,8 @@
|
||||
/* 选项卡导航 */
|
||||
.panel-tabs {
|
||||
display: flex;
|
||||
background: var(--color-bg-secondary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
background: #2a2a2a;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
@ -113,7 +113,7 @@
|
||||
padding: 12px 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
@ -121,14 +121,14 @@
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: var(--color-white-rgb-05);
|
||||
color: var(--color-text-primary);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
background: var(--color-bg-primary);
|
||||
color: #2a5caa;
|
||||
border-bottom-color: #2a5caa;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.tab-btn i {
|
||||
@ -155,16 +155,16 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--color-text-primary);
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 15px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--color-border-secondary);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
color: var(--color-primary);
|
||||
color: #2a5caa;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@ -175,12 +175,12 @@
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 40px 20px;
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loading-state i {
|
||||
color: var(--color-primary);
|
||||
color: #2a5caa;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@ -196,15 +196,15 @@
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: var(--color-white-rgb-05);
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
background: #2a2a2a;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.software-item-backend:hover {
|
||||
border-color: var(--color-border-primary);
|
||||
background: var(--color-white-rgb-1);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.software-info {
|
||||
@ -220,7 +220,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-primary-gradient);
|
||||
background: linear-gradient(135deg, #2a5caa 0%, #3d7bd8 100%);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
@ -232,19 +232,19 @@
|
||||
}
|
||||
|
||||
.software-name-backend {
|
||||
color: var(--color-text-primary);
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.software-status {
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.software-status.running {
|
||||
color: var(--color-text-success);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.software-actions {
|
||||
@ -271,41 +271,63 @@
|
||||
}
|
||||
|
||||
.action-btn.start {
|
||||
background: var(--color-success-gradient);
|
||||
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.start:hover:not(:disabled) {
|
||||
background: var(--color-success-gradient-hover);
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #81c784 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px var(--color-success-rgb-3);
|
||||
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.stop {
|
||||
background: var(--color-error-gradient);
|
||||
background: linear-gradient(135deg, #f44336 0%, #ef5350 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.stop:hover:not(:disabled) {
|
||||
background: var(--color-error-gradient-hover);
|
||||
background: linear-gradient(135deg, #ef5350 0%, #e57373 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px var(--color-error-rgb-3);
|
||||
box-shadow: 0 4px 8px rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.restart {
|
||||
background: var(--color-warning-gradient);
|
||||
background: linear-gradient(135deg, #ff9800 0%, #ffb74d 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.restart:hover:not(:disabled) {
|
||||
background: var(--color-warning-gradient-hover);
|
||||
background: linear-gradient(135deg, #ffb74d 0%, #ffcc02 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px var(--color-warning-rgb-3);
|
||||
box-shadow: 0 4px 8px rgba(255, 152, 0, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.refresh {
|
||||
background: linear-gradient(135deg, #2196f3 0%, #42a5f5 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.refresh:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #42a5f5 0%, #64b5f6 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.action-btn.clear {
|
||||
background: linear-gradient(135deg, #f44336 0%, #ef5350 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.clear:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #ef5350 0%, #e57373 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(244, 67, 54, 0.3);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
background: var(--color-white-rgb-1);
|
||||
color: var(--color-text-disabled);
|
||||
background: #1a1a1a;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
box-shadow: none;
|
||||
@ -315,7 +337,7 @@
|
||||
.refresh-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
@ -323,8 +345,8 @@
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-rgb-1);
|
||||
color: #2a5caa;
|
||||
background: rgba(42, 92, 170, 0.1);
|
||||
}
|
||||
|
||||
/* 日志面板样式 */
|
||||
@ -348,10 +370,10 @@
|
||||
|
||||
.filter-select, .filter-input {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
background: var(--color-white-rgb-05);
|
||||
color: var(--color-text-primary);
|
||||
background: #2a2a2a;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@ -365,20 +387,20 @@
|
||||
|
||||
.filter-select:focus, .filter-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
border-color: #2a5caa;
|
||||
}
|
||||
|
||||
.logs-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
background: var(--color-white-rgb-05);
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid var(--color-border-secondary);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
@ -387,7 +409,7 @@
|
||||
}
|
||||
|
||||
.log-item:hover {
|
||||
background: var(--color-white-rgb-1);
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
@ -398,18 +420,18 @@
|
||||
}
|
||||
|
||||
.log-operation {
|
||||
color: var(--color-text-primary);
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.log-details {
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@ -420,11 +442,11 @@
|
||||
gap: 10px;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-tertiary);
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.log-user {
|
||||
color: var(--color-primary);
|
||||
color: #2a5caa;
|
||||
}
|
||||
|
||||
.log-status {
|
||||
@ -435,13 +457,13 @@
|
||||
}
|
||||
|
||||
.log-status.success {
|
||||
background: var(--color-success-rgb-1);
|
||||
color: var(--color-text-success);
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.log-status.failed {
|
||||
background: var(--color-error-rgb-1);
|
||||
color: var(--color-text-error);
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.logs-pagination {
|
||||
@ -451,34 +473,34 @@
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid var(--color-border-secondary);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
background: var(--color-white-rgb-05);
|
||||
color: var(--color-text-secondary);
|
||||
background: #2a2a2a;
|
||||
color: #cccccc;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
border-color: #2a5caa;
|
||||
color: #2a5caa;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
background: var(--color-white-rgb-1);
|
||||
color: var(--color-text-disabled);
|
||||
background: #1a1a1a;
|
||||
color: #999999;
|
||||
cursor: not-allowed;
|
||||
border-color: var(--color-border-secondary);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.page-info {
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@ -493,8 +515,8 @@
|
||||
|
||||
.stat-card {
|
||||
padding: 15px;
|
||||
background: var(--color-white-rgb-05);
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
background: #2a2a2a;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
@ -503,23 +525,23 @@
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
color: #2a5caa;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.operation-types {
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--color-border-secondary);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.operation-types h5 {
|
||||
color: var(--color-text-primary);
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 10px 0;
|
||||
@ -533,10 +555,10 @@
|
||||
|
||||
.type-tag {
|
||||
padding: 4px 8px;
|
||||
background: var(--color-white-rgb-05);
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
background: #2a2a2a;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: var(--color-text-secondary);
|
||||
color: #cccccc;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@ -59,25 +59,25 @@
|
||||
>
|
||||
<div class="software-info">
|
||||
<div class="software-icon-backend">
|
||||
<i :class="software.icon"></i>
|
||||
<i class="fas fa-desktop"></i>
|
||||
</div>
|
||||
<div class="software-details">
|
||||
<div class="software-name-backend">{{ software.name }}</div>
|
||||
<div class="software-status" :class="{ running: software.isRunning }">
|
||||
{{ software.isRunning ? '运行中' : '已停止' }}
|
||||
<div class="software-name-backend">{{ software.name || software.id }}</div>
|
||||
<div class="software-status" :class="{ running: isRunning(software.status) }">
|
||||
{{ getStatusText(software.status) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="software-actions">
|
||||
<button
|
||||
v-if="!software.isRunning"
|
||||
v-if="!isRunning(software.status)"
|
||||
class="action-btn start"
|
||||
@click="startSoftware(software.id)"
|
||||
>
|
||||
<i class="fas fa-play"></i>启动
|
||||
</button>
|
||||
<button
|
||||
v-if="software.isRunning"
|
||||
v-if="isRunning(software.status)"
|
||||
class="action-btn stop"
|
||||
@click="stopSoftware(software.id)"
|
||||
>
|
||||
@ -86,7 +86,7 @@
|
||||
<button
|
||||
class="action-btn restart"
|
||||
@click="restartSoftware(software.id)"
|
||||
:disabled="!software.isRunning"
|
||||
:disabled="!isRunning(software.status)"
|
||||
>
|
||||
<i class="fas fa-redo"></i>重启
|
||||
</button>
|
||||
@ -104,10 +104,10 @@
|
||||
<i class="fas fa-history"></i>操作日志
|
||||
</h4>
|
||||
<div class="logs-actions">
|
||||
<button class="action-btn" @click="refreshLogs">
|
||||
<button class="action-btn refresh" @click="refreshLogs">
|
||||
<i class="fas fa-sync-alt"></i>刷新
|
||||
</button>
|
||||
<button class="action-btn" @click="clearLogs">
|
||||
<button class="action-btn clear" @click="clearLogs">
|
||||
<i class="fas fa-trash"></i>清理
|
||||
</button>
|
||||
</div>
|
||||
@ -137,10 +137,10 @@
|
||||
<div class="log-operation">{{ log.operation }}</div>
|
||||
<div class="log-time">{{ formatTime(log.timestamp) }}</div>
|
||||
</div>
|
||||
<div class="log-details">{{ log.details }}</div>
|
||||
<div class="log-details" v-if="log.details">{{ log.details }}</div>
|
||||
<div class="log-meta">
|
||||
<span class="log-user">{{ log.user }}</span>
|
||||
<span class="log-status" :class="log.status">{{ log.status }}</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -165,20 +165,20 @@
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ stats.totalOperations }}</span>
|
||||
<span class="stat-label">总操作次数</span>
|
||||
<span class="stat-value">{{ stats.totalOperations || 0 }}</span>
|
||||
<span class="stat-label">总日志数</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ stats.successfulOperations }}</span>
|
||||
<span class="stat-label">成功操作</span>
|
||||
<span class="stat-value">{{ stats.successfulOperations || 0 }}</span>
|
||||
<span class="stat-label">用户操作</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ stats.failedOperations }}</span>
|
||||
<span class="stat-label">失败操作</span>
|
||||
<span class="stat-value">{{ stats.failedOperations || 0 }}</span>
|
||||
<span class="stat-label">错误日志</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value">{{ stats.activeConnections }}</span>
|
||||
<span class="stat-label">活动连接</span>
|
||||
<span class="stat-value">{{ stats.activeConnections || 0 }}</span>
|
||||
<span class="stat-label">系统操作</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -201,7 +201,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import websocketService from '@/services/websocketService'
|
||||
|
||||
// Props
|
||||
defineProps({
|
||||
@ -217,48 +218,17 @@ defineEmits(['close'])
|
||||
// 当前活动选项卡
|
||||
const activeTab = ref('software')
|
||||
|
||||
// 后端连接状态
|
||||
const backendConnected = ref(true)
|
||||
|
||||
// 软件列表(模拟数据)
|
||||
const softwareList = ref([
|
||||
{
|
||||
id: 'creo',
|
||||
name: 'Creo',
|
||||
icon: 'fas fa-cogs',
|
||||
isRunning: true
|
||||
},
|
||||
{
|
||||
id: 'revit',
|
||||
name: 'Revit',
|
||||
icon: 'fas fa-building',
|
||||
isRunning: false
|
||||
},
|
||||
{
|
||||
id: 'autocad',
|
||||
name: 'AutoCAD',
|
||||
icon: 'fas fa-drafting-compass',
|
||||
isRunning: false
|
||||
},
|
||||
{
|
||||
id: 'solidworks',
|
||||
name: 'SolidWorks',
|
||||
icon: 'fas fa-hammer',
|
||||
isRunning: true
|
||||
},
|
||||
{
|
||||
id: 'catia',
|
||||
name: 'CATIA',
|
||||
icon: 'fas fa-cube',
|
||||
isRunning: false
|
||||
},
|
||||
{
|
||||
id: 'pdms',
|
||||
name: 'PDMS',
|
||||
icon: 'fas fa-industry',
|
||||
isRunning: false
|
||||
}
|
||||
])
|
||||
// WebSocket数据
|
||||
const backendConnected = ref(false)
|
||||
const softwareList = ref([])
|
||||
const logs = ref([])
|
||||
const stats = ref({
|
||||
totalOperations: 0,
|
||||
successfulOperations: 0,
|
||||
failedOperations: 0,
|
||||
activeConnections: 0
|
||||
})
|
||||
const operationTypes = ref([])
|
||||
|
||||
// 日志相关数据
|
||||
const logTypeFilter = ref('')
|
||||
@ -266,64 +236,6 @@ const operationFilter = ref('')
|
||||
const currentPage = ref(1)
|
||||
const logsPerPage = 10
|
||||
|
||||
// 模拟日志数据
|
||||
const logs = ref([
|
||||
{
|
||||
id: 1,
|
||||
operation: '启动Creo软件',
|
||||
details: 'Creo软件已成功启动,版本:9.0.0.0',
|
||||
timestamp: new Date(Date.now() - 5000),
|
||||
user: '管理员',
|
||||
status: 'success'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
operation: '打开模型文件',
|
||||
details: '成功打开模型文件:assembly.asm',
|
||||
timestamp: new Date(Date.now() - 15000),
|
||||
user: '用户1',
|
||||
status: 'success'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
operation: '连接测试',
|
||||
details: 'AutoCAD连接测试失败,无法建立连接',
|
||||
timestamp: new Date(Date.now() - 30000),
|
||||
user: '系统',
|
||||
status: 'failed'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
operation: '模型分析',
|
||||
details: '完成层级结构分析,发现127个组件',
|
||||
timestamp: new Date(Date.now() - 60000),
|
||||
user: '用户2',
|
||||
status: 'success'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
operation: '导出文件',
|
||||
details: '成功导出STEP格式文件',
|
||||
timestamp: new Date(Date.now() - 120000),
|
||||
user: '用户1',
|
||||
status: 'success'
|
||||
}
|
||||
])
|
||||
|
||||
// 统计数据(模拟)
|
||||
const stats = ref({
|
||||
totalOperations: 156,
|
||||
successfulOperations: 142,
|
||||
failedOperations: 14,
|
||||
activeConnections: 2
|
||||
})
|
||||
|
||||
// 操作类型(模拟)
|
||||
const operationTypes = ref([
|
||||
'软件启动', '文件打开', '模型分析', '格式转换',
|
||||
'数据导出', '连接测试', '系统配置', '用户管理'
|
||||
])
|
||||
|
||||
// 过滤后的日志
|
||||
const filteredLogs = computed(() => {
|
||||
let filtered = logs.value
|
||||
@ -350,65 +262,185 @@ const totalPages = computed(() => {
|
||||
return Math.ceil(logs.value.length / logsPerPage)
|
||||
})
|
||||
|
||||
// WebSocket事件监听器管理
|
||||
let listeners = []
|
||||
|
||||
const initListeners = () => {
|
||||
// 监听连接状态
|
||||
const onStateChange = (state) => {
|
||||
backendConnected.value = state.newState === 'CONNECTED'
|
||||
}
|
||||
websocketService.on('onStateChange', onStateChange)
|
||||
listeners.push(['onStateChange', onStateChange])
|
||||
|
||||
// 监听软件列表更新
|
||||
const onSoftwareUpdate = (list) => {
|
||||
softwareList.value = list
|
||||
}
|
||||
websocketService.on('onSoftwareUpdate', onSoftwareUpdate)
|
||||
listeners.push(['onSoftwareUpdate', onSoftwareUpdate])
|
||||
|
||||
// 监听日志更新
|
||||
const onLogUpdate = (logData) => {
|
||||
if (Array.isArray(logData)) {
|
||||
logs.value = logData
|
||||
} else if (logData.logs) {
|
||||
logs.value = logData.logs
|
||||
}
|
||||
}
|
||||
websocketService.on('onLogUpdate', onLogUpdate)
|
||||
listeners.push(['onLogUpdate', onLogUpdate])
|
||||
|
||||
// 监听统计更新
|
||||
const onStatsUpdate = (newStats) => {
|
||||
stats.value = {
|
||||
totalOperations: newStats.total_logs || 0,
|
||||
successfulOperations: newStats.user_operations || 0,
|
||||
failedOperations: newStats.error_logs || 0,
|
||||
activeConnections: newStats.active_connections || 0
|
||||
}
|
||||
}
|
||||
websocketService.on('onStatsUpdate', onStatsUpdate)
|
||||
listeners.push(['onStatsUpdate', onStatsUpdate])
|
||||
|
||||
// 监听操作类型更新
|
||||
const onOperationTypesUpdate = (types) => {
|
||||
operationTypes.value = types
|
||||
}
|
||||
websocketService.on('onOperationTypesUpdate', onOperationTypesUpdate)
|
||||
listeners.push(['onOperationTypesUpdate', onOperationTypesUpdate])
|
||||
}
|
||||
|
||||
const removeListeners = () => {
|
||||
listeners.forEach(([event, callback]) => {
|
||||
websocketService.off(event, callback)
|
||||
})
|
||||
listeners = []
|
||||
}
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
initListeners()
|
||||
|
||||
// 获取当前状态
|
||||
backendConnected.value = websocketService.connectionState === 'CONNECTED'
|
||||
softwareList.value = websocketService.softwareList
|
||||
stats.value = websocketService.logStats || stats.value
|
||||
operationTypes.value = websocketService.operationTypes
|
||||
})
|
||||
|
||||
// 组件卸载时只清理监听器,不关闭WebSocket
|
||||
onUnmounted(() => {
|
||||
removeListeners()
|
||||
})
|
||||
|
||||
// 方法
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
}
|
||||
|
||||
const refreshSoftwareList = () => {
|
||||
// 刷新软件列表逻辑
|
||||
websocketService.getSoftwareList()
|
||||
}
|
||||
|
||||
const startSoftware = (id) => {
|
||||
const software = softwareList.value.find(s => s.id === id)
|
||||
if (software) {
|
||||
software.isRunning = true
|
||||
}
|
||||
websocketService.startSoftware(id)
|
||||
}
|
||||
|
||||
const stopSoftware = (id) => {
|
||||
const software = softwareList.value.find(s => s.id === id)
|
||||
if (software) {
|
||||
software.isRunning = false
|
||||
}
|
||||
websocketService.stopSoftware(id)
|
||||
}
|
||||
|
||||
const restartSoftware = (id) => {
|
||||
const software = softwareList.value.find(s => s.id === id)
|
||||
if (software) {
|
||||
// 重启软件逻辑
|
||||
}
|
||||
websocketService.restartSoftware(id)
|
||||
}
|
||||
|
||||
const refreshLogs = () => {
|
||||
// 刷新日志逻辑
|
||||
const filters = {}
|
||||
if (logTypeFilter.value) filters.log_type = logTypeFilter.value
|
||||
if (operationFilter.value) filters.operation = operationFilter.value
|
||||
filters.limit = logsPerPage
|
||||
filters.offset = (currentPage.value - 1) * logsPerPage
|
||||
|
||||
websocketService.queryLogs(filters)
|
||||
}
|
||||
|
||||
const clearLogs = () => {
|
||||
logs.value = []
|
||||
websocketService.cleanupLogs()
|
||||
}
|
||||
|
||||
const refreshStats = () => {
|
||||
// 刷新统计信息逻辑
|
||||
websocketService.getLogStats()
|
||||
websocketService.getOperationTypes()
|
||||
}
|
||||
|
||||
// 辅助方法
|
||||
const isRunning = (status) => {
|
||||
return ['running', 'started', 'active', 'online', 'idle', 'ready', 'connected'].includes(status)
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'running': '运行中',
|
||||
'stopped': '已停止',
|
||||
'starting': '启动中...',
|
||||
'stopping': '停止中...',
|
||||
'restarting': '重启中...',
|
||||
'started': '运行中',
|
||||
'active': '运行中',
|
||||
'online': '运行中',
|
||||
'idle': '运行中',
|
||||
'ready': '运行中',
|
||||
'connected': '运行中',
|
||||
'disconnected': '已停止',
|
||||
'offline': '已停止',
|
||||
'inactive': '已停止',
|
||||
'terminated': '已停止',
|
||||
'error': '错误',
|
||||
'failed': '启动失败',
|
||||
'unknown': '检测中...'
|
||||
}
|
||||
return statusMap[status] || '检测中...'
|
||||
}
|
||||
|
||||
const getLogStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'success': '成功',
|
||||
'failed': '失败',
|
||||
'pending': '进行中'
|
||||
}
|
||||
return statusMap[status] || '成功'
|
||||
}
|
||||
|
||||
const formatTime = (timestamp) => {
|
||||
return timestamp.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
if (!timestamp) return ''
|
||||
|
||||
try {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch (error) {
|
||||
return timestamp
|
||||
}
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
refreshLogs()
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
refreshLogs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -6,6 +6,17 @@ const API_CONFIG = {
|
||||
BASE_URL: import.meta.env.MODE === 'production' ? 'https://api.miany.com' : 'http://localhost',
|
||||
TIMEOUT: 30000, // 30秒超时
|
||||
|
||||
// WebSocket配置
|
||||
WEBSOCKET: {
|
||||
BASE_URL: 'ws://localhost:8000',
|
||||
ENDPOINT: '/api/v1/ws/connect',
|
||||
CLIENT_ID: 'web_client',
|
||||
USER_ID: 'admin',
|
||||
RECONNECT_ATTEMPTS: 5,
|
||||
RECONNECT_DELAY: 1000, // 1秒
|
||||
HEARTBEAT_INTERVAL: 30000 // 30秒
|
||||
},
|
||||
|
||||
// 通用API端点
|
||||
COMMON_ENDPOINTS: {
|
||||
health: '/health',
|
||||
@ -240,6 +251,11 @@ export const getGeometryOptimizationDefaults = () => {
|
||||
return API_CONFIG.GEOMETRY_OPTIMIZATION
|
||||
}
|
||||
|
||||
// 获取WebSocket配置
|
||||
export const getWebSocketConfig = () => {
|
||||
return API_CONFIG.WEBSOCKET
|
||||
}
|
||||
|
||||
// HTTP请求头配置
|
||||
export const getRequestHeaders = () => {
|
||||
return {
|
||||
|
||||
@ -6,6 +6,7 @@ import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { useAuthStore } from './stores/auth'
|
||||
import websocketService from './services/websocketService'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@ -17,4 +18,7 @@ app.use(ElementPlus)
|
||||
const authStore = useAuthStore()
|
||||
authStore.initAuth()
|
||||
|
||||
// 启动WebSocket连接
|
||||
websocketService.connect()
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
392
src/services/websocketService.js
Normal file
392
src/services/websocketService.js
Normal file
@ -0,0 +1,392 @@
|
||||
// WebSocket服务类 - 统一管理后台WebSocket连接
|
||||
// 基于参考项目接口格式实现
|
||||
|
||||
import { getWebSocketConfig } from '@/config/cad'
|
||||
|
||||
class WebSocketService {
|
||||
constructor() {
|
||||
this.config = getWebSocketConfig()
|
||||
this.ws = null
|
||||
this.connectionState = 'DISCONNECTED' // CONNECTING, CONNECTED, DISCONNECTED, ERROR
|
||||
this.reconnectAttempts = 0
|
||||
this.heartbeatTimer = null
|
||||
|
||||
// 状态数据
|
||||
this.softwareList = []
|
||||
this.logs = []
|
||||
this.logStats = null
|
||||
this.operationTypes = []
|
||||
this.connectedUsers = []
|
||||
|
||||
// 事件监听器
|
||||
this.listeners = {
|
||||
onOpen: [],
|
||||
onMessage: [],
|
||||
onClose: [],
|
||||
onError: [],
|
||||
onStateChange: [],
|
||||
onSoftwareUpdate: [],
|
||||
onLogUpdate: [],
|
||||
onStatsUpdate: [],
|
||||
onOperationTypesUpdate: []
|
||||
}
|
||||
|
||||
console.log('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}`
|
||||
}
|
||||
|
||||
// 连接WebSocket
|
||||
connect() {
|
||||
if (this.connectionState === 'CONNECTING' || this.connectionState === 'CONNECTED') {
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
this.emit('onOpen', event)
|
||||
|
||||
// 连接成功后立即获取初始数据
|
||||
setTimeout(() => {
|
||||
this.getSoftwareList()
|
||||
this.getLogStats()
|
||||
this.getOperationTypes()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data)
|
||||
console.log('收到管理后台消息:', message)
|
||||
this.handleMessage(message)
|
||||
this.emit('onMessage', message)
|
||||
} catch (error) {
|
||||
console.error('解析管理后台消息失败:', error, event.data)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
console.log('统一管理后台连接关闭:', event)
|
||||
this.setState('DISCONNECTED')
|
||||
this.stopHeartbeat()
|
||||
this.emit('onClose', event)
|
||||
|
||||
// 自动重连
|
||||
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()
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onerror = (event) => {
|
||||
console.error('统一管理后台连接错误:', event)
|
||||
this.setState('ERROR')
|
||||
this.emit('onError', event)
|
||||
}
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
disconnect() {
|
||||
console.log('主动断开统一管理后台连接')
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, 'Client disconnect')
|
||||
this.ws = null
|
||||
}
|
||||
this.stopHeartbeat()
|
||||
this.setState('DISCONNECTED')
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 处理收到的消息
|
||||
handleMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'info':
|
||||
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 'error':
|
||||
console.error('管理后台错误:', message.message)
|
||||
break
|
||||
default:
|
||||
console.log('未处理的消息类型:', message.type, message)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理info类型消息
|
||||
handleInfoMessage(message) {
|
||||
if (message.data) {
|
||||
if (message.data.software_list) {
|
||||
this.softwareList = message.data.software_list
|
||||
this.emit('onSoftwareUpdate', this.softwareList)
|
||||
}
|
||||
|
||||
if (message.data.logs) {
|
||||
this.logs = message.data.logs
|
||||
this.emit('onLogUpdate', this.logs)
|
||||
}
|
||||
|
||||
if (message.data.stats) {
|
||||
this.logStats = message.data.stats
|
||||
this.emit('onStatsUpdate', this.logStats)
|
||||
}
|
||||
|
||||
if (message.data.operations) {
|
||||
this.operationTypes = message.data.operations
|
||||
this.emit('onOperationTypesUpdate', this.operationTypes)
|
||||
}
|
||||
|
||||
if (message.data.connected_users) {
|
||||
this.connectedUsers = message.data.connected_users
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理软件列表更新
|
||||
handleSoftwareListUpdate(message) {
|
||||
if (message.data && message.data.software_list) {
|
||||
this.softwareList = message.data.software_list
|
||||
console.log('软件列表已更新:', this.softwareList)
|
||||
this.emit('onSoftwareUpdate', this.softwareList)
|
||||
}
|
||||
}
|
||||
|
||||
// API方法 - 心跳检测
|
||||
ping() {
|
||||
return this.send({ type: 'ping' })
|
||||
}
|
||||
|
||||
// API方法 - 获取服务状态
|
||||
getStatus() {
|
||||
return this.send({ type: 'get_status' })
|
||||
}
|
||||
|
||||
// API方法 - 获取软件列表
|
||||
getSoftwareList() {
|
||||
return this.send({ type: 'get_software_list' })
|
||||
}
|
||||
|
||||
// API方法 - 启动软件
|
||||
startSoftware(softwareId) {
|
||||
if (!softwareId || typeof softwareId !== 'string' || softwareId.length === 0) {
|
||||
console.error('启动软件失败: 无效的软件ID', softwareId)
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('启动软件:', softwareId)
|
||||
return this.send({
|
||||
type: 'start_software',
|
||||
software_id: softwareId
|
||||
})
|
||||
}
|
||||
|
||||
// API方法 - 停止软件
|
||||
stopSoftware(softwareId) {
|
||||
if (!softwareId || typeof softwareId !== 'string' || softwareId.length === 0) {
|
||||
console.error('停止软件失败: 无效的软件ID', softwareId)
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('停止软件:', softwareId)
|
||||
return this.send({
|
||||
type: 'stop_software',
|
||||
software_id: softwareId
|
||||
})
|
||||
}
|
||||
|
||||
// API方法 - 重启软件
|
||||
restartSoftware(softwareId) {
|
||||
if (!softwareId || typeof softwareId !== 'string' || softwareId.length === 0) {
|
||||
console.error('重启软件失败: 无效的软件ID', softwareId)
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('重启软件:', softwareId)
|
||||
return this.send({
|
||||
type: 'restart_software',
|
||||
software_id: softwareId
|
||||
})
|
||||
}
|
||||
|
||||
// API方法 - 记录操作日志
|
||||
logOperation(operation, details = '', options = {}) {
|
||||
if (!operation || typeof operation !== 'string' || operation.length === 0) {
|
||||
console.error('记录日志失败: 操作名称不能为空')
|
||||
return false
|
||||
}
|
||||
|
||||
const logData = {
|
||||
type: 'log_operation',
|
||||
operation: operation.substring(0, 100), // 限制长度
|
||||
details: details ? details.substring(0, 1000) : '', // 限制长度
|
||||
action_type: options.action_type || 'execute',
|
||||
target_object: options.target_object || '',
|
||||
status: options.status || 'success',
|
||||
duration: options.duration || 0,
|
||||
operation_category: options.operation_category || '软件控制'
|
||||
}
|
||||
|
||||
console.log('记录操作日志:', logData)
|
||||
return this.send(logData)
|
||||
}
|
||||
|
||||
// API方法 - 查询操作日志
|
||||
queryLogs(filters = {}) {
|
||||
const queryData = {
|
||||
type: 'query_logs',
|
||||
limit: filters.limit || 100,
|
||||
offset: filters.offset || 0
|
||||
}
|
||||
|
||||
// 添加可选的筛选条件
|
||||
if (filters.log_type) queryData.log_type = filters.log_type
|
||||
if (filters.operation) queryData.operation = filters.operation
|
||||
if (filters.user_id_filter) queryData.user_id_filter = filters.user_id_filter
|
||||
if (filters.level) queryData.level = filters.level
|
||||
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)
|
||||
}
|
||||
|
||||
// API方法 - 根据ID获取日志
|
||||
getLogById(logId) {
|
||||
if (!logId || typeof logId !== 'string' || logId.length === 0) {
|
||||
console.error('获取日志失败: 无效的日志ID', logId)
|
||||
return false
|
||||
}
|
||||
|
||||
return this.send({
|
||||
type: 'get_log_by_id',
|
||||
log_id: logId
|
||||
})
|
||||
}
|
||||
|
||||
// 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' })
|
||||
}
|
||||
|
||||
// 心跳检测
|
||||
startHeartbeat() {
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.connectionState === 'CONNECTED') {
|
||||
this.ping()
|
||||
}
|
||||
}, this.config.HEARTBEAT_INTERVAL)
|
||||
}
|
||||
|
||||
stopHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer)
|
||||
this.heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 状态管理
|
||||
setState(newState) {
|
||||
if (this.connectionState !== newState) {
|
||||
const oldState = this.connectionState
|
||||
this.connectionState = newState
|
||||
console.log(`统一管理后台状态变化: ${oldState} -> ${newState}`)
|
||||
this.emit('onStateChange', { oldState, newState })
|
||||
}
|
||||
}
|
||||
|
||||
// 事件系统
|
||||
on(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback)
|
||||
} else {
|
||||
console.warn('未知事件类型:', event)
|
||||
}
|
||||
}
|
||||
|
||||
off(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
const index = this.listeners[event].indexOf(callback)
|
||||
if (index > -1) {
|
||||
this.listeners[event].splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(callback => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error('事件回调执行失败:', event, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前状态
|
||||
getState() {
|
||||
return {
|
||||
connectionState: this.connectionState,
|
||||
softwareList: this.softwareList,
|
||||
logs: this.logs,
|
||||
logStats: this.logStats,
|
||||
operationTypes: this.operationTypes,
|
||||
connectedUsers: this.connectedUsers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const websocketService = new WebSocketService()
|
||||
|
||||
// 默认导出
|
||||
export default websocketService
|
||||
Loading…
Reference in New Issue
Block a user