- 移除首页的'日新'按钮,每个龙虾卡片只保留'详情'按钮
- 日新功能整合到 LobsterDetail 组件内(弹窗形式)
- 删除 MemoryPage 路由组件(不再需要独立路由)
- 简化 App.js 路由配置,只保留两条路由:
* / - Dashboard 首页
* /lobster/:id - LobsterDetail 详情页
交互流程:
1. 首页点击'详情' → 进入龙虾详情页
2. 详情页点击'日新' → 打开 MemoryModal 弹窗
3. 弹窗内切换标签:成才之路 / 工作记忆
4. 关闭弹窗 → 返回详情页
优点:
- 首页更简洁,一个卡片一个按钮
- 所有龙虾共用弹窗组件,无需 n 个路由
- 功能整合,逻辑更清晰
- 更符合 React 组件化设计
📖 苟日新,日日新,又日新
183 lines
3.7 KiB
JavaScript
183 lines
3.7 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import axios from 'axios';
|
||
|
||
const API_BASE = 'http://localhost:8000/api';
|
||
|
||
function Dashboard() {
|
||
const [lobsters, setLobsters] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const navigate = useNavigate();
|
||
|
||
useEffect(() => {
|
||
fetchLobsters();
|
||
const interval = setInterval(fetchLobsters, 5000);
|
||
return () => clearInterval(interval);
|
||
}, []);
|
||
|
||
const fetchLobsters = async () => {
|
||
try {
|
||
const response = await axios.get(`${API_BASE}/lobsters/`);
|
||
setLobsters(response.data);
|
||
setLoading(false);
|
||
} catch (error) {
|
||
console.error('获取龙虾状态失败:', error);
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return <div className="loading">加载中...</div>;
|
||
}
|
||
|
||
return (
|
||
<div className="dashboard">
|
||
<h1>🦞 龙虾舰队监控中心</h1>
|
||
<div className="lobster-grid">
|
||
{lobsters.map(lobster => (
|
||
<div key={lobster.id} className="lobster-card">
|
||
<div className="lobster-header">
|
||
<span className="lobster-name">{lobster.emoji} {lobster.name}</span>
|
||
<span className={`status status-${lobster.status}`}>{lobster.status}</span>
|
||
</div>
|
||
<div className="lobster-info">
|
||
<p>专长:{lobster.specialty}</p>
|
||
<p>端口:{lobster.port}</p>
|
||
<p>容器:{lobster.container}</p>
|
||
</div>
|
||
<div className="lobster-actions">
|
||
<button className="detail-btn" onClick={() => navigate(`/lobster/${lobster.id}`)}>
|
||
📊 详情
|
||
</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Dashboard;
|
||
|
||
// CSS 样式
|
||
const styles = `
|
||
.dashboard {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.dashboard h1 {
|
||
color: #1a365d;
|
||
margin-bottom: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.lobster-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
|
||
.lobster-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
transition: transform 0.2s;
|
||
}
|
||
|
||
.lobster-card:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.lobster-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #e2e8f0;
|
||
}
|
||
|
||
.lobster-name {
|
||
font-size: 1.3em;
|
||
font-weight: bold;
|
||
color: #2d3748;
|
||
}
|
||
|
||
.status {
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.85em;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.status-healthy {
|
||
background: #c6f6d5;
|
||
color: #22543d;
|
||
}
|
||
|
||
.status-warning {
|
||
background: #feebc8;
|
||
color: #744210;
|
||
}
|
||
|
||
.status-error {
|
||
background: #fed7d7;
|
||
color: #742a2a;
|
||
}
|
||
|
||
.lobster-info {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.lobster-info p {
|
||
margin: 8px 0;
|
||
color: #4a5568;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.lobster-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.lobster-actions button {
|
||
flex: 1;
|
||
padding: 10px 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 1em;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.detail-btn {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.detail-btn:hover {
|
||
opacity: 0.9;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 50px;
|
||
color: #718096;
|
||
font-size: 1.2em;
|
||
}
|
||
`;
|
||
|
||
// 注入样式
|
||
if (typeof document !== 'undefined') {
|
||
const styleSheet = document.createElement('style');
|
||
styleSheet.textContent = styles;
|
||
document.head.appendChild(styleSheet);
|
||
}
|