初步修改
This commit is contained in:
+5
-1
@@ -9,7 +9,11 @@
|
||||
"lint": "eslint .",
|
||||
"lint:src": "eslint \"src/**/*.{ts,tsx}\"",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"adminive:prepare": "node scripts/prepare_adminive.mjs",
|
||||
"predev": "npm run adminive:prepare",
|
||||
"prebuild": "npm run adminive:prepare",
|
||||
"prepreview": "npm run adminive:prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import {execFileSync} from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import {fileURLToPath} from 'node:url'
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const adminiveFrontend = path.resolve(scriptDir, '..', '..', 'Adminive', 'frontend')
|
||||
const amisPackagePath = path.join(adminiveFrontend, 'node_modules', 'amis', 'package.json')
|
||||
const requiredVersion = '6.13.0'
|
||||
if (fs.existsSync(amisPackagePath) && JSON.parse(fs.readFileSync(amisPackagePath, 'utf8')).version === requiredVersion) {
|
||||
process.exit(0)
|
||||
}
|
||||
execFileSync(process.execPath, [process.env.npm_execpath, 'ci', '--omit=dev', '--ignore-scripts'], {cwd: adminiveFrontend, stdio: 'inherit'})
|
||||
@@ -2,8 +2,6 @@
|
||||
import {Prefix} from "./Global.tsx";
|
||||
import React from "react";
|
||||
import {app} from "./App.tsx";
|
||||
import {Data_Source} from "./Data_Source/Data_Source.tsx";
|
||||
import {Data_Feed} from "./Data_Feed/Data_Feed.tsx";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@@ -110,32 +108,6 @@ export function Switch_Bool({that, field, ...props}) {
|
||||
}
|
||||
|
||||
|
||||
export function create_data_source_options() {
|
||||
let options: SelectProps['options'] = [];
|
||||
let t = app.setting.data_source_config;
|
||||
t.list.list.forEach((item: Data_Source) => {
|
||||
options.push({
|
||||
value: item.key,
|
||||
label: item.key
|
||||
});
|
||||
})
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
export function create_data_feed_options() {
|
||||
let options: SelectProps['options'] = [];
|
||||
let t = app.setting.data_feed_config;
|
||||
t.list.list.forEach((item: Data_Feed) => {
|
||||
options.push({
|
||||
value: item.key,
|
||||
label: item.key
|
||||
});
|
||||
})
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
export const Psc = {
|
||||
random_color(): string {
|
||||
return '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0');
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import axios from "axios"
|
||||
import {useEffect, useState, type CSSProperties} from "react"
|
||||
import {AmisPanel} from "./AmisPanel.tsx"
|
||||
type Composition_Node = {
|
||||
kind: string
|
||||
slot?: string
|
||||
children?: Composition_Node[]
|
||||
}
|
||||
type Slot_Contract = {
|
||||
component_kind: string
|
||||
schema: Record<string, unknown>
|
||||
}
|
||||
type Composition_Manifest = {
|
||||
view: {
|
||||
body: Composition_Node
|
||||
}
|
||||
slots: Record<string, Slot_Contract>
|
||||
}
|
||||
type Adminive_Response<T> = {
|
||||
status: number
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
const frontend_layout: Record<string, CSSProperties> = {
|
||||
mode_acs: {gridColumn: "1 / -1"},
|
||||
data_feed_settings: {gridColumn: "1 / -1"},
|
||||
data_sources: {gridColumn: "1 / -1"},
|
||||
data_feeds: {gridColumn: "1 / -1"},
|
||||
source_feed_relations: {gridColumn: "1 / -1"},
|
||||
mlat: {gridColumn: "1 / -1"},
|
||||
cesium_graphics: {gridColumn: "1 / -1"}
|
||||
}
|
||||
function collect_slots(node: Composition_Node, result: string[]) {
|
||||
if (node.kind === "slot" && node.slot) {
|
||||
result.push(node.slot)
|
||||
}
|
||||
for (const child of node.children ?? []) {
|
||||
collect_slots(child, result)
|
||||
}
|
||||
}
|
||||
export function Adminive_Settings() {
|
||||
const [manifest, set_manifest] = useState<Composition_Manifest | null>(null)
|
||||
const [error, set_error] = useState("")
|
||||
useEffect(() => {
|
||||
axios.get<Adminive_Response<Composition_Manifest>>("/api/adminive/config/manifest").then(response => {
|
||||
set_manifest(response.data.data)
|
||||
}).catch(reason => {
|
||||
set_error(reason instanceof Error ? reason.message : String(reason))
|
||||
})
|
||||
}, [])
|
||||
if (error) {
|
||||
return <div style={{color: "#ff4d4f", padding: 16}}>Adminive 配置加载失败:{error}</div>
|
||||
}
|
||||
if (!manifest) {
|
||||
return <div style={{padding: 16}}>Adminive 配置加载中...</div>
|
||||
}
|
||||
const slot_names: string[] = []
|
||||
collect_slots(manifest.view.body, slot_names)
|
||||
return (
|
||||
<div style={{display: "grid", gridTemplateColumns: "repeat(2, minmax(0, 1fr))", gap: 16, width: "100%"}}>
|
||||
{slot_names.map(name => {
|
||||
const slot = manifest.slots[name]
|
||||
if (!slot || slot.component_kind !== "amis_schema") {
|
||||
return null
|
||||
}
|
||||
return <div key={name} style={{minWidth: 0, ...frontend_layout[name]}}><AmisPanel schema={slot.schema} /></div>
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {useEffect, useRef, useState} from "react"
|
||||
import {amis_fetcher} from "./amis_fetcher.ts"
|
||||
import {embed_amis} from "./amis_runtime.ts"
|
||||
type Amis_Panel_Props = {
|
||||
schema: Record<string, unknown>
|
||||
}
|
||||
export function AmisPanel({schema}: Amis_Panel_Props) {
|
||||
const container = useRef<HTMLDivElement>(null)
|
||||
const [error, set_error] = useState("")
|
||||
useEffect(() => {
|
||||
if (!container.current) {
|
||||
return
|
||||
}
|
||||
set_error("")
|
||||
try {
|
||||
const scoped = embed_amis(container.current, schema, amis_fetcher)
|
||||
return () => scoped.unmount()
|
||||
} catch (reason) {
|
||||
set_error(reason instanceof Error ? reason.message : String(reason))
|
||||
}
|
||||
}, [schema])
|
||||
return error ? <div style={{color: "#ff4d4f"}}>{error}</div> : <div ref={container} />
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import axios, {type AxiosRequestConfig, type AxiosResponse} from "axios"
|
||||
import {emit_cesium_graphics_config, load_cesium_graphics_config} from "../Map/Cesium_Graphics_Config.ts"
|
||||
import type {Amis_Request} from "./amis_runtime.ts"
|
||||
const cesium_graphics_data_api = "/api/adminive/config/cesium_graphics/data"
|
||||
const data_sources_api = "/api/adminive/config/data_sources"
|
||||
export async function amis_fetcher(request: Amis_Request): Promise<AxiosResponse> {
|
||||
const method = (request.method ?? "get").toLowerCase()
|
||||
const query_method = method === "get" || method === "head"
|
||||
const config: AxiosRequestConfig = {
|
||||
url: request.url,
|
||||
method,
|
||||
headers: request.headers,
|
||||
params: query_method ? request.data : undefined,
|
||||
data: query_method ? undefined : request.data,
|
||||
responseType: request.responseType || "json",
|
||||
validateStatus: () => true,
|
||||
...(request.config ?? {})
|
||||
}
|
||||
const response = await axios(config)
|
||||
const success = response.status >= 200 && response.status < 300 && response.data?.status === 0
|
||||
if (method === "post" && request.url === cesium_graphics_data_api && success) {
|
||||
emit_cesium_graphics_config(await load_cesium_graphics_config())
|
||||
}
|
||||
if (method !== "get" && method !== "head" && request.url.startsWith(data_sources_api) && success) {
|
||||
window.dispatchEvent(new Event("ecap-data-sources-changed"))
|
||||
}
|
||||
return response
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
type Amis_Request = {
|
||||
url: string
|
||||
method?: string
|
||||
data?: unknown
|
||||
headers?: Record<string, string>
|
||||
responseType?: XMLHttpRequestResponseType
|
||||
config?: Record<string, unknown>
|
||||
}
|
||||
type Amis_Fetcher = (request: Amis_Request) => Promise<unknown>
|
||||
interface Amis_Scoped {
|
||||
unmount(): void
|
||||
}
|
||||
interface Amis_Embed_Module {
|
||||
embed(container: HTMLElement | string, schema: Record<string, unknown>, props?: Record<string, unknown>, env?: Record<string, unknown>): Amis_Scoped
|
||||
}
|
||||
declare global {
|
||||
interface Window {
|
||||
amisRequire?: (name: string) => unknown
|
||||
}
|
||||
}
|
||||
export type {Amis_Request}
|
||||
export function embed_amis(container: HTMLElement, schema: Record<string, unknown>, fetcher: Amis_Fetcher): Amis_Scoped {
|
||||
if (!window.amisRequire) {
|
||||
throw new Error("AMIS SDK runtime is not loaded")
|
||||
}
|
||||
const module = window.amisRequire("amis/embed") as Amis_Embed_Module
|
||||
return module.embed(container, schema, {}, {fetcher, theme: "cxd"})
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
import enhance from "../core/enhance.tsx";
|
||||
import {Button, Checkbox, CheckboxChangeEvent, message, Select, Space, Switch, Tag, Typography} from "antd";
|
||||
import {SettingOutlined} from "@ant-design/icons";
|
||||
import axios from "axios";
|
||||
import {baseURL, Prefix} from "../Global.tsx";
|
||||
import React from "react";
|
||||
import {Input_Port_Number, Input_String, Switch_Bool} from "../A_Global.tsx";
|
||||
import { State_Shower } from "../State_Shower.tsx";
|
||||
|
||||
const {Option} = Select;
|
||||
const { Text } = Typography;
|
||||
|
||||
class Output_Format {
|
||||
type: string = "";
|
||||
use_status: boolean = false;
|
||||
mode_s_output_type: string = "";
|
||||
use_mode_ac: boolean = false;
|
||||
sbs_only_pos: boolean = false;
|
||||
|
||||
|
||||
output(flush) {
|
||||
return (<>
|
||||
<Select
|
||||
prefix={<Prefix label="包格式"/>}
|
||||
defaultValue=""
|
||||
value={this.type}
|
||||
onChange={(value: string) => {
|
||||
this.type = value;
|
||||
flush();
|
||||
}}
|
||||
options={[
|
||||
{value: 'BIN', label: 'BIN'},
|
||||
{value: 'BIN_ID', label: 'BIN_ID'},
|
||||
{value: 'AVR', label: 'AVR'},
|
||||
{value: 'AVR_MLAT', label: 'AVR_MLAT'},
|
||||
{value: 'SBS', label: 'SBS'},
|
||||
]}
|
||||
/>
|
||||
{
|
||||
(this.type == "BIN" || this.type == "BIN_ID") &&
|
||||
<Checkbox checked={this.use_status} onChange={(e: CheckboxChangeEvent) => {
|
||||
this.use_status = e.target.checked;
|
||||
flush();
|
||||
}}>状态消息</Checkbox>
|
||||
}
|
||||
|
||||
{
|
||||
(this.type == "SBS") &&
|
||||
<Checkbox checked={this.sbs_only_pos} onChange={(e: CheckboxChangeEvent) => {
|
||||
this.sbs_only_pos = e.target.checked;
|
||||
flush();
|
||||
}}>SBS只输出带位置的飞机</Checkbox>
|
||||
}
|
||||
{
|
||||
(this.type != "SBS") &&
|
||||
<>
|
||||
<Select
|
||||
prefix={<Prefix label="Mode-S类型"/>}
|
||||
defaultValue=""
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
value={this.mode_s_output_type}
|
||||
onChange={(value: string) => {
|
||||
this.mode_s_output_type = value;
|
||||
flush();
|
||||
}}
|
||||
options={[
|
||||
{value: 'ALL_Mode_S', label: '全部的mode_s'},
|
||||
{value: 'DF_11_17_18', label: 'DF_11_17_18'},
|
||||
{value: 'NO_POS_Mode_S', label: '无位置的mode_s'},
|
||||
]}
|
||||
/>
|
||||
<Checkbox checked={this.use_mode_ac} onChange={(e: CheckboxChangeEvent) => {
|
||||
this.use_mode_ac = e.target.checked;
|
||||
flush();
|
||||
}}>Mode_AC</Checkbox>
|
||||
</>
|
||||
}
|
||||
</>);
|
||||
}
|
||||
}
|
||||
|
||||
export class Data_Feed extends enhance.Base {
|
||||
key: string = "未命名";
|
||||
enable: boolean = false;
|
||||
type: string = "";
|
||||
output_format: Output_Format = new Output_Format();
|
||||
state: State_Shower = new State_Shower(`${baseURL}/get_data_feed_state`, () => this.key);
|
||||
|
||||
onClick(that) {
|
||||
|
||||
}
|
||||
|
||||
center_with_add(): React.JSX.Element {
|
||||
return <></>
|
||||
}
|
||||
|
||||
center(): React.JSX.Element {
|
||||
return <></>
|
||||
}
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between", // 将子元素水平分散
|
||||
alignItems: "center", // 子元素垂直居中
|
||||
gap: "8px", // 设置元素之间的间距
|
||||
width: "100%" // 确保容器宽度占满父容器
|
||||
}}>
|
||||
|
||||
<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>
|
||||
<Button
|
||||
icon={<SettingOutlined/>}
|
||||
onClick={() => {
|
||||
this.onClick(this);
|
||||
}}>
|
||||
{"格式: " + this.output_format.type}
|
||||
</Button>
|
||||
{
|
||||
this.center_with_add()
|
||||
}
|
||||
{
|
||||
this.center()
|
||||
}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<this.state.x/>
|
||||
<Switch_Bool that={this} field={"enable"}></Switch_Bool>
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
axios.post(`${baseURL}/update_data_feed`, this).then((res) => {
|
||||
Object.assign(this, res.data);
|
||||
});
|
||||
message.success(`${this.type}类型,数据馈送源${this.key}设置成功!` );
|
||||
}}>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Data_Feed_Server extends Data_Feed {
|
||||
port: number = 0;
|
||||
dataSource: any[] = [];
|
||||
|
||||
// 新增:轮询定时器
|
||||
private pollTimer: number | null = null;
|
||||
private pollIntervalMs = 1000;
|
||||
|
||||
private fetchConnections = async () => {
|
||||
try {
|
||||
// 你原来这里用 post 并把 this 作为 body,我保留这个行为
|
||||
const res = await axios.post(`${baseURL}/get_data_feed_server_connect`, this);
|
||||
this.dataSource = (res.data ?? []).map((item: any, index: number) => ({
|
||||
...item,
|
||||
key: String(index),
|
||||
}));
|
||||
this.flush();
|
||||
} catch (e) {
|
||||
// 可选:失败不打断轮询
|
||||
// console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
private startPolling = () => {
|
||||
if (this.pollTimer !== null) return; // 已经在轮询
|
||||
void this.fetchConnections(); // 先立刻拉一次
|
||||
this.pollTimer = window.setInterval(() => {
|
||||
void this.fetchConnections();
|
||||
}, this.pollIntervalMs);
|
||||
};
|
||||
|
||||
private stopPolling = () => {
|
||||
if (this.pollTimer === null) return;
|
||||
window.clearInterval(this.pollTimer);
|
||||
this.pollTimer = null;
|
||||
};
|
||||
|
||||
refresh() {
|
||||
// refresh 保持可用:手动刷新一次
|
||||
void this.fetchConnections();
|
||||
}
|
||||
|
||||
center(): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
prefix={<Prefix label="已连接客户端" />}
|
||||
popupMatchSelectWidth={false}
|
||||
placeholder="展示所有连接"
|
||||
onChange={(value) => {
|
||||
console.log("你选择了:", value);
|
||||
}}
|
||||
value={""}
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
this.startPolling(); // 展开:1 秒刷新
|
||||
} else {
|
||||
this.stopPolling(); // 收起:停止刷新
|
||||
}
|
||||
}}
|
||||
>
|
||||
{this.dataSource.map((item: any) => (
|
||||
<Option key={item.key} value={item.key}>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{Object.entries(item)
|
||||
.filter(([k]) => k !== "key")
|
||||
.map(([k, v]) => (
|
||||
<div
|
||||
key={k}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: 8,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
<strong style={{ whiteSpace: "nowrap" }}>{k}:</strong>
|
||||
<span style={{ wordBreak: "break-all" }}>
|
||||
{typeof v === "object" ? JSON.stringify(v) : String(v)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
center_with_add(): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Input_Port_Number that={this} field={"port"} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// 可选:如果这个对象有生命周期/销毁点,记得调用,避免内存泄漏
|
||||
destroy() {
|
||||
this.stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
class Data_Feed_Client extends Data_Feed {
|
||||
url: string = "";
|
||||
port: number = 0;
|
||||
|
||||
center_with_add() {
|
||||
return <>
|
||||
<Input_String that={this} field={"url"} name="IP地址"/>
|
||||
<Input_Port_Number that={this} field={"port"}/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export class Data_Feed_TCP_Server extends Data_Feed_Server {
|
||||
|
||||
}
|
||||
|
||||
export class Data_Feed_UDP_Server extends Data_Feed_Server {
|
||||
|
||||
}
|
||||
|
||||
export class Data_Feed_TCP_Client extends Data_Feed_Client {
|
||||
|
||||
}
|
||||
|
||||
export class Data_Feed_UDP_Client extends Data_Feed_Client {
|
||||
|
||||
}
|
||||
|
||||
export function create_data_feed_from_type(type: string) {
|
||||
let ret: Data_Feed
|
||||
if (type === "Data_Feed_TCP_Server") {
|
||||
ret = new Data_Feed_TCP_Server();
|
||||
}
|
||||
if (type === "Data_Feed_TCP_Client") {
|
||||
ret = new Data_Feed_TCP_Client();
|
||||
}
|
||||
if (type === "Data_Feed_UDP_Server") {
|
||||
ret = new Data_Feed_UDP_Server();
|
||||
}
|
||||
if (type === "Data_Feed_UDP_Client") {
|
||||
ret = new Data_Feed_UDP_Client();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
import enhance from "../core/enhance.js";
|
||||
import {baseURL, List_Data, Prefix, row_style, setting_style} from "../Global.js";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxChangeEvent,
|
||||
Flex,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import axios from "axios";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
// @ts-ignore
|
||||
import { SettingOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
create_data_feed_from_type,
|
||||
Data_Feed,
|
||||
Data_Feed_TCP_Client,
|
||||
Data_Feed_TCP_Server,
|
||||
Data_Feed_UDP_Client,
|
||||
Data_Feed_UDP_Server
|
||||
} from "./Data_Feed.tsx";
|
||||
|
||||
|
||||
class Output_Setting extends enhance.Base {
|
||||
data: Data_Feed
|
||||
|
||||
}
|
||||
|
||||
|
||||
function assign(dest, origin) {
|
||||
Object.keys(origin).forEach((key) => {
|
||||
const value = origin[key];
|
||||
if (value && typeof value != 'object' && !Array.isArray(value) && typeof value !== 'function') {
|
||||
dest[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class Show_Data_Feed_Output extends enhance.Base {
|
||||
show(d: Data_Feed) {
|
||||
this.cur = d;
|
||||
this._show = true
|
||||
this.par.flush();
|
||||
}
|
||||
|
||||
par: any
|
||||
|
||||
constructor(_par: any) {
|
||||
super()
|
||||
this.par = _par;
|
||||
}
|
||||
|
||||
_show: boolean = false;
|
||||
cur: Data_Feed
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<Modal
|
||||
centered
|
||||
title="设置数据输出格式"
|
||||
open={this._show}
|
||||
// 对话框点确定
|
||||
onOk={() => {
|
||||
axios.post(`${baseURL}/update_data_feed`, this.cur)
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
message.success(`${this.cur.key}设置数据输出格式成功!`)
|
||||
}}
|
||||
onCancel={() => {
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
//alignItems:'center',
|
||||
gap: '16px',
|
||||
}}>
|
||||
{
|
||||
this.cur &&
|
||||
this.cur.output_format.output(this.par.flush)
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Data_Feed_Add_Helper extends enhance.Base {
|
||||
show(d: Data_Feed, index: number) {
|
||||
this._show = true
|
||||
this.par.flush();
|
||||
this.index = index;
|
||||
}
|
||||
key: string = "";
|
||||
par: Data_Feed_Config
|
||||
|
||||
constructor(_par: Data_Feed_Config) {
|
||||
super();
|
||||
this.par = _par;
|
||||
}
|
||||
|
||||
_show: boolean = false;
|
||||
type: string
|
||||
enable: boolean = false;
|
||||
new_create: Data_Feed = new Data_Feed()
|
||||
index
|
||||
|
||||
render() {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
return <>
|
||||
<Modal
|
||||
centered
|
||||
title="添加数据馈送源"
|
||||
open={this._show}
|
||||
// 对话框点确定
|
||||
onOk={() => {
|
||||
this.new_create.type = this.type;
|
||||
this.new_create.enable = this.enable;
|
||||
this.new_create.key = this.key;
|
||||
axios.post(`${baseURL}/insert_data_feed`, { index: this.index, data: this.new_create }).then(res => {
|
||||
this.par.refresh_from_res(res)
|
||||
})
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
}}
|
||||
onCancel={() => {
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
//alignItems:'center',
|
||||
gap: '16px',
|
||||
}}>
|
||||
<Input addonBefore="唯一名称" placeholder="请输入唯一名称"
|
||||
value={this.key}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.key = e.target.value;
|
||||
this.flush();
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
prefix={<Prefix label="数据源类型" />}
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
placeholder="请选择一个选项"
|
||||
value={this.type}
|
||||
onChange={(value) => {
|
||||
if (this.type != value) {
|
||||
this.type = value;
|
||||
this.par.flush();
|
||||
}
|
||||
this.new_create = create_data_feed_from_type(this.type);
|
||||
}}>
|
||||
<Option value="Data_Feed_TCP_Server">Data_Feed_TCP_Server</Option>
|
||||
<Option value="Data_Feed_TCP_Client">Data_Feed_TCP_Client</Option>
|
||||
<Option value="Data_Feed_UDP_Server">Data_Feed_UDP_Server</Option>
|
||||
<Option value="Data_Feed_UDP_Client">Data_Feed_UDP_Client</Option>
|
||||
</Select>
|
||||
|
||||
<Switch style={{
|
||||
alignSelf: 'flex-start'
|
||||
}} value={this.enable} onChange={(checked: boolean) => {
|
||||
this.enable = checked
|
||||
this.par.flush();
|
||||
}}></Switch>
|
||||
{
|
||||
this.new_create &&
|
||||
this.new_create.center_with_add()
|
||||
}
|
||||
{
|
||||
this.new_create &&
|
||||
this.new_create.output_format.output(this.par.flush)
|
||||
}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export class Data_Feed_Config extends enhance.Base {
|
||||
|
||||
packet_byte_size: number
|
||||
empty_wait_milliseconds: number
|
||||
|
||||
show_data_feed_output = new Show_Data_Feed_Output(this);
|
||||
add_helper = new Data_Feed_Add_Helper(this);
|
||||
list = new List_Data()
|
||||
|
||||
|
||||
toJSON() {
|
||||
const {show_data_feed_output,add_helper,list, ...rest} = this;
|
||||
return rest; // 返回去除这些属性后的对象
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.list.insert = (data: Data_Feed, index) => {
|
||||
this.add_helper.show(data, index)
|
||||
}
|
||||
|
||||
this.list.remove = (data: Data_Feed, index) => {
|
||||
axios.post(`${baseURL}/remove_data_feed`, { index: index }).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.up = (data: Data_Feed, index) => {
|
||||
axios.post(`${baseURL}/rise_data_feed`, { index: index }).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.down = (data: Data_Feed, index) => {
|
||||
axios.post(`${baseURL}/fall_data_feed`, { index: index }).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
on_mount() {
|
||||
axios.post(`${baseURL}/get_data_feed_config`).then((res) => {
|
||||
this.refresh_from_res(res);
|
||||
})
|
||||
}
|
||||
|
||||
refresh_from_res(res) {
|
||||
this.list.list = []
|
||||
this.packet_byte_size = res.data.packet_byte_size
|
||||
this.empty_wait_milliseconds = res.data.empty_wait_milliseconds
|
||||
res.data.list.forEach((data: any) => {
|
||||
let cur: Data_Feed = create_data_feed_from_type(data.type)
|
||||
assign(cur, data)
|
||||
assign(cur.output_format, data.output_format);
|
||||
cur.onClick = (d: Data_Feed) => {
|
||||
this.show_data_feed_output.show(d);
|
||||
}
|
||||
// @ts-ignore
|
||||
this.list.list.push(cur);
|
||||
})
|
||||
this.flush();
|
||||
}
|
||||
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
<h2 style={row_style}>数据馈送</h2>
|
||||
|
||||
<Flex gap="middle" justify='center' align='center'>
|
||||
<InputNumber
|
||||
addonBefore={"最小批大小"}
|
||||
addonAfter={"byte"}
|
||||
value={this.packet_byte_size} min={0}
|
||||
onChange={(value: number | null) => {
|
||||
this.packet_byte_size = value;
|
||||
this.flush();
|
||||
}} />
|
||||
<InputNumber
|
||||
addonBefore={"忙等时间"}
|
||||
addonAfter={"ms"}
|
||||
value={this.empty_wait_milliseconds}
|
||||
min={0}
|
||||
onChange={(value: number | null) => {
|
||||
// 注意:先更新实例属性,再传递给接口(避免传递旧值)
|
||||
this.empty_wait_milliseconds = value;
|
||||
this.flush();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
axios.post(`${baseURL}/update_data_feed_config`, this.toJSON()).then(res => {
|
||||
});
|
||||
message.success("属性设置成功!")
|
||||
}}>
|
||||
确定
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
|
||||
<this.list.x/>
|
||||
<this.show_data_feed_output.x/>
|
||||
<this.add_helper.x/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ export class Data_Source extends enhance.Base {
|
||||
|
||||
// 数据
|
||||
key: string = ""
|
||||
adminive_id: number = 0
|
||||
enable: boolean = false;
|
||||
type: string = ""; // 也就是类型名
|
||||
map_display: Data_Source_Map_Display_Config = default_map_display_config();
|
||||
@@ -127,19 +128,49 @@ export class Data_Source extends enhance.Base {
|
||||
keep_mode: boolean = false;
|
||||
|
||||
confirm() {
|
||||
this.normalize_map_display();
|
||||
const {
|
||||
active_aircraft, base_station, aircraftMap, manual_track_icao_set, monitor_all_aircraft_mode, connect_list, first,
|
||||
...rest
|
||||
} = this;
|
||||
axios.post(`${baseURL}/update_data_source`, rest).then((res) => {
|
||||
Object.assign(this, res.data);
|
||||
this.normalize_map_display();
|
||||
this.refresh_display("map2d");
|
||||
this.refresh_display("map3d");
|
||||
this.flush();
|
||||
message.success(`${this.type}类型,数据接收源${this.key}设置成功!`);
|
||||
});
|
||||
this.normalize_map_display()
|
||||
const payload: Record<string, unknown> = {
|
||||
enable: this.enable,
|
||||
config: {
|
||||
base_station_has_valid_position: this.base_station_has_valid_position,
|
||||
map_display: this.map_display,
|
||||
lat: this.lat,
|
||||
lon: this.lon,
|
||||
alt: this.alt,
|
||||
ignore_msg_time: this.ignore_msg_time,
|
||||
update_form_gps: this.update_form_gps,
|
||||
keep_mode: this.keep_mode
|
||||
}
|
||||
}
|
||||
const value = this as any
|
||||
if (this.type === "TCP_Client_Data_Source") {
|
||||
payload.ip = value.ip
|
||||
payload.port = value.port
|
||||
} else if (this.type === "Serial_Data_Source") {
|
||||
payload.port_name = value.port_name
|
||||
payload.baud_rate = value.baud_rate
|
||||
} else if (this.type === "File_Data_Source") {
|
||||
payload.file_path = value.file_path
|
||||
payload.data_type = value.data_type
|
||||
payload.play_mode = value.play_mode
|
||||
} else if (this.type === "Dll_Data_Source") {
|
||||
payload.library_path = value.library_path
|
||||
payload.function_name = value.function_name
|
||||
payload.data_type = value.data_type
|
||||
payload.buffer_size = value.buffer_size
|
||||
} else if (this.type === "Shared_Memory_Data_Source") {
|
||||
payload.shared_memory_name = value.shared_memory_name
|
||||
payload.shared_memory_size = value.shared_memory_size
|
||||
payload.data_type = value.data_type
|
||||
}
|
||||
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() {
|
||||
|
||||
@@ -1,245 +1,53 @@
|
||||
import enhance from "../core/enhance.js";
|
||||
import {Button, Flex, Input, InputNumber, List, Modal, Select, Switch, Typography} from "antd";
|
||||
import React from "react";
|
||||
import {baseURL, List_Data, Prefix, row_style, setting_style} from "../Global";
|
||||
import axios, {AxiosResponse} from "axios";
|
||||
|
||||
const {Option} = Select;
|
||||
import {ProList} from '@ant-design/pro-components';
|
||||
|
||||
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
MinusOutlined,
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
SubnodeOutlined
|
||||
} from "@ant-design/icons";
|
||||
import {app} from "../App.tsx";
|
||||
import {
|
||||
Data_Source,
|
||||
Dll_Data_Source,
|
||||
File_Data_Source,
|
||||
Serial_Data_Source, Shared_Memory_Data_Source,
|
||||
TCP_Client_Data_Source
|
||||
} from "./Data_Source.tsx";
|
||||
import {Data_Format, Input_Port_Number, Input_String, Play_Mode} from "../A_Global.tsx";
|
||||
import Title from "antd/es/skeleton/Title";
|
||||
|
||||
|
||||
export class Data_Source_Add_helper extends enhance.Base {
|
||||
key: string = "";
|
||||
data_source_type: string;
|
||||
new_create: Data_Source
|
||||
isModalOpen: boolean = false;
|
||||
index: any
|
||||
par: Data_Source_Config
|
||||
enable: boolean = false;
|
||||
constructor(par: Data_Source_Config) {
|
||||
super();
|
||||
this.par = par;
|
||||
}
|
||||
|
||||
show(index) {
|
||||
this.index = index;
|
||||
this.isModalOpen = true;
|
||||
this.par.flush();
|
||||
}
|
||||
|
||||
render(props: any): React.JSX.Element {
|
||||
return <Modal
|
||||
centered
|
||||
title="添加输入数据源"
|
||||
open={this.isModalOpen}
|
||||
// 对话框点确定
|
||||
onOk={() => {
|
||||
let cur = this.new_create;
|
||||
cur.key = this.key;
|
||||
cur.enable = this.enable;
|
||||
axios.post(`${baseURL}/insert_data_source`, {
|
||||
index: this.index,
|
||||
data: this.new_create
|
||||
}).then((res) => {
|
||||
this.par.refresh_from_res(res)
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
this.new_create = null;
|
||||
this.isModalOpen = false;
|
||||
this.flush();
|
||||
|
||||
}}
|
||||
onCancel={() => {
|
||||
this.isModalOpen = false;
|
||||
this.flush();
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Flex vertical gap={8}>
|
||||
<Input addonBefore="唯一名称" placeholder="请输入唯一名称"
|
||||
value={this.key}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.key = e.target.value;
|
||||
this.flush();
|
||||
}}
|
||||
/>
|
||||
<Switch style={{
|
||||
alignSelf: 'flex-start'
|
||||
}} value={this.enable} onChange={(checked: boolean) => {
|
||||
this.enable = checked
|
||||
this.par.flush();
|
||||
}}></Switch>
|
||||
<Select
|
||||
prefix="数据源类型"
|
||||
style={{width: '100%'}}
|
||||
placeholder="请选择一个选项"
|
||||
value={this.data_source_type}
|
||||
onChange={(value) => {
|
||||
if (this.data_source_type != value) {
|
||||
this.data_source_type = value;
|
||||
if (this.data_source_type === "Serial_Data_Source") {
|
||||
this.new_create = new Serial_Data_Source();
|
||||
}
|
||||
else if (this.data_source_type === "TCP_Client_Data_Source") {
|
||||
this.new_create = new TCP_Client_Data_Source();
|
||||
}
|
||||
else if (this.data_source_type === "File_Data_Source") {
|
||||
this.new_create = new File_Data_Source();
|
||||
}
|
||||
else if (this.data_source_type === "Dll_Data_Source") {
|
||||
this.new_create = new Dll_Data_Source();
|
||||
}
|
||||
else if (this.data_source_type === "Shared_Memory_Data_Source") {
|
||||
this.new_create = new Shared_Memory_Data_Source();
|
||||
}
|
||||
this.new_create.type = this.data_source_type;
|
||||
this.flush();
|
||||
this.new_create.flush = this.flush;
|
||||
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Option value="Serial_Data_Source">串口数据源</Option>
|
||||
<Option value="TCP_Client_Data_Source">tcp客户端数据源</Option>
|
||||
<Option value="File_Data_Source">文件数据源</Option>
|
||||
<Option value="Dll_Data_Source">动态库数据源</Option>
|
||||
<Option value="Shared_Memory_Data_Source">共享内存数据源</Option>
|
||||
</Select>
|
||||
|
||||
{
|
||||
this.new_create && <div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
}}>
|
||||
{
|
||||
this.new_create.center_with_add()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Flex>
|
||||
</Modal>
|
||||
}
|
||||
import axios from "axios"
|
||||
import {app} from "../App.tsx"
|
||||
import {List_Data} from "../Global.tsx"
|
||||
import {Data_Source, Dll_Data_Source, File_Data_Source, Serial_Data_Source, Shared_Memory_Data_Source, TCP_Client_Data_Source} from "./Data_Source.tsx"
|
||||
type Adminive_Data_Source_Row = {
|
||||
id: number
|
||||
key: string
|
||||
type: string
|
||||
enable: boolean
|
||||
config: Record<string, unknown>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
|
||||
// https://ant.design/components/list-cn
|
||||
export class Data_Source_Config extends enhance.Base {
|
||||
data: any
|
||||
function create_data_source(type: string): Data_Source {
|
||||
if (type === "Serial_Data_Source")
|
||||
return new Serial_Data_Source()
|
||||
if (type === "TCP_Client_Data_Source")
|
||||
return new TCP_Client_Data_Source()
|
||||
if (type === "File_Data_Source")
|
||||
return new File_Data_Source()
|
||||
if (type === "Dll_Data_Source")
|
||||
return new Dll_Data_Source()
|
||||
if (type === "Shared_Memory_Data_Source")
|
||||
return new Shared_Memory_Data_Source()
|
||||
throw new Error(`未知数据源类型: ${type}`)
|
||||
}
|
||||
export class Data_Source_Config {
|
||||
list = new List_Data()
|
||||
add_helper = new Data_Source_Add_helper(this);
|
||||
map(key: string): Data_Source | undefined {
|
||||
return this.list.list.find((item: Data_Source) => item.key === key);
|
||||
return this.list.list.find((item: Data_Source) => item.key === key)
|
||||
}
|
||||
|
||||
refresh_from_res(res: AxiosResponse<any, any>) {
|
||||
this.list.list = []
|
||||
this.data = res.data;
|
||||
|
||||
app.leaflet_map.data_source_show.data_source_config = this;
|
||||
app.leaflet_map.data_source_config = this;
|
||||
app.leaflet_map.data_source_show.flush();
|
||||
|
||||
|
||||
this.data.list.forEach((item: Data_Source) => {
|
||||
let ret: Data_Source;
|
||||
if (item.type === "Serial_Data_Source") {
|
||||
ret = new Serial_Data_Source();
|
||||
}
|
||||
else if (item.type === "TCP_Client_Data_Source") {
|
||||
ret = new TCP_Client_Data_Source();
|
||||
}
|
||||
else if (item.type === "File_Data_Source") {
|
||||
ret = new File_Data_Source();
|
||||
}
|
||||
else if (item.type === "Dll_Data_Source") {
|
||||
ret = new Dll_Data_Source();
|
||||
}
|
||||
else if (item.type === "Shared_Memory_Data_Source") {
|
||||
ret = new Shared_Memory_Data_Source();
|
||||
}
|
||||
// @ts-ignore
|
||||
this.list.list.push(ret);
|
||||
//ret = item
|
||||
|
||||
|
||||
Object.assign(ret, item);
|
||||
ret.normalize_map_display();
|
||||
//console.log(ret)
|
||||
//console.log(ret)
|
||||
})
|
||||
this.flush();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
console.log("数据源配置刷新 refresh");
|
||||
axios.post(`${baseURL}/get_mode_s_data_source_config`).then(res => {
|
||||
this.refresh_from_res(res);
|
||||
app.aircraft_list.load_default_data_source();
|
||||
})
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.refresh();
|
||||
|
||||
this.list.insert = (data: Data_Source, index) => {
|
||||
this.add_helper.show(index)
|
||||
}
|
||||
|
||||
this.list.remove = (data: Data_Source, index) => {
|
||||
axios.post(`${baseURL}/remove_data_source`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.up = (data: Data_Source, index) => {
|
||||
axios.post(`${baseURL}/rise_data_source`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.down = (data: Data_Source, index) => {
|
||||
axios.post(`${baseURL}/fall_data_source`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
window.addEventListener("ecap-data-sources-changed", () => void this.refresh())
|
||||
void this.refresh()
|
||||
}
|
||||
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
|
||||
<div style={setting_style}>
|
||||
<h2 style={row_style}>数据源</h2>
|
||||
<this.add_helper.x />
|
||||
<this.list.x />
|
||||
</div>
|
||||
|
||||
)
|
||||
async refresh() {
|
||||
const response = await axios.get("/api/adminive/config/data_sources", {params: {page: 1, perPage: 1000}})
|
||||
const rows = response.data?.data?.items as Adminive_Data_Source_Row[] ?? []
|
||||
this.list.list = rows.map(row => {
|
||||
const source = create_data_source(row.type)
|
||||
const {id, config, state, ...data} = row
|
||||
Object.assign(source, config, data)
|
||||
source.adminive_id = id
|
||||
source.normalize_map_display()
|
||||
return source
|
||||
})
|
||||
if (app.leaflet_map) {
|
||||
app.leaflet_map.data_source_show.data_source_config = this
|
||||
app.leaflet_map.data_source_config = this
|
||||
app.leaflet_map.data_source_show.flush()
|
||||
}
|
||||
app.aircraft_list?.load_default_data_source()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ 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 {default_cesium_graphics_config} from "../Map/Cesium_Render_Settings.tsx";
|
||||
import {default_cesium_graphics_config} from "../Map/Cesium_Graphics_Config.ts";
|
||||
import {tile_source_options, type Map_Tile_Type} from "../Map/Map_Resources.tsx";
|
||||
import {default_map_view_config} from "../Map/Map_View.tsx";
|
||||
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
import axios from "axios";
|
||||
|
||||
export type Cesium_Panel_Position = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export type Cesium_Graphics_Config = {
|
||||
// 显示 Cesium FPS/帧耗时诊断面板,不改变画质。
|
||||
debugShowFramesPerSecond: boolean
|
||||
// 限制 Cesium 渲染循环目标帧率,降低上限可减少 GPU 占用。
|
||||
targetFrameRate: number
|
||||
// 使用浏览器推荐分辨率,避免高 DPI 屏幕按物理像素渲染。
|
||||
useBrowserRecommendedResolution: boolean
|
||||
// WebGL 内部渲染分辨率缩放,越低越省 GPU 但越模糊。
|
||||
resolutionScale: number
|
||||
// MSAA 多重采样数量,1 表示关闭几何抗锯齿。
|
||||
msaaSamples: number
|
||||
// FXAA 后处理抗锯齿,开销低但可能让文字和细线变软。
|
||||
fxaa: boolean
|
||||
// 场景阴影开关,大量模型或地形开启后 GPU 开销明显。
|
||||
shadows: boolean
|
||||
// 地球光照开关,开启后地表按光源方向产生明暗。
|
||||
enableLighting: boolean
|
||||
// 真实日照模式,使用太阳方向、系统时间和动态大气表达昼夜状态。
|
||||
solarLighting: boolean
|
||||
// 真实日照模式下的太阳光强度,只影响视觉明暗。
|
||||
solarLightIntensity: number
|
||||
// 地球瓦片屏幕空间误差,值越大越快但地面细节越粗。
|
||||
maximumScreenSpaceError: number
|
||||
// 仅 3D 模式加载 Terrarium 真实地形,2D/2.5D 不加载。
|
||||
terrainEnabledIn3D: boolean
|
||||
// 地形垂直起伏倍率,只改变视觉起伏,不增加高程精度。
|
||||
terrainExaggeration: number
|
||||
// 限制 Terrarium 地形最高请求层级,减少无效子瓦片请求。
|
||||
terrainLimitMaximumLevel: boolean
|
||||
// 启用层级限制时允许请求的最高地形层级。
|
||||
terrainMaximumLevel: number
|
||||
// 已解码地形高度瓦片缓存数量,越大越占内存但回看更快。
|
||||
terrainCacheTiles: number
|
||||
// 地形遮挡判断开关,基于 Terrarium 高程做基站到飞机的通视分析。
|
||||
terrainOcclusionEnabled: boolean
|
||||
// 地表导航模式下在用户点击地面处显示当前导航参考点和地表类型。
|
||||
surfaceNavigationReferenceVisible: boolean
|
||||
// 在 3D 视图角落显示类似 Blender 的视角 XYZ 坐标轴。
|
||||
viewAxesVisible: boolean
|
||||
// 性能诊断面板在地图容器中的左上角位置。
|
||||
performancePanelPosition: Cesium_Panel_Position
|
||||
// ViewCube 面板在地图容器中的左上角位置。
|
||||
viewAxesPanelPosition: Cesium_Panel_Position
|
||||
// ViewCube SVG 控件的显示尺寸。
|
||||
viewAxesPanelSize: number
|
||||
// 通视分析沿 ECEF 视线采样的间距,越小越精细但请求和计算越多。
|
||||
occlusionSampleSpacingMeters: number
|
||||
// 视线相对地形至少需要保留的净空,低于该值视为遮挡。
|
||||
occlusionClearanceMarginMeters: number
|
||||
}
|
||||
function default_view_axes_panel_position(): Cesium_Panel_Position {
|
||||
const width = typeof window === "undefined" ? 1280 : window.innerWidth;
|
||||
return {x: Math.max(16, width - 116), y: 76};
|
||||
}
|
||||
export const default_cesium_graphics_config: Cesium_Graphics_Config = {
|
||||
debugShowFramesPerSecond: true,
|
||||
targetFrameRate: 30,
|
||||
useBrowserRecommendedResolution: true,
|
||||
resolutionScale: 0.7,
|
||||
msaaSamples: 1,
|
||||
fxaa: false,
|
||||
shadows: false,
|
||||
enableLighting: false,
|
||||
solarLighting: false,
|
||||
solarLightIntensity: 1.5,
|
||||
maximumScreenSpaceError: 4,
|
||||
terrainEnabledIn3D: false,
|
||||
terrainExaggeration: 1.0,
|
||||
terrainLimitMaximumLevel: true,
|
||||
terrainMaximumLevel: 15,
|
||||
terrainCacheTiles: 128,
|
||||
terrainOcclusionEnabled: false,
|
||||
surfaceNavigationReferenceVisible: false,
|
||||
viewAxesVisible: false,
|
||||
performancePanelPosition: {x: 12, y: 92},
|
||||
viewAxesPanelPosition: default_view_axes_panel_position(),
|
||||
viewAxesPanelSize: 84,
|
||||
occlusionSampleSpacingMeters: 250,
|
||||
occlusionClearanceMarginMeters: 10
|
||||
};
|
||||
export const cesium_graphics_config_event = "ecap_cesium_graphics_config_changed";
|
||||
type Cesium_Graphics_Config_Json = {
|
||||
debug_show_frames_per_second: boolean
|
||||
target_frame_rate: number
|
||||
use_browser_recommended_resolution: boolean
|
||||
resolution_scale: number
|
||||
msaa_samples: number
|
||||
fxaa: boolean
|
||||
shadows: boolean
|
||||
enable_lighting: boolean
|
||||
solar_lighting: boolean
|
||||
solar_light_intensity: number
|
||||
maximum_screen_space_error: number
|
||||
terrain_enabled_in_3d: boolean
|
||||
terrain_exaggeration: number
|
||||
terrain_limit_maximum_level: boolean
|
||||
terrain_maximum_level: number
|
||||
terrain_cache_tiles: number
|
||||
terrain_occlusion_enabled: boolean
|
||||
surface_navigation_reference_visible: boolean
|
||||
view_axes_visible: boolean
|
||||
performance_panel_x: number
|
||||
performance_panel_y: number
|
||||
view_axes_panel_x?: number
|
||||
view_axes_panel_y: number
|
||||
view_axes_panel_size: number
|
||||
occlusion_sample_spacing_meters: number
|
||||
occlusion_clearance_margin_meters: number
|
||||
}
|
||||
function number_value(value: unknown, fallback: number, min: number, max: number): number {
|
||||
const next = Number(value);
|
||||
if (!Number.isFinite(next)) return fallback;
|
||||
return Math.min(max, Math.max(min, next));
|
||||
}
|
||||
function unrestricted_number_value(value: unknown, fallback: number): number {
|
||||
const next = Number(value);
|
||||
return Number.isFinite(next) ? next : fallback;
|
||||
}
|
||||
function boolean_value(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
}
|
||||
function panel_position_value(value: Partial<Cesium_Panel_Position> | undefined, fallback: Cesium_Panel_Position): Cesium_Panel_Position {
|
||||
return {
|
||||
x: number_value(value?.x, fallback.x, 0, 100000),
|
||||
y: number_value(value?.y, fallback.y, 0, 100000)
|
||||
};
|
||||
}
|
||||
export function normalize_cesium_graphics_config(data: Partial<Cesium_Graphics_Config>): Cesium_Graphics_Config {
|
||||
return {
|
||||
debugShowFramesPerSecond: boolean_value(data.debugShowFramesPerSecond, default_cesium_graphics_config.debugShowFramesPerSecond),
|
||||
targetFrameRate: number_value(data.targetFrameRate, default_cesium_graphics_config.targetFrameRate, 1, 120),
|
||||
useBrowserRecommendedResolution: boolean_value(data.useBrowserRecommendedResolution, default_cesium_graphics_config.useBrowserRecommendedResolution),
|
||||
resolutionScale: number_value(data.resolutionScale, default_cesium_graphics_config.resolutionScale, 0.2, 1.5),
|
||||
msaaSamples: number_value(data.msaaSamples, default_cesium_graphics_config.msaaSamples, 1, 8),
|
||||
fxaa: boolean_value(data.fxaa, default_cesium_graphics_config.fxaa),
|
||||
shadows: boolean_value(data.shadows, default_cesium_graphics_config.shadows),
|
||||
enableLighting: boolean_value(data.enableLighting, default_cesium_graphics_config.enableLighting),
|
||||
solarLighting: boolean_value(data.solarLighting, default_cesium_graphics_config.solarLighting),
|
||||
solarLightIntensity: number_value(data.solarLightIntensity, default_cesium_graphics_config.solarLightIntensity, 0, 10),
|
||||
maximumScreenSpaceError: number_value(data.maximumScreenSpaceError, default_cesium_graphics_config.maximumScreenSpaceError, 1, 16),
|
||||
terrainEnabledIn3D: boolean_value(data.terrainEnabledIn3D, default_cesium_graphics_config.terrainEnabledIn3D),
|
||||
terrainExaggeration: unrestricted_number_value(data.terrainExaggeration, default_cesium_graphics_config.terrainExaggeration),
|
||||
terrainLimitMaximumLevel: boolean_value(data.terrainLimitMaximumLevel, default_cesium_graphics_config.terrainLimitMaximumLevel),
|
||||
terrainMaximumLevel: number_value(data.terrainMaximumLevel, default_cesium_graphics_config.terrainMaximumLevel, 0, 24),
|
||||
terrainCacheTiles: Math.round(number_value(data.terrainCacheTiles, default_cesium_graphics_config.terrainCacheTiles, 16, 2048)),
|
||||
terrainOcclusionEnabled: boolean_value(data.terrainOcclusionEnabled, default_cesium_graphics_config.terrainOcclusionEnabled),
|
||||
surfaceNavigationReferenceVisible: boolean_value(data.surfaceNavigationReferenceVisible, default_cesium_graphics_config.surfaceNavigationReferenceVisible),
|
||||
viewAxesVisible: boolean_value(data.viewAxesVisible, default_cesium_graphics_config.viewAxesVisible),
|
||||
performancePanelPosition: panel_position_value(data.performancePanelPosition, default_cesium_graphics_config.performancePanelPosition),
|
||||
viewAxesPanelPosition: panel_position_value(data.viewAxesPanelPosition, default_cesium_graphics_config.viewAxesPanelPosition),
|
||||
viewAxesPanelSize: number_value(data.viewAxesPanelSize, default_cesium_graphics_config.viewAxesPanelSize, 48, 240),
|
||||
occlusionSampleSpacingMeters: number_value(data.occlusionSampleSpacingMeters, default_cesium_graphics_config.occlusionSampleSpacingMeters, 10, 5000),
|
||||
occlusionClearanceMarginMeters: unrestricted_number_value(data.occlusionClearanceMarginMeters, default_cesium_graphics_config.occlusionClearanceMarginMeters)
|
||||
};
|
||||
}
|
||||
function from_server_config(data: Partial<Cesium_Graphics_Config_Json>): Cesium_Graphics_Config {
|
||||
return normalize_cesium_graphics_config({
|
||||
debugShowFramesPerSecond: data.debug_show_frames_per_second,
|
||||
targetFrameRate: data.target_frame_rate,
|
||||
useBrowserRecommendedResolution: data.use_browser_recommended_resolution,
|
||||
resolutionScale: data.resolution_scale,
|
||||
msaaSamples: data.msaa_samples,
|
||||
fxaa: data.fxaa,
|
||||
shadows: data.shadows,
|
||||
enableLighting: data.enable_lighting,
|
||||
solarLighting: data.solar_lighting,
|
||||
solarLightIntensity: data.solar_light_intensity,
|
||||
maximumScreenSpaceError: data.maximum_screen_space_error,
|
||||
terrainEnabledIn3D: data.terrain_enabled_in_3d,
|
||||
terrainExaggeration: data.terrain_exaggeration,
|
||||
terrainLimitMaximumLevel: data.terrain_limit_maximum_level,
|
||||
terrainMaximumLevel: data.terrain_maximum_level,
|
||||
terrainCacheTiles: data.terrain_cache_tiles,
|
||||
terrainOcclusionEnabled: data.terrain_occlusion_enabled,
|
||||
surfaceNavigationReferenceVisible: data.surface_navigation_reference_visible,
|
||||
viewAxesVisible: data.view_axes_visible,
|
||||
performancePanelPosition: {x: data.performance_panel_x, y: data.performance_panel_y},
|
||||
viewAxesPanelPosition: {x: data.view_axes_panel_x, y: data.view_axes_panel_y},
|
||||
viewAxesPanelSize: data.view_axes_panel_size,
|
||||
occlusionSampleSpacingMeters: data.occlusion_sample_spacing_meters,
|
||||
occlusionClearanceMarginMeters: data.occlusion_clearance_margin_meters
|
||||
});
|
||||
}
|
||||
function to_server_config(config: Cesium_Graphics_Config): Cesium_Graphics_Config_Json {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
return {
|
||||
debug_show_frames_per_second: normalized.debugShowFramesPerSecond,
|
||||
target_frame_rate: normalized.targetFrameRate,
|
||||
use_browser_recommended_resolution: normalized.useBrowserRecommendedResolution,
|
||||
resolution_scale: normalized.resolutionScale,
|
||||
msaa_samples: normalized.msaaSamples,
|
||||
fxaa: normalized.fxaa,
|
||||
shadows: normalized.shadows,
|
||||
enable_lighting: normalized.enableLighting,
|
||||
solar_lighting: normalized.solarLighting,
|
||||
solar_light_intensity: normalized.solarLightIntensity,
|
||||
maximum_screen_space_error: normalized.maximumScreenSpaceError,
|
||||
terrain_enabled_in_3d: normalized.terrainEnabledIn3D,
|
||||
terrain_exaggeration: normalized.terrainExaggeration,
|
||||
terrain_limit_maximum_level: normalized.terrainLimitMaximumLevel,
|
||||
terrain_maximum_level: normalized.terrainMaximumLevel,
|
||||
terrain_cache_tiles: normalized.terrainCacheTiles,
|
||||
terrain_occlusion_enabled: normalized.terrainOcclusionEnabled,
|
||||
surface_navigation_reference_visible: normalized.surfaceNavigationReferenceVisible,
|
||||
view_axes_visible: normalized.viewAxesVisible,
|
||||
performance_panel_x: normalized.performancePanelPosition.x,
|
||||
performance_panel_y: normalized.performancePanelPosition.y,
|
||||
view_axes_panel_x: normalized.viewAxesPanelPosition.x,
|
||||
view_axes_panel_y: normalized.viewAxesPanelPosition.y,
|
||||
view_axes_panel_size: normalized.viewAxesPanelSize,
|
||||
occlusion_sample_spacing_meters: normalized.occlusionSampleSpacingMeters,
|
||||
occlusion_clearance_margin_meters: normalized.occlusionClearanceMarginMeters
|
||||
};
|
||||
}
|
||||
export async function load_cesium_graphics_config(): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.get<Cesium_Graphics_Config_Json>("/map/graphics");
|
||||
return from_server_config(response.data);
|
||||
}
|
||||
export async function save_cesium_graphics_config(config: Cesium_Graphics_Config): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.post<Cesium_Graphics_Config_Json>("/map/graphics", to_server_config(config));
|
||||
const normalized = from_server_config(response.data);
|
||||
emit_cesium_graphics_config(normalized);
|
||||
return normalized;
|
||||
}
|
||||
export function emit_cesium_graphics_config(config: Cesium_Graphics_Config) {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: normalized}));
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
save_cesium_graphics_config,
|
||||
type Cesium_Graphics_Config,
|
||||
type Cesium_Panel_Position
|
||||
} from "./Cesium_Render_Settings.tsx";
|
||||
} from "./Cesium_Graphics_Config.ts";
|
||||
import {
|
||||
default_map_view_config,
|
||||
load_map_view_config,
|
||||
|
||||
@@ -1,461 +0,0 @@
|
||||
import React from "react";
|
||||
import {Button, InputNumber, message, Space, Switch, Tooltip} from "antd";
|
||||
import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||
import axios from "axios";
|
||||
import enhance from "../core/enhance.tsx";
|
||||
import {col_style, row_style, setting_style} from "../Global.tsx";
|
||||
|
||||
export type Cesium_Panel_Position = {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
export type Cesium_Graphics_Config = {
|
||||
// 显示 Cesium FPS/帧耗时诊断面板,不改变画质。
|
||||
debugShowFramesPerSecond: boolean
|
||||
// 限制 Cesium 渲染循环目标帧率,降低上限可减少 GPU 占用。
|
||||
targetFrameRate: number
|
||||
// 使用浏览器推荐分辨率,避免高 DPI 屏幕按物理像素渲染。
|
||||
useBrowserRecommendedResolution: boolean
|
||||
// WebGL 内部渲染分辨率缩放,越低越省 GPU 但越模糊。
|
||||
resolutionScale: number
|
||||
// MSAA 多重采样数量,1 表示关闭几何抗锯齿。
|
||||
msaaSamples: number
|
||||
// FXAA 后处理抗锯齿,开销低但可能让文字和细线变软。
|
||||
fxaa: boolean
|
||||
// 场景阴影开关,大量模型或地形开启后 GPU 开销明显。
|
||||
shadows: boolean
|
||||
// 地球光照开关,开启后地表按光源方向产生明暗。
|
||||
enableLighting: boolean
|
||||
// 真实日照模式,使用太阳方向、系统时间和动态大气表达昼夜状态。
|
||||
solarLighting: boolean
|
||||
// 真实日照模式下的太阳光强度,只影响视觉明暗。
|
||||
solarLightIntensity: number
|
||||
// 地球瓦片屏幕空间误差,值越大越快但地面细节越粗。
|
||||
maximumScreenSpaceError: number
|
||||
// 仅 3D 模式加载 Terrarium 真实地形,2D/2.5D 不加载。
|
||||
terrainEnabledIn3D: boolean
|
||||
// 地形垂直起伏倍率,只改变视觉起伏,不增加高程精度。
|
||||
terrainExaggeration: number
|
||||
// 限制 Terrarium 地形最高请求层级,减少无效子瓦片请求。
|
||||
terrainLimitMaximumLevel: boolean
|
||||
// 启用层级限制时允许请求的最高地形层级。
|
||||
terrainMaximumLevel: number
|
||||
// 已解码地形高度瓦片缓存数量,越大越占内存但回看更快。
|
||||
terrainCacheTiles: number
|
||||
// 地形遮挡判断开关,基于 Terrarium 高程做基站到飞机的通视分析。
|
||||
terrainOcclusionEnabled: boolean
|
||||
// 地表导航模式下在用户点击地面处显示当前导航参考点和地表类型。
|
||||
surfaceNavigationReferenceVisible: boolean
|
||||
// 在 3D 视图角落显示类似 Blender 的视角 XYZ 坐标轴。
|
||||
viewAxesVisible: boolean
|
||||
// 性能诊断面板在地图容器中的左上角位置。
|
||||
performancePanelPosition: Cesium_Panel_Position
|
||||
// ViewCube 面板在地图容器中的左上角位置。
|
||||
viewAxesPanelPosition: Cesium_Panel_Position
|
||||
// ViewCube SVG 控件的显示尺寸。
|
||||
viewAxesPanelSize: number
|
||||
// 通视分析沿 ECEF 视线采样的间距,越小越精细但请求和计算越多。
|
||||
occlusionSampleSpacingMeters: number
|
||||
// 视线相对地形至少需要保留的净空,低于该值视为遮挡。
|
||||
occlusionClearanceMarginMeters: number
|
||||
}
|
||||
function default_view_axes_panel_position(): Cesium_Panel_Position {
|
||||
const width = typeof window === "undefined" ? 1280 : window.innerWidth;
|
||||
return {x: Math.max(16, width - 116), y: 76};
|
||||
}
|
||||
export const default_cesium_graphics_config: Cesium_Graphics_Config = {
|
||||
debugShowFramesPerSecond: true,
|
||||
targetFrameRate: 30,
|
||||
useBrowserRecommendedResolution: true,
|
||||
resolutionScale: 0.7,
|
||||
msaaSamples: 1,
|
||||
fxaa: false,
|
||||
shadows: false,
|
||||
enableLighting: false,
|
||||
solarLighting: false,
|
||||
solarLightIntensity: 1.5,
|
||||
maximumScreenSpaceError: 4,
|
||||
terrainEnabledIn3D: false,
|
||||
terrainExaggeration: 1.0,
|
||||
terrainLimitMaximumLevel: true,
|
||||
terrainMaximumLevel: 15,
|
||||
terrainCacheTiles: 128,
|
||||
terrainOcclusionEnabled: false,
|
||||
surfaceNavigationReferenceVisible: false,
|
||||
viewAxesVisible: false,
|
||||
performancePanelPosition: {x: 12, y: 92},
|
||||
viewAxesPanelPosition: default_view_axes_panel_position(),
|
||||
viewAxesPanelSize: 84,
|
||||
occlusionSampleSpacingMeters: 250,
|
||||
occlusionClearanceMarginMeters: 10
|
||||
};
|
||||
export const cesium_graphics_config_event = "ecap_cesium_graphics_config_changed";
|
||||
type Number_Config_Key = "targetFrameRate" | "resolutionScale" | "msaaSamples" | "solarLightIntensity" | "maximumScreenSpaceError" | "terrainExaggeration" | "terrainMaximumLevel" | "terrainCacheTiles" | "viewAxesPanelSize" | "occlusionSampleSpacingMeters" | "occlusionClearanceMarginMeters"
|
||||
type Panel_Position_Config_Key = "performancePanelPosition" | "viewAxesPanelPosition"
|
||||
type Boolean_Config_Key = Exclude<keyof Cesium_Graphics_Config, Number_Config_Key | Panel_Position_Config_Key>
|
||||
const graphics_presets: Record<"low" | "medium" | "high", Cesium_Graphics_Config> = {
|
||||
low: {...default_cesium_graphics_config},
|
||||
medium: {
|
||||
debugShowFramesPerSecond: true,
|
||||
targetFrameRate: 45,
|
||||
useBrowserRecommendedResolution: true,
|
||||
resolutionScale: 0.85,
|
||||
msaaSamples: 1,
|
||||
fxaa: true,
|
||||
shadows: false,
|
||||
enableLighting: false,
|
||||
solarLighting: false,
|
||||
solarLightIntensity: 1.5,
|
||||
maximumScreenSpaceError: 3,
|
||||
terrainEnabledIn3D: true,
|
||||
terrainExaggeration: 1.0,
|
||||
terrainLimitMaximumLevel: true,
|
||||
terrainMaximumLevel: 15,
|
||||
terrainCacheTiles: 128,
|
||||
terrainOcclusionEnabled: false,
|
||||
surfaceNavigationReferenceVisible: false,
|
||||
viewAxesVisible: false,
|
||||
performancePanelPosition: {x: 12, y: 92},
|
||||
viewAxesPanelPosition: default_view_axes_panel_position(),
|
||||
viewAxesPanelSize: 84,
|
||||
occlusionSampleSpacingMeters: 250,
|
||||
occlusionClearanceMarginMeters: 10
|
||||
},
|
||||
high: {
|
||||
debugShowFramesPerSecond: true,
|
||||
targetFrameRate: 60,
|
||||
useBrowserRecommendedResolution: false,
|
||||
resolutionScale: 1.0,
|
||||
msaaSamples: 4,
|
||||
fxaa: true,
|
||||
shadows: false,
|
||||
enableLighting: true,
|
||||
solarLighting: true,
|
||||
solarLightIntensity: 1.5,
|
||||
maximumScreenSpaceError: 2,
|
||||
terrainEnabledIn3D: true,
|
||||
terrainExaggeration: 1.0,
|
||||
terrainLimitMaximumLevel: true,
|
||||
terrainMaximumLevel: 17,
|
||||
terrainCacheTiles: 256,
|
||||
terrainOcclusionEnabled: false,
|
||||
surfaceNavigationReferenceVisible: false,
|
||||
viewAxesVisible: false,
|
||||
performancePanelPosition: {x: 12, y: 92},
|
||||
viewAxesPanelPosition: default_view_axes_panel_position(),
|
||||
viewAxesPanelSize: 84,
|
||||
occlusionSampleSpacingMeters: 250,
|
||||
occlusionClearanceMarginMeters: 10
|
||||
}
|
||||
};
|
||||
const graphics_help: Record<keyof Cesium_Graphics_Config, string[]> = {
|
||||
debugShowFramesPerSecond: ["显示 FPS、每帧耗时和诊断信息。", "只用于判断卡顿,基本不影响画质。"],
|
||||
targetFrameRate: ["限制 Cesium 渲染循环的目标帧率。", "越高越流畅,GPU 占用越高;集显建议 30。"],
|
||||
useBrowserRecommendedResolution: ["开启后按 CSS 像素渲染,忽略高 DPI 物理像素倍率。", "高 DPI 屏幕上通常能显著降低集显压力。"],
|
||||
resolutionScale: ["调整 WebGL 内部渲染分辨率。", "0.7 约等于渲染 49% 像素;越低越快但更模糊。"],
|
||||
msaaSamples: ["几何边缘多重采样抗锯齿。", "1 表示关闭;数值越高边缘越平滑,GPU 开销越大。"],
|
||||
fxaa: ["屏幕空间后处理抗锯齿。", "开销通常低于 MSAA,但可能让文字和细线变软。"],
|
||||
shadows: ["控制模型、Primitive 和地形阴影。", "画面更真实,但对大量模型和地形开销明显。"],
|
||||
enableLighting: ["根据场景光源给地球表面做明暗。", "这个开关不驱动系统时间;需要真实昼夜时打开“真实日照”。"],
|
||||
solarLighting: ["使用 Cesium 太阳方向、系统时间和动态大气表达昼夜。", "地球和地面对象保持地固坐标,不手动旋转地球;需要更真实的时间态势时开启。"],
|
||||
solarLightIntensity: ["真实日照模式下的太阳光强度。", "数值越大白天越亮、明暗对比越强;不会改变飞机、基站和轨迹数据。"],
|
||||
maximumScreenSpaceError: ["控制地球瓦片 LOD 的屏幕误差。", "数值越大越快但地面更粗糙;4 是集显平衡档。"],
|
||||
terrainEnabledIn3D: ["3D 模式下加载 Terrarium 真实高程地形。", "会增加网络、解码、CPU 和 GPU 压力;2D/2.5D 默认不加载。"],
|
||||
terrainExaggeration: ["控制地形垂直方向的视觉夸张倍率。", "1.0 为真实高度;0.0 压平地形;只改变视觉起伏,不增加高程精度,前端不限制范围。"],
|
||||
terrainLimitMaximumLevel: ["限制 Terrarium 地形继续请求更深层级。", "只限制高程层级,减少无意义子瓦片请求。"],
|
||||
terrainMaximumLevel: ["地形允许加载的最高瓦片层级。", "只有启用地形且限制层级时生效;越高越精细也越耗资源。"],
|
||||
terrainCacheTiles: ["内存中保留的已解码地形瓦片数量。", "越大回看越快但占用更多内存;过小会增加重复请求和解码。"],
|
||||
terrainOcclusionEnabled: ["基于 Terrarium 高程做基站到飞机的地形通视分析。", "开启后会显示绿色/红色视线和首次遮挡点;只判断地形,不判断建筑、树木或无线电链路。"],
|
||||
surfaceNavigationReferenceVisible: ["地表导航模式下,在用户点击地面处显示参考点。", "标签会显示当前使用椭球地表还是 Terrarium 地形,用于判断相机操作参考面。"],
|
||||
viewAxesVisible: ["在 3D 视图角落显示类似 Blender 的视角 XYZ 控件。", "点击六个方向点会切换相机视角,拖拽控件会围绕当前地表参考点旋转;不会在地面或模型上添加 primitive。"],
|
||||
performancePanelPosition: ["性能诊断面板的位置。", "在地图上拖动面板标题并点击确定后保存。"],
|
||||
viewAxesPanelPosition: ["ViewCube 面板的位置。", "在地图上拖动面板标题并点击确定后保存。"],
|
||||
viewAxesPanelSize: ["ViewCube 控件的像素尺寸。", "修改后会立即改变右上角控件大小,保存调试显示或面板确定后持久化。"],
|
||||
occlusionSampleSpacingMeters: ["通视分析沿三维直线的采样间距。", "越小越容易发现狭窄山脊,但请求和计算更多;建议 100 到 500 米。"],
|
||||
occlusionClearanceMarginMeters: ["视线相对地形的最小净空裕量。", "净空小于等于该值时判为遮挡;可用来给高程误差留余量。"]
|
||||
};
|
||||
type Cesium_Graphics_Config_Json = {
|
||||
debug_show_frames_per_second: boolean
|
||||
target_frame_rate: number
|
||||
use_browser_recommended_resolution: boolean
|
||||
resolution_scale: number
|
||||
msaa_samples: number
|
||||
fxaa: boolean
|
||||
shadows: boolean
|
||||
enable_lighting: boolean
|
||||
solar_lighting: boolean
|
||||
solar_light_intensity: number
|
||||
maximum_screen_space_error: number
|
||||
terrain_enabled_in_3d: boolean
|
||||
terrain_exaggeration: number
|
||||
terrain_limit_maximum_level: boolean
|
||||
terrain_maximum_level: number
|
||||
terrain_cache_tiles: number
|
||||
terrain_occlusion_enabled: boolean
|
||||
surface_navigation_reference_visible: boolean
|
||||
view_axes_visible: boolean
|
||||
performance_panel_x: number
|
||||
performance_panel_y: number
|
||||
view_axes_panel_x?: number
|
||||
view_axes_panel_y: number
|
||||
view_axes_panel_size: number
|
||||
occlusion_sample_spacing_meters: number
|
||||
occlusion_clearance_margin_meters: number
|
||||
}
|
||||
function number_value(value: unknown, fallback: number, min: number, max: number): number {
|
||||
const next = Number(value);
|
||||
if (!Number.isFinite(next)) return fallback;
|
||||
return Math.min(max, Math.max(min, next));
|
||||
}
|
||||
function unrestricted_number_value(value: unknown, fallback: number): number {
|
||||
const next = Number(value);
|
||||
return Number.isFinite(next) ? next : fallback;
|
||||
}
|
||||
function boolean_value(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
}
|
||||
function panel_position_value(value: Partial<Cesium_Panel_Position> | undefined, fallback: Cesium_Panel_Position): Cesium_Panel_Position {
|
||||
return {
|
||||
x: number_value(value?.x, fallback.x, 0, 100000),
|
||||
y: number_value(value?.y, fallback.y, 0, 100000)
|
||||
};
|
||||
}
|
||||
export function normalize_cesium_graphics_config(data: Partial<Cesium_Graphics_Config>): Cesium_Graphics_Config {
|
||||
return {
|
||||
debugShowFramesPerSecond: boolean_value(data.debugShowFramesPerSecond, default_cesium_graphics_config.debugShowFramesPerSecond),
|
||||
targetFrameRate: number_value(data.targetFrameRate, default_cesium_graphics_config.targetFrameRate, 1, 120),
|
||||
useBrowserRecommendedResolution: boolean_value(data.useBrowserRecommendedResolution, default_cesium_graphics_config.useBrowserRecommendedResolution),
|
||||
resolutionScale: number_value(data.resolutionScale, default_cesium_graphics_config.resolutionScale, 0.2, 1.5),
|
||||
msaaSamples: number_value(data.msaaSamples, default_cesium_graphics_config.msaaSamples, 1, 8),
|
||||
fxaa: boolean_value(data.fxaa, default_cesium_graphics_config.fxaa),
|
||||
shadows: boolean_value(data.shadows, default_cesium_graphics_config.shadows),
|
||||
enableLighting: boolean_value(data.enableLighting, default_cesium_graphics_config.enableLighting),
|
||||
solarLighting: boolean_value(data.solarLighting, default_cesium_graphics_config.solarLighting),
|
||||
solarLightIntensity: number_value(data.solarLightIntensity, default_cesium_graphics_config.solarLightIntensity, 0, 10),
|
||||
maximumScreenSpaceError: number_value(data.maximumScreenSpaceError, default_cesium_graphics_config.maximumScreenSpaceError, 1, 16),
|
||||
terrainEnabledIn3D: boolean_value(data.terrainEnabledIn3D, default_cesium_graphics_config.terrainEnabledIn3D),
|
||||
terrainExaggeration: unrestricted_number_value(data.terrainExaggeration, default_cesium_graphics_config.terrainExaggeration),
|
||||
terrainLimitMaximumLevel: boolean_value(data.terrainLimitMaximumLevel, default_cesium_graphics_config.terrainLimitMaximumLevel),
|
||||
terrainMaximumLevel: number_value(data.terrainMaximumLevel, default_cesium_graphics_config.terrainMaximumLevel, 0, 24),
|
||||
terrainCacheTiles: Math.round(number_value(data.terrainCacheTiles, default_cesium_graphics_config.terrainCacheTiles, 16, 2048)),
|
||||
terrainOcclusionEnabled: boolean_value(data.terrainOcclusionEnabled, default_cesium_graphics_config.terrainOcclusionEnabled),
|
||||
surfaceNavigationReferenceVisible: boolean_value(data.surfaceNavigationReferenceVisible, default_cesium_graphics_config.surfaceNavigationReferenceVisible),
|
||||
viewAxesVisible: boolean_value(data.viewAxesVisible, default_cesium_graphics_config.viewAxesVisible),
|
||||
performancePanelPosition: panel_position_value(data.performancePanelPosition, default_cesium_graphics_config.performancePanelPosition),
|
||||
viewAxesPanelPosition: panel_position_value(data.viewAxesPanelPosition, default_cesium_graphics_config.viewAxesPanelPosition),
|
||||
viewAxesPanelSize: number_value(data.viewAxesPanelSize, default_cesium_graphics_config.viewAxesPanelSize, 48, 240),
|
||||
occlusionSampleSpacingMeters: number_value(data.occlusionSampleSpacingMeters, default_cesium_graphics_config.occlusionSampleSpacingMeters, 10, 5000),
|
||||
occlusionClearanceMarginMeters: unrestricted_number_value(data.occlusionClearanceMarginMeters, default_cesium_graphics_config.occlusionClearanceMarginMeters)
|
||||
};
|
||||
}
|
||||
function from_server_config(data: Partial<Cesium_Graphics_Config_Json>): Cesium_Graphics_Config {
|
||||
return normalize_cesium_graphics_config({
|
||||
debugShowFramesPerSecond: data.debug_show_frames_per_second,
|
||||
targetFrameRate: data.target_frame_rate,
|
||||
useBrowserRecommendedResolution: data.use_browser_recommended_resolution,
|
||||
resolutionScale: data.resolution_scale,
|
||||
msaaSamples: data.msaa_samples,
|
||||
fxaa: data.fxaa,
|
||||
shadows: data.shadows,
|
||||
enableLighting: data.enable_lighting,
|
||||
solarLighting: data.solar_lighting,
|
||||
solarLightIntensity: data.solar_light_intensity,
|
||||
maximumScreenSpaceError: data.maximum_screen_space_error,
|
||||
terrainEnabledIn3D: data.terrain_enabled_in_3d,
|
||||
terrainExaggeration: data.terrain_exaggeration,
|
||||
terrainLimitMaximumLevel: data.terrain_limit_maximum_level,
|
||||
terrainMaximumLevel: data.terrain_maximum_level,
|
||||
terrainCacheTiles: data.terrain_cache_tiles,
|
||||
terrainOcclusionEnabled: data.terrain_occlusion_enabled,
|
||||
surfaceNavigationReferenceVisible: data.surface_navigation_reference_visible,
|
||||
viewAxesVisible: data.view_axes_visible,
|
||||
performancePanelPosition: {x: data.performance_panel_x, y: data.performance_panel_y},
|
||||
viewAxesPanelPosition: {x: data.view_axes_panel_x, y: data.view_axes_panel_y},
|
||||
viewAxesPanelSize: data.view_axes_panel_size,
|
||||
occlusionSampleSpacingMeters: data.occlusion_sample_spacing_meters,
|
||||
occlusionClearanceMarginMeters: data.occlusion_clearance_margin_meters
|
||||
});
|
||||
}
|
||||
function to_server_config(config: Cesium_Graphics_Config): Cesium_Graphics_Config_Json {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
return {
|
||||
debug_show_frames_per_second: normalized.debugShowFramesPerSecond,
|
||||
target_frame_rate: normalized.targetFrameRate,
|
||||
use_browser_recommended_resolution: normalized.useBrowserRecommendedResolution,
|
||||
resolution_scale: normalized.resolutionScale,
|
||||
msaa_samples: normalized.msaaSamples,
|
||||
fxaa: normalized.fxaa,
|
||||
shadows: normalized.shadows,
|
||||
enable_lighting: normalized.enableLighting,
|
||||
solar_lighting: normalized.solarLighting,
|
||||
solar_light_intensity: normalized.solarLightIntensity,
|
||||
maximum_screen_space_error: normalized.maximumScreenSpaceError,
|
||||
terrain_enabled_in_3d: normalized.terrainEnabledIn3D,
|
||||
terrain_exaggeration: normalized.terrainExaggeration,
|
||||
terrain_limit_maximum_level: normalized.terrainLimitMaximumLevel,
|
||||
terrain_maximum_level: normalized.terrainMaximumLevel,
|
||||
terrain_cache_tiles: normalized.terrainCacheTiles,
|
||||
terrain_occlusion_enabled: normalized.terrainOcclusionEnabled,
|
||||
surface_navigation_reference_visible: normalized.surfaceNavigationReferenceVisible,
|
||||
view_axes_visible: normalized.viewAxesVisible,
|
||||
performance_panel_x: normalized.performancePanelPosition.x,
|
||||
performance_panel_y: normalized.performancePanelPosition.y,
|
||||
view_axes_panel_x: normalized.viewAxesPanelPosition.x,
|
||||
view_axes_panel_y: normalized.viewAxesPanelPosition.y,
|
||||
view_axes_panel_size: normalized.viewAxesPanelSize,
|
||||
occlusion_sample_spacing_meters: normalized.occlusionSampleSpacingMeters,
|
||||
occlusion_clearance_margin_meters: normalized.occlusionClearanceMarginMeters
|
||||
};
|
||||
}
|
||||
export async function load_cesium_graphics_config(): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.get<Cesium_Graphics_Config_Json>("/map/graphics");
|
||||
return from_server_config(response.data);
|
||||
}
|
||||
export async function save_cesium_graphics_config(config: Cesium_Graphics_Config): Promise<Cesium_Graphics_Config> {
|
||||
const response = await axios.post<Cesium_Graphics_Config_Json>("/map/graphics", to_server_config(config));
|
||||
const normalized = from_server_config(response.data);
|
||||
emit_cesium_graphics_config(normalized);
|
||||
return normalized;
|
||||
}
|
||||
export function emit_cesium_graphics_config(config: Cesium_Graphics_Config) {
|
||||
const normalized = normalize_cesium_graphics_config(config);
|
||||
window.dispatchEvent(new CustomEvent<Cesium_Graphics_Config>(cesium_graphics_config_event, {detail: normalized}));
|
||||
}
|
||||
export class Cesium_Render_Settings extends enhance.Base {
|
||||
config: Cesium_Graphics_Config = {...default_cesium_graphics_config};
|
||||
number_input_drafts: Partial<Record<Number_Config_Key, number | null>> = {};
|
||||
async on_mount() {
|
||||
this.config = await load_cesium_graphics_config();
|
||||
this.number_input_drafts = {};
|
||||
this.flush();
|
||||
}
|
||||
number_input_value(key: Number_Config_Key): number | null {
|
||||
return Object.prototype.hasOwnProperty.call(this.number_input_drafts, key) ? this.number_input_drafts[key]! : this.config[key];
|
||||
}
|
||||
set_number(key: Number_Config_Key, value: number | null) {
|
||||
this.number_input_drafts[key] = value;
|
||||
if (value === null) {
|
||||
this.flush();
|
||||
return;
|
||||
}
|
||||
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
||||
emit_cesium_graphics_config(this.config);
|
||||
this.flush();
|
||||
}
|
||||
blur_number(key: Number_Config_Key) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.number_input_drafts, key)) {
|
||||
delete this.number_input_drafts[key];
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
set_boolean(key: Boolean_Config_Key, value: boolean) {
|
||||
this.config = normalize_cesium_graphics_config({...this.config, [key]: value});
|
||||
emit_cesium_graphics_config(this.config);
|
||||
this.flush();
|
||||
}
|
||||
reset() {
|
||||
this.config = {...default_cesium_graphics_config};
|
||||
this.number_input_drafts = {};
|
||||
emit_cesium_graphics_config(this.config);
|
||||
this.flush();
|
||||
}
|
||||
apply_preset(key: keyof typeof graphics_presets) {
|
||||
this.config = {...graphics_presets[key]};
|
||||
this.number_input_drafts = {};
|
||||
emit_cesium_graphics_config(this.config);
|
||||
this.flush();
|
||||
}
|
||||
async save() {
|
||||
this.config = await save_cesium_graphics_config(this.config);
|
||||
this.number_input_drafts = {};
|
||||
message.success("Cesium画质设置已保存");
|
||||
this.flush();
|
||||
}
|
||||
render_label(label: string, key: keyof Cesium_Graphics_Config) {
|
||||
return (
|
||||
<Space size={4}>
|
||||
<span>{label}</span>
|
||||
<Tooltip placement="right" title={<div style={{maxWidth: 360}}>{graphics_help[key].map((line) => <div key={line}>{line}</div>)}</div>}>
|
||||
<QuestionCircleOutlined style={{color: "#1677ff"}} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
render_switch(label: string, key: Boolean_Config_Key) {
|
||||
return (
|
||||
<Space style={{display: "flex", justifyContent: "space-between", width: 280}}>
|
||||
{this.render_label(label, key)}
|
||||
<Switch checked={this.config[key]} onChange={(checked) => this.set_boolean(key, checked)} />
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
render_number(label: string, key: Number_Config_Key, min: number, max: number, step: number) {
|
||||
return (
|
||||
<InputNumber
|
||||
addonBefore={this.render_label(label, key)}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={this.number_input_value(key)}
|
||||
style={{width: 280}}
|
||||
onChange={(value) => this.set_number(key, value)}
|
||||
onBlur={() => this.blur_number(key)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
render_terrain_exaggeration() {
|
||||
const disabled = !this.config.terrainEnabledIn3D;
|
||||
return (
|
||||
<Space direction="vertical" size={4} style={{width: 280}}>
|
||||
{this.render_label("地形起伏倍率", "terrainExaggeration")}
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
disabled={disabled}
|
||||
value={this.number_input_value("terrainExaggeration")}
|
||||
style={{width: 280}}
|
||||
onChange={(value) => this.set_number("terrainExaggeration", value)}
|
||||
onBlur={() => this.blur_number("terrainExaggeration")}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
<h2 style={row_style}>Cesium画质设置</h2>
|
||||
<Space direction="vertical" size={8} style={col_style}>
|
||||
<Space>
|
||||
<Button onClick={() => this.apply_preset("low")}>低画质</Button>
|
||||
<Button onClick={() => this.apply_preset("medium")}>中画质</Button>
|
||||
<Button onClick={() => this.apply_preset("high")}>高画质</Button>
|
||||
</Space>
|
||||
{this.render_switch("显示FPS", "debugShowFramesPerSecond")}
|
||||
{this.render_number("目标帧率", "targetFrameRate", 1, 120, 1)}
|
||||
{this.render_switch("浏览器推荐分辨率", "useBrowserRecommendedResolution")}
|
||||
{this.render_number("分辨率缩放", "resolutionScale", 0.2, 1.5, 0.1)}
|
||||
{this.render_number("MSAA采样", "msaaSamples", 1, 8, 1)}
|
||||
{this.render_switch("FXAA", "fxaa")}
|
||||
{this.render_switch("阴影", "shadows")}
|
||||
{this.render_switch("地球光照", "enableLighting")}
|
||||
{this.render_switch("真实日照", "solarLighting")}
|
||||
{this.render_number("太阳光强度", "solarLightIntensity", 0, 10, 0.1)}
|
||||
{this.render_number("地球细节误差", "maximumScreenSpaceError", 1, 16, 1)}
|
||||
{this.render_switch("3D启用地形", "terrainEnabledIn3D")}
|
||||
{this.render_terrain_exaggeration()}
|
||||
{this.render_switch("限制地形层级", "terrainLimitMaximumLevel")}
|
||||
{this.render_number("地形最大层级", "terrainMaximumLevel", 0, 24, 1)}
|
||||
{this.render_number("地形缓存瓦片数", "terrainCacheTiles", 16, 2048, 16)}
|
||||
{this.render_switch("地形遮挡判断", "terrainOcclusionEnabled")}
|
||||
{this.render_switch("地表导航参考", "surfaceNavigationReferenceVisible")}
|
||||
{this.render_switch("视角坐标轴", "viewAxesVisible")}
|
||||
{this.render_number("遮挡采样间距", "occlusionSampleSpacingMeters", 10, 5000, 10)}
|
||||
{this.render_number("遮挡净空裕量", "occlusionClearanceMarginMeters", -1000, 10000, 1)}
|
||||
<Space>
|
||||
<Button onClick={() => this.reset()}>恢复默认画质</Button>
|
||||
<Button type="primary" onClick={() => this.save()}>确定</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+7
-187
@@ -1,21 +1,14 @@
|
||||
import enhance from "./core/enhance.tsx";
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Button, Checkbox, CheckboxChangeEvent, ConfigProvider, Flex, InputNumber, List, Select, Tree } from "antd";
|
||||
import { Radio } from 'antd';
|
||||
import {Button, Flex, InputNumber, Select} from "antd";
|
||||
import { Input } from 'antd';
|
||||
import { Switch } from 'antd';
|
||||
import {G, baseURL, Center_Server_Config, setting_style, row_style, col_style} from "./Global.js";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import {baseURL, setting_style, row_style, col_style} from "./Global.js";
|
||||
import { Data_Source_Config } from "./Data_Source/Data_Source_Config.tsx";
|
||||
import { Data_Feed_Config } from "./Data_Feed/Data_Feed_Config.tsx";
|
||||
import { Device_Config } from "./Device_Config.tsx";
|
||||
import axios from "axios";
|
||||
import { ReloadOutlined } from "@ant-design/icons";
|
||||
import { Source_Feed_Relation_Config } from "./Source_Feed_Relation/Source_Feed_Relation_Config.tsx";
|
||||
import { Input_Port_Number } from "./A_Global.tsx";
|
||||
import { PersistentScroll } from "./PersistentScroll.tsx";
|
||||
import { External_Resources_Manager } from "./External_Resources_Manager/External_Resources_Manager.tsx";
|
||||
import { Cesium_Render_Settings } from "./Map/Cesium_Render_Settings.tsx";
|
||||
import {Adminive_Settings} from "./Adminive/Adminive_Settings.tsx";
|
||||
import { Cesium_Model_Settings } from "./Map/Cesium_Model_Settings.tsx";
|
||||
|
||||
const { TextArea } = Input;
|
||||
@@ -73,167 +66,6 @@ export class Console extends enhance.Base {
|
||||
}
|
||||
|
||||
|
||||
class Mode_S_Basic_Config extends enhance.Base {
|
||||
timeout_seconds: number = 0;
|
||||
read_milliseconds: number = 0;
|
||||
max_speed_m_s: number = 0;
|
||||
air_pos_timeout: number = 0;
|
||||
aircraft_change_list_min_position_points: number = 2;
|
||||
aircraft_change_list_adsb_range_filter: boolean = true;
|
||||
aircraft_change_list_adsb_range_factor: number = 1.2;
|
||||
adsb_theoretical_target_altitude_meters: number = 10000;
|
||||
number_input_drafts: Record<string, number | null> = {};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
axios.post(`${baseURL}/get_mode_s_basic_config`, {}).then(res => {
|
||||
this.timeout_seconds = res.data.timeout_seconds;
|
||||
this.max_speed_m_s = res.data.max_speed_m_s;
|
||||
this.air_pos_timeout = res.data.air_pos_timeout;
|
||||
this.read_milliseconds = res.data.read_milliseconds;
|
||||
this.aircraft_change_list_min_position_points = res.data.aircraft_change_list_min_position_points;
|
||||
this.aircraft_change_list_adsb_range_filter = res.data.aircraft_change_list_adsb_range_filter;
|
||||
this.aircraft_change_list_adsb_range_factor = res.data.aircraft_change_list_adsb_range_factor;
|
||||
this.adsb_theoretical_target_altitude_meters = res.data.adsb_theoretical_target_altitude_meters;
|
||||
this.flush();
|
||||
})
|
||||
}
|
||||
number_input_value(key: string, value: number): number | null {
|
||||
return Object.prototype.hasOwnProperty.call(this.number_input_drafts, key) ? this.number_input_drafts[key] : value;
|
||||
}
|
||||
change_number_input(key: string, value: number | null, commit: (value: number) => void) {
|
||||
this.number_input_drafts[key] = value;
|
||||
if (value !== null) {
|
||||
commit(value);
|
||||
}
|
||||
this.flush();
|
||||
}
|
||||
blur_number_input(key: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.number_input_drafts, key)) {
|
||||
delete this.number_input_drafts[key];
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
|
||||
update(){
|
||||
axios.post(`${baseURL}/set_mode_s_basic_config`, this).then(res => {
|
||||
this.flush();
|
||||
|
||||
})
|
||||
message.success("mode_s基础属性设置成功!")
|
||||
}
|
||||
|
||||
|
||||
render(props: any) {
|
||||
|
||||
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
|
||||
<h2 style={row_style}>Mode_S_基本设置</h2>
|
||||
<InputNumber
|
||||
addonBefore={"超时时间"}
|
||||
addonAfter={"s"}
|
||||
value={this.number_input_value("timeout_seconds", this.timeout_seconds)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("timeout_seconds", value, (value) => {
|
||||
this.timeout_seconds = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("timeout_seconds")}
|
||||
/>
|
||||
<InputNumber
|
||||
addonBefore={"飞机最快速度"}
|
||||
addonAfter={"m/s"}
|
||||
value={this.number_input_value("max_speed_m_s", this.max_speed_m_s)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("max_speed_m_s", value, (value) => {
|
||||
this.max_speed_m_s = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("max_speed_m_s")}
|
||||
/>
|
||||
<InputNumber
|
||||
addonBefore={"cpr超时时间"}
|
||||
addonAfter={"s"}
|
||||
value={this.number_input_value("air_pos_timeout", this.air_pos_timeout)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("air_pos_timeout", value, (value) => {
|
||||
this.air_pos_timeout = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("air_pos_timeout")}
|
||||
/>
|
||||
<InputNumber
|
||||
addonBefore={"读取数据源延时"}
|
||||
addonAfter={"ms"}
|
||||
value={this.number_input_value("read_milliseconds", this.read_milliseconds)}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("read_milliseconds", value, (value) => {
|
||||
this.read_milliseconds = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("read_milliseconds")}
|
||||
/>
|
||||
<InputNumber
|
||||
addonBefore={"最少位置点"}
|
||||
value={this.number_input_value("aircraft_change_list_min_position_points", this.aircraft_change_list_min_position_points)}
|
||||
min={1}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("aircraft_change_list_min_position_points", value, (value) => {
|
||||
this.aircraft_change_list_min_position_points = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("aircraft_change_list_min_position_points")}
|
||||
/>
|
||||
<Flex gap="middle" justify='center' align='center'>
|
||||
<span>基站理论范围过滤</span>
|
||||
<Switch
|
||||
checked={this.aircraft_change_list_adsb_range_filter}
|
||||
onChange={(checked) => {
|
||||
this.aircraft_change_list_adsb_range_filter = checked;
|
||||
this.flush();
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<InputNumber
|
||||
addonBefore={"范围过滤系数"}
|
||||
value={this.number_input_value("aircraft_change_list_adsb_range_factor", this.aircraft_change_list_adsb_range_factor)}
|
||||
step={0.1}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("aircraft_change_list_adsb_range_factor", value, (value) => {
|
||||
this.aircraft_change_list_adsb_range_factor = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("aircraft_change_list_adsb_range_factor")}
|
||||
/>
|
||||
<InputNumber
|
||||
addonBefore={"理论目标高度"}
|
||||
addonAfter={"m"}
|
||||
value={this.number_input_value("adsb_theoretical_target_altitude_meters", this.adsb_theoretical_target_altitude_meters)}
|
||||
step={100}
|
||||
onChange={(value: number | null) => {
|
||||
this.change_number_input("adsb_theoretical_target_altitude_meters", value, (value) => {
|
||||
this.adsb_theoretical_target_altitude_meters = value;
|
||||
});
|
||||
}}
|
||||
onBlur={() => this.blur_number_input("adsb_theoretical_target_altitude_meters")}
|
||||
/>
|
||||
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
this.update();
|
||||
}}>
|
||||
确定
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Mode_S_Basic_Config
|
||||
|
||||
import { Upload, Space, Modal, Progress, message } from 'antd';
|
||||
import { UploadOutlined, DownloadOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
||||
|
||||
@@ -582,15 +414,11 @@ export class Online_Upgrade extends enhance.Base {
|
||||
|
||||
export class Settings extends enhance.Base {
|
||||
// 第一步:仅声明子组件属性,不即时实例化
|
||||
data_feed_config: Data_Feed_Config | null = null;
|
||||
console: Console | null = null;
|
||||
device_config: Device_Config | null = null;
|
||||
data_source_config: Data_Source_Config | null = null;
|
||||
mode_s_config: Mode_S_Basic_Config | null = null;
|
||||
source_feed_relation_config: Source_Feed_Relation_Config | null = null;
|
||||
device_config: Device_Config | null = null;
|
||||
config_Import_Export_Upgrade: Online_Upgrade | null = null;
|
||||
external_resources_manager: External_Resources_Manager | null = null;
|
||||
cesium_render_settings: Cesium_Render_Settings | null = null;
|
||||
cesium_model_settings: Cesium_Model_Settings | null = null;
|
||||
|
||||
isRebooting: boolean = false;
|
||||
@@ -603,21 +431,17 @@ export class Settings extends enhance.Base {
|
||||
|
||||
// 新增:子组件初始化方法
|
||||
initSubComponents = () => {
|
||||
this.data_feed_config = new Data_Feed_Config();
|
||||
this.console = new Console();
|
||||
this.device_config = new Device_Config();
|
||||
this.data_source_config = new Data_Source_Config();
|
||||
this.mode_s_config = new Mode_S_Basic_Config();
|
||||
this.source_feed_relation_config = new Source_Feed_Relation_Config();
|
||||
this.device_config = new Device_Config();
|
||||
this.config_Import_Export_Upgrade = new Online_Upgrade();
|
||||
this.external_resources_manager = new External_Resources_Manager();
|
||||
this.cesium_render_settings = new Cesium_Render_Settings();
|
||||
this.cesium_model_settings = new Cesium_Model_Settings();
|
||||
};
|
||||
|
||||
render(props: any) {
|
||||
// 第三步:渲染前校验子组件是否初始化完成
|
||||
if (!this.data_feed_config || !this.console || !this.device_config || !this.data_source_config || !this.mode_s_config || !this.source_feed_relation_config || !this.config_Import_Export_Upgrade || !this.external_resources_manager || !this.cesium_render_settings || !this.cesium_model_settings) {
|
||||
if (!this.console || !this.data_source_config || !this.device_config || !this.config_Import_Export_Upgrade || !this.external_resources_manager || !this.cesium_model_settings) {
|
||||
return <div style={{ textAlign: 'center', padding: '20px' }}>设置组件初始化中...</div>;
|
||||
}
|
||||
|
||||
@@ -630,13 +454,9 @@ export class Settings extends enhance.Base {
|
||||
// alignItems: 'center',
|
||||
}}>
|
||||
|
||||
<this.mode_s_config.x></this.mode_s_config.x>
|
||||
<this.data_source_config.x></this.data_source_config.x>
|
||||
<this.data_feed_config.x></this.data_feed_config.x>
|
||||
<this.source_feed_relation_config.x></this.source_feed_relation_config.x>
|
||||
<Adminive_Settings />
|
||||
<this.external_resources_manager.x></this.external_resources_manager.x>
|
||||
<this.config_Import_Export_Upgrade.x></this.config_Import_Export_Upgrade.x>
|
||||
<this.cesium_render_settings.x></this.cesium_render_settings.x>
|
||||
<this.cesium_model_settings.x></this.cesium_model_settings.x>
|
||||
|
||||
<br></br>
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import enhance from "../core/enhance.tsx";
|
||||
import {Button, message, Space, Switch, Tag, Typography} from "antd";
|
||||
import {SettingOutlined} from "@ant-design/icons";
|
||||
import axios from "axios";
|
||||
import {baseURL} from "../Global.tsx";
|
||||
import React from "react";
|
||||
import {Data_Feed, Data_Feed_TCP_Client, Data_Feed_TCP_Server} from "../Data_Feed/Data_Feed.tsx";
|
||||
import {Switch_Bool} from "../A_Global.tsx";
|
||||
const { Text } = Typography;
|
||||
|
||||
export class Source_Feed_Relation extends enhance.Base {
|
||||
key: string = "未命名";
|
||||
enable: boolean = false;
|
||||
type: string = "";
|
||||
source_key: string = "";
|
||||
feed_key: string = "";
|
||||
|
||||
|
||||
center(flush) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
onClick(that) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between", // 将子元素水平分散
|
||||
alignItems: "center", // 子元素垂直居中
|
||||
gap: "8px", // 设置元素之间的间距
|
||||
width: "100%" // 确保容器宽度占满父容器
|
||||
}}>
|
||||
<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(this.flush)
|
||||
}
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<Switch_Bool that={this} field="enable"/>
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
axios.post(`${baseURL}/update_source_feed_relation`, this).then((res) => {
|
||||
Object.assign(this, res.data);
|
||||
});
|
||||
message.success(`${this.type}类型,数据接收数据馈送连接关系 ${this.key}设置成功!` );
|
||||
}}>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class One_to_One_Relation extends Source_Feed_Relation {
|
||||
center(flush) {
|
||||
return <>
|
||||
<Button
|
||||
icon={<SettingOutlined/>}
|
||||
onClick={() => {
|
||||
this.onClick(this);
|
||||
}}>
|
||||
{"数据源: " + this.source_key}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SettingOutlined/>}
|
||||
onClick={() => {
|
||||
this.onClick(this);
|
||||
}}>
|
||||
{"馈送点: " + this.feed_key}
|
||||
</Button>
|
||||
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
export class First_Source_To_All_Feed_Relation extends Source_Feed_Relation {
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function create_source_feed_relation_from_type(type: string) {
|
||||
let ret: Source_Feed_Relation
|
||||
if (type === "First_Source_To_All_Feed_Relation") {
|
||||
ret = new First_Source_To_All_Feed_Relation();
|
||||
}
|
||||
if (type === "One_to_One_Relation") {
|
||||
ret = new One_to_One_Relation();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
import enhance from "../core/enhance.tsx";
|
||||
import {baseURL, List_Data, Prefix, row_style, setting_style} from "../Global.tsx";
|
||||
import axios from "axios";
|
||||
import {
|
||||
create_source_feed_relation_from_type,
|
||||
First_Source_To_All_Feed_Relation,
|
||||
One_to_One_Relation,
|
||||
Source_Feed_Relation
|
||||
} from "./Source_Feed_Relation.tsx";
|
||||
import {Flex, Input, InputNumber, Modal, Select, SelectProps, Switch} from "antd";
|
||||
import React from "react";
|
||||
import {app} from "../App.tsx";
|
||||
import {Data_Source} from "../Data_Source/Data_Source.tsx";
|
||||
import {
|
||||
Data_Feed,
|
||||
Data_Feed_TCP_Client,
|
||||
Data_Feed_TCP_Server,
|
||||
Data_Feed_UDP_Client,
|
||||
Data_Feed_UDP_Server
|
||||
} from "../Data_Feed/Data_Feed.tsx";
|
||||
import {create_data_feed_options, create_data_source_options, Input_String} from "../A_Global.tsx";
|
||||
|
||||
const {Option} = Select;
|
||||
let name = "source_feed_relation"
|
||||
|
||||
const options: SelectProps['options'] = [];
|
||||
|
||||
for (let i = 10; i < 36; i++) {
|
||||
options.push({
|
||||
value: i.toString(36) + i,
|
||||
label: i.toString(36) + i,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class Show_Add_Source_Feed_Relation {
|
||||
show(d: Source_Feed_Relation, index: number) {
|
||||
this._show = true
|
||||
this.par.flush();
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
par: Source_Feed_Relation_Config
|
||||
|
||||
constructor(_par: Source_Feed_Relation_Config) {
|
||||
this.par = _par;
|
||||
}
|
||||
|
||||
_show: boolean = false;
|
||||
type: string = "One_to_One_Relation"
|
||||
// enable: boolean = false;
|
||||
new_create: Source_Feed_Relation = new Source_Feed_Relation()
|
||||
index: number
|
||||
|
||||
x() {
|
||||
// @ts-ignore
|
||||
// @ts-ignore
|
||||
return <>
|
||||
<Modal
|
||||
centered
|
||||
title="添加数据源-馈送关系"
|
||||
open={this._show}
|
||||
// 对话框点确定
|
||||
onOk={() => {
|
||||
this.new_create.type = this.type;
|
||||
//
|
||||
console.log(this.new_create);
|
||||
axios.post(`${baseURL}/insert_${name}`, {index: this.index, data: this.new_create}).then(res => {
|
||||
this.par.refresh_from_res(res)
|
||||
})
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
}}
|
||||
onCancel={() => {
|
||||
this._show = false;
|
||||
this.par.flush();
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
//alignItems:'center',
|
||||
gap: '16px',
|
||||
}}>
|
||||
|
||||
{/*<Input_String that={this.new_create} field={"key"} name={"请输入唯一名称"}></Input_String>*/}
|
||||
<Input addonBefore="唯一名称" placeholder="请输入唯一名称"
|
||||
value={this.new_create.key}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.new_create.key = e.target.value;
|
||||
this.par.flush();
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
prefix={<Prefix label="数据源类型"/>}
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
placeholder="请选择一个选项"
|
||||
value={this.type}
|
||||
onChange={(value) => {
|
||||
if (this.type != value) {
|
||||
this.type = value;
|
||||
this.par.flush();
|
||||
}
|
||||
this.new_create = create_source_feed_relation_from_type(this.type);
|
||||
}}>
|
||||
|
||||
<Option value="One_to_One_Relation">一对一</Option>
|
||||
<Option value="First_Source_To_All_Feed_Relation">首源-所有馈送点</Option>
|
||||
|
||||
</Select>
|
||||
|
||||
<Switch style={{
|
||||
alignSelf: 'flex-start'
|
||||
}} value={this.new_create.enable} onChange={(checked: boolean) => {
|
||||
this.new_create.enable = checked
|
||||
this.par.flush();
|
||||
}}></Switch>
|
||||
|
||||
|
||||
{/*{*/}
|
||||
{/* this.new_create &&*/}
|
||||
{/* this.new_create.center(this.par.flush)*/}
|
||||
{/*}*/}
|
||||
|
||||
<Select
|
||||
prefix={<Prefix label="数据来源"/>}
|
||||
placeholder="请选择数据来源"
|
||||
onChange={(value) => {
|
||||
this.new_create.source_key = value;
|
||||
}}
|
||||
options={create_data_source_options()}
|
||||
/>
|
||||
|
||||
<Select
|
||||
prefix={<Prefix label="数据馈送"/>}
|
||||
placeholder="请选择数据馈送"
|
||||
onChange={(value) => {
|
||||
this.new_create.feed_key = value;
|
||||
}}
|
||||
options={create_data_feed_options()}
|
||||
/>
|
||||
|
||||
|
||||
{/*{*/}
|
||||
{/* this.new_create &&*/}
|
||||
{/* this.new_create.output_format.output(this.par.flush)*/}
|
||||
{/*}*/}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function assign(dest, origin) {
|
||||
Object.keys(origin).forEach((key) => {
|
||||
const value = origin[key];
|
||||
if (value && typeof value != 'object' && !Array.isArray(value) && typeof value !== 'function') {
|
||||
dest[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class Source_Feed_Relation_Config extends enhance.Base {
|
||||
list = new List_Data();
|
||||
add_helper = new Show_Add_Source_Feed_Relation(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
axios.post(`${baseURL}/get_${name}_config`).then((res) => {
|
||||
this.refresh_from_res(res);
|
||||
})
|
||||
this.list.insert = (data: Source_Feed_Relation, index) => {
|
||||
this.add_helper.show(data, index)
|
||||
}
|
||||
|
||||
this.list.remove = (data: Source_Feed_Relation, index) => {
|
||||
axios.post(`${baseURL}/remove_${name}`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.up = (data: Source_Feed_Relation, index) => {
|
||||
axios.post(`${baseURL}/rise_${name}`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
|
||||
this.list.down = (data: Source_Feed_Relation, index) => {
|
||||
axios.post(`${baseURL}/fall_${name}`, {index: index}).then((res) => {
|
||||
this.refresh_from_res(res)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
refresh_from_res(res) {
|
||||
this.list.list = []
|
||||
|
||||
res.data.list.forEach((data: any) => {
|
||||
let cur: Source_Feed_Relation = create_source_feed_relation_from_type(data.type)
|
||||
assign(cur, data)
|
||||
|
||||
cur.onClick = (d: Source_Feed_Relation) => {
|
||||
|
||||
}
|
||||
// @ts-ignore
|
||||
this.list.list.push(cur);
|
||||
})
|
||||
this.flush();
|
||||
}
|
||||
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={setting_style} >
|
||||
<h2 style={row_style}>数据源-数据馈送-关系</h2>
|
||||
|
||||
<this.list.x></this.list.x>
|
||||
{
|
||||
this.add_helper.x()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
+81
-1
@@ -6,6 +6,86 @@ import { fileURLToPath } from 'url'
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const cesiumSource = path.join(__dirname, 'node_modules', 'cesium', 'Build', 'Cesium')
|
||||
const modelSource = path.join(__dirname, 'model')
|
||||
const adminiveFrontend = path.join(__dirname, '..', 'Adminive', 'frontend')
|
||||
const amisSdkSource = path.join(adminiveFrontend, 'node_modules', 'amis', 'sdk')
|
||||
const amisPackage = JSON.parse(fs.readFileSync(path.join(adminiveFrontend, 'node_modules', 'amis', 'package.json'), 'utf8'))
|
||||
const amisSdkUrlPrefix = `/ui/vendor/amis/${amisPackage.version}/`
|
||||
function amisContentType(fileName) {
|
||||
const extension = path.extname(fileName).toLowerCase()
|
||||
if (extension === '.css') return 'text/css; charset=utf-8'
|
||||
if (extension === '.js') return 'text/javascript; charset=utf-8'
|
||||
if (extension === '.json') return 'application/json; charset=utf-8'
|
||||
if (extension === '.svg') return 'image/svg+xml'
|
||||
if (extension === '.woff') return 'font/woff'
|
||||
if (extension === '.woff2') return 'font/woff2'
|
||||
if (extension === '.ttf') return 'font/ttf'
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
function serveAmisSdk(server) {
|
||||
server.middlewares.use((request, response, next) => {
|
||||
const pathname = new URL(request.url || '/', 'http://localhost').pathname
|
||||
if (!pathname.startsWith(amisSdkUrlPrefix)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
const relativePath = decodeURIComponent(pathname.slice(amisSdkUrlPrefix.length))
|
||||
const sourcePath = path.resolve(amisSdkSource, relativePath)
|
||||
if (sourcePath !== amisSdkSource && !sourcePath.startsWith(`${amisSdkSource}${path.sep}`)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
try {
|
||||
response.statusCode = 200
|
||||
response.setHeader('Content-Type', amisContentType(sourcePath))
|
||||
response.end(fs.readFileSync(sourcePath))
|
||||
} catch {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
function amisSdkPlugin() {
|
||||
return {
|
||||
name: 'ecap-adminive-amis-sdk',
|
||||
enforce: 'pre',
|
||||
resolveId(source) {
|
||||
if (source === 'amis' || source.startsWith('amis/')) {
|
||||
this.error(`Do not import ${source} into the Vite module graph; use the AMIS SDK runtime`)
|
||||
}
|
||||
return null
|
||||
},
|
||||
buildStart() {
|
||||
const emitDirectory = (directory, prefix) => {
|
||||
for (const entry of fs.readdirSync(directory)) {
|
||||
const sourcePath = path.join(directory, entry)
|
||||
const relativePath = `${prefix}${entry}`
|
||||
if (fs.statSync(sourcePath).isDirectory()) {
|
||||
emitDirectory(sourcePath, `${relativePath}/`)
|
||||
} else {
|
||||
this.emitFile({type: 'asset', fileName: `vendor/amis/${amisPackage.version}/${relativePath}`, source: fs.readFileSync(sourcePath)})
|
||||
}
|
||||
}
|
||||
}
|
||||
emitDirectory(amisSdkSource, '')
|
||||
},
|
||||
transformIndexHtml: {
|
||||
order: 'post',
|
||||
handler() {
|
||||
return [
|
||||
{tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}sdk.css`}, injectTo: 'head-prepend'},
|
||||
{tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}helper.css`}, injectTo: 'head-prepend'},
|
||||
{tag: 'link', attrs: {rel: 'stylesheet', href: `${amisSdkUrlPrefix}iconfont.css`}, injectTo: 'head-prepend'},
|
||||
{tag: 'script', attrs: {src: `${amisSdkUrlPrefix}sdk.js`}, injectTo: 'head-prepend'}
|
||||
]
|
||||
}
|
||||
},
|
||||
configureServer(server) {
|
||||
serveAmisSdk(server)
|
||||
},
|
||||
configurePreviewServer(server) {
|
||||
serveAmisSdk(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
function copyDir(source, target) {
|
||||
fs.mkdirSync(target, { recursive: true })
|
||||
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
||||
@@ -98,7 +178,7 @@ export default defineConfig({
|
||||
define: {
|
||||
CESIUM_BASE_URL: JSON.stringify('/ui/cesium/')
|
||||
},
|
||||
plugins: [react(), cesiumAssetsPlugin(), spaAliasesPlugin()],
|
||||
plugins: [react(), amisSdkPlugin(), cesiumAssetsPlugin(), spaAliasesPlugin()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/map/imagery': {
|
||||
|
||||
Reference in New Issue
Block a user