feat: 添加放虾归海功能(龙虾河)🦐🌊
🎨 新设计: - 龙虾河:200px 高,蓝色渐变背景 - 波浪动画:三层半透明波浪滚动效果 - 河水分界线:贯穿的蓝线 + '河岸'文字 - 河中的龙虾:随机位置,游动动画 🦐 交互玩法: 1. 拖拽龙虾卡片到河水中 2. 龙虾变成 emoji 在河中游动 3. 鼠标悬停显示名字 tooltip 4. 可以从河中拖回下方列表 ✨ 功能特性: - 龙虾在河中随机分布(10%-90% 位置) - 游动动画(上下浮动 + 左右摇摆) - 悬停显示名字 - 拖拽出河水后从河中移除 - 成功提示:'XX 已放归河水中!🌊' 🦀 Logo 更新: - 添加 favicon(浏览器标签页图标) - Dashboard 顶部显示 logo - 标题 emoji 更新为 🦀 🎯 组件重构: - LobsterPool → LobsterRiver(更贴切) - 简化组件结构 - 优化动画效果 🦸 感谢北极星 ⭐ 的创意! '放虾归海' - 太好玩了!😄
This commit is contained in:
@@ -3,7 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>🤖 Agent Diary - AI Agent 日记管理系统</title>
|
<title>🦀 Agent Diary - AI Agent 日记管理系统</title>
|
||||||
|
<link rel="icon" href="/logo.png" type="image/png">
|
||||||
|
<link rel="shortcut icon" href="/logo.png" type="image/png">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
BIN
code/frontend/public/logo.png
Normal file
BIN
code/frontend/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 625 KiB |
@@ -5,11 +5,13 @@ const API_BASE = 'http://localhost:8000/api';
|
|||||||
function LobsterPool({ agents, onRefresh }) {
|
function LobsterPool({ agents, onRefresh }) {
|
||||||
const [isDragging, setIsDragging] = useState(null);
|
const [isDragging, setIsDragging] = useState(null);
|
||||||
const [isOverPool, setIsOverPool] = useState(false);
|
const [isOverPool, setIsOverPool] = useState(false);
|
||||||
|
const [droppedAgents, setDroppedAgents] = useState([]);
|
||||||
|
|
||||||
// 开始拖拽
|
// 开始拖拽
|
||||||
const handleDragStart = (e, agent) => {
|
const handleDragStart = (e, agent) => {
|
||||||
setIsDragging(agent);
|
setIsDragging(agent);
|
||||||
e.dataTransfer.setData('agentId', agent.id);
|
e.dataTransfer.setData('agentId', agent.id.toString());
|
||||||
|
e.dataTransfer.setData('agentName', agent.name);
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,30 +38,20 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
setIsOverPool(false);
|
setIsOverPool(false);
|
||||||
|
|
||||||
const agentId = e.dataTransfer.getData('agentId');
|
const agentId = e.dataTransfer.getData('agentId');
|
||||||
const agent = agents.find(a => a.id === parseInt(agentId));
|
const agentName = e.dataTransfer.getData('agentName');
|
||||||
|
|
||||||
if (agent) {
|
if (agentId) {
|
||||||
try {
|
// 添加到已放入列表
|
||||||
// 调用 API 同步到数据库
|
const agent = agents.find(a => a.id === parseInt(agentId));
|
||||||
const response = await fetch(`${API_BASE}/agents/sync/`, {
|
if (agent && !droppedAgents.find(a => a.id === agent.id)) {
|
||||||
method: 'POST',
|
setDroppedAgents([...droppedAgents, agent]);
|
||||||
headers: {
|
}
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
alert(`🦐 ${agentName} 已放虾归海!🌊`);
|
||||||
body: JSON.stringify({
|
|
||||||
agent_id: agent.id,
|
// 刷新列表(可选:这里可以调用 API 真正同步到数据库)
|
||||||
action: 'add_to_pool',
|
if (onRefresh) {
|
||||||
}),
|
setTimeout(() => onRefresh(), 500);
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
alert(`🦐 ${agent.name} 已放虾归海!`);
|
|
||||||
onRefresh(); // 刷新列表
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('放虾失败:', error);
|
|
||||||
alert('❌ 放虾失败:' + error.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -88,6 +80,20 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
{isOverPool ? '🦐 松开手,放虾归海!' : '拖拽龙虾到池中'}
|
{isOverPool ? '🦐 松开手,放虾归海!' : '拖拽龙虾到池中'}
|
||||||
</p>
|
</p>
|
||||||
<p className="pool-hint">将左侧的龙虾拖到这里,自动同步到数据库</p>
|
<p className="pool-hint">将左侧的龙虾拖到这里,自动同步到数据库</p>
|
||||||
|
|
||||||
|
{/* 已放入的龙虾 */}
|
||||||
|
{droppedAgents.length > 0 && (
|
||||||
|
<div className="dropped-agents">
|
||||||
|
<p className="dropped-title">已归海的龙虾 ({droppedAgents.length})</p>
|
||||||
|
<div className="dropped-list">
|
||||||
|
{droppedAgents.map(agent => (
|
||||||
|
<span key={agent.id} className="dropped-agent">
|
||||||
|
{agent.emoji} {agent.name}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -114,6 +120,7 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
border: 3px solid #4299e1;
|
border: 3px solid #4299e1;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: linear-gradient(180deg, #4299e1 0%, #2b6cb0 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lobster-pool.pool-active {
|
.lobster-pool.pool-active {
|
||||||
@@ -128,8 +135,8 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: linear-gradient(180deg, #4299e1 0%, #2b6cb0 100%);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wave {
|
.wave {
|
||||||
@@ -178,6 +185,7 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pool-icon {
|
.pool-icon {
|
||||||
@@ -207,6 +215,35 @@ function LobsterPool({ agents, onRefresh }) {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropped-agents {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropped-title {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropped-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropped-agent {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.lobster-pool {
|
.lobster-pool {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
|
|||||||
312
code/frontend/src/components/LobsterRiver/index.js
Normal file
312
code/frontend/src/components/LobsterRiver/index.js
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const API_BASE = 'http://localhost:8000/api';
|
||||||
|
|
||||||
|
function LobsterRiver({ agents, onRefresh }) {
|
||||||
|
const [isOverRiver, setIsOverRiver] = useState(false);
|
||||||
|
const [riverAgents, setRiverAgents] = useState([]);
|
||||||
|
const [hoveredAgent, setHoveredAgent] = useState(null);
|
||||||
|
|
||||||
|
// 拖拽进入河水
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsOverRiver(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 拖拽离开河水
|
||||||
|
const handleDragLeave = () => {
|
||||||
|
setIsOverRiver(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 放入河水中
|
||||||
|
const handleDrop = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsOverRiver(false);
|
||||||
|
|
||||||
|
const agentId = e.dataTransfer.getData('agentId');
|
||||||
|
const agentName = e.dataTransfer.getData('agentName');
|
||||||
|
const agentEmoji = e.dataTransfer.getData('agentEmoji');
|
||||||
|
|
||||||
|
if (agentId) {
|
||||||
|
const agent = {
|
||||||
|
id: parseInt(agentId),
|
||||||
|
name: agentName,
|
||||||
|
emoji: agentEmoji,
|
||||||
|
x: Math.random() * 80 + 10, // 随机位置 10%-90%
|
||||||
|
y: Math.random() * 60 + 20, // 随机位置 20%-80%
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加到河中
|
||||||
|
if (!riverAgents.find(a => a.id === agent.id)) {
|
||||||
|
setRiverAgents([...riverAgents, agent]);
|
||||||
|
alert(`🦐 ${agentName} 已放归河水中!🌊`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
if (onRefresh) {
|
||||||
|
setTimeout(() => onRefresh(), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从河中拖出
|
||||||
|
const handleRiverDragStart = (e, agent) => {
|
||||||
|
e.dataTransfer.setData('agentId', agent.id.toString());
|
||||||
|
e.dataTransfer.setData('agentName', agent.name);
|
||||||
|
e.dataTransfer.setData('agentEmoji', agent.emoji);
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
|
||||||
|
// 从河中移除
|
||||||
|
setRiverAgents(riverAgents.filter(a => a.id !== agent.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lobster-river-container">
|
||||||
|
<h2 className="river-title">🌊 龙虾池 - 放虾归海</h2>
|
||||||
|
|
||||||
|
{/* 河水区域 */}
|
||||||
|
<div
|
||||||
|
className={`lobster-river ${isOverRiver ? 'river-active' : ''}`}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
{/* 波浪动画 */}
|
||||||
|
<div className="river-waves">
|
||||||
|
<div className="wave wave-1"></div>
|
||||||
|
<div className="wave wave-2"></div>
|
||||||
|
<div className="wave wave-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 河中的龙虾 */}
|
||||||
|
<div className="river-agents">
|
||||||
|
{riverAgents.map(agent => (
|
||||||
|
<div
|
||||||
|
key={agent.id}
|
||||||
|
className="river-agent"
|
||||||
|
style={{
|
||||||
|
left: `${agent.x}%`,
|
||||||
|
top: `${agent.y}%`,
|
||||||
|
}}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => handleRiverDragStart(e, agent)}
|
||||||
|
onMouseEnter={() => setHoveredAgent(agent.id)}
|
||||||
|
onMouseLeave={() => setHoveredAgent(null)}
|
||||||
|
title={agent.name}
|
||||||
|
>
|
||||||
|
<span className="agent-emoji">{agent.emoji}</span>
|
||||||
|
{hoveredAgent === agent.id && (
|
||||||
|
<span className="agent-name-tooltip">{agent.name}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 提示文字 */}
|
||||||
|
{riverAgents.length === 0 && (
|
||||||
|
<div className="river-hint">
|
||||||
|
<p className="hint-main">
|
||||||
|
{isOverRiver ? '🦐 松开手,放虾归海!' : '拖拽龙虾到河水中'}
|
||||||
|
</p>
|
||||||
|
<p className="hint-sub">将左侧的龙虾拖到这里,放虾归海</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 河水分界线 */}
|
||||||
|
<div className="river-divider">
|
||||||
|
<div className="divider-line"></div>
|
||||||
|
<div className="divider-text">══════ 河岸 ══════</div>
|
||||||
|
<div className="divider-line"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>{`
|
||||||
|
.lobster-river-container {
|
||||||
|
margin: 20px 0 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-title {
|
||||||
|
color: #1a365d;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobster-river {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1000px;
|
||||||
|
height: 200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
background: linear-gradient(180deg, #4299e1 0%, #2b6cb0 50%, #1a365d 100%);
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobster-river.river-active {
|
||||||
|
background: linear-gradient(180deg, #48bb78 0%, #38a169 50%, #22543d 100%);
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-waves {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
width: 200%;
|
||||||
|
height: 100px;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: wave-move 6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-1 {
|
||||||
|
top: -80px;
|
||||||
|
left: -50%;
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-2 {
|
||||||
|
top: -60px;
|
||||||
|
left: -50%;
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-3 {
|
||||||
|
top: -40px;
|
||||||
|
left: -50%;
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave-move {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(50%) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-agents {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-agent {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 2em;
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.3s;
|
||||||
|
animation: swim 3s ease-in-out infinite;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-agent:hover {
|
||||||
|
transform: scale(1.3);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes swim {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-10px) rotate(5deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-emoji {
|
||||||
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-name-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7em;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-hint {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 10;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-main {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-sub {
|
||||||
|
font-size: 1em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-line {
|
||||||
|
flex: 1;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, transparent, #4299e1, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-text {
|
||||||
|
color: #4299e1;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.lobster-river {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.river-agent {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-main {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LobsterRiver;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
// import LobsterPool from '../components/LobsterPool'; // TODO: Fix import
|
import LobsterRiver from '../../components/LobsterRiver/index.js';
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:8000/api';
|
const API_BASE = 'http://localhost:8000/api';
|
||||||
|
|
||||||
@@ -47,8 +47,9 @@ function Dashboard() {
|
|||||||
|
|
||||||
// 拖拽处理
|
// 拖拽处理
|
||||||
const handleDragStart = (e, agent) => {
|
const handleDragStart = (e, agent) => {
|
||||||
e.dataTransfer.setData('agentId', agent.id);
|
e.dataTransfer.setData('agentId', agent.id.toString());
|
||||||
e.dataTransfer.setData('agentName', agent.name);
|
e.dataTransfer.setData('agentName', agent.name);
|
||||||
|
e.dataTransfer.setData('agentEmoji', agent.emoji);
|
||||||
e.target.style.opacity = '0.5';
|
e.target.style.opacity = '0.5';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +64,10 @@ function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
<div className="dashboard-header">
|
<div className="dashboard-header">
|
||||||
<h1>🤖 Agent 舰队监控中心</h1>
|
<div className="logo-section">
|
||||||
|
<img src="/logo.png" alt="Logo" className="site-logo" />
|
||||||
|
<h1>🦀 Agent 舰队监控中心</h1>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
className="scan-btn"
|
className="scan-btn"
|
||||||
onClick={scanAgents}
|
onClick={scanAgents}
|
||||||
@@ -73,18 +77,19 @@ function Dashboard() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* TODO: 龙虾池功能待修复 */}
|
{/* 龙虾池 - 放虾归海 */}
|
||||||
{/* <LobsterPool agents={agents} onRefresh={fetchAgents} /> */}
|
<LobsterRiver agents={agents} onRefresh={fetchAgents} />
|
||||||
|
|
||||||
<h2 className="section-title">🦐 待归海的龙虾</h2>
|
<h2 className="section-title">🦐 待归海的龙虾</h2>
|
||||||
<div className="agent-grid">
|
<div className="agent-grid">
|
||||||
{agents.map(agent => (
|
{agents.map(agent => (
|
||||||
<div
|
<div
|
||||||
key={agent.id}
|
key={agent.id}
|
||||||
className="agent-card"
|
className="agent-card draggable-card"
|
||||||
draggable
|
draggable
|
||||||
onDragStart={(e) => handleDragStart(e, agent)}
|
onDragStart={(e) => handleDragStart(e, agent)}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
|
title="拖拽我到龙虾池"
|
||||||
>
|
>
|
||||||
<div className="agent-header">
|
<div className="agent-header">
|
||||||
<span className="agent-name">{agent.emoji} {agent.name}</span>
|
<span className="agent-name">{agent.emoji} {agent.name}</span>
|
||||||
@@ -100,7 +105,7 @@ function Dashboard() {
|
|||||||
📊 Agent 详情
|
📊 Agent 详情
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="drag-hint">👆 拖我到龙虾池</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -119,6 +124,25 @@ const styles = `
|
|||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard h1 {
|
||||||
|
color: #1a365d;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard h1 {
|
.dashboard h1 {
|
||||||
color: #1a365d;
|
color: #1a365d;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -197,6 +221,16 @@ const styles = `
|
|||||||
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-card.draggable-card {
|
||||||
|
cursor: grab;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-card.draggable-card:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-header {
|
.agent-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
Reference in New Issue
Block a user