cosmo/frontend/src/pages/admin/StaticData.tsx

231 lines
6.2 KiB
TypeScript

/**
* Static Data Management Page
*/
import { useState, useEffect } from 'react';
import { Modal, Form, Input, Select } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { DataTable } from '../../components/admin/DataTable';
import { request } from '../../utils/request';
import { useToast } from '../../contexts/ToastContext';
interface StaticDataItem {
id: number;
category: string;
name: string;
name_zh: string;
data: any;
}
export function StaticData() {
const [loading, setLoading] = useState(false);
const [data, setData] = useState<StaticDataItem[]>([]);
const [filteredData, setFilteredData] = useState<StaticDataItem[]>([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingRecord, setEditingRecord] = useState<StaticDataItem | null>(null);
const [form] = Form.useForm();
const toast = useToast();
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
setLoading(true);
try {
const { data: result } = await request.get('/celestial/static/list');
setData(result.items || []);
setFilteredData(result.items || []);
} catch (error) {
toast.error('加载数据失败');
} finally {
setLoading(false);
}
};
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.category.toLowerCase().includes(lowerKeyword)
);
setFilteredData(filtered);
};
const handleAdd = () => {
setEditingRecord(null);
form.resetFields();
form.setFieldsValue({ category: 'star' });
setIsModalOpen(true);
};
const handleEdit = (record: StaticDataItem) => {
setEditingRecord(record);
// Convert JSON data to string for editing
form.setFieldsValue({
...record,
data: JSON.stringify(record.data, null, 2)
});
setIsModalOpen(true);
};
const handleDelete = async (record: StaticDataItem) => {
try {
await request.delete(`/celestial/static/${record.id}`);
toast.success('删除成功');
loadData();
} catch (error) {
toast.error('删除失败');
}
};
const handleModalOk = async () => {
try {
const values = await form.validateFields();
// Parse JSON data
try {
values.data = JSON.parse(values.data);
} catch (e) {
toast.error('JSON格式错误');
return;
}
if (editingRecord) {
await request.put(`/celestial/static/${editingRecord.id}`, values);
toast.success('更新成功');
} else {
await request.post('/celestial/static', values);
toast.success('创建成功');
}
setIsModalOpen(false);
loadData();
} catch (error) {
console.error(error);
}
};
const columns: ColumnsType<StaticDataItem> = [
{
title: 'ID',
dataIndex: 'id',
width: 80,
sorter: (a, b) => a.id - b.id,
},
{
title: '分类',
dataIndex: 'category',
width: 120,
filters: [
{ text: '恒星', value: 'star' },
{ text: '星座', value: 'constellation' },
{ text: '星系', value: 'galaxy' },
{ text: '星云', value: 'nebula' },
{ text: '小行星带', value: 'asteroid_belt' },
{ text: '柯伊伯带', value: 'kuiper_belt' },
{ text: '恒星际', value: 'interstellar' },
],
onFilter: (value, record) => record.category === value,
},
{
title: '英文名',
dataIndex: 'name',
sorter: (a, b) => a.name.localeCompare(b.name),
},
{
title: '中文名',
dataIndex: 'name_zh',
},
{
title: '数据 (JSON)',
dataIndex: 'data',
ellipsis: true,
render: (text) => JSON.stringify(text),
},
];
return (
<>
<DataTable
title="静态数据管理"
columns={columns}
dataSource={filteredData}
loading={loading}
total={filteredData.length}
onSearch={handleSearch}
onAdd={handleAdd}
onEdit={handleEdit}
onDelete={handleDelete}
rowKey="id"
pageSize={10}
/>
<Modal
title={editingRecord ? '编辑静态数据' : '新增静态数据'}
open={isModalOpen}
onOk={handleModalOk}
onCancel={() => setIsModalOpen(false)}
width={700}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name="category"
label="分类"
rules={[{ required: true }]}
>
<Select>
<Select.Option value="star"> (Star)</Select.Option>
<Select.Option value="constellation"> (Constellation)</Select.Option>
<Select.Option value="galaxy"> (Galaxy)</Select.Option>
<Select.Option value="nebula"> (Nebula)</Select.Option>
<Select.Option value="asteroid_belt"> (Asteroid Belt)</Select.Option>
<Select.Option value="kuiper_belt"> (Kuiper Belt)</Select.Option>
<Select.Option value="interstellar"> (Interstellar)</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="name"
label="英文名"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="name_zh"
label="中文名"
>
<Input />
</Form.Item>
<Form.Item
name="data"
label="JSON数据"
rules={[
{ required: true, message: '请输入JSON数据' },
{
validator: (_, value) => {
try {
JSON.parse(value);
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error('无效的JSON格式'));
}
}
}
]}
>
<Input.TextArea rows={15} className="font-mono" />
</Form.Item>
</Form>
</Modal>
</>
);
}