Add React frontend and Django backend code

- React components: Dashboard, MemoryCalendar, SearchBox, ToolList
- Django backend structure
- Package configuration files
This commit is contained in:
2026-04-01 20:43:25 +08:00
parent d80dc01bca
commit 1cbbf11fcf
6 changed files with 307 additions and 0 deletions

16
code/backend/manage.py Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django."
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,30 @@
{
"name": "lobster-monitor-frontend",
"version": "1.0.0",
"description": "龙虾舰队监控中心 - React 前端",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"axios": "^1.6.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@@ -0,0 +1,83 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const API_BASE = 'http://localhost:8000/api';
function MemoryCalendar({ lobsterId }) {
const [currentMonth, setCurrentMonth] = useState(new Date());
const [memoryDates, setMemoryDates] = useState([]);
const [selectedDate, setSelectedDate] = useState(null);
const [memoryContent, setMemoryContent] = useState('');
useEffect(() => {
fetchMemoryDates();
}, [currentMonth, lobsterId]);
const fetchMemoryDates = async () => {
try {
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/memory/dates/`);
setMemoryDates(response.data.dates);
} catch (error) {
console.error('获取记忆日期失败:', error);
}
};
const fetchMemory = async (date) => {
try {
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/memory/${date}/`);
setMemoryContent(response.data.content);
setSelectedDate(date);
} catch (error) {
console.error('获取记忆失败:', error);
}
};
const renderCalendar = () => {
const year = currentMonth.getFullYear();
const month = currentMonth.getMonth();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const days = [];
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const hasMemory = memoryDates.includes(dateStr);
days.push(
<div
key={day}
className={`calendar-day ${hasMemory ? 'has-memory' : ''} ${selectedDate === dateStr ? 'selected' : ''}`}
onClick={() => hasMemory && fetchMemory(dateStr)}
>
{day}
{hasMemory && <span className="memory-indicator"></span>}
</div>
);
}
return days;
};
return (
<div className="memory-calendar">
<div className="calendar-header">
<button onClick={() => setCurrentMonth(new Date(currentMonth.setMonth(currentMonth.getMonth() - 1)))}></button>
<span>{currentMonth.getFullYear()} {currentMonth.getMonth() + 1}</span>
<button onClick={() => setCurrentMonth(new Date(currentMonth.setMonth(currentMonth.getMonth() + 1)))}></button>
</div>
<div className="calendar-grid">
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
<div key={day} className="calendar-weekday">{day}</div>
))}
{renderCalendar()}
</div>
{memoryContent && (
<div className="memory-content">
<h3>📅 {selectedDate} 的记忆</h3>
<pre>{memoryContent}</pre>
</div>
)}
</div>
);
}
export default MemoryCalendar;

View File

@@ -0,0 +1,59 @@
import React, { useState } from 'react';
import axios from 'axios';
const API_BASE = 'http://localhost:8000/api';
function SearchBox({ lobsterId }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [searching, setSearching] = useState(false);
const handleSearch = async (e) => {
e.preventDefault();
if (!query.trim()) return;
setSearching(true);
try {
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/search/`, {
params: { q: query }
});
setResults(response.data.results);
} catch (error) {
console.error('搜索失败:', error);
} finally {
setSearching(false);
}
};
return (
<div className="search-box">
<form onSubmit={handleSearch}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索文档..."
/>
<button type="submit" disabled={searching}>
{searching ? '搜索中...' : '🔍 搜索'}
</button>
</form>
{results.length > 0 && (
<div className="search-results">
<h3>搜索结果 ({results.length})</h3>
{results.map((result, index) => (
<div key={index} className="search-result">
<h4>{result.title}</h4>
<p className="snippet">{result.snippet}</p>
<a href={result.url} target="_blank" rel="noopener noreferrer">
查看原文
</a>
</div>
))}
</div>
)}
</div>
);
}
export default SearchBox;

View File

@@ -0,0 +1,59 @@
import React from 'react';
function ToolList() {
const tools = [
{
name: 'Git 版本控制',
status: '🟢 运行中',
description: '代码版本管理服务',
access: [
{ type: 'Web', url: 'http://localhost:18003' },
{ type: 'Git', url: 'git://127.0.0.1:9418/monitor-dashboard.git' },
{ type: 'HTTP', url: 'http://localhost:18003/monitor-dashboard.git' }
],
usage: [
'克隆仓库git clone http://localhost:18003/monitor-dashboard.git',
'提交更改git add . && git commit -m "说明"',
'推送代码git push origin master'
]
}
];
return (
<div className="tool-list">
<h1>🛠 工具列表</h1>
{tools.map((tool, index) => (
<div key={index} className="tool-card">
<div className="tool-header">
<h2>{tool.name}</h2>
<span className="tool-status">{tool.status}</span>
</div>
<p className="tool-description">{tool.description}</p>
<div className="tool-access">
<h3>访问方式:</h3>
{tool.access.map((item, i) => (
<div key={i} className="access-item">
<span className="access-type">{item.type}:</span>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.url}
</a>
</div>
))}
</div>
<div className="tool-usage">
<h3>使用方法:</h3>
<ul>
{tool.usage.map((item, i) => (
<li key={i}><code>{item}</code></li>
))}
</ul>
</div>
</div>
))}
</div>
);
}
export default ToolList;

View File

@@ -0,0 +1,60 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const API_BASE = 'http://localhost:8000/api';
function Dashboard() {
const [lobsters, setLobsters] = useState([]);
const [loading, setLoading] = useState(true);
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 onClick={() => window.location.href = `/lobster/${lobster.id}`}>
📊 详情
</button>
<button onClick={() => window.location.href = `/lobster/${lobster.id}/memory`}>
🧠 记忆
</button>
</div>
</div>
))}
</div>
</div>
);
}
export default Dashboard;