2026-07-21 12:09:51 +00:00
|
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
2026-09-20 05:48:43 +00:00
|
|
|
|
import { Badge, Button, Form, Modal, Popconfirm, Select, Space, Tag, Tooltip } from 'antd';
|
|
|
|
|
|
import { GlobalOutlined, StarOutlined } from '@ant-design/icons';
|
2025-12-01 08:52:04 +00:00
|
|
|
|
import type { ColumnsType } from 'antd/es/table';
|
2025-12-04 12:37:10 +00:00
|
|
|
|
|
2025-11-30 02:43:47 +00:00
|
|
|
|
import { DataTable } from '../../components/admin/DataTable';
|
2026-09-20 05:48:43 +00:00
|
|
|
|
import { AdminPage } from '../../components/admin/AdminPage';
|
|
|
|
|
|
import { useListPageSize } from './useListPageSize';
|
2025-12-01 08:52:04 +00:00
|
|
|
|
import { useToast } from '../../contexts/ToastContext';
|
2026-07-21 12:09:51 +00:00
|
|
|
|
import { request } from '../../utils/request';
|
|
|
|
|
|
import { CelestialBodyModal } from './celestial-bodies/CelestialBodyModal';
|
|
|
|
|
|
import type { CelestialBody, StarSystem } from './celestial-bodies/types';
|
|
|
|
|
|
|
|
|
|
|
|
const bodyTypeLabels: Record<string, string> = {
|
|
|
|
|
|
star: '恒星',
|
|
|
|
|
|
planet: '行星',
|
|
|
|
|
|
dwarf_planet: '矮行星',
|
|
|
|
|
|
satellite: '卫星',
|
|
|
|
|
|
comet: '彗星',
|
|
|
|
|
|
probe: '探测器',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function getErrorDetail(error: unknown, fallback: string) {
|
|
|
|
|
|
if (typeof error !== 'object' || error === null || !('response' in error)) return fallback;
|
|
|
|
|
|
const response = (error as { response?: { data?: { detail?: string } } }).response;
|
|
|
|
|
|
return response?.data?.detail || fallback;
|
2025-11-29 15:10:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
function filterBodies(items: CelestialBody[], searchTerm: string) {
|
|
|
|
|
|
const normalized = searchTerm.toLowerCase();
|
|
|
|
|
|
return items.filter((item) =>
|
|
|
|
|
|
item.name.toLowerCase().includes(normalized)
|
|
|
|
|
|
|| item.name_zh?.toLowerCase().includes(normalized)
|
|
|
|
|
|
|| item.id.toLowerCase().includes(normalized)
|
|
|
|
|
|
);
|
2025-12-06 09:06:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-29 15:10:00 +00:00
|
|
|
|
export function CelestialBodies() {
|
2026-09-20 05:48:43 +00:00
|
|
|
|
// 每页数量由系统参数 page_size 控制
|
|
|
|
|
|
const systemPageSize = useListPageSize();
|
2025-11-29 15:10:00 +00:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [data, setData] = useState<CelestialBody[]>([]);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const [keyword, setKeyword] = useState('');
|
2025-12-06 09:06:39 +00:00
|
|
|
|
const [starSystems, setStarSystems] = useState<StarSystem[]>([]);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const [selectedSystemId, setSelectedSystemId] = useState<number | null>(1);
|
2025-11-30 02:43:47 +00:00
|
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
|
|
const [editingRecord, setEditingRecord] = useState<CelestialBody | null>(null);
|
2025-11-30 05:25:41 +00:00
|
|
|
|
const [searching, setSearching] = useState(false);
|
|
|
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
2025-11-30 15:04:04 +00:00
|
|
|
|
const [uploading, setUploading] = useState(false);
|
|
|
|
|
|
const [refreshResources, setRefreshResources] = useState(0);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const [form] = Form.useForm();
|
2025-12-01 08:52:04 +00:00
|
|
|
|
const toast = useToast();
|
2025-11-29 15:10:00 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const filteredData = useMemo(() => filterBodies(data, keyword), [data, keyword]);
|
2025-11-29 15:10:00 +00:00
|
|
|
|
|
2025-12-06 09:06:39 +00:00
|
|
|
|
useEffect(() => {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
request.get('/star-systems', { params: { limit: 1000 } })
|
|
|
|
|
|
.then(({ data: result }) => setStarSystems(result.systems || []))
|
|
|
|
|
|
.catch(() => toast.error('加载恒星系统列表失败'));
|
|
|
|
|
|
}, [toast]);
|
2025-12-06 09:06:39 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
useEffect(() => {
|
2025-12-06 09:06:39 +00:00
|
|
|
|
if (selectedSystemId === null) {
|
|
|
|
|
|
setData([]);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
setLoading(true);
|
|
|
|
|
|
request.get('/celestial/list', { params: { system_id: selectedSystemId } })
|
|
|
|
|
|
.then(({ data: result }) => {
|
|
|
|
|
|
const bodies = result.bodies || [];
|
|
|
|
|
|
setData(bodies);
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => toast.error('加载数据失败'))
|
|
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
|
|
}, [selectedSystemId, toast]);
|
|
|
|
|
|
|
|
|
|
|
|
const reloadData = async () => {
|
|
|
|
|
|
if (selectedSystemId === null) return;
|
2025-11-29 15:10:00 +00:00
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const { data: result } = await request.get('/celestial/list', { params: { system_id: selectedSystemId } });
|
|
|
|
|
|
const bodies = result.bodies || [];
|
|
|
|
|
|
setData(bodies);
|
|
|
|
|
|
} catch {
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.error('加载数据失败');
|
2025-11-29 15:10:00 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const handleSearch = (searchTerm: string) => {
|
|
|
|
|
|
setKeyword(searchTerm);
|
2025-11-30 02:43:47 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
|
|
|
setEditingRecord(null);
|
2025-11-30 05:25:41 +00:00
|
|
|
|
setSearchQuery('');
|
2026-07-21 12:09:51 +00:00
|
|
|
|
form.resetFields();
|
|
|
|
|
|
form.setFieldsValue({ is_active: true, type: 'probe', system_id: selectedSystemId });
|
2025-11-30 02:43:47 +00:00
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const handleNasaSearch = async () => {
|
2025-11-30 05:25:41 +00:00
|
|
|
|
if (!searchQuery.trim()) {
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.warning('请输入天体名称或ID');
|
2025-11-30 05:25:41 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setSearching(true);
|
|
|
|
|
|
try {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const { data: result } = await request.get('/celestial/search', { params: { name: searchQuery } });
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
toast.error(result.error || '查询失败');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-12-04 12:37:10 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const existingBody = data.find((body) => body.id === result.data.id);
|
|
|
|
|
|
if (existingBody) {
|
|
|
|
|
|
Modal.warning({
|
|
|
|
|
|
title: '天体已存在',
|
|
|
|
|
|
content: (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p>找到天体: <strong>{result.data.full_name}</strong></p>
|
|
|
|
|
|
<p>ID: <strong>{result.data.id}</strong></p>
|
|
|
|
|
|
<p style={{ color: '#faad14', marginTop: 10 }}>该天体已在数据库中,名称为: <strong>{existingBody.name}</strong></p>
|
|
|
|
|
|
<p style={{ fontSize: 12, color: '#888' }}>如需修改,请在列表中直接编辑该天体。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
2025-11-30 05:25:41 +00:00
|
|
|
|
});
|
2026-07-21 12:09:51 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-30 05:25:41 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
form.setFieldsValue({ id: result.data.id, name: result.data.name });
|
|
|
|
|
|
if (/^-?\d+$/.test(result.data.id)) {
|
|
|
|
|
|
toast.success(`找到天体: ${result.data.full_name}`);
|
2025-11-30 05:25:41 +00:00
|
|
|
|
} else {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
Modal.warning({
|
|
|
|
|
|
title: '找到天体,但请确认 ID',
|
|
|
|
|
|
content: (
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<p>找到天体: <strong>{result.data.full_name}</strong></p>
|
|
|
|
|
|
<p>自动填充的 ID 为: <strong>{result.data.id}</strong></p>
|
|
|
|
|
|
<p style={{ color: '#faad14' }}>建议手动确认数字 ID,以便后续查询位置数据。</p>
|
|
|
|
|
|
<p style={{ fontSize: 12, color: '#888' }}>
|
|
|
|
|
|
可在 <a href="https://ssd.jpl.nasa.gov/horizons/" target="_blank" rel="noopener noreferrer">NASA Horizons</a> 查询准确 ID。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
|
|
|
|
|
});
|
2025-11-30 05:25:41 +00:00
|
|
|
|
}
|
2026-07-21 12:09:51 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast.error(getErrorDetail(error, '查询失败'));
|
2025-11-30 05:25:41 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setSearching(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-28 02:35:34 +00:00
|
|
|
|
const handleEdit = async (record: CelestialBody) => {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
form.resetFields();
|
2025-11-30 02:43:47 +00:00
|
|
|
|
setEditingRecord(record);
|
2025-12-10 08:49:16 +00:00
|
|
|
|
|
|
|
|
|
|
let extraData = record.extra_data;
|
|
|
|
|
|
if (typeof extraData === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
extraData = JSON.parse(extraData);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
} catch {
|
2025-12-10 08:49:16 +00:00
|
|
|
|
extraData = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-28 02:35:34 +00:00
|
|
|
|
let orbitInfo = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data: orbitData } = await request.get(`/celestial/orbits/${record.id}`);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
if (orbitData) orbitInfo = { num_points: orbitData.num_points, period_days: orbitData.period_days };
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
orbitInfo = null;
|
2025-12-28 02:35:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
form.setFieldsValue({ ...record, extra_data: extraData || {}, orbit_info: orbitInfo });
|
2025-11-30 02:43:47 +00:00
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleDelete = async (record: CelestialBody) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request.delete(`/celestial/${record.id}`);
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.success('删除成功');
|
2026-07-21 12:09:51 +00:00
|
|
|
|
await reloadData();
|
|
|
|
|
|
} catch {
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.error('删除失败');
|
2025-11-30 02:43:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleStatusChange = async (record: CelestialBody, checked: boolean) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request.put(`/celestial/${record.id}`, { is_active: checked });
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const updated = data.map((item) => item.id === record.id ? { ...item, is_active: checked } : item);
|
|
|
|
|
|
setData(updated);
|
|
|
|
|
|
toast.success('状态更新成功');
|
|
|
|
|
|
} catch {
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.error('状态更新失败');
|
2025-11-30 02:43:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleModalOk = async () => {
|
|
|
|
|
|
try {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const values = await form.validateFields();
|
2025-11-30 02:43:47 +00:00
|
|
|
|
if (editingRecord) {
|
|
|
|
|
|
await request.put(`/celestial/${editingRecord.id}`, values);
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.success('更新成功');
|
2025-11-30 02:43:47 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
await request.post('/celestial/', values);
|
2025-12-01 08:52:04 +00:00
|
|
|
|
toast.success('创建成功');
|
2025-11-30 02:43:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
setIsModalOpen(false);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
await reloadData();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (typeof error === 'object' && error !== null && 'errorFields' in error) {
|
2025-12-04 12:37:10 +00:00
|
|
|
|
toast.error('请填写所有必填字段');
|
|
|
|
|
|
} else {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
toast.error(getErrorDetail(error, '操作失败'));
|
2025-12-04 12:37:10 +00:00
|
|
|
|
}
|
2025-11-30 02:43:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-30 15:04:04 +00:00
|
|
|
|
const handleResourceUpload = async (file: File, resourceType: string) => {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
if (!editingRecord) return false;
|
2025-11-30 15:04:04 +00:00
|
|
|
|
setUploading(true);
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append('file', file);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await request.post(
|
|
|
|
|
|
`/celestial/resources/upload?body_id=${editingRecord.id}&resource_type=${resourceType}`,
|
|
|
|
|
|
formData,
|
2026-07-21 12:09:51 +00:00
|
|
|
|
{ headers: { 'Content-Type': 'multipart/form-data' } },
|
2025-11-30 15:04:04 +00:00
|
|
|
|
);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
toast.success(response.data.message);
|
|
|
|
|
|
setRefreshResources((value) => value + 1);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast.error(getErrorDetail(error, '上传失败'));
|
2025-11-30 15:04:04 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setUploading(false);
|
|
|
|
|
|
}
|
2026-07-21 12:09:51 +00:00
|
|
|
|
return false;
|
2025-11-30 15:04:04 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const handleResourceDelete = async (resourceId: number) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request.delete(`/celestial/resources/${resourceId}`);
|
|
|
|
|
|
toast.success('删除成功');
|
|
|
|
|
|
setRefreshResources((value) => value + 1);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast.error(getErrorDetail(error, '删除失败'));
|
2025-12-10 08:49:16 +00:00
|
|
|
|
}
|
2026-07-21 12:09:51 +00:00
|
|
|
|
};
|
2025-12-10 08:49:16 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const handleGenerateOrbit = async (record: CelestialBody) => {
|
|
|
|
|
|
if (!['planet', 'dwarf_planet'].includes(record.type)) return;
|
2025-12-10 08:49:16 +00:00
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
await request.post(`/celestial/admin/orbits/generate?body_ids=${record.id}`);
|
2025-12-11 08:31:26 +00:00
|
|
|
|
toast.success('轨道生成任务已启动,请前往"系统任务"查看进度', 5000);
|
2026-07-21 12:09:51 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
toast.error(getErrorDetail(error, '轨道生成任务启动失败'));
|
2025-12-10 08:49:16 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-29 15:10:00 +00:00
|
|
|
|
const columns: ColumnsType<CelestialBody> = [
|
2026-07-21 12:09:51 +00:00
|
|
|
|
{ title: 'ID', dataIndex: 'id', key: 'id', width: 100, sorter: (a, b) => a.id.localeCompare(b.id) },
|
|
|
|
|
|
{ title: '英文名', dataIndex: 'name', key: 'name', sorter: (a, b) => a.name.localeCompare(b.name) },
|
|
|
|
|
|
{ title: '中文名', dataIndex: 'name_zh', key: 'name_zh' },
|
2025-12-06 09:06:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
title: '所属系统',
|
|
|
|
|
|
dataIndex: 'system_id',
|
|
|
|
|
|
key: 'system_id',
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
render: (systemId: number) => {
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const system = starSystems.find((item) => item.id === systemId);
|
|
|
|
|
|
return system ? <Tag color="blue" icon={<StarOutlined />}>{system.name_zh || system.name}</Tag> : '-';
|
2025-12-06 09:06:39 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-11-29 15:10:00 +00:00
|
|
|
|
{
|
|
|
|
|
|
title: '类型',
|
|
|
|
|
|
dataIndex: 'type',
|
|
|
|
|
|
key: 'type',
|
2026-07-21 12:09:51 +00:00
|
|
|
|
filters: Object.entries(bodyTypeLabels).map(([value, text]) => ({ text, value })),
|
2025-11-30 02:43:47 +00:00
|
|
|
|
onFilter: (value, record) => record.type === value,
|
2026-07-21 12:09:51 +00:00
|
|
|
|
render: (type: string) => bodyTypeLabels[type] || type,
|
2025-11-29 15:10:00 +00:00
|
|
|
|
},
|
2026-07-21 12:09:51 +00:00
|
|
|
|
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
2025-11-30 05:25:41 +00:00
|
|
|
|
{
|
|
|
|
|
|
title: '资源配置',
|
|
|
|
|
|
key: 'resources',
|
|
|
|
|
|
width: 120,
|
2026-07-21 12:09:51 +00:00
|
|
|
|
render: (_, record) => record.has_resources
|
|
|
|
|
|
? <Badge status="success" text={`${Object.keys(record.resources || {}).length} 类`} />
|
|
|
|
|
|
: <Badge status="default" text="未配置" />,
|
2025-11-30 05:25:41 +00:00
|
|
|
|
},
|
2025-11-29 15:10:00 +00:00
|
|
|
|
];
|
|
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
const selectedSystem = starSystems.find((system) => system.id === selectedSystemId);
|
|
|
|
|
|
|
2025-11-29 15:10:00 +00:00
|
|
|
|
return (
|
2026-09-20 05:48:43 +00:00
|
|
|
|
<AdminPage
|
|
|
|
|
|
icon={<GlobalOutlined />}
|
|
|
|
|
|
title="天体数据管理" description="按恒星系统维护天体基础信息、显示资源与轨道数据">
|
2025-11-30 02:43:47 +00:00
|
|
|
|
<DataTable
|
2025-11-29 15:10:00 +00:00
|
|
|
|
columns={columns}
|
2025-11-30 02:43:47 +00:00
|
|
|
|
dataSource={filteredData}
|
2025-11-29 15:10:00 +00:00
|
|
|
|
loading={loading}
|
2025-11-30 02:43:47 +00:00
|
|
|
|
total={filteredData.length}
|
2026-09-20 05:48:43 +00:00
|
|
|
|
onRefresh={() => void reloadData()}
|
2025-11-30 02:43:47 +00:00
|
|
|
|
onSearch={handleSearch}
|
2026-09-20 05:48:43 +00:00
|
|
|
|
searchPlaceholder="搜索 ID / 英文名 / 中文名"
|
2025-11-30 02:43:47 +00:00
|
|
|
|
onAdd={handleAdd}
|
2026-09-20 05:48:43 +00:00
|
|
|
|
addText="新增天体"
|
2025-11-30 02:43:47 +00:00
|
|
|
|
onEdit={handleEdit}
|
|
|
|
|
|
onDelete={handleDelete}
|
2026-09-20 05:48:43 +00:00
|
|
|
|
deleteConfirmTitle="确认删除该天体?"
|
|
|
|
|
|
deleteConfirmDescription="删除后该天体的位置与资源数据将不可用"
|
2025-11-30 02:43:47 +00:00
|
|
|
|
onStatusChange={handleStatusChange}
|
|
|
|
|
|
statusField="is_active"
|
|
|
|
|
|
rowKey="id"
|
2026-09-20 05:48:43 +00:00
|
|
|
|
pageSize={systemPageSize}
|
|
|
|
|
|
toolbar={
|
|
|
|
|
|
<Space size={8}>
|
|
|
|
|
|
<StarOutlined style={{ color: 'var(--adm-primary)' }} />
|
|
|
|
|
|
<Select
|
|
|
|
|
|
showSearch
|
|
|
|
|
|
style={{ width: 260 }}
|
|
|
|
|
|
value={selectedSystemId}
|
|
|
|
|
|
onChange={setSelectedSystemId}
|
|
|
|
|
|
placeholder="选择恒星系统"
|
|
|
|
|
|
loading={starSystems.length === 0}
|
|
|
|
|
|
options={starSystems.map((system) => ({ value: system.id, label: system.name_zh || system.name }))}
|
|
|
|
|
|
filterOption={(input, option) => {
|
|
|
|
|
|
const system = starSystems.find((item) => item.id === option?.value);
|
|
|
|
|
|
const searchText = input.toLowerCase();
|
|
|
|
|
|
return Boolean(system && (
|
|
|
|
|
|
system.name.toLowerCase().includes(searchText)
|
|
|
|
|
|
|| system.name_zh?.toLowerCase().includes(searchText)
|
|
|
|
|
|
|| system.id.toString().includes(searchText)
|
|
|
|
|
|
));
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{selectedSystem ? (
|
|
|
|
|
|
<span className="adm-cell-sub">当前:{selectedSystem.name_zh || selectedSystem.name}</span>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
2025-12-10 08:49:16 +00:00
|
|
|
|
customActions={(record) => {
|
|
|
|
|
|
const canGenerateOrbit = ['planet', 'dwarf_planet'].includes(record.type);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Popconfirm
|
|
|
|
|
|
title="确认生成轨道"
|
2025-12-11 08:31:26 +00:00
|
|
|
|
description={`确定要为 ${record.name_zh || record.name} 生成轨道吗?`}
|
2025-12-10 08:49:16 +00:00
|
|
|
|
onConfirm={() => handleGenerateOrbit(record)}
|
|
|
|
|
|
okText="确认"
|
|
|
|
|
|
cancelText="取消"
|
|
|
|
|
|
disabled={!canGenerateOrbit}
|
|
|
|
|
|
>
|
2026-09-20 05:48:43 +00:00
|
|
|
|
<Tooltip title={canGenerateOrbit ? '生成轨道数据' : '仅行星和矮行星可生成轨道'}>
|
|
|
|
|
|
<Button type="text" size="small" disabled={!canGenerateOrbit}>
|
|
|
|
|
|
生成轨道
|
|
|
|
|
|
</Button>
|
2025-12-10 08:49:16 +00:00
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</Popconfirm>
|
|
|
|
|
|
);
|
|
|
|
|
|
}}
|
2025-11-29 15:10:00 +00:00
|
|
|
|
/>
|
2025-11-30 02:43:47 +00:00
|
|
|
|
|
2026-07-21 12:09:51 +00:00
|
|
|
|
<CelestialBodyModal
|
|
|
|
|
|
key={`${editingRecord?.id || 'new'}-${isModalOpen}`}
|
|
|
|
|
|
form={form}
|
|
|
|
|
|
record={editingRecord}
|
2025-11-30 02:43:47 +00:00
|
|
|
|
open={isModalOpen}
|
|
|
|
|
|
onOk={handleModalOk}
|
|
|
|
|
|
onCancel={() => setIsModalOpen(false)}
|
2026-07-21 12:09:51 +00:00
|
|
|
|
searchQuery={searchQuery}
|
|
|
|
|
|
onSearchQueryChange={setSearchQuery}
|
|
|
|
|
|
onNasaSearch={handleNasaSearch}
|
|
|
|
|
|
searching={searching}
|
|
|
|
|
|
uploading={uploading}
|
|
|
|
|
|
refreshResources={refreshResources}
|
|
|
|
|
|
onResourceUpload={handleResourceUpload}
|
|
|
|
|
|
onResourceDelete={handleResourceDelete}
|
|
|
|
|
|
toast={toast}
|
|
|
|
|
|
/>
|
2026-09-20 05:48:43 +00:00
|
|
|
|
</AdminPage>
|
2025-11-29 15:10:00 +00:00
|
|
|
|
);
|
2025-11-30 15:04:04 +00:00
|
|
|
|
}
|