From 2be6dc84f56ee35aa1fb96bb459026afc6c821ce Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Sun, 9 Aug 2026 21:13:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=A9=E5=87=8F=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.js | 2 +- src/Adminive/ArchiveOperations.tsx | 2 +- src/Adminive/Backend_Fields.tsx | 19 +- src/App.tsx | 130 ++---- src/Base_Drawer.tsx | 56 +-- src/Data_Source/Data_Source.tsx | 11 - .../Data_Source_Aircraft_Stream.ts | 2 +- src/Data_Source/Data_Source_Config.tsx | 28 +- src/Data_Source/Data_Source_Map_Runtime.ts | 22 +- src/Data_Source/Data_Source_Show.tsx | 50 +- src/Global.ts | 40 ++ src/Global.tsx | 181 -------- src/Map/Aircraft.tsx | 304 ++---------- src/Map/Aircraft_Info_Show.tsx | 204 +++------ src/Map/Aircraft_List.tsx | 4 +- src/Map/Aircraft_Model.tsx | 4 +- src/Map/Base_Station.tsx | 8 +- src/Map/Cesium_Map.tsx | 54 +-- src/Map/Cesium_Model_Settings.tsx | 9 +- src/Map/Leaflet_Map.tsx | 145 +----- src/Map/Map_Models.tsx | 2 +- src/Map/Map_Resources.tsx | 6 +- src/Map/Map_View.tsx | 1 - src/Map/Tree_Show.tsx | 431 +++--------------- src/Map/WebGL_Support.ts | 2 +- src/PersistentScroll.tsx | 64 +-- src/Refresh.tsx | 152 ++---- src/Setting.tsx | 2 +- src/core/enhance.tsx | 65 +-- 29 files changed, 409 insertions(+), 1591 deletions(-) create mode 100644 src/Global.ts delete mode 100644 src/Global.tsx diff --git a/eslint.config.js b/eslint.config.js index 16192a5..96009cb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' export default [ - { ignores: ['dist/**', 'wwwroot/**', 'htdocs/**', 'cesium/**', 'node_modules/**'] }, + { ignores: ['dist/**', 'public/**', 'wwwroot/**', 'htdocs/**', 'cesium/**', 'node_modules/**'] }, { files: ['src/**/*.{ts,tsx}'], languageOptions: { diff --git a/src/Adminive/ArchiveOperations.tsx b/src/Adminive/ArchiveOperations.tsx index e3c9845..249fa03 100644 --- a/src/Adminive/ArchiveOperations.tsx +++ b/src/Adminive/ArchiveOperations.tsx @@ -2,7 +2,7 @@ import {DownloadOutlined, UploadOutlined} from "@ant-design/icons" import {Button, Input, message, Modal, Progress, Select, Space, Upload} from "antd" import axios from "axios" import {useMemo, useState} from "react" -export type Archive_Format = { +type Archive_Format = { label: string value: string format: string diff --git a/src/Adminive/Backend_Fields.tsx b/src/Adminive/Backend_Fields.tsx index 47f9a49..9617026 100644 --- a/src/Adminive/Backend_Fields.tsx +++ b/src/Adminive/Backend_Fields.tsx @@ -21,6 +21,7 @@ export type Backend_Form_Section = { descriptor_path?: string fields?: string[] layout?: Backend_Field_Layout[] + modes?: string[] } export type Backend_Field_Layout = { @@ -38,6 +39,22 @@ export type Backend_Disabled_Rule = { } } +export type Backend_Schema_Node = Backend_Form_Section & { + descriptor_fields?: Backend_Field_Descriptor[] + rules?: Backend_Disabled_Rule[] + sections?: Backend_Schema_Node[] + [key: string]: unknown +} + +export function backend_schema_node(value: unknown, ...path: string[]): Backend_Schema_Node | undefined { + let node = value; + for (const key of path) { + if (!node || typeof node !== "object") return undefined; + node = (node as Record)[key]; + } + return node && typeof node === "object" ? node as Backend_Schema_Node : undefined; +} + type Json_Object = Record export function get_path(root: Json_Object, path = ""): any { @@ -45,7 +62,7 @@ export function get_path(root: Json_Object, path = ""): any { return path.split(".").reduce((value: any, key) => value?.[key], root); } -export function fields_at_path(fields: Backend_Field_Descriptor[], path = ""): Backend_Field_Descriptor[] { +function fields_at_path(fields: Backend_Field_Descriptor[], path = ""): Backend_Field_Descriptor[] { if (!path) return fields; let current = fields; for (const name of path.split(".")) { diff --git a/src/App.tsx b/src/App.tsx index a74ac7c..13ce7e3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,12 @@ import enhance from './core/enhance.tsx'; import {useEffect} from 'react'; import {SettingOutlined} from '@ant-design/icons'; -import {Menu, MenuProps, message} from 'antd'; +import {Menu, type MenuProps, message} from 'antd'; import { BrowserRouter, Navigate, - NavigateFunction, + type NavigateFunction, Route, Routes, useLocation, @@ -20,7 +20,7 @@ import { import React from 'react'; import ReactDOM from 'react-dom/client'; import axios from "axios"; -import {baseURL} from "./Global.tsx"; +import {baseURL} from "./Global.ts"; import {is_cesium_webgl_available, webgl_unavailable_message} from "./Map/WebGL_Support.ts"; import {Data_Source_Config} from "./Data_Source/Data_Source_Config.tsx"; @@ -37,11 +37,11 @@ L.Icon.Default.mergeOptions({ }); class App extends enhance.Base { - leaflet_map: Leaflet_Map | null = null; + leaflet_map: Leaflet_Map; cesium_map: any = null; - setting: Settings | null = null; - aircraft_list: Aircraft_List | null = null; - data_source_config: Data_Source_Config | null = null; + setting: Settings; + aircraft_list: Aircraft_List; + data_source_config: Data_Source_Config; // @ts-ignore navigate: NavigateFunction @@ -56,7 +56,10 @@ class App extends enhance.Base { constructor() { super(); - this.initSubComponents(); + this.leaflet_map = new Leaflet_Map(); + this.aircraft_list = new Aircraft_List(); + this.setting = new Settings(); + this.data_source_config = new Data_Source_Config(this.on_data_sources_changed); this.menuItems = [ { @@ -93,28 +96,12 @@ class App extends enhance.Base { }) } - initSubComponents = () => { - this.leaflet_map = new Leaflet_Map(); - this.aircraft_list = new Aircraft_List(); - this.data_source_config = new Data_Source_Config(this.on_data_sources_changed); - - // 延迟初始化Settings,避免“初始化前访问”错误 - setTimeout(() => { - this.setting = new Settings(); - this.flush(); - }, 0); - }; - on_data_sources_changed = () => { - if (!this.data_source_config) return; - - if (this.leaflet_map) { - this.leaflet_map.data_source_config = this.data_source_config; - this.leaflet_map.data_source_show.data_source_config = this.data_source_config; - this.leaflet_map.data_source_show.flush(); - this.leaflet_map.flush(); - } - this.aircraft_list?.data_sources_changed(); + this.leaflet_map.data_source_config = this.data_source_config; + this.leaflet_map.data_source_show.data_source_config = this.data_source_config; + this.leaflet_map.data_source_show.flush(); + this.leaflet_map.flush(); + this.aircraft_list.data_sources_changed(); this.cesium_map?.request_sync_data_sources?.(); this.flush(); }; @@ -176,18 +163,18 @@ class App extends enhance.Base { } render() { - if (!this.leaflet_map || !this.setting || !this.aircraft_list || !this.data_source_config) { - return
组件初始化中...
; - } - + const map2d =
; + const settings = ; + const aircraftList =
; + const map3d = (fallback: string) => this.webgl_supported + ?
+ 3D地图加载中...
}> + + + + : ; return ( -
- +
- - -
- )} - /> - - -
- )} - /> - - )} - /> - - )} - /> - - 3D地图加载中...}> - - - : - )} - /> - - 3D地图加载中...}> - - - : - )} - /> - - - - )} - /> + {["/map", "/ui/map"].map(path => )} + {["/settings", "/ui/settings"].map(path => )} + + + {["/aircraftlist", "/ui/aircraftlist"].map(path => )} }/> diff --git a/src/Base_Drawer.tsx b/src/Base_Drawer.tsx index 80038ce..fb1e957 100644 --- a/src/Base_Drawer.tsx +++ b/src/Base_Drawer.tsx @@ -4,13 +4,7 @@ import {Drawer, Switch} from "antd"; import {PersistentScroll} from "./PersistentScroll.tsx"; export class Base_Drawer extends enhance.Base { - have_open: boolean = false; - - on_mount() { - super.on_mount(); - } - - + have_open = false; toggleOpen = () => { this.have_open = !this.have_open; @@ -18,55 +12,23 @@ export class Base_Drawer extends enhance.Base { }; getSwitchStyle(placement: string): React.CSSProperties { - const common: React.CSSProperties = { - position: "absolute", - zIndex: 9999, + const common: React.CSSProperties = {position: "absolute", zIndex: 9999}; + if (placement === "right") return {...common, right: 8, top: 8}; + if (placement === "top" || placement === "bottom") return { + ...common, + [placement]: 0, + left: "50%", + transform: "translateX(-50%)" }; - - - switch (placement) { - case "left": - return { ...common, left: 8, top: 8 }; - - case "right": - return { ...common, right: 8, top: 8 }; - - case "top": - return { - ...common, - top: 0, - left: "50%", - transform: "translateX(-50%)" - }; - - case "bottom": - return { - ...common, - bottom: 0, - left: "50%", - transform: "translateX(-50%)" - }; - - default: - return { ...common, left: 8, top: 8 }; - } + return {...common, left: 8, top: 8}; } - - - - - - render(props: any): React.JSX.Element { const content = props.children; const placement = props.placement || "right"; const header = props.header; const scrollKey = props.scrollKey || "Base_Drawer"; - // 按钮样式自动根据 placement 调整 const switchStyle = this.getSwitchStyle(placement); - - return ( <> - rules?: Backend_Disabled_Rule[] - tiles: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]} - view3d: { - graphics: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]} - map_view: Backend_Form_Section & {descriptor_fields: Backend_Field_Descriptor[]} - } -} - export class Data_Source_Config { private sources: Data_Source[] = [] - descriptor_fields: Backend_Field_Descriptor[] = [] config_descriptor_fields: Backend_Field_Descriptor[] = [] - sidebar_schema: Data_Source_Sidebar_Schema | null = null - loading = true - error = "" + sidebar_schema: Backend_Schema_Node | null = null constructor(private readonly on_change?: () => void) { window.addEventListener("ecap-data-sources-changed", () => void this.refresh()) @@ -67,7 +47,6 @@ export class Data_Source_Config { if (!descriptor || !Array.isArray(descriptor.fields) || !descriptor.sidebar) { throw new Error("数据源接口未返回后端字段与右侧栏描述") } - this.descriptor_fields = descriptor.fields this.config_descriptor_fields = descriptor.fields.find((field: Backend_Field_Descriptor) => field.name === "config")?.children || [] this.sidebar_schema = descriptor.sidebar const editable_fields = descriptor.fields @@ -88,12 +67,9 @@ export class Data_Source_Config { source.normalize_map_display() return source }) - this.error = "" } catch (error) { - this.error = error instanceof Error ? error.message : String(error) console.error("加载后端数据源模型失败", error) } finally { - this.loading = false this.on_change?.() } } diff --git a/src/Data_Source/Data_Source_Map_Runtime.ts b/src/Data_Source/Data_Source_Map_Runtime.ts index 2977056..132f745 100644 --- a/src/Data_Source/Data_Source_Map_Runtime.ts +++ b/src/Data_Source/Data_Source_Map_Runtime.ts @@ -2,10 +2,9 @@ import axios from "axios"; import {message} from "antd"; import L from "leaflet"; import {app} from "../App.tsx"; -import {baseURL, darkenColor} from "../Global.tsx"; +import {baseURL, darkenColor} from "../Global.ts"; import {Aircraft} from "../Map/Aircraft.tsx"; import {Base_Station} from "../Map/Base_Station.tsx"; -import {Camera_Control_Mode} from "../Map/Camera_Control_Mode.ts"; import type {Data_Source, Map_Display_Mode} from "./Data_Source.tsx"; import {data_source_aircraft_stream} from "./Data_Source_Aircraft_Stream.ts"; @@ -44,17 +43,6 @@ export class Data_Source_Map_Runtime { app.leaflet_map?.map?.setView([this.source.lat, this.source.lon], 12); } - center_earth_view() { app.cesium_map?.center_earth(); } - look_straight_down_view() { app.cesium_map?.look_straight_down(); } - north_up_view() { app.cesium_map?.north_up(); } - clear_camera_tracking() { app.cesium_map?.clear_camera_tracking(); } - restore_previous_camera_view() { app.cesium_map?.restore_previous_view(); } - fly_to_global_view() { app.cesium_map?.fly_to_global_view(); } - cesium_camera_control_mode(): Camera_Control_Mode { - return app.cesium_map?.camera_control_mode() || Camera_Control_Mode.Surface_Navigation; - } - set_cesium_camera_control_mode(mode: Camera_Control_Mode) { app.cesium_map?.set_camera_control_mode(mode); } - async refresh() { if (this.source.key) await this.refresh_base_station_location(); data_source_aircraft_stream.ensure_open(); @@ -222,14 +210,6 @@ export class Data_Source_Map_Runtime { this.refresh_aircraft_range(); } - apply_aircraft_snapshot(items: any[]) { - const alive = new Set(items.map(item => item.icao)); - for (const icao of Array.from(this.aircraft_map.keys())) { - if (!alive.has(icao)) this.remove_aircraft(icao); - } - this.apply_aircraft_items(items); - } - apply_track_stream_list(list: any[]) { for (const item of list) { const aircraft = this.aircraft_map.get(item.icao); diff --git a/src/Data_Source/Data_Source_Show.tsx b/src/Data_Source/Data_Source_Show.tsx index 25c80ad..350a021 100644 --- a/src/Data_Source/Data_Source_Show.tsx +++ b/src/Data_Source/Data_Source_Show.tsx @@ -16,9 +16,9 @@ import {Data_Source, type Map_Display_Mode} from "./Data_Source.tsx"; import {Base_Drawer} from "../Base_Drawer.tsx"; import {Camera_Control_Mode} from "../Map/Camera_Control_Mode.ts"; import {app} from "../App.tsx"; -import {lightenColor} from "../Global.tsx"; +import {lightenColor} from "../Global.ts"; import {tile_source_options, type Map_Tile_Type} from "../Map/Map_Resources.tsx"; -import {Backend_Fields} from "../Adminive/Backend_Fields.tsx"; +import {backend_schema_node, Backend_Fields} from "../Adminive/Backend_Fields.tsx"; const {Title, Text} = Typography; @@ -239,7 +239,7 @@ export class Data_Source_Show extends enhance.Base { ] : [{value: "imagery", label: "影像瓦片"}]; const source_options = tile_source_options(map?.map_resources || null, tile_type); const source_key = this.selected_tile_source_key(map, tile_type, source_options); - const tile_schema = this.data_source_config?.sidebar_schema?.tiles; + const tile_schema = backend_schema_node(this.data_source_config?.sidebar_schema, "tiles"); return (
{tile_schema?.title ?? "瓦片显示"} @@ -258,7 +258,7 @@ export class Data_Source_Show extends enhance.Base { options={source_options} disabled={!source_options.length} onChange={(key: string) => this.change_tile_source(map, tile_type, key)}/> - {tile_type === "imagery" && tile_schema && + {tile_type === "imagery" && tile_schema?.descriptor_fields && { @@ -324,15 +324,16 @@ export class Data_Source_Show extends enhance.Base { const mode = map?.camera_control_mode() || Camera_Control_Mode.Surface_Navigation; const graphics_config = map?.graphics_config || {}; const map_view_config = map?.map_view_config || {}; - const schema = this.data_source_config?.sidebar_schema?.view3d; + const schema = backend_schema_node(this.data_source_config?.sidebar_schema, "view3d"); + const graphics_schema = backend_schema_node(schema, "graphics"); + const map_view_schema = backend_schema_node(schema, "map_view"); const group_style: React.CSSProperties = { marginTop: 8 }; const grid_style: React.CSSProperties = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(86px, 1fr))", - gap: 6, - maxWidth: 320 + gap: 6 }; return (
@@ -371,20 +372,20 @@ export class Data_Source_Show extends enhance.Base {
- {schema && <> - { map?.apply_graphics_config(); this.flush(); - }}/> - : null} + {map_view_schema?.descriptor_fields ? + { if (map) map.map_view_config = map_view_config; this.flush(); - }}/> - } + }}/> : null} - - - - {this.selected_key === this.BASE_INFORMATION && ( - - )} - {this.selected_key === this.GENERAL_INFORMATION && ( - - )} - {this.selected_key === this.DETAILED_INFORMATION && ( - - )} -
{/* 空白区域,保持布局 */} -
- - - ); + const view = this.views[this.selected_key]; + const Tree = view.tree.x; + return + +
+ ({ + key, label, icon: + }))}/> + + + + +
+ + + +
+
; } } diff --git a/src/Map/Aircraft_List.tsx b/src/Map/Aircraft_List.tsx index 70fdc6c..65de3d5 100644 --- a/src/Map/Aircraft_List.tsx +++ b/src/Map/Aircraft_List.tsx @@ -2,7 +2,7 @@ import enhance from "../core/enhance.tsx"; import {Button, Flex, Input, InputNumber, Layout, Select, Space, Table} from "antd"; import React, {HTMLProps} from "react"; import axios from "axios"; -import {baseURL, Prefix} from "../Global.tsx"; +import {baseURL} from "../Global.ts"; import {app} from "../App.tsx"; import {SearchOutlined} from "@ant-design/icons"; import type {TableColumnType} from "antd"; @@ -245,7 +245,7 @@ export class Aircraft_List extends enhance.Base { return - 数据来源} placeholder="请选择数据来源" value={this.data_source_key} options={(app.data_source_config?.enabled() ?? []).map(item => ({ diff --git a/src/Map/Aircraft_Model.tsx b/src/Map/Aircraft_Model.tsx index 0aa9234..cc38114 100644 --- a/src/Map/Aircraft_Model.tsx +++ b/src/Map/Aircraft_Model.tsx @@ -85,7 +85,7 @@ export function orientation_from_item(item: any, fallback: Aircraft_Orientation_ roll: Number(source.roll ?? fallback.roll ?? 0) } } -export function resolve_aircraft_status(item: any, previous_alt?: number): Aircraft_Status { +function resolve_aircraft_status(item: any, previous_alt?: number): Aircraft_Status { const vert_speed = item.vert_speed if (typeof vert_speed === "number") { if (vert_speed === 0) return Aircraft_Status.Level @@ -101,7 +101,7 @@ export function resolve_aircraft_status(item: any, previous_alt?: number): Aircr } return Aircraft_Status.Level } -export function aircraft_status_text(status: Aircraft_Status): string { +function aircraft_status_text(status: Aircraft_Status): string { return { [Aircraft_Status.Level]: "平飞", [Aircraft_Status.Descending]: "下降", diff --git a/src/Map/Base_Station.tsx b/src/Map/Base_Station.tsx index 1ababad..85dcf9b 100644 --- a/src/Map/Base_Station.tsx +++ b/src/Map/Base_Station.tsx @@ -1,5 +1,5 @@ import L from "leaflet"; -import {darkenColor, G} from "../Global.tsx"; +import {darkenColor} from "../Global.ts"; import {app} from "../App.tsx"; import {Data_Source} from "../Data_Source/Data_Source.tsx"; @@ -175,7 +175,11 @@ export class Base_Station { } set_popup(key: string, d: any) { - this.that.bindPopup("key:" + key + "
" + G.to_popup_text(d)); + const content = document.createElement("div"); + content.innerText = [`key: ${key}`, ...Object.entries(d).map(([name, value]) => + `${name}: ${JSON.stringify(value)}`)].join("\n"); + content.style.whiteSpace = "pre-line"; + this.that.bindPopup(content); } get_distance_from_base_station(lat: number, lng: number) { diff --git a/src/Map/Cesium_Map.tsx b/src/Map/Cesium_Map.tsx index fb184f1..9931a57 100644 --- a/src/Map/Cesium_Map.tsx +++ b/src/Map/Cesium_Map.tsx @@ -118,7 +118,6 @@ export class Cesium_Map extends enhance.Base { scene_mode: Scene_Mode_Key = "3d"; current_imagery_key: string = ""; graphics_config: Cesium_Graphics_Config = {...default_cesium_graphics_config}; - tile_display_level_draft: number | null | undefined = undefined; displayed_imagery_minimum_level: number | null = null; displayed_imagery_maximum_level: number | null = null; displayed_terrain_minimum_level: number | null = null; @@ -350,41 +349,16 @@ export class Cesium_Map extends enhance.Base { this.flush(); } change_tile_display_maximum_level(value: number | null) { - this.tile_display_level_draft = value; - if (value === null) { - this.flush(); - return; - } + if (value === null) return; const config = this.map_view_config || default_map_view_config; this.map_view_config = {...config, map3d: {...config.map3d, tile_display_maximum_level: value}}; this.refresh_imagery_layer(); this.viewer?.scene.requestRender(); this.flush(); } - blur_tile_display_maximum_level() { - if (this.tile_display_level_draft !== undefined) { - this.tile_display_level_draft = undefined; - this.flush(); - } - } base_station_fly_to_height_offset_meters(): number { return (this.map_view_config || default_map_view_config).base_station_fly_to_height_offset_meters; } - change_base_station_fly_to_height_offset_meters(value: number) { - const config = this.map_view_config || default_map_view_config; - this.map_view_config = {...config, base_station_fly_to_height_offset_meters: Math.max(100, value)}; - this.flush(); - } - set_surface_navigation_reference_visible(value: boolean) { - this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, surface_navigation_reference_visible: value}); - this.apply_graphics_config(); - this.flush(); - } - set_view_axes_visible(value: boolean) { - this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, view_axes_visible: value}); - this.apply_graphics_config(); - this.flush(); - } change_view_axes_panel_size(value: number) { this.graphics_config = normalize_cesium_graphics_config({...this.graphics_config, view_axes_panel_size: value}); this.flush(); @@ -411,24 +385,6 @@ export class Cesium_Map extends enhance.Base { message.success("地图视图已保存"); this.flush(); } - change_scene_mode(mode: Scene_Mode_Key) { - if (!this.viewer) return; - this.scene_mode = mode; - if (mode !== "3d") { - this.destroy_surface_navigation_reference(); - } - this.update_terrain_provider(); - if (mode === "2d") { - this.viewer.scene.morphTo2D(0.5); - } - else if (mode === "2.5d") { - this.viewer.scene.morphToColumbusView(0.5); - } - else { - this.viewer.scene.morphTo3D(0.5); - } - this.flush(); - } to_cesium_scene_mode(mode: Scene_Mode_Key): Cesium.SceneMode { if (mode === "2d") return Cesium.SceneMode.SCENE2D; if (mode === "2.5d") return Cesium.SceneMode.COLUMBUS_VIEW; @@ -762,9 +718,6 @@ export class Cesium_Map extends enhance.Base { request_sync_data_sources() { this.data_source_sync.request(); } - sync_data_sources() { - this.data_source_sync.sync(); - } sync_base_station(ds: Data_Source, alive_base_stations: Set) { if (!this.viewer) return; const station = ds.base_station; @@ -1195,9 +1148,6 @@ export class Cesium_Map extends enhance.Base { sync_aircraft(key: string, aircraft: Aircraft) { this.aircraft_layer.sync(key, aircraft); } - create_aircraft_record(key: string, aircraft: Aircraft, position: Cesium.Cartesian3, orientation: Cesium.Quaternion): Aircraft_Entity_Record { - return this.aircraft_layer.create_record(key, aircraft, position, orientation); - } ensure_aircraft_track_entities(key: string, aircraft: Aircraft, record: Aircraft_Entity_Record) { this.aircraft_layer.ensure_track_entities(key, aircraft, record); } @@ -1697,7 +1647,7 @@ export class Cesium_Map extends enhance.Base { type Cesium_Map_View_Props = { initialSceneMode?: Scene_Mode_Key } -export function Cesium_Map_View({initialSceneMode = "3d"}: Cesium_Map_View_Props) { +function Cesium_Map_View({initialSceneMode = "3d"}: Cesium_Map_View_Props) { const map_ref = React.useRef(null); if (!map_ref.current) { map_ref.current = new Cesium_Map(initialSceneMode); diff --git a/src/Map/Cesium_Model_Settings.tsx b/src/Map/Cesium_Model_Settings.tsx index 0a56b59..978ef3e 100644 --- a/src/Map/Cesium_Model_Settings.tsx +++ b/src/Map/Cesium_Model_Settings.tsx @@ -2,7 +2,6 @@ import React from "react"; import {Button, Card, message, Select, Space, Upload} from "antd"; import {UploadOutlined} from "@ant-design/icons"; import enhance from "../core/enhance.tsx"; -import {col_style, row_style, setting_style} from "../Global.tsx"; import { empty_map_model_config, load_map_model_config, @@ -85,7 +84,7 @@ export class Cesium_Model_Settings extends enhance.Base { if (!get_path(this.config, path)) return null; return ( -

{title}

+

{title}

field.name)}} onChange={() => this.flush()}/> @@ -127,9 +126,9 @@ export class Cesium_Model_Settings extends enhance.Base { render() { return ( -
-

{this.schema.title ?? "Cesium模型设置"}

- +
+

{this.schema.title ?? "Cesium模型设置"}

+ {(this.schema.groups || []).map(group => this.render_model(group.title, group.path, group.upload))} {(this.schema.collections || []).map(collection => this.render_collection(collection))} diff --git a/src/Map/Leaflet_Map.tsx b/src/Map/Leaflet_Map.tsx index a38acc0..6552c8d 100644 --- a/src/Map/Leaflet_Map.tsx +++ b/src/Map/Leaflet_Map.tsx @@ -2,17 +2,14 @@ import "leaflet/dist/leaflet.css" import L from 'leaflet'; import React from 'react'; import enhance from "../core/enhance.tsx"; -import {baseURL, host} from "../Global.js"; import {Aircraft} from "./Aircraft.tsx" import {Button, Space, message} from "antd"; -import axios from "axios"; import {Aircraft_Info_Show} from "./Aircraft_Info_Show.tsx"; import {Data_Source_Config} from "../Data_Source/Data_Source_Config.tsx"; import {Data_Source_Show} from "../Data_Source/Data_Source_Show.tsx"; import {app} from "../App.tsx"; -//import {Base_Station} from "./Base_Station.tsx"; import {Data_Source} from "../Data_Source/Data_Source.tsx"; import { imagery_source_for_key, @@ -23,59 +20,27 @@ import { import {default_map_view_config, load_map_view_config, save_map_view_config, type Map_Tile_View_Config, type Map_View_Config} from "./Map_View.tsx"; -export class POS { - lat: number; - lon: number; -} - - - - export class Leaflet_Map extends enhance.Base { // @ts-ignore map: L.Map = null; data_source_show: Data_Source_Show = new Data_Source_Show(); // @ts-ignore - aircraft_info_show: Aircraft_Info_Show = new Aircraft_Info_Show(this); + aircraft_info_show: Aircraft_Info_Show = new Aircraft_Info_Show(); data_source_config: Data_Source_Config | null = null tile_layer: L.TileLayer | null = null map_resources: Map_Resources_Metadata | null = null map_view_config: Map_View_Config | null = null current_imagery_key: string = "" context_menu: {x: number, y: number, aircraft?: Aircraft, base_station?: Data_Source} | null = null - tile_display_level_draft: number | null | undefined = undefined tile_level_listener = () => this.data_source_show.flush() private savedCenter: L.LatLng | null = null; private savedZoom: number | null = null; - intervalId: number | null = null; - autoRefreshTimer = null; // 用于存储定时器ID - refreshInterval : number = 1; // 用于存储定时器ID + autoRefreshTimer: ReturnType | null = null; - private is_click_on_map_entity(e: L.LeafletMouseEvent): boolean { - const target = e.originalEvent.target; - if (!(target instanceof Element)) { - return false; - } - - return Boolean(target.closest([ - ".leaflet-marker-icon", - ".leaflet-interactive", - ".leaflet-popup", - ".leaflet-tooltip", - ".leaflet-control", - ].join(","))); - } - - private clear_active_aircraft_by_map_click = (e: L.LeafletMouseEvent) => { - - console.log(e); + private clear_active_aircraft_by_map_click = () => { const had_context_menu = this.context_menu !== null; this.context_menu = null; - // if (this.is_click_on_map_entity(e)) { - // return; - // } - let changed = false; this.list().forEach((ds: Data_Source) => { if (!ds.active_aircraft) return; @@ -96,24 +61,15 @@ export class Leaflet_Map extends enhance.Base { on_mount: () => void = () => { - console.log("地图加载"); this.map = this.load_map(); this.load_imagery_layer(); this.map.on("click", this.clear_active_aircraft_by_map_click); this.map.on("zoomend", this.tile_level_listener); - this.map.whenReady(() => { - - }); - - if (this.savedCenter && this.savedZoom) { + if (this.savedCenter && this.savedZoom !== null) { this.map.setView(this.savedCenter, this.savedZoom); } - - if (this.data_source_config) { - const ds_list = this.data_source_config.all(); - ds_list.forEach((ds: Data_Source) => { - if (!ds.enable) return; // ✅ 跳过当前项 + this.data_source_config.enabled().forEach((ds: Data_Source) => { for (const aircraft of ds.aircraftMap.values()) { aircraft.on_mount(); } @@ -124,16 +80,8 @@ export class Leaflet_Map extends enhance.Base { - this.autoRefreshTimer = setInterval(() => { - if (this.data_source_config){ - const ds_list = this.data_source_config.all(); - ds_list.forEach((ds: Data_Source) => { - if (!ds.enable) return; // ✅ 跳过当前项 - ds.refresh() - }); - } - - }, this.refreshInterval * 1000); // 以秒为单位 + this.autoRefreshTimer = setInterval(() => + this.data_source_config?.enabled().forEach(ds => ds.refresh()), 1000); this.flush(); @@ -143,7 +91,6 @@ export class Leaflet_Map extends enhance.Base { on_un_mount: () => void = () => { - console.log("地图卸载"); if (this.map) { this.savedCenter = this.map.getCenter(); this.savedZoom = this.map.getZoom(); @@ -156,7 +103,7 @@ export class Leaflet_Map extends enhance.Base { map.removeLayer(layer); }); if (this.autoRefreshTimer) { - clearInterval(this.autoRefreshTimer); // 清除定时器 + clearInterval(this.autoRefreshTimer); this.autoRefreshTimer = null; } this.map = null @@ -219,62 +166,12 @@ export class Leaflet_Map extends enhance.Base { a.drawer.toggleOpen() } - if (a.selected_key === a.DETAILED_INFORMATION) { - axios.post(`${baseURL}/get_aircraft_detail_info`, { - data_source_key: that.data_source.key, - icao: that.icao, - }).then(res => { - a.set_detailed_information(res.data); - a.flush(); - }) - } - if (a.selected_key === a.BASE_INFORMATION) { - axios.post(`${baseURL}/get_aircraft_base_info`, { - data_source_key: that.data_source.key, - icao: that.icao, - }).then(res => { - a.set_base_information(res.data); - a.flush(); - }) - } + a.refresh(a.selected_key); } - timer - mlat_source: Data_Source = new Data_Source(); - mlat: Map = new Map(); - - constructor() { - super(); - this.mlat_source.key = "mlat_source"; - - // this.timer = setInterval(() => { - // this.mlat_source.aircraft_show = true; - // axios.post(`${baseURL}/get_mlat_list`, {}) - // .then(res => { - // - // //console.log(res.data); - // res.data.forEach(item => { - // //console.log(item); - // let mlat_air = this.mlat.get(item.icao); - // if (!mlat_air) { - // mlat_air = new Aircraft(item.icao, this.mlat_source) - // mlat_air.mlat_show = true; - // this.mlat.set(item.icao, mlat_air); - // } - // - // mlat_air.add_malt_data(item); - // - // }); - // }) - // .catch(err => { - // console.error("请求出错:", err); - // }); - // }, 1000); - } - INIT_EXTENT = L.latLngBounds( [36.42, 119.98], // 西南角: [38.40, 122.56] // 东北角: @@ -284,24 +181,14 @@ export class Leaflet_Map extends enhance.Base { const MAP_CENTER: [number, number] = [34.3227, 118.5525]; - // Leaflet 默认图标路径(你原来需要的话保留) - L.Icon.Default.imagePath = `http://${host}/images/`; - - // 1) 创建地图(等价 MapContainer) + L.Icon.Default.imagePath = "/images/"; const map = L.map("map", { center: MAP_CENTER, zoom: 3, - // minZoom: 3, - // maxZoom: 9, zoomControl: false, attributionControl: false, doubleClickZoom: false, - // preferCanvas: true, // 可选 }); - - // 3) 等价 MapContent: fitBounds + on_mount - // INIT_EXTENT 必须是 Leaflet 接受的 bounds 格式: - // [[southLat, westLng], [northLat, eastLng]] map.fitBounds(this.INIT_EXTENT, { padding: [50, 50], maxZoom: 10, @@ -366,22 +253,12 @@ export class Leaflet_Map extends enhance.Base { this.flush(); } change_tile_display_maximum_level(value: number | null) { - this.tile_display_level_draft = value; - if (value === null) { - this.flush(); - return; - } + if (value === null) return; const config = this.map_view_config || default_map_view_config; this.map_view_config = {...config, map2d: {...config.map2d, tile_display_maximum_level: value}}; this.set_imagery_layer(); this.flush(); } - blur_tile_display_maximum_level() { - if (this.tile_display_level_draft !== undefined) { - this.tile_display_level_draft = undefined; - this.flush(); - } - } async save_current_map_view() { const config = this.map_view_config || default_map_view_config; this.map_view_config = await save_map_view_config({ diff --git a/src/Map/Map_Models.tsx b/src/Map/Map_Models.tsx index 69b78c8..3e031a7 100644 --- a/src/Map/Map_Models.tsx +++ b/src/Map/Map_Models.tsx @@ -27,6 +27,6 @@ export async function upload_map_model(model_type: string, file: File, aircraft_ return response.data; } -export function emit_map_model_config(config: Map_Model_Config) { +function emit_map_model_config(config: Map_Model_Config) { window.dispatchEvent(new CustomEvent(map_model_config_event, {detail: config})); } diff --git a/src/Map/Map_Resources.tsx b/src/Map/Map_Resources.tsx index 44efecb..c0f39a9 100644 --- a/src/Map/Map_Resources.tsx +++ b/src/Map/Map_Resources.tsx @@ -1,8 +1,8 @@ import axios from "axios"; export type Map_Tile_Y_Axis = "xyz" | "tms" -export type Map_Tile_Projection = "web_mercator" -export type Map_Tile_Encoding = "terrarium" | string +type Map_Tile_Projection = "web_mercator" +type Map_Tile_Encoding = "terrarium" | string export type Map_Tile_Type = "imagery" | "terrain" export type Map_Tile_Metadata = { url: string @@ -42,7 +42,7 @@ export function terrain_source_for_key(resources: Map_Resources_Metadata, key: s if (!source) throw new Error("No terrain source configured"); return source; } -export function tile_sources_for_type(resources: Map_Resources_Metadata, tile_type: Map_Tile_Type): Map_Imagery_Source_Metadata[] { +function tile_sources_for_type(resources: Map_Resources_Metadata, tile_type: Map_Tile_Type): Map_Imagery_Source_Metadata[] { return tile_type === "terrain" ? resources.terrain_sources : resources.imagery_sources; } export function tile_source_options(resources: Map_Resources_Metadata | null, tile_type: Map_Tile_Type) { diff --git a/src/Map/Map_View.tsx b/src/Map/Map_View.tsx index bda8fe7..13d5405 100644 --- a/src/Map/Map_View.tsx +++ b/src/Map/Map_View.tsx @@ -5,7 +5,6 @@ export type Map_Camera_View = Record export type Map_Tile_View_Config = Record export type Map_View_Config = Record -export const default_map_camera_view: Map_Camera_View = {}; export const default_map_view_config: Map_View_Config = {map2d: {}, map3d: {}, camera: {}}; export async function load_map_view_config(): Promise { diff --git a/src/Map/Tree_Show.tsx b/src/Map/Tree_Show.tsx index 8f8860f..28fc8f5 100644 --- a/src/Map/Tree_Show.tsx +++ b/src/Map/Tree_Show.tsx @@ -1,404 +1,121 @@ -import React from "react"; +import React from "react"; +import {message, Tree, type TreeProps} from "antd"; import enhance from "../core/enhance.tsx"; -import {message, Tree, TreeProps} from "antd"; type TreeData = NonNullable; type TreeNode = TreeData[number]; +function stop_event(event: React.SyntheticEvent) { + event.preventDefault(); + event.stopPropagation(); +} + export class Tree_Show extends enhance.Base { expandedKeys: React.Key[] = []; - selectedKeys: React.Key[] = []; - checkedKeys: TreeProps["checkedKeys"] = []; - private treeData: TreeProps["treeData"] = []; private jsonData: unknown = null; - private uniq(keys: React.Key[]): React.Key[] { - return Array.from(new Set(keys)); - } - - private findNode( - nodes: TreeProps["treeData"] | undefined, - key: React.Key - ): TreeNode | undefined { + private find_node(nodes: TreeProps["treeData"] | undefined, key: React.Key): TreeNode | undefined { for (const node of nodes ?? []) { - if (node.key === key) { - return node; - } - - const found = this.findNode( - node.children as TreeProps["treeData"] | undefined, - key - ); - - if (found) { - return found; - } + if (node.key === key) return node; + const found = this.find_node(node.children as TreeProps["treeData"] | undefined, key); + if (found) return found; } - return undefined; } - /** - * 当前节点是否已经展开 - */ - private isExpanded(key: React.Key): boolean { - return this.expandedKeys.includes(key); - } - - /** - * 收集当前节点 + 所有子孙节点中可展开的 key - */ - private collectSelfAndChildrenKeys(node: TreeNode): React.Key[] { + private expandable_keys(node: TreeNode, include_self: boolean): React.Key[] { const result: React.Key[] = []; - - const walk = (current: TreeNode) => { + const walk = (current: TreeNode, include: boolean) => { const children = current.children as TreeNode[] | undefined; - - if (children?.length) { - result.push(current.key); - - for (const child of children) { - walk(child); - } - } + if (!children?.length) return; + if (include) result.push(current.key); + for (const child of children) walk(child, true); }; - - walk(node); - + walk(node, include_self); return result; } - /** - * 收集所有子孙节点中可展开的 key,不包含自己 - */ - private collectChildrenKeys(node: TreeNode): React.Key[] { - const result: React.Key[] = []; - - const walk = (children?: TreeNode[]) => { - for (const child of children ?? []) { - const childChildren = child.children as TreeNode[] | undefined; - - if (childChildren?.length) { - result.push(child.key); - walk(childChildren); - } - } - }; - - walk(node.children as TreeNode[] | undefined); - - return result; - } - private jsonValue(key: React.Key): unknown { + private json_value(key: React.Key): unknown { let value = this.jsonData; for (const part of String(key).split("-")) { const index = Number(part); - if (Array.isArray(value)) { - value = value[index]; - } else if (value && typeof value === "object") { - value = Object.values(value as Record)[index]; - } else { - return value; - } + if (Array.isArray(value)) value = value[index]; + else if (value && typeof value === "object") value = Object.values(value as Record)[index]; + else break; } return value; } - private copyJson(key: React.Key, event: React.MouseEvent) { - event.preventDefault(); - event.stopPropagation(); - navigator.clipboard.writeText(JSON.stringify(this.jsonValue(key), null, 2) ?? "null").then(() => { + + private copy_json(key: React.Key, event: React.MouseEvent) { + stop_event(event); + void navigator.clipboard.writeText(JSON.stringify(this.json_value(key), null, 2) ?? "null").then(() => { message.success("JSON已复制"); }); } - /** - * 左侧按钮: - * 只展开 / 收起当前节点 - * - * 展开当前节点时,会清掉它下面所有子孙 expanded key。 - * 这样不会出现“点自己展开,下面之前展开过的子节点也跟着展开”的问题。 - */ - private toggleSelf(key: React.Key, event: React.MouseEvent) { - event.preventDefault(); - event.stopPropagation(); - - const node = this.findNode(this.treeData, key); - - if (!node) { - return; - } - - const childrenKeys = new Set(this.collectChildrenKeys(node)); - - if (this.isExpanded(key)) { - /** - * 收起当前节点: - * 当前节点和所有子孙节点都从 expandedKeys 移除。 - */ - this.expandedKeys = this.expandedKeys.filter( - item => item !== key && !childrenKeys.has(item) - ); - } else { - /** - * 只展开当前节点: - * 加入当前节点 key,同时移除子孙展开 key。 - */ - this.expandedKeys = this.uniq([ - ...this.expandedKeys.filter(item => !childrenKeys.has(item)), - key, - ]); - } - + private toggle_self(key: React.Key, event: React.MouseEvent) { + stop_event(event); + const node = this.find_node(this.treeData, key); + if (!node) return; + const next = new Set(this.expandedKeys); + for (const descendant of this.expandable_keys(node, false)) next.delete(descendant); + if (next.has(key)) next.delete(key); + else next.add(key); + this.expandedKeys = [...next]; this.flush(); } - /** - * 判断当前节点及其所有子孙是否全部展开 - */ - private isSelfAndChildrenExpanded(key: React.Key): boolean { - const node = this.findNode(this.treeData, key); - - if (!node) { - return false; - } - - const keys = this.collectSelfAndChildrenKeys(node); - const expandedKeySet = new Set(this.expandedKeys); - - return keys.length > 0 && keys.every(item => expandedKeySet.has(item)); - } - - /** - * 右侧按钮: - * 全部展开 / 全部收起 - */ - private toggleAllChildren(key: React.Key, event: React.MouseEvent) { - event.preventDefault(); - event.stopPropagation(); - - const node = this.findNode(this.treeData, key); - - if (!node) { - return; - } - - const keys = this.collectSelfAndChildrenKeys(node); - const keySet = new Set(keys); - - const isAllExpanded = keys.every(item => this.expandedKeys.includes(item)); - - if (isAllExpanded) { - /** - * 全部收起: - * 当前节点 + 所有子孙节点全部移除。 - */ - this.expandedKeys = this.expandedKeys.filter( - item => !keySet.has(item) - ); - } else { - /** - * 全部展开: - * 当前节点 + 所有子孙节点全部加入。 - */ - this.expandedKeys = this.uniq([ - ...this.expandedKeys, - ...keys, - ]); - } - + private toggle_tree(key: React.Key, event: React.MouseEvent) { + stop_event(event); + const node = this.find_node(this.treeData, key); + if (!node) return; + const keys = this.expandable_keys(node, true); + const next = new Set(this.expandedKeys); + if (keys.every(item => next.has(item))) keys.forEach(item => next.delete(item)); + else keys.forEach(item => next.add(item)); + this.expandedKeys = [...next]; this.flush(); } - /** - * 自定义 switcherIcon。 - * - * 注意: - * 根 span 也做 stopPropagation。 - * 这样即使点到两个按钮之间的空隙,也不会触发 antd 自带展开逻辑。 - */ - private renderSwitcherIcon = (nodeProps: any): React.ReactNode => { - const key: React.Key = nodeProps.eventKey ?? nodeProps.key; - const isLeaf = Boolean(nodeProps.isLeaf); + private button(title: string, text: string, onClick: (event: React.MouseEvent) => void) { + return ; + } - if (isLeaf) { - return ( - - - - ); + private render_switcher = (node: any): React.ReactNode => { + const key: React.Key = node.eventKey ?? node.key; + if (node.isLeaf) { + return + {this.button("复制JSON", "⧉", event => this.copy_json(key, event))} + ; } - - const expanded = this.isExpanded(key); - const isAllExpanded = this.isSelfAndChildrenExpanded(key); - - return ( - { - event.preventDefault(); - event.stopPropagation(); - }} - onClick={(event) => { - event.preventDefault(); - event.stopPropagation(); - }} - > - - - - - - ); - }; - - onSelect: TreeProps["onSelect"] = (selectedKeys) => { - this.selectedKeys = selectedKeys; - this.flush(); - }; - - onCheck: TreeProps["onCheck"] = (checkedKeys) => { - this.checkedKeys = checkedKeys; - this.flush(); - }; - - /** - * 理论上,鼠标点击 switcher 已经被我们拦截了。 - * - * 这里保留 onExpand,是为了兼容: - * 1. 键盘操作; - * 2. antd 内部其他方式触发展开; - * 3. 外部 props 控制展开。 - */ - onExpand: TreeProps["onExpand"] = (expandedKeys) => { - this.expandedKeys = expandedKeys as React.Key[]; - this.flush(); + const expanded = this.expandedKeys.includes(key); + const branch_keys = this.expandable_keys(this.find_node(this.treeData, key)!, true); + const all_expanded = branch_keys.every(item => this.expandedKeys.includes(item)); + return + {this.button(expanded ? "收起当前节点" : "展开当前节点", expanded ? "▼" : "▶", + event => this.toggle_self(key, event))} + {this.button(all_expanded ? "全部收起" : "全部展开", all_expanded ? "⤴" : "⤵", + event => this.toggle_tree(key, event))} + {this.button("复制JSON", "⧉", event => this.copy_json(key, event))} + ; }; render(props: TreeProps & {jsonData?: unknown}): React.JSX.Element { const {jsonData, ...treeProps} = props; this.treeData = treeProps.treeData ?? []; this.jsonData = jsonData ?? null; - - return ( - <> - - - - - ); + return <> + + { this.expandedKeys = keys as React.Key[]; this.flush(); }}/> + ; } } diff --git a/src/Map/WebGL_Support.ts b/src/Map/WebGL_Support.ts index bc53468..71e6339 100644 --- a/src/Map/WebGL_Support.ts +++ b/src/Map/WebGL_Support.ts @@ -18,7 +18,7 @@ function create_webgl_context(canvas: HTMLCanvasElement, name: WebGL_Context_Nam return null; } } -export function can_create_webgl_context(): boolean { +function can_create_webgl_context(): boolean { if (typeof document === "undefined") return false; const canvas = document.createElement("canvas"); const options: WebGL_Context_Options = {alpha: false, antialias: false, depth: true, stencil: false, failIfMajorPerformanceCaveat: true, powerPreference: "high-performance"}; diff --git a/src/PersistentScroll.tsx b/src/PersistentScroll.tsx index 595a603..312fcd4 100644 --- a/src/PersistentScroll.tsx +++ b/src/PersistentScroll.tsx @@ -1,62 +1,22 @@ -import React, {useLayoutEffect, useRef} from 'react'; - +import type {CSSProperties, ReactNode} from "react"; +import {useLayoutEffect, useRef} from "react"; type PersistentScrollProps = { - children: React.ReactNode; - - /** 用于区分不同内容的 key(tab / 模块) */ + children: ReactNode; scrollKey: string; - - /** 可选:监听滚动变化 */ - onScrollChange?: (scrollTop: number) => void; - - style?: React.CSSProperties; + style?: CSSProperties; }; -/** 组件级滚动缓存(不落地、不跨刷新) */ const scrollCache: Record = {}; -export const PersistentScroll: React.FC = ({ - children, - scrollKey, - onScrollChange, - style, - }) => { +export function PersistentScroll({children, scrollKey, style}: PersistentScrollProps) { const ref = useRef(null); - - /** 渲染后恢复滚动位置 */ useLayoutEffect(() => { - const el = ref.current; - if (!el) return; - - const top = scrollCache[scrollKey] ?? 0; - el.scrollTop = top; + if (ref.current) ref.current.scrollTop = scrollCache[scrollKey] ?? 0; }, [scrollKey]); - - /** 记录滚动 */ - const handleScroll = () => { - const el = ref.current; - if (!el) return; - - scrollCache[scrollKey] = el.scrollTop; - onScrollChange?.(el.scrollTop); - }; - - return ( - -
- {children} -
- ); -}; + return
scrollCache[scrollKey] = event.currentTarget.scrollTop} + style={{gap: 8, width: "100%", height: "100%", overflow: "auto", ...style}}> + {children} +
; +} diff --git a/src/Refresh.tsx b/src/Refresh.tsx index e3fc0b7..0ff8d48 100644 --- a/src/Refresh.tsx +++ b/src/Refresh.tsx @@ -1,140 +1,80 @@ -import {Button, Form, Input, Modal, Switch} from "antd"; -import enhance from "./core/enhance.tsx"; import {ReloadOutlined, SettingOutlined} from "@ant-design/icons"; +import {Button, Form, Input, Modal, Switch} from "antd"; +import type {ChangeEvent} from "react"; +import enhance from "./core/enhance.tsx"; export class Refresh extends enhance.Base { - isAutoRefresh : boolean = true; - isModalVisible : boolean = false; - refreshInterval : number = 1; - autoRefreshTimer = null; // 用于存储定时器ID - refresh : () => void + isAutoRefresh = true; + isModalVisible = false; + refreshInterval = 1; + autoRefreshTimer: ReturnType | null = null; - constructor(refresh : () => void) { + constructor(readonly refresh: () => void) { super(); - this.refresh = refresh; } on_mount() { - this.toggleAutoRefresh(this.isAutoRefresh) + this.toggleAutoRefresh(this.isAutoRefresh); } on_un_mount() { - if (this.isAutoRefresh) { - this.stopAutoRefresh(); - } + this.stopAutoRefresh(); } - // 切换自动刷新模式 - toggleAutoRefresh = (checked) => { + toggleAutoRefresh = (checked: boolean) => { this.isAutoRefresh = checked; - if (this.isAutoRefresh) { - this.startAutoRefresh(); - } else { - this.stopAutoRefresh(); - } - this.flush(); // 更新界面 - } + if (checked) this.startAutoRefresh(); + else this.stopAutoRefresh(); + this.flush(); + }; - // 启动自动刷新定时器 startAutoRefresh = () => { - if (this.autoRefreshTimer) { - clearInterval(this.autoRefreshTimer); // 清除之前的定时器 - } + this.stopAutoRefresh(); + this.autoRefreshTimer = setInterval(this.refresh, this.refreshInterval * 1000); + }; - // 每隔 refreshInterval 秒进行一次刷新 - this.autoRefreshTimer = setInterval(() => { - this.refresh(); // 调用手动刷新的方法 - }, this.refreshInterval * 1000); // 以秒为单位 - } - - // 停止自动刷新定时器 stopAutoRefresh = () => { - if (this.autoRefreshTimer) { - clearInterval(this.autoRefreshTimer); // 清除定时器 - this.autoRefreshTimer = null; - } - } + if (this.autoRefreshTimer) clearInterval(this.autoRefreshTimer); + this.autoRefreshTimer = null; + }; - // 显示设置对话框 showModal = () => { this.isModalVisible = true; - this.flush(); // 更新界面 + this.flush(); }; - // 关闭设置对话框 - handleCancel = () => { + closeModal = () => { this.isModalVisible = false; - this.flush(); // 更新界面 + this.flush(); }; - // 确认设置并关闭对话框 handleOk = () => { - this.isModalVisible = false; - this.flush(); // 更新界面 - if (this.isAutoRefresh) { - this.startAutoRefresh(); // 如果启用了自动刷新,启动定时器 - } else { - this.stopAutoRefresh(); // 否则停止定时器 - } + this.closeModal(); + if (this.isAutoRefresh) this.startAutoRefresh(); }; - // 输入框值的变化 - handleIntervalChange = (e) => { - this.refreshInterval = Number(e.target.value); - this.flush(); // 更新界面 - if (this.isAutoRefresh) { - this.startAutoRefresh(); // 如果启用了自动刷新,重新启动定时器 - } + handleIntervalChange = (event: ChangeEvent) => { + this.refreshInterval = Math.max(1, Number(event.target.value) || 1); + if (this.isAutoRefresh) this.startAutoRefresh(); + this.flush(); }; render() { - return ( -
- - - - - {/* 设置对话框 */} - -
- - - - - - -
-
-
- ); + return
+
; } } diff --git a/src/Setting.tsx b/src/Setting.tsx index 1214905..12be5ca 100644 --- a/src/Setting.tsx +++ b/src/Setting.tsx @@ -3,6 +3,6 @@ import {Adminive_Settings} from "./Adminive/Adminive_Settings.tsx" import {PersistentScroll} from "./PersistentScroll.tsx" export class Settings extends enhance.Base { render() { - return
+ return } } diff --git a/src/core/enhance.tsx b/src/core/enhance.tsx index d06c8fd..54d1dce 100644 --- a/src/core/enhance.tsx +++ b/src/core/enhance.tsx @@ -1,72 +1,33 @@ -import React, {useEffect, useState} from "react"; - -let _flush = () => { - -} +import {useEffect, useState} from "react"; +const idle_flush = () => {}; class Base { - mounted: boolean = false; - flush = _flush + mounted = false; + flush = idle_flush; + on_mount() {} + on_un_mount() {} + use_hook() {} + render(_props?: any) { return
==Base==
; } - - before_first_render() { - }; - - on_mount() { - }; - - on_un_mount() { - }; - - // 用于使用自定义hook - use_hook() { - } - - // 这些函数不用箭头函数是为了父类可以通过super调用 - render(_props?: any) { - return
==Base==
- } - - - // x是一个函数组件,x代表jsx或者tsx 这个目前只能写成箭头函数不知道为什么 x = (props: any) => { - let [flush, setFlush] = useState(0); - if (this.flush === _flush) { - this.flush = (): void => { - if (flush !== Number.MAX_SAFE_INTEGER) { - // 使用函数式更新来确保每次更新都基于最新的 `prevFlush` 值。 - // 这样可以避免由于 React 状态更新是异步的而导致的状态不一致问题。 - // `prevFlush` 是当前最新的 `flush` 状态,它会被 React 自动传递给更新函数,确保每次都使用最新的状态值。 - // 通过返回 `prevFlush + 1`,更新后的 `flush` 状态将会是前一个值加 1。 - setFlush((prevFlush: number): number => prevFlush + 1); - } else { - setFlush(0); - } - }; - this.before_first_render(); + const [, set_revision] = useState(0); + if (this.flush === idle_flush) { + this.flush = () => set_revision(value => value === Number.MAX_SAFE_INTEGER ? 0 : value + 1); } this.use_hook(); useEffect(() => { this.mounted = true; this.on_mount(); - return () => { this.mounted = false; this.on_un_mount(); - this.flush = _flush; + this.flush = idle_flush; }; }, []); return this.render(props); } } - - -export default { - Base -}; - - - +export default {Base};