2025-11-29 15:10:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Celestial Bodies Management Page
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { useState, useEffect } from 'react';
|
2025-11-30 02:43:47 +00:00
|
|
|
|
import { message, Modal, Form, Input, Select, Switch, InputNumber } from 'antd';
|
2025-11-29 15:10:00 +00:00
|
|
|
|
import type { ColumnsType } from 'antd/es/table';
|
2025-11-30 02:43:47 +00:00
|
|
|
|
import { DataTable } from '../../components/admin/DataTable';
|
2025-11-29 15:10:00 +00:00
|
|
|
|
import { request } from '../../utils/request';
|
|
|
|
|
|
|
|
|
|
|
|
interface CelestialBody {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
name_zh: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
description: string;
|
2025-11-30 02:43:47 +00:00
|
|
|
|
is_active: boolean;
|
2025-11-29 15:10:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function CelestialBodies() {
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [data, setData] = useState<CelestialBody[]>([]);
|
2025-11-30 02:43:47 +00:00
|
|
|
|
const [filteredData, setFilteredData] = useState<CelestialBody[]>([]);
|
|
|
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
|
|
const [editingRecord, setEditingRecord] = useState<CelestialBody | null>(null);
|
|
|
|
|
|
const [form] = Form.useForm();
|
2025-11-29 15:10:00 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
loadData();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const loadData = async () => {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data: result } = await request.get('/celestial/list');
|
|
|
|
|
|
setData(result.bodies || []);
|
2025-11-30 02:43:47 +00:00
|
|
|
|
setFilteredData(result.bodies || []);
|
2025-11-29 15:10:00 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('加载数据失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-30 02:43:47 +00:00
|
|
|
|
// Search handler
|
|
|
|
|
|
const handleSearch = (keyword: string) => {
|
|
|
|
|
|
const lowerKeyword = keyword.toLowerCase();
|
|
|
|
|
|
const filtered = data.filter(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
item.name.toLowerCase().includes(lowerKeyword) ||
|
|
|
|
|
|
item.name_zh?.toLowerCase().includes(lowerKeyword) ||
|
|
|
|
|
|
item.id.includes(lowerKeyword)
|
|
|
|
|
|
);
|
|
|
|
|
|
setFilteredData(filtered);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Add handler
|
|
|
|
|
|
const handleAdd = () => {
|
|
|
|
|
|
setEditingRecord(null);
|
|
|
|
|
|
form.resetFields();
|
|
|
|
|
|
// Default values
|
|
|
|
|
|
form.setFieldsValue({ is_active: true, type: 'probe' });
|
|
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Edit handler
|
|
|
|
|
|
const handleEdit = (record: CelestialBody) => {
|
|
|
|
|
|
setEditingRecord(record);
|
|
|
|
|
|
form.setFieldsValue(record);
|
|
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Delete handler
|
|
|
|
|
|
const handleDelete = async (record: CelestialBody) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request.delete(`/celestial/${record.id}`);
|
|
|
|
|
|
message.success('删除成功');
|
|
|
|
|
|
loadData();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('删除失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Status change handler
|
|
|
|
|
|
const handleStatusChange = async (record: CelestialBody, checked: boolean) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await request.put(`/celestial/${record.id}`, { is_active: checked });
|
|
|
|
|
|
message.success(`状态更新成功`);
|
|
|
|
|
|
// Update local state to avoid full reload
|
|
|
|
|
|
const newData = data.map(item =>
|
|
|
|
|
|
item.id === record.id ? { ...item, is_active: checked } : item
|
|
|
|
|
|
);
|
|
|
|
|
|
setData(newData);
|
|
|
|
|
|
setFilteredData(newData); // Should re-filter if needed, but simplistic here
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('状态更新失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Form submit
|
|
|
|
|
|
const handleModalOk = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const values = await form.validateFields();
|
|
|
|
|
|
|
|
|
|
|
|
if (editingRecord) {
|
|
|
|
|
|
// Update
|
|
|
|
|
|
await request.put(`/celestial/${editingRecord.id}`, values);
|
|
|
|
|
|
message.success('更新成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Create
|
|
|
|
|
|
await request.post('/celestial/', values);
|
|
|
|
|
|
message.success('创建成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setIsModalOpen(false);
|
|
|
|
|
|
loadData();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(error);
|
|
|
|
|
|
// message.error('操作失败'); // request interceptor might already handle this
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-29 15:10:00 +00:00
|
|
|
|
const columns: ColumnsType<CelestialBody> = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: 'ID',
|
|
|
|
|
|
dataIndex: 'id',
|
|
|
|
|
|
key: 'id',
|
|
|
|
|
|
width: 100,
|
2025-11-30 02:43:47 +00:00
|
|
|
|
sorter: (a, b) => a.id.localeCompare(b.id),
|
2025-11-29 15:10:00 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '英文名',
|
|
|
|
|
|
dataIndex: 'name',
|
|
|
|
|
|
key: 'name',
|
2025-11-30 02:43:47 +00:00
|
|
|
|
sorter: (a, b) => a.name.localeCompare(b.name),
|
2025-11-29 15:10:00 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '中文名',
|
|
|
|
|
|
dataIndex: 'name_zh',
|
|
|
|
|
|
key: 'name_zh',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '类型',
|
|
|
|
|
|
dataIndex: 'type',
|
|
|
|
|
|
key: 'type',
|
2025-11-30 02:43:47 +00:00
|
|
|
|
filters: [
|
|
|
|
|
|
{ text: '行星', value: 'planet' },
|
|
|
|
|
|
{ text: '恒星', value: 'star' },
|
|
|
|
|
|
{ text: '卫星', value: 'satellite' },
|
|
|
|
|
|
{ text: '探测器', value: 'probe' },
|
|
|
|
|
|
{ text: '矮行星', value: 'dwarf_planet' },
|
|
|
|
|
|
],
|
|
|
|
|
|
onFilter: (value, record) => record.type === value,
|
2025-11-29 15:10:00 +00:00
|
|
|
|
render: (type: string) => {
|
|
|
|
|
|
const typeMap: Record<string, string> = {
|
|
|
|
|
|
star: '恒星',
|
|
|
|
|
|
planet: '行星',
|
|
|
|
|
|
dwarf_planet: '矮行星',
|
2025-11-30 02:43:47 +00:00
|
|
|
|
satellite: '卫星',
|
2025-11-29 15:10:00 +00:00
|
|
|
|
probe: '探测器',
|
|
|
|
|
|
};
|
|
|
|
|
|
return typeMap[type] || type;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '描述',
|
|
|
|
|
|
dataIndex: 'description',
|
|
|
|
|
|
key: 'description',
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2025-11-30 02:43:47 +00:00
|
|
|
|
<>
|
|
|
|
|
|
<DataTable
|
|
|
|
|
|
title="天体数据管理"
|
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}
|
|
|
|
|
|
onSearch={handleSearch}
|
|
|
|
|
|
onAdd={handleAdd}
|
|
|
|
|
|
onEdit={handleEdit}
|
|
|
|
|
|
onDelete={handleDelete}
|
|
|
|
|
|
onStatusChange={handleStatusChange}
|
|
|
|
|
|
statusField="is_active"
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
pageSize={10}
|
2025-11-29 15:10:00 +00:00
|
|
|
|
/>
|
2025-11-30 02:43:47 +00:00
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title={editingRecord ? '编辑天体' : '新增天体'}
|
|
|
|
|
|
open={isModalOpen}
|
|
|
|
|
|
onOk={handleModalOk}
|
|
|
|
|
|
onCancel={() => setIsModalOpen(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form
|
|
|
|
|
|
form={form}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="id"
|
|
|
|
|
|
label="JPL ID"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入JPL Horizons ID' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input disabled={!!editingRecord} placeholder="例如:-31 (Voyager 1) 或 399 (Earth)" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="name"
|
|
|
|
|
|
label="英文名"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入英文名' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="例如:Voyager 1" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="name_zh"
|
|
|
|
|
|
label="中文名"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="例如:旅行者1号" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="type"
|
|
|
|
|
|
label="类型"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择类型' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Select.Option value="planet">行星</Select.Option>
|
|
|
|
|
|
<Select.Option value="dwarf_planet">矮行星</Select.Option>
|
|
|
|
|
|
<Select.Option value="satellite">卫星</Select.Option>
|
|
|
|
|
|
<Select.Option value="probe">探测器</Select.Option>
|
|
|
|
|
|
<Select.Option value="star">恒星</Select.Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="is_active"
|
|
|
|
|
|
label="状态"
|
|
|
|
|
|
valuePropName="checked"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Switch checkedChildren="启用" unCheckedChildren="禁用" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="description"
|
|
|
|
|
|
label="描述"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.TextArea rows={4} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
</>
|
2025-11-29 15:10:00 +00:00
|
|
|
|
);
|
2025-11-30 02:43:47 +00:00
|
|
|
|
}
|