[feat]:[FL-127][雷电幅值统计页面优化]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
chengkai3
2026-06-15 13:54:15 +08:00
parent 3450e6da23
commit c081177c37
+128 -43
View File
@@ -21,7 +21,7 @@ import {
} from "antd";
import { MoreOutlined } from "@ant-design/icons";
import type { ColumnsType } from "antd/es/table";
import { useCallback, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
import { useAuth } from "@/components/auth-provider";
@@ -39,6 +39,7 @@ import type {
LightningCurrentSampleItem,
LightningPolarity,
} from "@/types/auth";
import type { CSSProperties } from "react";
type ImportFormValues = {
event_id: string;
@@ -87,11 +88,16 @@ function formatPolarity(polarity: LightningPolarity): string {
return "未知";
}
const LIGHTNING_TABLE_MIN_SCROLL_Y = 180;
const LIGHTNING_TABLE_VIEWPORT_GAP = 40;
const LIGHTNING_TABLE_FALLBACK_RESERVE = 220;
export default function AdminLightningCurrentsPage() {
const { user, initializing, hasPermission, fetchWithAuth } = useAuth();
const queryClient = useQueryClient();
const uploadInputRef = useRef<HTMLInputElement | null>(null);
const [importForm] = Form.useForm<ImportFormValues>();
const tableScrollAnchorRef = useRef<HTMLDivElement | null>(null);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
@@ -100,6 +106,7 @@ export default function AdminLightningCurrentsPage() {
const [detailModalOpen, setDetailModalOpen] = useState(false);
const [sampleModalOpen, setSampleModalOpen] = useState(false);
const [selectedEventForModal, setSelectedEventForModal] = useState<LightningCurrentEventSummary | null>(null);
const [tableScrollY, setTableScrollY] = useState(LIGHTNING_TABLE_MIN_SCROLL_Y);
const canRead = hasPermission("lightning.read") || hasPermission("lightning.manage");
const canManage = hasPermission("lightning.manage");
@@ -184,6 +191,74 @@ export default function AdminLightningCurrentsPage() {
}, [refreshAll]),
);
const updateTableScrollY = useCallback(() => {
if (typeof window === "undefined") {
return;
}
const anchor = tableScrollAnchorRef.current;
if (!anchor) {
return;
}
const anchorTop = anchor.getBoundingClientRect().top;
const tableWrapper = anchor.querySelector<HTMLElement>(".ant-table-wrapper");
const tableBody = anchor.querySelector<HTMLElement>(".ant-table-body");
let nextHeight = Math.floor(window.innerHeight - anchorTop - LIGHTNING_TABLE_FALLBACK_RESERVE);
if (tableWrapper) {
const wrapperRect = tableWrapper.getBoundingClientRect();
const bodyHeight = tableBody?.getBoundingClientRect().height ?? LIGHTNING_TABLE_MIN_SCROLL_Y;
const nonBodyHeight = Math.max(0, wrapperRect.height - bodyHeight);
const topGap = Math.max(0, wrapperRect.top - anchorTop);
nextHeight = Math.floor(window.innerHeight - anchorTop - topGap - nonBodyHeight - LIGHTNING_TABLE_VIEWPORT_GAP);
}
const clampedHeight = Math.max(LIGHTNING_TABLE_MIN_SCROLL_Y, nextHeight);
setTableScrollY((previous) => (Math.abs(previous - clampedHeight) <= 1 ? previous : clampedHeight));
}, []);
useEffect(() => {
if (typeof window === "undefined") {
return;
}
window.requestAnimationFrame(updateTableScrollY);
}, [error, listError, sampleError, statsError, events.length, eventsQuery.isFetching, updateTableScrollY]);
useEffect(() => {
if (typeof window === "undefined") {
return;
}
const onViewportChange = () => {
window.requestAnimationFrame(updateTableScrollY);
};
window.addEventListener("resize", onViewportChange);
return () => {
window.removeEventListener("resize", onViewportChange);
};
}, [updateTableScrollY]);
useEffect(() => {
if (typeof window === "undefined" || typeof ResizeObserver === "undefined") {
return;
}
const anchor = tableScrollAnchorRef.current;
if (!anchor) {
return;
}
const resizeObserver = new ResizeObserver(() => {
window.requestAnimationFrame(updateTableScrollY);
});
resizeObserver.observe(anchor);
return () => {
resizeObserver.disconnect();
};
}, [updateTableScrollY]);
const importMutation = useMutation({
mutationFn: async (file: File) => {
if (!canManage) {
@@ -338,20 +413,10 @@ export default function AdminLightningCurrentsPage() {
{
title: "操作",
key: "actions",
width: 100,
width: 200,
fixed: "right",
render: (_: unknown, row) => {
const moreMenuItems: MenuProps["items"] = [
{
key: "exceedance",
label: "峰值超越概率(P 曲线)",
onClick: () => openExceedanceModal(row),
},
{
key: "detail",
label: "雷电流事件详情",
onClick: () => openDetailModal(row),
},
{
key: "sample",
label: "采样预览",
@@ -369,28 +434,42 @@ export default function AdminLightningCurrentsPage() {
];
return (
<Dropdown
menu={{
items: moreMenuItems,
onClick: ({ key }) => {
if (key === "delete") {
Modal.confirm({
title: "删除事件",
content: `确认删除事件 ${row.event_id} 吗?`,
okText: "删除",
cancelText: "取消",
okButtonProps: { danger: true },
onOk: async () => {
await deleteMutation.mutateAsync(row.id);
},
});
}
},
}}
trigger={["click"]}
>
<Button size="small" icon={<MoreOutlined />} />
</Dropdown>
<Space wrap>
<Button
size="small"
onClick={() => openExceedanceModal(row)}
>
</Button>
<Button
size="small"
onClick={() => openDetailModal(row)}
>
</Button>
<Dropdown
menu={{
items: moreMenuItems,
onClick: ({ key }) => {
if (key === "delete") {
Modal.confirm({
title: "删除事件",
content: `确认删除事件 ${row.event_id} 吗?`,
okText: "删除",
cancelText: "取消",
okButtonProps: { danger: true },
onOk: async () => {
await deleteMutation.mutateAsync(row.id);
},
});
}
},
}}
trigger={["click"]}
>
<Button size="small" icon={<MoreOutlined />} />
</Dropdown>
</Space>
);
},
},
@@ -446,7 +525,7 @@ export default function AdminLightningCurrentsPage() {
return (
<Space direction="vertical" size={16} className="w-full">
<Card
title="导入记录管理"
title="雷电幅值统计"
extra={
canManage && (
<Button type="primary" onClick={() => setImportModalOpen(true)}>
@@ -455,14 +534,20 @@ export default function AdminLightningCurrentsPage() {
)
}
>
<Table<LightningCurrentEventSummary>
rowKey={(row) => row.id}
columns={eventColumns}
dataSource={events}
loading={eventsQuery.isFetching}
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `${total}` }}
scroll={{ x: 1700 }}
/>
<div
ref={tableScrollAnchorRef}
className="lightning-table-anchor"
style={{ "--lightning-table-body-min-height": `${tableScrollY}px` } as CSSProperties}
>
<Table<LightningCurrentEventSummary>
rowKey={(row) => row.id}
columns={eventColumns}
dataSource={events}
loading={eventsQuery.isFetching}
pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `${total}` }}
scroll={{ x: 1800, y: tableScrollY }}
/>
</div>
</Card>
<Modal