## Changelog ### New Features #### Cable Graph Service - Add cable_graph.py for finding shortest path between landing points - Implement haversine distance calculation for great circle distances - Support for dateline crossing (longitude normalization) - NetworkX-based graph for optimal path finding #### Data Collectors - Add ArcGISCableCollector for fetching submarine cable data from ArcGIS GeoJSON API - Add FAOLandingPointCollector for fetching landing point data from FAO CSV API ### Backend Changes #### API Updates - auth.py: Update authentication logic - datasources.py: Add datasource endpoints and management - visualization.py: Add visualization API endpoints - config.py: Update configuration settings - security.py: Improve security settings #### Models & Schemas - task.py: Update task model with new fields - token.py: Update token schema #### Services - collectors/base.py: Improve base collector with better error handling - collectors/__init__.py: Register new collectors - scheduler.py: Update scheduler logic - tasks/scheduler.py: Add task scheduling ### Frontend Changes - AppLayout.tsx: Improve layout component - index.css: Add global styles - DataSources.tsx: Enhance data sources management page - vite.config.ts: Add Vite configuration for earth module
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { ReactNode, useState } from 'react'
|
|
import { Layout, Menu, Typography, Button } from 'antd'
|
|
import {
|
|
DashboardOutlined,
|
|
DatabaseOutlined,
|
|
UserOutlined,
|
|
SettingOutlined,
|
|
BarChartOutlined,
|
|
MenuUnfoldOutlined,
|
|
MenuFoldOutlined,
|
|
} from '@ant-design/icons'
|
|
import { Link, useLocation } from 'react-router-dom'
|
|
import { useAuthStore } from '../../stores/auth'
|
|
|
|
const { Header, Sider, Content } = Layout
|
|
const { Text } = Typography
|
|
|
|
interface AppLayoutProps {
|
|
children: ReactNode
|
|
}
|
|
|
|
function AppLayout({ children }: AppLayoutProps) {
|
|
const location = useLocation()
|
|
const { user, logout } = useAuthStore()
|
|
const [collapsed, setCollapsed] = useState(false)
|
|
|
|
const menuItems = [
|
|
{ key: '/', icon: <DashboardOutlined />, label: <Link to="/">仪表盘</Link> },
|
|
{ key: '/datasources', icon: <DatabaseOutlined />, label: <Link to="/datasources">数据源</Link> },
|
|
{ key: '/data', icon: <BarChartOutlined />, label: <Link to="/data">采集数据</Link> },
|
|
{ key: '/users', icon: <UserOutlined />, label: <Link to="/users">用户管理</Link> },
|
|
{ key: '/settings', icon: <SettingOutlined />, label: <Link to="/settings">系统配置</Link> },
|
|
]
|
|
|
|
return (
|
|
<Layout className="dashboard-layout">
|
|
<Sider
|
|
width={240}
|
|
collapsedWidth={80}
|
|
collapsible
|
|
collapsed={collapsed}
|
|
onCollapse={setCollapsed}
|
|
className="dashboard-sider"
|
|
>
|
|
<div style={{ height: 64, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
{collapsed ? (
|
|
<Text strong style={{ color: 'white', fontSize: 20 }}>🌏</Text>
|
|
) : (
|
|
<Text strong style={{ color: 'white', fontSize: 18 }}>智能星球</Text>
|
|
)}
|
|
</div>
|
|
<Menu
|
|
theme="dark"
|
|
mode="inline"
|
|
selectedKeys={[location.pathname]}
|
|
items={menuItems}
|
|
/>
|
|
</Sider>
|
|
<Layout>
|
|
<Header className="dashboard-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 16px' }}>
|
|
<Button
|
|
type="text"
|
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
style={{ fontSize: 16 }}
|
|
/>
|
|
<Text strong>欢迎, {user?.username}</Text>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
<Button type="link" danger onClick={logout}>退出登录</Button>
|
|
</div>
|
|
</Header>
|
|
<Content className="dashboard-content" style={{ padding: 24, minHeight: '100%', overflow: 'auto' }}>
|
|
{children}
|
|
</Content>
|
|
</Layout>
|
|
</Layout>
|
|
)
|
|
}
|
|
|
|
export default AppLayout
|