/** * 我的天体(普通用户) * * 左侧是已关注天体列表,右侧是天体资料与相关天象事件。 */ import { useCallback, useEffect, useState } from 'react'; import { Button, Card, Col, Descriptions, Empty, Row, Table, Tag } from 'antd'; import { ReloadOutlined, RocketOutlined, StarFilled, StarOutlined } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; import { request } from '../../utils/request'; import { useToast } from '../../contexts/ToastContext'; import { AdminPage } from '../../components/admin/AdminPage'; interface FollowedBody { id: string; name: string; name_zh?: string | null; type: string; is_active: boolean; followed_at?: string; } interface BodyEvent { id: number; title: string; event_type: string; event_time: string; description: string; details?: Record; } const BODY_TYPE_LABELS: Record = { star: '恒星', planet: '行星', dwarf_planet: '矮行星', satellite: '卫星', comet: '彗星', asteroid: '小行星', probe: '探测器', }; const BODY_TYPE_COLORS: Record = { star: 'gold', planet: 'blue', dwarf_planet: 'cyan', satellite: 'geekblue', comet: 'purple', asteroid: 'volcano', probe: 'magenta', }; const EVENT_TYPE_LABELS: Record = { approach: '接近', close_approach: '近距离接近', eclipse: '食', conjunction: '合', opposition: '冲', transit: '凌', }; const EVENT_TYPE_COLORS: Record = { approach: 'blue', close_approach: 'magenta', eclipse: 'purple', conjunction: 'cyan', opposition: 'orange', transit: 'green', }; export function MyCelestialBodies() { const [loading, setLoading] = useState(false); const [bodies, setBodies] = useState([]); const [selectedBody, setSelectedBody] = useState(null); const [events, setEvents] = useState([]); const [eventsLoading, setEventsLoading] = useState(false); const toast = useToast(); const loadEvents = useCallback(async (body: FollowedBody) => { setEventsLoading(true); try { const { data } = await request.get('/events', { params: { body_id: body.id, limit: 100 } }); setEvents(data || []); } catch { toast.error('加载天体事件失败'); setEvents([]); } finally { setEventsLoading(false); } }, [toast]); const loadFollowedBodies = useCallback(async () => { setLoading(true); try { const { data } = await request.get('/social/follows'); const list = data || []; setBodies(list); if (list.length > 0) { const next = list.find((item) => item.id === selectedBody?.id) ?? list[0]; setSelectedBody(next); await loadEvents(next); } else { setSelectedBody(null); setEvents([]); } } catch { toast.error('加载关注列表失败'); } finally { setLoading(false); } }, [loadEvents, selectedBody?.id, toast]); useEffect(() => { void loadFollowedBodies(); // 仅在首次进入页面时加载关注列表,后续交互自行刷新。 // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleSelectBody = async (body: FollowedBody) => { setSelectedBody(body); await loadEvents(body); }; const handleUnfollow = async (bodyId: string) => { try { await request.delete(`/social/follow/${bodyId}`); toast.success('已取消关注'); if (selectedBody?.id === bodyId) { setSelectedBody(null); setEvents([]); } await loadFollowedBodies(); } catch { toast.error('取消关注失败'); } }; const eventColumns: ColumnsType = [ { title: '事件', dataIndex: 'title', key: 'title', ellipsis: true, width: '40%' }, { title: '类型', dataIndex: 'event_type', key: 'event_type', width: 160, render: (type: string) => ( {EVENT_TYPE_LABELS[type] || type} ), filters: Object.entries(EVENT_TYPE_LABELS).map(([value, text]) => ({ text, value })), onFilter: (value, record) => record.event_type === value, }, { title: '时间', dataIndex: 'event_time', key: 'event_time', width: 180, render: (time: string) => new Date(time).toLocaleString('zh-CN'), sorter: (a, b) => new Date(a.event_time).getTime() - new Date(b.event_time).getTime(), }, ]; return ( } title="我的天体" description="查看已关注天体及其相关天象事件"> 关注列表 {bodies.length} } extra={} style={{ height: 520 }} > {bodies.length === 0 && !loading ? (
在可视化首页点击天体,进入详情后即可关注
) : (
{bodies.map((body) => (
void handleSelectBody(body)} >
{body.name_zh || body.name} {BODY_TYPE_LABELS[body.type] || body.type}
{body.followed_at ? `关注于 ${new Date(body.followed_at).toLocaleDateString('zh-CN')}` : body.name}
))}
)}
{selectedBody.name_zh || selectedBody.name} {BODY_TYPE_LABELS[selectedBody.type] || selectedBody.type} ) : '天体资料' } > {selectedBody ? ( {selectedBody.id} {BODY_TYPE_LABELS[selectedBody.type] || selectedBody.type} {selectedBody.name_zh || '-'} {selectedBody.name} {selectedBody.is_active ? '活跃' : '已归档'} {selectedBody.followed_at ? new Date(selectedBody.followed_at).toLocaleString('zh-CN') : '-'} ) : ( )} `共 ${count} 条` }} locale={{ emptyText: , }} expandable={{ expandedRowRender: (record) => (
描述:{record.description || '-'}
{record.details ? (
{JSON.stringify(record.details, null, 2)}
) : null}
), }} /> ); }