Files
openclaw-monitor/code/frontend/src/components/MemoryModal/index.js
flying-hero 57fa27c616 feat: 添加记忆功能 - 日记查看器和日历组件
- 后端 API: 获取日记日期列表和详情
- 前端组件:记忆弹窗、日历组件
- 点击记忆按钮查看龙虾工作日记
- 日历高亮显示有日记的日期
2026-04-01 22:36:06 +08:00

319 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
const API_BASE = 'http://localhost:8000/api';
function MemoryModal({ lobsterId, lobsterName, onClose }) {
const [dates, setDates] = useState([]);
const [selectedDate, setSelectedDate] = useState('');
const [content, setContent] = useState('');
const [currentMonth, setCurrentMonth] = useState(new Date());
// 加载有日记的日期
useEffect(() => {
fetch(`${API_BASE}/lobsters/${lobsterId}/memory/dates/`)
.then(r => r.json())
.then(data => {
setDates(data.dates);
if (data.dates.length > 0) {
setSelectedDate(data.dates[0]);
}
});
}, [lobsterId]);
// 加载选中日期的日记
useEffect(() => {
if (selectedDate) {
fetch(`${API_BASE}/lobsters/${lobsterId}/memory/${selectedDate}/`)
.then(r => r.json())
.then(data => {
if (data.content) {
setContent(data.content);
}
});
}
}, [selectedDate, lobsterId]);
// 渲染日历
const renderCalendar = () => {
const year = currentMonth.getFullYear();
const month = currentMonth.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const daysInMonth = lastDay.getDate();
const startWeekday = firstDay.getDay();
const days = [];
// 空白格子
for (let i = 0; i < startWeekday; i++) {
days.push(<div key={`empty-${i}`} className="calendar-day empty"></div>);
}
// 日期格子
for (let day = 1; day <= daysInMonth; day++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const hasMemory = dates.includes(dateStr);
const isSelected = selectedDate === dateStr;
days.push(
<div
key={day}
className={`calendar-day ${hasMemory ? 'has-memory' : ''} ${isSelected ? 'selected' : ''}`}
onClick={() => hasMemory && setSelectedDate(dateStr)}
>
{day}
</div>
);
}
return days;
};
const changeMonth = (delta) => {
setCurrentMonth(new Date(currentMonth.setMonth(currentMonth.getMonth() + delta)));
};
return (
<div className="memory-modal-overlay" onClick={onClose}>
<div className="memory-modal" onClick={e => e.stopPropagation()}>
<div className="memory-modal-header">
<h2>📔 {lobsterName} - 工作日记</h2>
<button className="close-btn" onClick={onClose}>×</button>
</div>
<div className="memory-modal-body">
{/* 日记内容 */}
<div className="memory-content">
{content ? (
<div className="memory-text">
<h3>📅 {selectedDate}</h3>
<pre>{content}</pre>
</div>
) : (
<div className="memory-empty">
<p>这一天还没有日记</p>
</div>
)}
</div>
{/* 日历 */}
<div className="memory-calendar-panel">
<div className="calendar-header">
<button onClick={() => changeMonth(-1)}></button>
<span>{currentMonth.getFullYear()} {currentMonth.getMonth() + 1}</span>
<button onClick={() => changeMonth(1)}></button>
</div>
<div className="calendar-grid">
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
<div key={day} className="calendar-weekday">{day}</div>
))}
{renderCalendar()}
</div>
<div className="calendar-legend">
<span className="legend-item has-memory"> 有日记</span>
<span className="legend-item no-memory"> 无日记</span>
</div>
</div>
</div>
</div>
<style>{`
.memory-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.memory-modal {
background: white;
border-radius: 16px;
width: 90%;
max-width: 1000px;
max-height: 90vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.memory-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #e2e8f0;
}
.memory-modal-header h2 {
color: #1a365d;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 2em;
cursor: pointer;
color: #718096;
}
.close-btn:hover {
color: #2d3748;
}
.memory-modal-body {
display: grid;
grid-template-columns: 1fr 350px;
gap: 20px;
padding: 20px;
overflow: hidden;
}
.memory-content {
background: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
overflow-y: auto;
max-height: 600px;
}
.memory-text h3 {
color: #553c9a;
margin-bottom: 15px;
}
.memory-text pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: #2d3748;
line-height: 1.6;
}
.memory-empty {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #a0aec0;
}
.memory-calendar-panel {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 15px;
background: white;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.calendar-header button {
background: #4299e1;
color: white;
border: none;
padding: 5px 12px;
border-radius: 6px;
cursor: pointer;
}
.calendar-header button:hover {
background: #3182ce;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.calendar-weekday {
text-align: center;
font-size: 0.8em;
color: #718096;
padding: 8px 0;
font-weight: 500;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
font-size: 0.9em;
cursor: pointer;
transition: all 0.2s;
}
.calendar-day:hover:not(.empty) {
background: #e2e8f0;
}
.calendar-day.empty {
cursor: default;
}
.calendar-day.has-memory {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: bold;
}
.calendar-day.selected {
border: 2px solid #ed8936;
background: #f6ad55;
color: white;
}
.calendar-legend {
margin-top: 15px;
display: flex;
gap: 20px;
font-size: 0.85em;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
}
.legend-item.has-memory::before {
content: '●';
color: #667eea;
}
.legend-item.no-memory::before {
content: '○';
color: #a0aec0;
}
@media (max-width: 768px) {
.memory-modal-body {
grid-template-columns: 1fr;
}
.memory-calendar-panel {
order: -1;
}
}
`}</style>
</div>
);
}
export default MemoryModal;