635 lines
22 KiB
TypeScript
635 lines
22 KiB
TypeScript
import React from "react";
|
||
import enhance from "../core/enhance.tsx";
|
||
import {Button, InputNumber, message, Select, Space, Switch, Tag, Typography} from "antd";
|
||
import axios from "axios";
|
||
import {baseURL, darkenColor, lightenColor, Prefix, row_style, server_url, StateHolder} from "../Global.tsx";
|
||
import {Base_Station} from "../Map/Base_Station.tsx";
|
||
import {Aircraft, Aircraft_Track_Point} from "../Map/Aircraft.tsx";
|
||
import {app} from "../App.tsx";
|
||
import {Leaflet_Map} from "../Map/Leaflet_Map.tsx";
|
||
import {Data_Format, Input_Number, Input_Port_Number, Input_String, Play_Mode, Switch_Bool} from "../A_Global.tsx";
|
||
import {Data_Source_Show} from "./Data_Source_Show.tsx";
|
||
import {Camera_Control_Mode} from "../Map/Camera_Control_Mode.ts";
|
||
import L from "leaflet";
|
||
import {State_Shower} from "../State_Shower.tsx";
|
||
|
||
const {Text} = Typography;
|
||
const {Option} = Select;
|
||
|
||
export type Map_Display_Mode = "map2d" | "map3d"
|
||
export type Data_Source_Map_Display_Data = Record<string, any>
|
||
export type Data_Source_Map_Display_Config = Record<Map_Display_Mode, Data_Source_Map_Display_Data>
|
||
export class Data_Source extends enhance.Base {
|
||
timer
|
||
|
||
state: State_Shower = new State_Shower(`${baseURL}/get_data_source_state`, () => this.key);
|
||
|
||
// 数据
|
||
key: string = ""
|
||
adminive_id: number = 0
|
||
enable: boolean = false;
|
||
type: string = ""; // 也就是类型名
|
||
map_display: Data_Source_Map_Display_Config = {map2d: {}, map3d: {}};
|
||
lat: number = 0;
|
||
lon: number = 0;
|
||
alt: number = 0;
|
||
base_station_has_valid_position: boolean = false;
|
||
base_station_adsb_range_meters: number = 0;
|
||
base_station_adsb_target_altitude_meters: number = 0;
|
||
update_form_gps: boolean = false;
|
||
ignore_msg_time: boolean = false;
|
||
|
||
backend_config_fields: string[] = [];
|
||
backend_editable_fields: string[] = [];
|
||
|
||
constructor() {
|
||
super();
|
||
}
|
||
normalize_map_display() {
|
||
const display = this.map_display && typeof this.map_display === "object" ? this.map_display : {} as Data_Source_Map_Display_Config;
|
||
display.map2d = display.map2d && typeof display.map2d === "object" ? display.map2d : {};
|
||
display.map3d = display.map3d && typeof display.map3d === "object" ? display.map3d : {};
|
||
this.map_display = display;
|
||
}
|
||
map_style(mode: Map_Display_Mode): Data_Source_Map_Display_Data {
|
||
return this.map_display[mode];
|
||
}
|
||
map2d_style(): Data_Source_Map_Display_Data {
|
||
return this.map_style("map2d");
|
||
}
|
||
map3d_style(): Data_Source_Map_Display_Data {
|
||
return this.map_style("map3d");
|
||
}
|
||
refresh_display(mode: Map_Display_Mode) {
|
||
if (mode === "map2d") {
|
||
this.refresh_base_station();
|
||
this.refresh_aircraft_list();
|
||
} else {
|
||
app.cesium_map?.request_sync_data_sources();
|
||
}
|
||
}
|
||
|
||
// 其他数据
|
||
base_station: Base_Station = new Base_Station(this);
|
||
aircraftMap: Map<string, Aircraft> = new Map()
|
||
// intervalId: number;
|
||
active_aircraft: Aircraft = null;
|
||
manual_track_icao_set: Set<string> = new Set();
|
||
monitor_all_aircraft_mode: boolean = false;
|
||
aircraft_change_versions: Map<string, string> = new Map();
|
||
connect_list: [] = []
|
||
first: boolean = true;
|
||
|
||
|
||
keep_mode: boolean = false;
|
||
|
||
confirm() {
|
||
this.normalize_map_display()
|
||
const payload: Record<string, unknown> = {}
|
||
const value = this as any;
|
||
for (const field of this.backend_editable_fields) {
|
||
if (field === "config") {
|
||
payload.config = Object.fromEntries(this.backend_config_fields.map(name => [name, value[name]]));
|
||
} else {
|
||
payload[field] = value[field];
|
||
}
|
||
}
|
||
axios.patch(`/api/adminive/config/data_sources/${this.adminive_id}`, payload).then(() => {
|
||
this.refresh_display("map2d")
|
||
this.refresh_display("map3d")
|
||
this.flush()
|
||
message.success(`${this.type}类型,数据接收源${this.key}设置成功!`)
|
||
}).catch(error => {
|
||
message.error(error.response?.data?.msg || "数据源设置失败")
|
||
})
|
||
}
|
||
|
||
toJSON() {
|
||
const {active_aircraft, base_station, aircraftMap, manual_track_icao_set, monitor_all_aircraft_mode, connect_list, first, backend_config_fields, backend_editable_fields, ...rest} = this;
|
||
void backend_config_fields;
|
||
void backend_editable_fields;
|
||
return rest; // 返回去除这些属性后的对象
|
||
}
|
||
locate_base_station() {
|
||
if (!this.base_station_has_valid_position) {
|
||
message.warning("当前基站没有有效位置");
|
||
return;
|
||
}
|
||
if (app.cesium_map) {
|
||
app.cesium_map.fly_to_base_station(this);
|
||
return;
|
||
}
|
||
app.leaflet_map?.map?.setView([this.lat, this.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.key) {
|
||
await this.refresh_base_station_location();
|
||
}
|
||
aircraft_stream.ensure_open();
|
||
aircraft_stream.schedule_subscribe();
|
||
if (this.active_aircraft && !this.is_aircraft_track_monitored(this.active_aircraft.icao)) {
|
||
aircraft_stream.schedule_subscribe();
|
||
}
|
||
}
|
||
manual_track_icao_list(): string[] {
|
||
return Array.from(this.manual_track_icao_set).sort();
|
||
}
|
||
is_manual_tracking_aircraft(icao: string): boolean {
|
||
return this.manual_track_icao_set.has(icao);
|
||
}
|
||
is_aircraft_track_monitored(icao: string): boolean {
|
||
return this.monitor_all_aircraft_mode || this.is_manual_tracking_aircraft(icao);
|
||
}
|
||
request_aircraft_stream_update() {
|
||
aircraft_stream.send_subscription();
|
||
}
|
||
async set_manual_tracking_aircraft(icao: string, enabled: boolean) {
|
||
if (enabled) {
|
||
this.manual_track_icao_set.add(icao);
|
||
} else {
|
||
this.manual_track_icao_set.delete(icao);
|
||
const aircraft = this.aircraftMap.get(icao);
|
||
aircraft?.hide_path();
|
||
}
|
||
aircraft_stream.send_subscription();
|
||
this.refresh_manual_tracking_views();
|
||
}
|
||
async set_monitor_all_aircraft_mode(enabled: boolean) {
|
||
this.monitor_all_aircraft_mode = enabled;
|
||
if (!enabled) {
|
||
for (const aircraft of this.aircraftMap.values()) {
|
||
if (!this.manual_track_icao_set.has(aircraft.icao)) {
|
||
aircraft.hide_path();
|
||
}
|
||
}
|
||
}
|
||
aircraft_stream.send_subscription();
|
||
this.refresh_manual_tracking_views();
|
||
}
|
||
clear_manual_tracking_aircraft(icao?: string) {
|
||
if (icao) {
|
||
this.manual_track_icao_set.delete(icao);
|
||
this.aircraftMap.get(icao)?.hide_path();
|
||
} else {
|
||
for (const tracked_icao of this.manual_track_icao_set) {
|
||
if (!this.monitor_all_aircraft_mode) this.aircraftMap.get(tracked_icao)?.hide_path();
|
||
}
|
||
this.manual_track_icao_set.clear();
|
||
}
|
||
aircraft_stream.send_subscription();
|
||
this.refresh_manual_tracking_views();
|
||
}
|
||
clear_all_aircraft_tracking() {
|
||
this.monitor_all_aircraft_mode = false;
|
||
for (const aircraft of this.aircraftMap.values()) {
|
||
aircraft.hide_path();
|
||
}
|
||
this.manual_track_icao_set.clear();
|
||
aircraft_stream.send_subscription();
|
||
this.refresh_manual_tracking_views();
|
||
}
|
||
refresh_manual_tracking_views() {
|
||
app.cesium_map?.request_sync_data_sources();
|
||
app.leaflet_map?.data_source_show?.flush();
|
||
}
|
||
|
||
async refresh_base_station_location() {
|
||
try {
|
||
const response = await axios.post(`${baseURL}/get_base_station_location`, {
|
||
data_source_key: this.key
|
||
});
|
||
|
||
let d = response.data;
|
||
this.base_station.set_popup(this.key, d);
|
||
const gps_has_valid_position = Boolean(d.has_valid_position ?? d["GPS有效定位"]);
|
||
this.base_station_adsb_range_meters = Number(d.adsb_theoretical_detection_range_meters || 0);
|
||
this.base_station_adsb_target_altitude_meters = Number(d.adsb_theoretical_target_altitude_meters || 0);
|
||
if (this.update_form_gps) {
|
||
this.base_station_has_valid_position = gps_has_valid_position;
|
||
if (gps_has_valid_position) {
|
||
this.lat = d.latitude;
|
||
this.lon = d.longitude;
|
||
this.alt = d.height;
|
||
}
|
||
let dss: Data_Source_Show | null = app.leaflet_map?.data_source_show ?? null;
|
||
if (dss?.mounted) {
|
||
dss.flush();
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error("refresh base station location failed:", err);
|
||
} finally {
|
||
this.base_station.set_pos(this.lat, this.lon, this.base_station_has_valid_position);
|
||
let ds = app.get_Data_Source(this.key)
|
||
ds?.refresh_base_station();
|
||
this.first = false;
|
||
}
|
||
}
|
||
|
||
|
||
refresh_aircraft_list() {
|
||
for (const [key, aircraft] of this.aircraftMap) {
|
||
aircraft.refresh()
|
||
}
|
||
}
|
||
|
||
refresh_base_station() {
|
||
const map = app.leaflet_map?.map;
|
||
if (!map) return;
|
||
const style = this.map2d_style();
|
||
if (this.enable) {
|
||
if (style.base_station_show && this.base_station_has_valid_position) {
|
||
this.base_station.set_color(darkenColor(style.base_station_color, 0.5))
|
||
this.base_station.show(map);
|
||
this.base_station.set_pos(this.lat, this.lon, this.base_station_has_valid_position);
|
||
this.base_station.on_mount()
|
||
} else {
|
||
this.base_station.hide();
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
get_aircraft(icao: string): Aircraft {
|
||
if (!this.aircraftMap.has(icao)) {
|
||
return null;
|
||
}
|
||
return this.aircraftMap.get(icao)!;
|
||
}
|
||
create_or_get_aircraft(item: any): Aircraft {
|
||
let aircraft: Aircraft = this.get_aircraft(item.icao);
|
||
if (aircraft == null) {
|
||
aircraft = new Aircraft(item.icao, this);
|
||
aircraft.refresh();
|
||
}
|
||
return aircraft;
|
||
}
|
||
apply_aircraft_items(items: any[]) {
|
||
items.forEach((item: any) => {
|
||
const aircraft = this.create_or_get_aircraft(item);
|
||
aircraft.refresh_from_change_list(item);
|
||
this.aircraftMap.set(item.icao, aircraft);
|
||
this.aircraft_change_versions.set(item.icao, String(item.change_version || ""));
|
||
});
|
||
this.refresh_aircraft_range();
|
||
}
|
||
remove_aircraft(icao: string) {
|
||
const aircraft = this.aircraftMap.get(icao);
|
||
if (!aircraft) return;
|
||
aircraft.hide_track()
|
||
aircraft.pos_marker.remove();
|
||
this.aircraftMap.delete(icao);
|
||
this.aircraft_change_versions.delete(icao);
|
||
if (this.active_aircraft === aircraft) {
|
||
this.active_aircraft = null;
|
||
}
|
||
}
|
||
refresh_aircraft_range() {
|
||
let station = this.base_station;
|
||
let max_distance = 0;
|
||
let max_plane_pos: L.LatLng | null = null;
|
||
let max_aircraft_icao = "";
|
||
this.aircraftMap.forEach((aircraft) => {
|
||
let lat = aircraft.data_model.lat;
|
||
let lon = aircraft.data_model.lon;
|
||
if (station.pos) {
|
||
let distance = station.get_distance_from_base_station(lat, lon);
|
||
if (distance > max_distance) {
|
||
max_distance = distance;
|
||
max_plane_pos = new L.LatLng(lat, lon);
|
||
max_aircraft_icao = aircraft.icao;
|
||
}
|
||
}
|
||
});
|
||
if (max_plane_pos) {
|
||
station.update_current_range(max_distance, max_plane_pos, max_aircraft_icao);
|
||
if (!this.keep_mode || !station.max_plane_pos || max_distance > station.maxDistance) {
|
||
station.update_range(max_distance, max_plane_pos);
|
||
} else {
|
||
station.refresh_max_tooltip();
|
||
}
|
||
} else {
|
||
station.clear_current_range();
|
||
if (!this.keep_mode || !station.max_plane_pos) {
|
||
station.clear_range();
|
||
}
|
||
}
|
||
}
|
||
apply_aircraft_delta(change_list: any[], removed_icaos: string[]) {
|
||
this.apply_aircraft_items(change_list);
|
||
removed_icaos.forEach((icao) => this.remove_aircraft(icao));
|
||
this.refresh_aircraft_range();
|
||
}
|
||
apply_aircraft_snapshot(items: any[]) {
|
||
const alive = new Set<string>();
|
||
items.forEach((item) => alive.add(item.icao));
|
||
for (const icao of Array.from(this.aircraftMap.keys())) {
|
||
if (!alive.has(icao)) {
|
||
this.remove_aircraft(icao);
|
||
}
|
||
}
|
||
this.apply_aircraft_items(items);
|
||
}
|
||
apply_track_stream_list(list: any[]) {
|
||
list.forEach((item: any) => {
|
||
const aircraft = this.aircraftMap.get(item.icao);
|
||
if (!aircraft) {
|
||
return;
|
||
}
|
||
aircraft.is_show_path = true;
|
||
aircraft.append_path_response(item);
|
||
});
|
||
}
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<div>不应该出现的 Data Source Center!</div>
|
||
)
|
||
}
|
||
|
||
center(): React.JSX.Element {
|
||
return (
|
||
<></>
|
||
)
|
||
}
|
||
|
||
|
||
render(props: any) {
|
||
return (
|
||
<div style={row_style}>
|
||
<Select
|
||
prefix={<Prefix label="已连接馈送点"/>}
|
||
popupMatchSelectWidth={false}
|
||
placeholder="展示所有连接"
|
||
onChange={(value) => {
|
||
console.log('你选择了:', value);
|
||
}}
|
||
value={""}
|
||
onOpenChange={(open) => {
|
||
if (open) {
|
||
axios.post(`${baseURL}/get_data_source_connect`, this).then((res) => {
|
||
this.connect_list = res.data
|
||
console.log(res.data);
|
||
this.flush();
|
||
});
|
||
} else {
|
||
|
||
}
|
||
}}
|
||
>
|
||
{this.connect_list.map((item: any) => (
|
||
<Option key={item.key} value={item.key} style={{
|
||
display: 'flex',
|
||
flexDirection: 'row',
|
||
}}>
|
||
<strong>{item.key}</strong>
|
||
</Option>
|
||
))}
|
||
</Select>
|
||
<Button type="dashed">
|
||
<Space size={6}>
|
||
<Tag color="blue" style={{marginInlineEnd: 0}}>{this.type}</Tag>
|
||
<Text type="secondary">:</Text>
|
||
<Text>{this.key}</Text>
|
||
</Space>
|
||
</Button>
|
||
{this.center_with_add()}
|
||
{this.center()}
|
||
|
||
<div style={{
|
||
display: "flex",
|
||
flexDirection: "row",
|
||
justifyContent: "space-between",
|
||
alignItems: "center",
|
||
gap: '8px',
|
||
}}>
|
||
<this.state.x/>
|
||
<Switch_Bool that={this} field={"enable"}></Switch_Bool>
|
||
<Button type="primary" autoInsertSpace onClick={() => {
|
||
this.confirm();
|
||
}}>
|
||
确定
|
||
</Button>
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
class Aircraft_Stream_Client {
|
||
ws: WebSocket | null = null;
|
||
subscribe_timer: number | null = null;
|
||
reconnect_timer: number | null = null;
|
||
ensure_open() {
|
||
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
||
return;
|
||
}
|
||
this.ws = new WebSocket(`ws://${server_url}/ws/aircraft_stream`);
|
||
this.ws.onopen = () => this.send_subscription();
|
||
this.ws.onmessage = (event) => this.handle_message(event);
|
||
this.ws.onclose = () => this.schedule_reconnect();
|
||
this.ws.onerror = () => this.ws?.close();
|
||
}
|
||
schedule_reconnect() {
|
||
this.ws = null;
|
||
if (this.reconnect_timer !== null) {
|
||
return;
|
||
}
|
||
this.reconnect_timer = window.setTimeout(() => {
|
||
this.reconnect_timer = null;
|
||
this.ensure_open();
|
||
this.schedule_subscribe();
|
||
}, 1000);
|
||
}
|
||
schedule_subscribe() {
|
||
this.ensure_open();
|
||
if (this.subscribe_timer !== null) {
|
||
return;
|
||
}
|
||
this.subscribe_timer = window.setTimeout(() => {
|
||
this.subscribe_timer = null;
|
||
this.send_subscription();
|
||
}, 50);
|
||
}
|
||
send_subscription() {
|
||
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||
return;
|
||
}
|
||
this.ws.send(JSON.stringify({
|
||
type: "subscribe",
|
||
sources: this.source_payloads()
|
||
}));
|
||
}
|
||
source_payloads() {
|
||
const list = app.data_source_config?.enabled() ?? [];
|
||
return list.map((ds: Data_Source) => this.source_payload(ds));
|
||
}
|
||
source_payload(ds: Data_Source) {
|
||
return {
|
||
key: ds.key,
|
||
monitor_all_aircraft_mode: ds.monitor_all_aircraft_mode,
|
||
manual_track_icaos: Array.from(this.track_icaos(ds)),
|
||
aircraft_versions: Object.fromEntries(ds.aircraft_change_versions),
|
||
track_last_size: Object.fromEntries(Array.from(this.track_icaos(ds)).map((icao) => [icao, ds.aircraftMap.get(icao)?.last_size || 0]))
|
||
};
|
||
}
|
||
track_icaos(ds: Data_Source): Set<string> {
|
||
const ret = new Set<string>();
|
||
if (ds.monitor_all_aircraft_mode) {
|
||
for (const icao of ds.aircraftMap.keys()) {
|
||
ret.add(icao);
|
||
}
|
||
} else {
|
||
for (const icao of ds.manual_track_icao_set) {
|
||
ret.add(icao);
|
||
}
|
||
}
|
||
if (ds.active_aircraft) {
|
||
ret.add(ds.active_aircraft.icao);
|
||
}
|
||
const selected = app.cesium_map?.selected_aircraft as Aircraft | null | undefined;
|
||
if (selected?.data_source === ds) {
|
||
ret.add(selected.icao);
|
||
}
|
||
return ret;
|
||
}
|
||
handle_message(event: MessageEvent) {
|
||
const data = JSON.parse(String(event.data));
|
||
if (data.type !== "aircraft_update" || !Array.isArray(data.sources)) {
|
||
return;
|
||
}
|
||
for (const source of data.sources) {
|
||
const ds = app.get_Data_Source(source.key);
|
||
if (!ds) {
|
||
continue;
|
||
}
|
||
ds.apply_aircraft_delta(Array.isArray(source.change_list) ? source.change_list : [], Array.isArray(source.removed_icaos) ? source.removed_icaos : []);
|
||
ds.apply_track_stream_list(Array.isArray(source.tracks) ? source.tracks : []);
|
||
}
|
||
app.cesium_map?.request_sync_data_sources();
|
||
}
|
||
}
|
||
const aircraft_stream = new Aircraft_Stream_Client();
|
||
|
||
|
||
export class Serial_Data_Source extends Data_Source {
|
||
port_name: string = "";
|
||
baud_rate: number = 0;
|
||
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<>
|
||
<Input_String that={this} field="port_name" name="串口名"/>
|
||
<Input_Number that={this} field={"baud_rate"} name={"波特率"} min={0}></Input_Number>
|
||
</>
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
export class TCP_Client_Data_Source extends Data_Source {
|
||
ip: string = "";
|
||
port: number = 0;
|
||
|
||
center(): React.JSX.Element {
|
||
return (
|
||
<>
|
||
|
||
</>
|
||
);
|
||
}
|
||
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<>
|
||
<Input_String that={this} field={"ip"} name={"IP地址"}/>
|
||
<Input_Port_Number that={this} field={"port"}/>
|
||
</>
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
export class File_Data_Source extends Data_Source {
|
||
file_path: string = "";
|
||
data_type: string = "BIN";
|
||
play_mode: string = "one";
|
||
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<div style={{
|
||
display: "flex",
|
||
flexDirection: "row",
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
gap: "8px",
|
||
}}>
|
||
<Input_String that={this} field="file_path" name="文件路径"/>
|
||
<Data_Format that={this} field="data_type"/>
|
||
<Play_Mode that={this} field="play_mode"/>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
export class Dll_Data_Source extends Data_Source {
|
||
library_path: string = "";
|
||
function_name: string = "";
|
||
data_type: string = "BIN";
|
||
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<>
|
||
<Input_String that={this} field="library_path" name="库路径" style={{width: 200}}/>
|
||
<Input_String that={this} field="function_name" name="函数名" style={{width: 200}}/>
|
||
<Data_Format that={this} field="data_type" name="数据类型"/>
|
||
</>
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
export class Shared_Memory_Data_Source extends Data_Source {
|
||
shared_memory_name: string = "";
|
||
shared_memory_size: number
|
||
data_type: string = "BIN";
|
||
|
||
center_with_add(): React.JSX.Element {
|
||
return (
|
||
<>
|
||
<Input_String that={this} field="shared_memory_name" name="名称" style={{width: 200}}/>
|
||
<Input_String that={this} field="shared_memory_size" name="大小" style={{width: 200}}/>
|
||
<Data_Format that={this} field="data_type" name="数据类型"/>
|
||
</>
|
||
);
|
||
}
|
||
}
|