refactor: Standardize toast notifications by replacing antd message with ToastContext
parent
241a6cb562
commit
e2a9052e57
|
|
@ -1,6 +1,5 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { message } from 'antd';
|
||||
import { useSpaceData } from './hooks/useSpaceData';
|
||||
import { useHistoricalData } from './hooks/useHistoricalData';
|
||||
import { useTrajectory } from './hooks/useTrajectory';
|
||||
|
|
@ -16,6 +15,7 @@ import { AuthModal } from './components/AuthModal';
|
|||
import { MessageBoard } from './components/MessageBoard';
|
||||
import { auth } from './utils/auth';
|
||||
import type { CelestialBody } from './types';
|
||||
import { useToast } from './contexts/ToastContext';
|
||||
|
||||
// Timeline configuration - will be fetched from backend later
|
||||
const TIMELINE_DAYS = 30; // Total days in timeline range
|
||||
|
|
@ -24,6 +24,7 @@ const PREFS_KEY = 'cosmo_preferences';
|
|||
|
||||
function App() {
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
|
||||
// Load preferences
|
||||
const [isTimelineMode, setIsTimelineMode] = useState(false); // Usually not persisted
|
||||
|
|
@ -102,7 +103,7 @@ function App() {
|
|||
// Screenshot handler with auth check
|
||||
const handleScreenshot = useCallback(() => {
|
||||
if (!user) {
|
||||
message.warning('请先登录以拍摄宇宙快照');
|
||||
toast.warning('请先登录以拍摄宇宙快照');
|
||||
setShowAuthModal(true);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { X, Ruler, Activity, Radar } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { Modal, message, Spin } from 'antd';
|
||||
import { request } from '../utils/request';
|
||||
import type { CelestialBody } from '../types';
|
||||
import { TerminalModal } from './TerminalModal';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
interface FocusInfoProps {
|
||||
body: CelestialBody | null;
|
||||
|
|
@ -10,6 +11,7 @@ interface FocusInfoProps {
|
|||
}
|
||||
|
||||
export function FocusInfo({ body, onClose }: FocusInfoProps) {
|
||||
const toast = useToast();
|
||||
const [showTerminal, setShowTerminal] = useState(false);
|
||||
const [terminalData, setTerminalData] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -31,7 +33,7 @@ export function FocusInfo({ body, onClose }: FocusInfoProps) {
|
|||
setTerminalData(data.raw_data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
message.error('连接 NASA Horizons 失败');
|
||||
toast.error('连接 NASA Horizons 失败');
|
||||
// If failed, maybe show error in terminal
|
||||
setTerminalData("CONNECTION FAILED.\n\nError establishing link with JPL Horizons System.\nCheck connection frequencies.");
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Input, Button, message } from 'antd';
|
||||
import { Input, Button } from 'antd';
|
||||
import { Send, MessageSquare } from 'lucide-react';
|
||||
import { TerminalModal } from './TerminalModal';
|
||||
import { request } from '../utils/request';
|
||||
import { auth } from '../utils/auth';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
|
|
@ -19,6 +20,7 @@ interface MessageBoardProps {
|
|||
}
|
||||
|
||||
export function MessageBoard({ open, onClose }: MessageBoardProps) {
|
||||
const toast = useToast();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -64,12 +66,12 @@ export function MessageBoard({ open, onClose }: MessageBoardProps) {
|
|||
|
||||
const user = auth.getUser();
|
||||
if (!user) {
|
||||
message.warning('请先登录');
|
||||
toast.warning('请先登录');
|
||||
return;
|
||||
}
|
||||
|
||||
if (content.length > 20) {
|
||||
message.warning('消息不能超过20字');
|
||||
toast.warning('消息不能超过20字');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +81,7 @@ export function MessageBoard({ open, onClose }: MessageBoardProps) {
|
|||
setInputValue('');
|
||||
await fetchMessages();
|
||||
} catch (err) {
|
||||
message.error('发送失败');
|
||||
toast.error('发送失败');
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ interface Toast {
|
|||
}
|
||||
|
||||
interface ToastContextValue {
|
||||
showToast: (message: string, type?: ToastType, duration?: number) => void;
|
||||
success: (message: string, duration?: number) => void;
|
||||
error: (message: string, duration?: number) => void;
|
||||
warning: (message: string, duration?: number) => void;
|
||||
info: (message: string, duration?: number) => void;
|
||||
showToast: (message: string, type?: ToastType, duration?: number) => string;
|
||||
success: (message: string, duration?: number) => string;
|
||||
error: (message: string, duration?: number) => string;
|
||||
warning: (message: string, duration?: number) => string;
|
||||
info: (message: string, duration?: number) => string;
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
// Context
|
||||
|
|
@ -72,6 +73,8 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|||
}, duration);
|
||||
timersRef.current.set(id, timer);
|
||||
}
|
||||
|
||||
return id;
|
||||
}, [removeToast]);
|
||||
|
||||
// Convenience methods
|
||||
|
|
@ -81,7 +84,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|||
const info = useCallback((msg: string, d?: number) => showToast(msg, 'info', d), [showToast]);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ showToast, success, error, warning, info }}>
|
||||
<ToastContext.Provider value={{ showToast, success, error, warning, info, removeToast }}>
|
||||
{children}
|
||||
|
||||
{/* Toast Container - Top Right */}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
import { useCallback } from 'react';
|
||||
import html2canvas from 'html2canvas';
|
||||
import { message } from 'antd';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
export function useScreenshot() {
|
||||
const toast = useToast();
|
||||
|
||||
const takeScreenshot = useCallback(async (username: string = 'Explorer') => {
|
||||
// 1. Find the container that includes both the Canvas and the HTML overlays (labels)
|
||||
const element = document.getElementById('cosmo-scene-container');
|
||||
if (!element) {
|
||||
console.error('Scene container not found');
|
||||
message.error('无法找到截图区域');
|
||||
toast.error('无法找到截图区域');
|
||||
return;
|
||||
}
|
||||
|
||||
const hideMessage = message.loading('正在生成宇宙快照...', 0);
|
||||
const toastId = toast.info('正在生成宇宙快照...', 0);
|
||||
|
||||
try {
|
||||
// 2. Use html2canvas to capture the visual composite
|
||||
|
|
@ -107,15 +109,15 @@ export function useScreenshot() {
|
|||
link.href = dataUrl;
|
||||
link.click();
|
||||
|
||||
message.success('宇宙快照已保存');
|
||||
toast.success('宇宙快照已保存');
|
||||
|
||||
} catch (err) {
|
||||
console.error('Screenshot failed:', err);
|
||||
message.error('截图失败,请稍后重试');
|
||||
toast.error('截图失败,请稍后重试');
|
||||
} finally {
|
||||
hideMessage();
|
||||
toast.removeToast(toastId);
|
||||
}
|
||||
}, []);
|
||||
}, [toast]);
|
||||
|
||||
return { takeScreenshot };
|
||||
}
|
||||
|
|
@ -3,30 +3,33 @@
|
|||
*/
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Form, Input, Button, Card, message } from 'antd';
|
||||
import { Form, Input, Button, Card } from 'antd';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { authAPI } from '../utils/request';
|
||||
import { auth } from '../utils/auth';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
|
||||
export function Login() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
const onFinish = async (values: { username: string; password: string }) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await authAPI.login(values.username, values.password);
|
||||
|
||||
|
||||
// Save token and user info
|
||||
auth.setToken(data.access_token);
|
||||
auth.setUser(data.user);
|
||||
|
||||
message.success('登录成功!');
|
||||
|
||||
// Redirect to admin dashboard
|
||||
|
||||
toast.success('登录成功!');
|
||||
|
||||
// Navigate to admin dashboard
|
||||
navigate('/admin');
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.detail || '登录失败,请检查用户名和密码');
|
||||
console.error('Login failed:', error);
|
||||
toast.error(error.response?.data?.detail || '登录失败,请检查用户名和密码');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Layout, Menu, Avatar, Dropdown, message } from 'antd';
|
||||
import { Layout, Menu, Avatar, Dropdown } from 'antd';
|
||||
import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
|
|
@ -21,6 +21,7 @@ import {
|
|||
import type { MenuProps } from 'antd';
|
||||
import { authAPI } from '../../utils/request';
|
||||
import { auth } from '../../utils/auth';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
const { Header, Sider, Content } = Layout;
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ export function AdminLayout() {
|
|||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const user = auth.getUser();
|
||||
const toast = useToast();
|
||||
|
||||
// Load menus from backend
|
||||
useEffect(() => {
|
||||
|
|
@ -54,7 +56,7 @@ export function AdminLayout() {
|
|||
const { data } = await authAPI.getMenus();
|
||||
setMenus(data);
|
||||
} catch (error) {
|
||||
message.error('加载菜单失败');
|
||||
toast.error('加载菜单失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -85,7 +87,7 @@ export function AdminLayout() {
|
|||
try {
|
||||
await authAPI.logout();
|
||||
auth.logout();
|
||||
message.success('登出成功');
|
||||
toast.success('登出成功');
|
||||
navigate('/login');
|
||||
} catch (error) {
|
||||
// Even if API fails, clear local auth
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
/**
|
||||
* Celestial Bodies Management Page
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { message, Modal, Form, Input, Select, Switch, InputNumber, Tag, Badge, Descriptions, Button, Space, Alert, Upload, Popconfirm, Row, Col } from 'antd';
|
||||
import { Modal, Form, Input, Select, Switch, InputNumber, Tag, Badge, Descriptions, Button, Space, Alert, Upload, Popconfirm, Row, Col } from 'antd';
|
||||
import { CheckCircleOutlined, CloseCircleOutlined, SearchOutlined, UploadOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DataTable } from '../../components/admin/DataTable';
|
||||
import { request } from '../../utils/request';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
interface CelestialBody {
|
||||
id: string;
|
||||
|
|
@ -38,6 +36,7 @@ export function CelestialBodies() {
|
|||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [refreshResources, setRefreshResources] = useState(0);
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
|
|
@ -50,7 +49,7 @@ export function CelestialBodies() {
|
|||
setData(result.bodies || []);
|
||||
setFilteredData(result.bodies || []);
|
||||
} catch (error) {
|
||||
message.error('加载数据失败');
|
||||
toast.error('加载数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -81,7 +80,7 @@ export function CelestialBodies() {
|
|||
// Search NASA Horizons by name
|
||||
const handleNASASearch = async () => {
|
||||
if (!searchQuery.trim()) {
|
||||
message.warning('请输入天体名称或ID');
|
||||
toast.warning('请输入天体名称或ID');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ export function CelestialBodies() {
|
|||
const isNumericId = /^-?\d+$/.test(result.data.id);
|
||||
|
||||
if (isNumericId) {
|
||||
message.success(`找到天体: ${result.data.full_name}`);
|
||||
toast.success(`找到天体: ${result.data.full_name}`);
|
||||
} else {
|
||||
// Warn user that ID might need manual correction
|
||||
Modal.warning({
|
||||
|
|
@ -122,10 +121,10 @@ export function CelestialBodies() {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
message.error(result.error || '查询失败');
|
||||
toast.error(result.error || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.detail || '查询失败');
|
||||
toast.error(error.response?.data?.detail || '查询失败');
|
||||
} finally {
|
||||
setSearching(false);
|
||||
}
|
||||
|
|
@ -142,10 +141,10 @@ export function CelestialBodies() {
|
|||
const handleDelete = async (record: CelestialBody) => {
|
||||
try {
|
||||
await request.delete(`/celestial/${record.id}`);
|
||||
message.success('删除成功');
|
||||
toast.success('删除成功');
|
||||
loadData();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
toast.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -153,7 +152,7 @@ export function CelestialBodies() {
|
|||
const handleStatusChange = async (record: CelestialBody, checked: boolean) => {
|
||||
try {
|
||||
await request.put(`/celestial/${record.id}`, { is_active: checked });
|
||||
message.success(`状态更新成功`);
|
||||
toast.success(`状态更新成功`);
|
||||
// Update local state to avoid full reload
|
||||
const newData = data.map(item =>
|
||||
item.id === record.id ? { ...item, is_active: checked } : item
|
||||
|
|
@ -161,7 +160,7 @@ export function CelestialBodies() {
|
|||
setData(newData);
|
||||
setFilteredData(newData); // Should re-filter if needed, but simplistic here
|
||||
} catch (error) {
|
||||
message.error('状态更新失败');
|
||||
toast.error('状态更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -173,25 +172,25 @@ export function CelestialBodies() {
|
|||
if (editingRecord) {
|
||||
// Update
|
||||
await request.put(`/celestial/${editingRecord.id}`, values);
|
||||
message.success('更新成功');
|
||||
toast.success('更新成功');
|
||||
} else {
|
||||
// Create
|
||||
await request.post('/celestial/', values);
|
||||
message.success('创建成功');
|
||||
toast.success('创建成功');
|
||||
}
|
||||
|
||||
setIsModalOpen(false);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// message.error('操作失败'); // request interceptor might already handle this
|
||||
// toast.error('操作失败'); // request interceptor might already handle this
|
||||
}
|
||||
};
|
||||
|
||||
// Handle resource upload
|
||||
const handleResourceUpload = async (file: File, resourceType: string) => {
|
||||
if (!editingRecord) {
|
||||
message.error('请先选择要编辑的天体');
|
||||
toast.error('请先选择要编辑的天体');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -210,11 +209,11 @@ export function CelestialBodies() {
|
|||
}
|
||||
);
|
||||
|
||||
message.success(`${response.data.message} (上传到 ${response.data.upload_directory} 目录)`);
|
||||
toast.success(`${response.data.message} (上传到 ${response.data.upload_directory} 目录)`);
|
||||
setRefreshResources(prev => prev + 1); // Trigger reload
|
||||
return false; // Prevent default upload behavior
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.detail || '上传失败');
|
||||
toast.error(error.response?.data?.detail || '上传失败');
|
||||
return false;
|
||||
} finally {
|
||||
setUploading(false);
|
||||
|
|
@ -225,10 +224,10 @@ export function CelestialBodies() {
|
|||
const handleResourceDelete = async (resourceId: number) => {
|
||||
try {
|
||||
await request.delete(`/celestial/resources/${resourceId}`);
|
||||
message.success('删除成功');
|
||||
toast.success('删除成功');
|
||||
setRefreshResources(prev => prev + 1); // Trigger reload
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.detail || '删除失败');
|
||||
toast.error(error.response?.data?.detail || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -433,6 +432,7 @@ export function CelestialBodies() {
|
|||
onDelete={handleResourceDelete}
|
||||
uploading={uploading}
|
||||
refreshTrigger={refreshResources}
|
||||
toast={toast}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
|
|
@ -451,6 +451,7 @@ function ResourceManager({
|
|||
onDelete,
|
||||
uploading,
|
||||
refreshTrigger,
|
||||
toast,
|
||||
}: {
|
||||
bodyId: string;
|
||||
bodyType: string;
|
||||
|
|
@ -460,6 +461,7 @@ function ResourceManager({
|
|||
onDelete: (resourceId: number) => Promise<void>;
|
||||
uploading: boolean;
|
||||
refreshTrigger: number;
|
||||
toast: any;
|
||||
}) {
|
||||
const [currentResources, setCurrentResources] = useState(resources);
|
||||
|
||||
|
|
@ -477,7 +479,7 @@ function ResourceManager({
|
|||
setCurrentResources(grouped);
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('加载资源列表失败');
|
||||
toast.error('加载资源列表失败');
|
||||
});
|
||||
}, [refreshTrigger, bodyId]);
|
||||
|
||||
|
|
@ -547,9 +549,9 @@ function ResourceManager({
|
|||
request.put(`/celestial/resources/${res.id}`, {
|
||||
extra_data: { ...res.extra_data, scale: newScale }
|
||||
}).then(() => {
|
||||
message.success('缩放参数已更新');
|
||||
toast.success('缩放参数已更新');
|
||||
}).catch(() => {
|
||||
message.error('更新失败');
|
||||
toast.error('更新失败');
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
/**
|
||||
* Dashboard Page
|
||||
*/
|
||||
import { Card, Row, Col, Statistic, message } from 'antd';
|
||||
import { Card, Row, Col, Statistic } from 'antd';
|
||||
import { GlobalOutlined, RocketOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { request } from '../../utils/request';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
export function Dashboard() {
|
||||
const [totalUsers, setTotalUsers] = useState<number | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserCount = async () => {
|
||||
|
|
@ -19,7 +21,7 @@ export function Dashboard() {
|
|||
setTotalUsers(response.data.total_users);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user count:', error);
|
||||
message.error('无法获取用户总数');
|
||||
toast.error('无法获取用户总数');
|
||||
setTotalUsers(0); // Set to 0 or handle error display
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
Checkbox,
|
||||
DatePicker,
|
||||
Button,
|
||||
message,
|
||||
Badge,
|
||||
Spin,
|
||||
Typography,
|
||||
|
|
@ -31,6 +30,7 @@ import type { Dayjs } from 'dayjs';
|
|||
import dayjs from 'dayjs';
|
||||
import isBetween from 'dayjs/plugin/isBetween';
|
||||
import { request } from '../../utils/request';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
// Extend dayjs with isBetween plugin
|
||||
dayjs.extend(isBetween);
|
||||
|
|
@ -63,6 +63,7 @@ export function NASADownload() {
|
|||
const [loadingDates, setLoadingDates] = useState(false);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] = useState({ current: 0, total: 0 });
|
||||
const toast = useToast();
|
||||
|
||||
// Type name mapping
|
||||
const typeNames: Record<string, string> = {
|
||||
|
|
@ -91,7 +92,7 @@ export function NASADownload() {
|
|||
const { data } = await request.get('/celestial/positions/download/bodies');
|
||||
setBodies(data.bodies || {});
|
||||
} catch (error) {
|
||||
message.error('加载天体列表失败');
|
||||
toast.error('加载天体列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -123,7 +124,7 @@ export function NASADownload() {
|
|||
|
||||
setAvailableDates(allDates);
|
||||
} catch (error) {
|
||||
message.error('加载数据状态失败');
|
||||
toast.error('加载数据状态失败');
|
||||
} finally {
|
||||
setLoadingDates(false);
|
||||
}
|
||||
|
|
@ -154,7 +155,7 @@ export function NASADownload() {
|
|||
|
||||
const handleDownload = async (selectedDate?: Dayjs) => {
|
||||
if (selectedBodies.length === 0) {
|
||||
message.warning('请先选择至少一个天体');
|
||||
toast.warning('请先选择至少一个天体');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +190,10 @@ export function NASADownload() {
|
|||
setDownloadProgress({ current: datesToDownload.length, total: datesToDownload.length });
|
||||
|
||||
if (data.total_success > 0) {
|
||||
message.success(`成功下载 ${data.total_success} 条数据${data.total_failed > 0 ? `,${data.total_failed} 条失败` : ''}`);
|
||||
toast.success(`成功下载 ${data.total_success} 条数据${data.total_failed > 0 ? `,${data.total_failed} 条失败` : ''}`);
|
||||
loadAvailableDates();
|
||||
} else {
|
||||
message.error('下载失败');
|
||||
toast.error('下载失败');
|
||||
}
|
||||
} else {
|
||||
// Async download for range
|
||||
|
|
@ -200,10 +201,10 @@ export function NASADownload() {
|
|||
body_ids: selectedBodies,
|
||||
dates: datesToDownload
|
||||
});
|
||||
message.success('后台下载任务已启动,请前往“系统任务”查看进度');
|
||||
toast.success('后台下载任务已启动,请前往“系统任务”查看进度');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('请求失败');
|
||||
toast.error('请求失败');
|
||||
} finally {
|
||||
setDownloading(false);
|
||||
setDownloadProgress({ current: 0, total: 0 });
|
||||
|
|
@ -240,17 +241,17 @@ export function NASADownload() {
|
|||
const inRange = date.isBetween(dateRange[0], dateRange[1], 'day', '[]');
|
||||
|
||||
if (!inRange) {
|
||||
message.warning('请选择在日期范围内的日期');
|
||||
toast.warning('请选择在日期范围内的日期');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasData) {
|
||||
message.info('该日期已有数据');
|
||||
toast.info('该日期已有数据');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedBodies.length === 0) {
|
||||
message.warning('请先选择天体');
|
||||
toast.warning('请先选择天体');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
* Static Data Management Page
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { message, Modal, Form, Input, Select } from 'antd';
|
||||
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;
|
||||
|
|
@ -22,6 +23,7 @@ export function StaticData() {
|
|||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [editingRecord, setEditingRecord] = useState<StaticDataItem | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
|
|
@ -34,7 +36,7 @@ export function StaticData() {
|
|||
setData(result.items || []);
|
||||
setFilteredData(result.items || []);
|
||||
} catch (error) {
|
||||
message.error('加载数据失败');
|
||||
toast.error('加载数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -71,10 +73,10 @@ export function StaticData() {
|
|||
const handleDelete = async (record: StaticDataItem) => {
|
||||
try {
|
||||
await request.delete(`/celestial/static/${record.id}`);
|
||||
message.success('删除成功');
|
||||
toast.success('删除成功');
|
||||
loadData();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
toast.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,16 +88,16 @@ export function StaticData() {
|
|||
try {
|
||||
values.data = JSON.parse(values.data);
|
||||
} catch (e) {
|
||||
message.error('JSON格式错误');
|
||||
toast.error('JSON格式错误');
|
||||
return;
|
||||
}
|
||||
|
||||
if (editingRecord) {
|
||||
await request.put(`/celestial/static/${editingRecord.id}`, values);
|
||||
message.success('更新成功');
|
||||
toast.success('更新成功');
|
||||
} else {
|
||||
await request.post('/celestial/static', values);
|
||||
message.success('创建成功');
|
||||
toast.success('创建成功');
|
||||
}
|
||||
|
||||
setIsModalOpen(false);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
* System Settings Management Page
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { message, Modal, Form, Input, InputNumber, Switch, Select, Button, Card, Descriptions, Badge, Space, Popconfirm, Alert, Divider } from 'antd';
|
||||
import { Modal, Form, Input, InputNumber, Switch, Select, Button, Card, Descriptions, Badge, Space, Popconfirm, Alert, Divider } from 'antd';
|
||||
import { ReloadOutlined, ClearOutlined, WarningOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DataTable } from '../../components/admin/DataTable';
|
||||
import { request } from '../../utils/request';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
interface SystemSetting {
|
||||
id: number;
|
||||
|
|
@ -38,6 +39,7 @@ export function SystemSettings() {
|
|||
const [editingRecord, setEditingRecord] = useState<SystemSetting | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [clearingCache, setClearingCache] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
|
|
@ -50,7 +52,7 @@ export function SystemSettings() {
|
|||
setData(result.settings || []);
|
||||
setFilteredData(result.settings || []);
|
||||
} catch (error) {
|
||||
message.error('加载数据失败');
|
||||
toast.error('加载数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -95,10 +97,10 @@ export function SystemSettings() {
|
|||
const handleDelete = async (record: SystemSetting) => {
|
||||
try {
|
||||
await request.delete(`/system/settings/${record.key}`);
|
||||
message.success('删除成功');
|
||||
toast.success('删除成功');
|
||||
loadData();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
toast.error('删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -110,11 +112,11 @@ export function SystemSettings() {
|
|||
if (editingRecord) {
|
||||
// Update
|
||||
await request.put(`/system/settings/${editingRecord.key}`, values);
|
||||
message.success('更新成功');
|
||||
toast.success('更新成功');
|
||||
} else {
|
||||
// Create
|
||||
await request.post('/system/settings', values);
|
||||
message.success('创建成功');
|
||||
toast.success('创建成功');
|
||||
}
|
||||
|
||||
setIsModalOpen(false);
|
||||
|
|
@ -129,7 +131,7 @@ export function SystemSettings() {
|
|||
setClearingCache(true);
|
||||
try {
|
||||
const { data } = await request.post('/system/cache/clear');
|
||||
message.success(
|
||||
toast.success(
|
||||
<>
|
||||
<div>{data.message}</div>
|
||||
<div style={{ fontSize: 12, color: '#888', marginTop: 4 }}>
|
||||
|
|
@ -140,7 +142,7 @@ export function SystemSettings() {
|
|||
);
|
||||
loadData();
|
||||
} catch (error) {
|
||||
message.error('清除缓存失败');
|
||||
toast.error('清除缓存失败');
|
||||
} finally {
|
||||
setClearingCache(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,31 +2,28 @@
|
|||
* User Management Page
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import { message, Modal, Button, Popconfirm } from 'antd';
|
||||
import { Modal, Button, Popconfirm } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { DataTable } from '../../components/admin/DataTable';
|
||||
import { request } from '../../utils/request';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import { DataTable } from '../../components/admin/DataTable';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
|
||||
interface UserItem {
|
||||
id: number;
|
||||
username: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
email: string | null;
|
||||
full_name: string | null;
|
||||
is_active: boolean;
|
||||
roles: string[];
|
||||
last_login_at: string;
|
||||
last_login_at: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export function Users() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<UserItem[]>([]);
|
||||
const [filteredData, setFilteredData] = useState<UserItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const toast = useToast();
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -35,19 +32,23 @@ export function Users() {
|
|||
setData(result.users || []);
|
||||
setFilteredData(result.users || []);
|
||||
} catch (error) {
|
||||
message.error('加载用户数据失败');
|
||||
console.error(error);
|
||||
toast.error('加载用户数据失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handleSearch = (keyword: string) => {
|
||||
const lowerKeyword = keyword.toLowerCase();
|
||||
const filtered = data.filter(
|
||||
(item) =>
|
||||
item.username.toLowerCase().includes(lowerKeyword) ||
|
||||
item.full_name?.toLowerCase().includes(lowerKeyword) ||
|
||||
item.email?.toLowerCase().includes(lowerKeyword)
|
||||
const filtered = data.filter(item =>
|
||||
item.username.toLowerCase().includes(lowerKeyword) ||
|
||||
(item.email && item.email.toLowerCase().includes(lowerKeyword)) ||
|
||||
(item.full_name && item.full_name.toLowerCase().includes(lowerKeyword))
|
||||
);
|
||||
setFilteredData(filtered);
|
||||
};
|
||||
|
|
@ -55,24 +56,24 @@ export function Users() {
|
|||
const handleStatusChange = async (record: UserItem, checked: boolean) => {
|
||||
try {
|
||||
await request.put(`/users/${record.id}/status`, { is_active: checked });
|
||||
message.success(`用户 ${record.username} 状态更新成功`);
|
||||
|
||||
const newData = data.map(item =>
|
||||
item.id === record.id ? { ...item, is_active: checked } : item
|
||||
);
|
||||
toast.success(`用户 ${record.username} 状态更新成功`);
|
||||
// Update local state
|
||||
const newData = data.map(item => item.id === record.id ? { ...item, is_active: checked } : item);
|
||||
setData(newData);
|
||||
setFilteredData(newData);
|
||||
setFilteredData(newData); // Also update filtered view if needed, simplified here
|
||||
loadData(); // Reload to be sure
|
||||
} catch (error) {
|
||||
message.error('状态更新失败');
|
||||
console.error(error);
|
||||
toast.error('状态更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetPassword = async (record: UserItem) => {
|
||||
try {
|
||||
await request.post(`/users/${record.id}/reset-password`);
|
||||
message.success(`用户 ${record.username} 密码已重置`);
|
||||
toast.success(`用户 ${record.username} 密码已重置`);
|
||||
} catch (error) {
|
||||
message.error('密码重置失败');
|
||||
toast.error('密码重置失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue