feat: 添加外部应用卡片和可复制优化

- 新增'📱 外部应用'卡片,显示飞书应用信息
  * 应用名称和图标
  * 应用 ID(等宽字体显示)
  * 一键复制按钮
  * 未配置应用显示友好提示

- 优化工作区路径显示
  * 代码样式(等宽字体 + 背景色)
  * 添加'📋 复制'按钮
  * 复制成功有提示消息

- 后端添加龙虾应用配置
  * 飞行侠:IT 项目推广运营平台 (cli_a92413cfb0791bce)
  * 道童:道德经新解 (cli_a9439b614f38dbd2)
  * 其他龙虾:未配置

- 样式优化
  * 绿色渐变复制按钮
  * 悬停动画效果
  * 响应式布局

📱 让重要信息一键可复制
This commit is contained in:
2026-04-02 18:44:20 +08:00
parent f5ac1e85bf
commit b852186920
2 changed files with 164 additions and 8 deletions

View File

@@ -10,12 +10,12 @@ import re
# 龙虾配置
LOBSTERS = [
{'id': 1, 'name': '飞行侠', 'emoji': '🦸', 'port': 18789, 'specialty': '主力/通用', 'container': 'openclaw-instance2'},
{'id': 2, 'name': '道童', 'emoji': '☯️', 'port': 18889, 'specialty': '道德经注解', 'container': 'openclaw-gateway-2'},
{'id': 3, 'name': '墨子', 'emoji': '🔧', 'port': 18689, 'specialty': '代码专家', 'container': 'openclaw-coder'},
{'id': 4, 'name': '织网者', 'emoji': '🕸️', 'port': 18589, 'specialty': '网站制作', 'container': 'openclaw-web'},
{'id': 5, 'name': '费曼', 'emoji': '⚛️', 'port': 18989, 'specialty': '物理研究', 'container': 'openclaw-physics'},
{'id': 6, 'name': '守望者', 'emoji': '👁️', 'port': 18080, 'specialty': '舰队监控', 'container': 'openclaw-watcher'},
{'id': 1, 'name': '飞行侠', 'emoji': '🦸', 'port': 18789, 'specialty': '主力/通用', 'container': 'openclaw-instance2', 'app_name': 'IT 项目推广运营平台', 'app_id': 'cli_a92413cfb0791bce'},
{'id': 2, 'name': '道童', 'emoji': '☯️', 'port': 18889, 'specialty': '道德经注解', 'container': 'openclaw-gateway-2', 'app_name': '道德经新解', 'app_id': 'cli_a9439b614f38dbd2'},
{'id': 3, 'name': '墨子', 'emoji': '🔧', 'port': 18689, 'specialty': '代码专家', 'container': 'openclaw-coder', 'app_name': '未配置', 'app_id': ''},
{'id': 4, 'name': '织网者', 'emoji': '🕸️', 'port': 18589, 'specialty': '网站制作', 'container': 'openclaw-web', 'app_name': '未配置', 'app_id': ''},
{'id': 5, 'name': '费曼', 'emoji': '⚛️', 'port': 18989, 'specialty': '物理研究', 'container': 'openclaw-physics', 'app_name': '未配置', 'app_id': ''},
{'id': 6, 'name': '守望者', 'emoji': '👁️', 'port': 18080, 'specialty': '舰队监控', 'container': 'openclaw-watcher', 'app_name': '未配置', 'app_id': ''},
]
@api_view(['GET'])

View File

@@ -103,9 +103,21 @@ function LobsterDetail() {
<span className="info-value code">{lobster.container}</span>
</div>
{lobster.workspace && (
<div className="info-row">
<div className="info-row workspace-row">
<span className="info-label">工作区</span>
<span className="info-value code">{lobster.workspace}</span>
<div className="workspace-value">
<code>{lobster.workspace}</code>
<button
className="copy-btn"
onClick={() => {
navigator.clipboard.writeText(lobster.workspace);
alert('工作区路径已复制到剪贴板!📋');
}}
title="复制路径"
>
📋 复制
</button>
</div>
</div>
)}
<div className="info-row">
@@ -150,6 +162,41 @@ function LobsterDetail() {
</div>
{/* 状态历史卡片 */}
<div className="info-card">
<div className="card-header">
<h2>📱 外部应用</h2>
</div>
<div className="card-body">
{lobster.app_name && lobster.app_name !== '未配置' ? (
<div className="app-info">
<div className="app-icon">🪵</div>
<div className="app-details">
<div className="app-name">{lobster.app_name}</div>
<div className="app-id">
<code>{lobster.app_id}</code>
<button
className="copy-btn small"
onClick={() => {
navigator.clipboard.writeText(lobster.app_id);
alert('应用 ID 已复制到剪贴板!📋');
}}
title="复制应用 ID"
>
📋 复制
</button>
</div>
</div>
</div>
) : (
<div className="app-empty">
<p>😕 暂无外部应用</p>
<p className="app-hint">这只龙虾还没有关联外部应用哦~</p>
</div>
)}
</div>
</div>
{/* 运行统计卡片 */}
<div className="info-card">
<div className="card-header">
<h2>📊 运行统计</h2>
@@ -282,6 +329,54 @@ function LobsterDetail() {
border-radius: 4px;
}
.workspace-row {
align-items: flex-start;
}
.workspace-value {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
justify-content: flex-end;
}
.workspace-value code {
font-family: 'Courier New', monospace;
font-size: 0.85em;
background: #f7fafc;
padding: 6px 10px;
border-radius: 4px;
color: #2d3748;
word-break: break-all;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
}
.copy-btn {
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
color: white;
border: none;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.85em;
font-weight: 600;
white-space: nowrap;
transition: all 0.2s;
}
.copy-btn:hover {
opacity: 0.9;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(72, 187, 120, 0.3);
}
.copy-btn:active {
transform: translateY(0);
}
.status-badge {
display: flex;
align-items: center;
@@ -362,6 +457,67 @@ function LobsterDetail() {
font-size: 0.9em;
}
.app-info {
display: flex;
align-items: flex-start;
gap: 15px;
padding: 10px 0;
}
.app-icon {
font-size: 2.5em;
line-height: 1;
}
.app-details {
flex: 1;
min-width: 0;
}
.app-name {
font-size: 1.1em;
font-weight: 600;
color: #2d3748;
margin-bottom: 8px;
}
.app-id {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.app-id code {
font-family: 'Courier New', monospace;
font-size: 0.85em;
background: #f7fafc;
padding: 6px 10px;
border-radius: 4px;
color: #4a5568;
word-break: break-all;
}
.copy-btn.small {
padding: 4px 10px;
font-size: 0.8em;
}
.app-empty {
text-align: center;
padding: 30px 20px;
color: #a0aec0;
}
.app-empty p {
margin: 8px 0;
}
.app-hint {
font-size: 0.9em;
color: #cbd5e0;
}
.detail-loading, .detail-error {
display: flex;
flex-direction: column;