new branch
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { Table, Tag, Card, Row, Col, Statistic, Button, Modal, Space, Descriptions } from 'antd'
|
||||
import { AlertOutlined, InfoCircleOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
|
||||
interface Alert {
|
||||
id: number
|
||||
@@ -140,7 +141,7 @@ function Alerts() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppLayout>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
@@ -173,7 +174,7 @@ function Alerts() {
|
||||
title="告警列表"
|
||||
extra={<Button icon={<ReloadOutlined />} onClick={fetchAlerts}>刷新</Button>}
|
||||
>
|
||||
<Table columns={columns} dataSource={alerts} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} />
|
||||
<Table columns={columns} dataSource={alerts} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 'max-content' }} tableLayout="fixed" />
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
@@ -212,7 +213,7 @@ function Alerts() {
|
||||
</Descriptions>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import {
|
||||
Table, Tag, Space, Card, Row, Col, Select, Input, Button,
|
||||
Statistic, Modal, Descriptions, Spin, Empty, Tooltip
|
||||
} from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import {
|
||||
DatabaseOutlined, GlobalOutlined, CloudServerOutlined,
|
||||
AppstoreOutlined, EyeOutlined, SearchOutlined
|
||||
} from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
|
||||
interface CollectedData {
|
||||
id: number
|
||||
@@ -153,17 +155,77 @@ function DataList() {
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const columns = [
|
||||
const [columnsWidth, setColumnsWidth] = useState<Record<string, number>>({
|
||||
id: 60,
|
||||
name: 300,
|
||||
source: 150,
|
||||
data_type: 100,
|
||||
country: 100,
|
||||
value: 100,
|
||||
collected_at: 160,
|
||||
action: 80,
|
||||
})
|
||||
|
||||
const resizeRef = useRef<{ startX: number; startWidth: number; key: string } | null>(null)
|
||||
|
||||
const handleResizeStart = (key: string) => (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
resizeRef.current = {
|
||||
startX: e.clientX,
|
||||
startWidth: columnsWidth[key],
|
||||
key,
|
||||
}
|
||||
document.addEventListener('mousemove', handleResizeMove)
|
||||
document.addEventListener('mouseup', handleResizeEnd)
|
||||
}
|
||||
|
||||
const handleResizeMove = (e: MouseEvent) => {
|
||||
if (!resizeRef.current) return
|
||||
const diff = e.clientX - resizeRef.current.startX
|
||||
const newWidth = Math.max(50, resizeRef.current.startWidth + diff)
|
||||
setColumnsWidth((prev) => ({
|
||||
...prev,
|
||||
[resizeRef.current!.key]: newWidth,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleResizeEnd = () => {
|
||||
resizeRef.current = null
|
||||
document.removeEventListener('mousemove', handleResizeMove)
|
||||
document.removeEventListener('mouseup', handleResizeEnd)
|
||||
}
|
||||
|
||||
const columns: ColumnsType<CollectedData> = [
|
||||
{
|
||||
title: 'ID',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>ID</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('id')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
width: columnsWidth.id,
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>名称</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('name')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: columnsWidth.name,
|
||||
ellipsis: true,
|
||||
render: (name: string, record: CollectedData) => (
|
||||
<Tooltip title={name}>
|
||||
@@ -174,49 +236,95 @@ function DataList() {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '数据源',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>数据源</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('source')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'source',
|
||||
key: 'source',
|
||||
width: 150,
|
||||
width: columnsWidth.source,
|
||||
render: (source: string) => (
|
||||
<Tag icon={getSourceIcon(source)}>{source}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>类型</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('data_type')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'data_type',
|
||||
key: 'data_type',
|
||||
width: 120,
|
||||
width: columnsWidth.data_type,
|
||||
render: (type: string) => (
|
||||
<Tag color={getTypeColor(type)}>{type}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '国家/地区',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>国家/地区</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('country')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'country',
|
||||
key: 'country',
|
||||
width: 120,
|
||||
width: columnsWidth.country,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '数值',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>数值</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('value')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
width: 120,
|
||||
width: columnsWidth.value,
|
||||
render: (value: string | null, record: CollectedData) => (
|
||||
value ? `${value} ${record.unit || ''}` : '-'
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '采集时间',
|
||||
title: () => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||
<span>采集时间</span>
|
||||
<div
|
||||
className="resize-handle"
|
||||
onMouseDown={handleResizeStart('collected_at')}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
dataIndex: 'collected_at',
|
||||
key: 'collected_at',
|
||||
width: 180,
|
||||
width: columnsWidth.collected_at,
|
||||
render: (time: string) => new Date(time).toLocaleString('zh-CN'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 80,
|
||||
width: columnsWidth.action,
|
||||
render: (_: unknown, record: CollectedData) => (
|
||||
<Button
|
||||
type="link"
|
||||
@@ -230,7 +338,7 @@ function DataList() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppLayout>
|
||||
<h2>采集数据管理</h2>
|
||||
|
||||
{/* Summary Cards */}
|
||||
@@ -305,6 +413,8 @@ function DataList() {
|
||||
dataSource={data}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
scroll={{ x: 'max-content' }}
|
||||
tableLayout="fixed"
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize,
|
||||
@@ -361,7 +471,7 @@ function DataList() {
|
||||
<Empty description="暂无数据" />
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SyncOutlined
|
||||
} from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
|
||||
interface BuiltInDataSource {
|
||||
id: number
|
||||
@@ -326,6 +327,8 @@ function DataSources() {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 'max-content' }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -352,6 +355,8 @@ function DataSources() {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 'max-content' }}
|
||||
tableLayout="fixed"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -360,7 +365,7 @@ function DataSources() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppLayout>
|
||||
<h2>数据源管理</h2>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab} items={tabItems} />
|
||||
|
||||
@@ -665,7 +670,7 @@ function DataSources() {
|
||||
</Form>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
15
frontend/src/pages/Earth/Earth.tsx
Normal file
15
frontend/src/pages/Earth/Earth.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
function Earth() {
|
||||
return (
|
||||
<iframe
|
||||
src="/earth/3dearthmult.html"
|
||||
style={{
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
border: 'none',
|
||||
}}
|
||||
title="3D Earth"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Earth
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { Table, Tag, Card, Row, Col, Statistic, Button } from 'antd'
|
||||
import { ReloadOutlined, CheckCircleOutlined, CloseCircleOutlined, SyncOutlined } from '@ant-design/icons'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
|
||||
interface Task {
|
||||
id: number
|
||||
@@ -107,7 +108,7 @@ function Tasks() {
|
||||
const successRate = tasks.length > 0 ? (statusCounts.success / tasks.length) * 100 : 0
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppLayout>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
@@ -144,9 +145,9 @@ function Tasks() {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table columns={columns} dataSource={tasks} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} />
|
||||
<Table columns={columns} dataSource={tasks} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 'max-content' }} tableLayout="fixed" />
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
|
||||
import { Table, Button, Tag, Space, message, Modal, Form, Input, Select } from 'antd'
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import axios from 'axios'
|
||||
import AppLayout from '../../components/AppLayout/AppLayout'
|
||||
|
||||
interface User {
|
||||
id: number
|
||||
@@ -113,12 +114,12 @@ function Users() {
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppLayout>
|
||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<h2>用户管理</h2>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>添加用户</Button>
|
||||
</div>
|
||||
<Table columns={columns} dataSource={users} rowKey="id" loading={loading} />
|
||||
<Table columns={columns} dataSource={users} rowKey="id" loading={loading} scroll={{ x: 'max-content' }} tableLayout="fixed" />
|
||||
<Modal
|
||||
title={editingUser ? '编辑用户' : '添加用户'}
|
||||
open={modalVisible}
|
||||
@@ -150,7 +151,7 @@ function Users() {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user