| import { ThemeProvider, createTheme } from '@mui/material/styles'; |
| import CssBaseline from '@mui/material/CssBaseline'; |
| import Container from '@mui/material/Container'; |
| import { useState, useEffect } from 'react'; |
| import { AppBar, Toolbar, Typography, Button, Box, useTheme, useMediaQuery } from '@mui/material'; |
| import DnsTable from './components/DnsTable'; |
| import config from './config'; |
| import HistoryDialog from './components/HistoryDialog'; |
| import { theme } from './theme'; |
| import HistoryIcon from '@mui/icons-material/History'; |
| import Footer from './components/Footer'; |
| |
| function App() { |
| const [hosts, setHosts] = useState(null); |
| const [title, setTitle] = useState('DNS 配置管理'); |
| const [hints, setHints] = useState({}); |
| const [versions, setVersions] = useState({ cn: 0, oversea: 0 }); |
| const [historyOpen, setHistoryOpen] = useState(false); |
| const [history, setHistory] = useState({ cn: [], oversea: [] }); |
| const theme = useTheme(); |
| const isMobile = useMediaQuery(theme.breakpoints.down('sm')); |
| |
| useEffect(() => { |
| fetchConfig(); |
| }, []); |
| |
| const fetchConfig = async () => { |
| try { |
| const response = await fetch(`${config.api.baseUrl}/api/config`); |
| const data = await response.json(); |
| if (data.error) { |
| alert('加载配置失败: ' + data.error); |
| return; |
| } |
| console.log('Fetched config:', data); |
| setHosts(data.hosts); |
| setTitle(data.title); |
| setHints(data.hints); |
| setVersions({ |
| cn: data.cnVersion, |
| oversea: data.overseaVersion |
| }); |
| } catch (error) { |
| alert('加载配置失败: ' + error.message); |
| } |
| }; |
| |
| const fetchHistory = async () => { |
| try { |
| const response = await fetch(`${config.api.baseUrl}/api/history`); |
| const data = await response.json(); |
| if (data.error) { |
| alert('加载历史记录失败: ' + data.error); |
| return; |
| } |
| setHistory(data); |
| } catch (error) { |
| alert('加载历史记录失败: ' + error.message); |
| } |
| }; |
| |
| const handleSave = async (newHosts) => { |
| try { |
| const response = await fetch(`${config.api.baseUrl}/api/config`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ hosts: newHosts }), |
| }); |
| |
| const data = await response.json(); |
| if (data.error) { |
| alert('保存失败: ' + data.error); |
| return false; |
| } |
| |
| // 重新加载配置 |
| await fetchConfig(); |
| return true; |
| } catch (error) { |
| alert('保存失败: ' + error.message); |
| return false; |
| } |
| }; |
| |
| const handleRestoreVersion = async (type, version) => { |
| try { |
| const response = await fetch(`${config.api.baseUrl}/api/history/restore`, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ type, version }), |
| }); |
| |
| const data = await response.json(); |
| if (data.error) { |
| alert('恢复配置失败: ' + data.error); |
| return; |
| } |
| |
| alert('配置已恢复'); |
| setHistoryOpen(false); |
| fetchConfig(); // 重新加载配置 |
| } catch (error) { |
| alert('恢复配置失败: ' + error.message); |
| } |
| }; |
| |
| return ( |
| <ThemeProvider theme={theme}> |
| <CssBaseline /> |
| <Box |
| sx={{ |
| display: 'flex', |
| flexDirection: 'column', |
| minHeight: '100vh' |
| }} |
| > |
| <AppBar |
| position="static" |
| elevation={0} |
| sx={{ |
| backgroundColor: 'background.paper', |
| borderBottom: '1px solid', |
| borderColor: 'divider' |
| }} |
| > |
| <Toolbar sx={{ flexDirection: isMobile ? 'column' : 'row', py: isMobile ? 2 : 0 }}> |
| <Typography |
| variant={isMobile ? "h6" : "h6"} |
| component="div" |
| sx={{ |
| flexGrow: isMobile ? 0 : 1, |
| color: 'text.primary', |
| fontWeight: 600, |
| mb: isMobile ? 2 : 0 |
| }} |
| > |
| {title} |
| </Typography> |
| <Box sx={{ |
| display: 'flex', |
| alignItems: 'center', |
| gap: 2, |
| flexDirection: isMobile ? 'row' : 'row', |
| flexWrap: 'wrap', |
| justifyContent: 'center' |
| }}> |
| <Box sx={{ |
| display: 'flex', |
| alignItems: 'center', |
| gap: 2, |
| flexWrap: 'wrap', |
| justifyContent: 'center' |
| }}> |
| <Box sx={{ |
| display: 'flex', |
| alignItems: 'center', |
| gap: 1, |
| minWidth: isMobile ? '120px' : 'auto' |
| }}> |
| <Typography |
| variant="body2" |
| color="text.secondary" |
| noWrap |
| > |
| 国内版本 |
| </Typography> |
| <Typography |
| variant="body2" |
| sx={{ |
| backgroundColor: 'primary.main', |
| color: 'white', |
| px: 1.5, |
| py: 0.5, |
| borderRadius: 2, |
| fontWeight: 500, |
| minWidth: '40px', |
| textAlign: 'center' |
| }} |
| > |
| v{versions.cn} |
| </Typography> |
| </Box> |
| <Box sx={{ |
| display: 'flex', |
| alignItems: 'center', |
| gap: 1, |
| minWidth: isMobile ? '120px' : 'auto' |
| }}> |
| <Typography |
| variant="body2" |
| color="text.secondary" |
| noWrap |
| > |
| 海外版本 |
| </Typography> |
| <Typography |
| variant="body2" |
| sx={{ |
| backgroundColor: 'secondary.main', |
| color: 'white', |
| px: 1.5, |
| py: 0.5, |
| borderRadius: 2, |
| fontWeight: 500, |
| minWidth: '40px', |
| textAlign: 'center' |
| }} |
| > |
| v{versions.oversea} |
| </Typography> |
| </Box> |
| </Box> |
| <Button |
| variant="outlined" |
| color="primary" |
| onClick={() => { |
| fetchHistory(); |
| setHistoryOpen(true); |
| }} |
| startIcon={<HistoryIcon />} |
| sx={{ |
| minWidth: isMobile ? '120px' : 'auto', |
| height: '36px' |
| }} |
| > |
| 历史记录 |
| </Button> |
| </Box> |
| </Toolbar> |
| </AppBar> |
| |
| <Container |
| maxWidth="lg" |
| sx={{ |
| mt: 4, |
| mb: 4, |
| flex: 1 // 让内容区域占据剩余空间 |
| }} |
| > |
| <DnsTable |
| hosts={hosts} |
| hints={hints} |
| onSave={handleSave} |
| /> |
| </Container> |
| |
| <Footer /> |
| |
| <HistoryDialog |
| open={historyOpen} |
| onClose={() => setHistoryOpen(false)} |
| history={history} |
| onRestore={handleRestoreVersion} |
| /> |
| </Box> |
| </ThemeProvider> |
| ); |
| } |
| |
| export default App; |