修复问题
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import axios from "axios"
|
||||
import {useEffect, useState, type CSSProperties} from "react"
|
||||
import {useEffect, useMemo, useState} from "react"
|
||||
import {Cesium_Model_Settings, type Cesium_Model_Settings_Schema} from "../Map/Cesium_Model_Settings.tsx"
|
||||
import {AmisPanel} from "./AmisPanel.tsx"
|
||||
import {ArchiveOperations, type Archive_Operations_Schema} from "./ArchiveOperations.tsx"
|
||||
type Composition_Node = {
|
||||
kind: string
|
||||
slot?: string
|
||||
@@ -8,6 +10,10 @@ type Composition_Node = {
|
||||
}
|
||||
type Slot_Contract = {
|
||||
component_kind: string
|
||||
descriptor_api?: string
|
||||
view_api?: string
|
||||
data_api?: string
|
||||
amis_api?: string
|
||||
schema: Record<string, unknown>
|
||||
}
|
||||
type Composition_Manifest = {
|
||||
@@ -21,22 +27,20 @@ type Adminive_Response<T> = {
|
||||
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)
|
||||
}
|
||||
if (node.kind === "slot" && node.slot) result.push(node.slot)
|
||||
for (const child of node.children ?? []) collect_slots(child, result)
|
||||
}
|
||||
function CesiumModelsSlot({slot}: {slot: Slot_Contract}) {
|
||||
const manager = useMemo(() => new Cesium_Model_Settings(slot.schema as unknown as Cesium_Model_Settings_Schema), [slot])
|
||||
const Component = manager.x
|
||||
return <Component />
|
||||
}
|
||||
function render_slot(name: string, slot: Slot_Contract) {
|
||||
if (slot.component_kind === "amis_schema") return <AmisPanel schema={slot.schema} />
|
||||
if (slot.component_kind === "ecap_archive_operations") return <ArchiveOperations schema={slot.schema as unknown as Archive_Operations_Schema} />
|
||||
if (slot.component_kind === "ecap_cesium_models") return <CesiumModelsSlot slot={slot} />
|
||||
return <div style={{padding: 16, color: "#ff4d4f"}}>未注册的 Adminive 前端组件:{name} ({slot.component_kind})</div>
|
||||
}
|
||||
export function Adminive_Settings() {
|
||||
const [manifest, set_manifest] = useState<Composition_Manifest | null>(null)
|
||||
@@ -48,23 +52,12 @@ export function Adminive_Settings() {
|
||||
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>
|
||||
}
|
||||
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>
|
||||
)
|
||||
return <div style={{display: "grid", gridTemplateColumns: "minmax(0, 1fr)", gap: 16, width: "100%"}}>{slot_names.map(name => {
|
||||
const slot = manifest.slots[name]
|
||||
return slot ? <div key={name} style={{minWidth: 0}}>{render_slot(name, slot)}</div> : null
|
||||
})}</div>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {emit_cesium_graphics_config, load_cesium_graphics_config} from "../Map/C
|
||||
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"
|
||||
const raw_action_apis = new Set(["/api/refresh_external_databases", "/api/refresh_external_database", "/api/upload_external_database", "/api/clear_external_database_table", "/api/restart_device"])
|
||||
export async function amis_fetcher(request: Amis_Request): Promise<AxiosResponse> {
|
||||
const method = (request.method ?? "get").toLowerCase()
|
||||
const query_method = method === "get" || method === "head"
|
||||
@@ -17,6 +18,10 @@ export async function amis_fetcher(request: Amis_Request): Promise<AxiosResponse
|
||||
...(request.config ?? {})
|
||||
}
|
||||
const response = await axios(config)
|
||||
const pathname = new URL(request.url, window.location.href).pathname
|
||||
if (response.status >= 200 && response.status < 300 && raw_action_apis.has(pathname) && response.data?.status === undefined) {
|
||||
response.data = {status: 0, msg: "", data: response.data}
|
||||
}
|
||||
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())
|
||||
|
||||
@@ -15,11 +15,22 @@ import {
|
||||
|
||||
type Model_Number_Key = keyof Pick<Map_Model_Item_Config, "builtInSize" | "headingOffsetDegrees" | "pitchOffsetDegrees" | "rollOffsetDegrees">
|
||||
type Model_Upload_Type = "aircraft" | "base_station" | "device" | "aircraft_type"
|
||||
export type Cesium_Model_Settings_Schema = {
|
||||
title?: string
|
||||
data_api: string
|
||||
upload_accept: string
|
||||
aircraft_types: string[]
|
||||
}
|
||||
export class Cesium_Model_Settings extends enhance.Base {
|
||||
config: Map_Model_Config = {...empty_map_model_config};
|
||||
number_input_drafts: Record<string, number | null> = {};
|
||||
schema: Cesium_Model_Settings_Schema;
|
||||
constructor(schema: Cesium_Model_Settings_Schema) {
|
||||
super();
|
||||
this.schema = schema;
|
||||
}
|
||||
async on_mount() {
|
||||
this.config = await load_map_model_config();
|
||||
this.config = await load_map_model_config(this.schema.data_api);
|
||||
this.number_input_drafts = {};
|
||||
this.flush();
|
||||
}
|
||||
@@ -28,7 +39,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
message.error("只支持上传 .glb 模型");
|
||||
return false;
|
||||
}
|
||||
this.config = await upload_map_model(model_type, file, aircraft_model_key);
|
||||
this.config = await upload_map_model(model_type, file, aircraft_model_key, this.schema.data_api);
|
||||
message.success(this.model_upload_message(model_type));
|
||||
this.flush();
|
||||
return false;
|
||||
@@ -62,7 +73,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
this.flush();
|
||||
}
|
||||
async save() {
|
||||
this.config = await save_map_model_config(this.config);
|
||||
this.config = await save_map_model_config(this.config, this.schema.data_api);
|
||||
this.number_input_drafts = {};
|
||||
message.success("模型配置已保存");
|
||||
this.flush();
|
||||
@@ -84,7 +95,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
<Space wrap>
|
||||
<span style={{width: 210}}>{option.label}</span>
|
||||
<Input value={model.url} readOnly style={{width: 360}} />
|
||||
<Upload accept=".glb" showUploadList={false} beforeUpload={(file) => this.upload("aircraft_type", file, option.key)}>
|
||||
<Upload accept={this.schema.upload_accept} showUploadList={false} beforeUpload={(file) => this.upload("aircraft_type", file, option.key)}>
|
||||
<Button icon={<UploadOutlined />}>上传GLB</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
@@ -97,7 +108,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
<Space direction="vertical" size={4}>
|
||||
<Space wrap>
|
||||
<Input addonBefore={label} value={model.url} readOnly style={{width: 360}} />
|
||||
<Upload accept=".glb" showUploadList={false} beforeUpload={(file) => this.upload(model_type, file)}>
|
||||
<Upload accept={this.schema.upload_accept} showUploadList={false} beforeUpload={(file) => this.upload(model_type, file)}>
|
||||
<Button icon={<UploadOutlined />}>上传GLB</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
@@ -108,7 +119,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
render(props: any) {
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
<h2 style={row_style}>Cesium模型设置</h2>
|
||||
<h2 style={row_style}>{this.schema.title ?? "Cesium模型设置"}</h2>
|
||||
<Space direction="vertical" size={8} style={col_style}>
|
||||
<h3 style={row_style}>默认飞机模型</h3>
|
||||
{this.render_model_upload("飞机模型", this.config.aircraftModel, "aircraft")}
|
||||
@@ -117,7 +128,7 @@ export class Cesium_Model_Settings extends enhance.Base {
|
||||
<h3 style={row_style}>设备模型</h3>
|
||||
{this.render_model_upload("设备模型", this.config.deviceModel, "device")}
|
||||
<h3 style={row_style}>按涡流/目标类型选择飞机模型</h3>
|
||||
{aircraft_model_type_options.map(option => this.render_aircraft_type_model(option))}
|
||||
{this.schema.aircraft_types.map(key => this.render_aircraft_type_model(aircraft_model_type_options.find(option => option.key === key) ?? {key, label: key}))}
|
||||
<Button type="primary" onClick={() => this.save()}>保存模型配置</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
@@ -114,22 +114,22 @@ function to_server_config(config: Map_Model_Config): Map_Model_Config_Json {
|
||||
device_model: to_server_model_item(config.deviceModel)
|
||||
};
|
||||
}
|
||||
export async function load_map_model_config(): Promise<Map_Model_Config> {
|
||||
const response = await axios.get<Map_Model_Config_Json>("/map/models");
|
||||
export async function load_map_model_config(api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const response = await axios.get<Map_Model_Config_Json>(api);
|
||||
return from_server_config(response.data);
|
||||
}
|
||||
export async function save_map_model_config(config: Map_Model_Config): Promise<Map_Model_Config> {
|
||||
const response = await axios.post<Map_Model_Config_Json>("/map/models", to_server_config(config));
|
||||
export async function save_map_model_config(config: Map_Model_Config, api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const response = await axios.post<Map_Model_Config_Json>(api, to_server_config(config));
|
||||
const saved_config = from_server_config(response.data);
|
||||
emit_map_model_config(saved_config);
|
||||
return saved_config;
|
||||
}
|
||||
export async function upload_map_model(model_type: "aircraft" | "base_station" | "device" | "aircraft_type", file: File, aircraft_model_key?: string): Promise<Map_Model_Config> {
|
||||
export async function upload_map_model(model_type: "aircraft" | "base_station" | "device" | "aircraft_type", file: File, aircraft_model_key?: string, api = "/map/models"): Promise<Map_Model_Config> {
|
||||
const form = new FormData();
|
||||
form.append("model_type", model_type);
|
||||
if (aircraft_model_key) form.append("aircraft_model_key", aircraft_model_key);
|
||||
form.append("file", file);
|
||||
const response = await axios.post<Map_Model_Config_Json>("/map/models", form);
|
||||
const response = await axios.post<Map_Model_Config_Json>(api, form);
|
||||
const config = from_server_config(response.data);
|
||||
emit_map_model_config(config);
|
||||
return config;
|
||||
|
||||
+5
-485
@@ -1,488 +1,8 @@
|
||||
import enhance from "./core/enhance.tsx";
|
||||
import {Button, Flex, InputNumber, Select} from "antd";
|
||||
import { Input } from 'antd';
|
||||
import {baseURL, setting_style, row_style, col_style} from "./Global.js";
|
||||
import { Data_Source_Config } from "./Data_Source/Data_Source_Config.tsx";
|
||||
import { Device_Config } from "./Device_Config.tsx";
|
||||
import axios from "axios";
|
||||
import { ReloadOutlined } from "@ant-design/icons";
|
||||
import { PersistentScroll } from "./PersistentScroll.tsx";
|
||||
import { External_Resources_Manager } from "./External_Resources_Manager/External_Resources_Manager.tsx";
|
||||
import {Adminive_Settings} from "./Adminive/Adminive_Settings.tsx";
|
||||
import { Cesium_Model_Settings } from "./Map/Cesium_Model_Settings.tsx";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
|
||||
export class Console extends enhance.Base {
|
||||
serial_number: number = 0;
|
||||
log: string = "";
|
||||
|
||||
style = {
|
||||
"width": "60vw",
|
||||
}
|
||||
|
||||
render(props: any) {
|
||||
return (
|
||||
<>
|
||||
<Flex gap="middle" vertical justify='center' align='center'>
|
||||
<h2 style={row_style}>控制台输出</h2>
|
||||
<Flex gap="middle" justify='center' align='center'>
|
||||
<InputNumber addonBefore={"当前序列号"} value={this.serial_number}
|
||||
onChange={(value: number | null) => {
|
||||
this.serial_number = value;
|
||||
this.flush();
|
||||
}}></InputNumber>
|
||||
<Button type="primary" autoInsertSpace onClick={() => {
|
||||
this.log = "";
|
||||
this.flush();
|
||||
}}>
|
||||
清除控制台
|
||||
</Button>
|
||||
<Button type="primary" autoInsertSpace
|
||||
icon={<ReloadOutlined></ReloadOutlined>}
|
||||
onClick={() => {
|
||||
axios.post(`${baseURL}/get_debug_log`, { serial_number: this.serial_number })
|
||||
.then(res => {
|
||||
this.serial_number = res.data.serial_number;
|
||||
const decoded = decodeURIComponent(escape(atob(res.data.log)));
|
||||
this.log += decoded;
|
||||
this.flush();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error fetching log:", error);
|
||||
});
|
||||
this.flush();
|
||||
}}
|
||||
>
|
||||
拉取新的log
|
||||
</Button>
|
||||
</Flex>
|
||||
<TextArea value={this.log} rows={20} style={this.style} />
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import { Upload, Space, Modal, Progress, message } from 'antd';
|
||||
import { UploadOutlined, DownloadOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
||||
|
||||
type ArchiveFormatOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
format: string;
|
||||
filter: string;
|
||||
extension: string;
|
||||
};
|
||||
|
||||
const archiveFormatOptions: ArchiveFormatOption[] = [
|
||||
{ label: 'ZIP (.zip)', value: 'zip', format: 'zip', filter: '', extension: 'zip' },
|
||||
{ label: 'TAR (.tar)', value: 'tar', format: 'pax', filter: '', extension: 'tar' },
|
||||
{ label: 'TAR.GZ (.tar.gz)', value: 'tar.gz', format: 'pax', filter: 'gzip', extension: 'tar.gz' },
|
||||
{ label: 'TAR.XZ (.tar.xz)', value: 'tar.xz', format: 'pax', filter: 'xz', extension: 'tar.xz' },
|
||||
{ label: 'TAR.ZST (.tar.zst)', value: 'tar.zst', format: 'pax', filter: 'zstd', extension: 'tar.zst' },
|
||||
{ label: 'TAR.BZ2 (.tar.bz2)', value: 'tar.bz2', format: 'pax', filter: 'bzip2', extension: 'tar.bz2' },
|
||||
{ label: 'TAR.LZ4 (.tar.lz4)', value: 'tar.lz4', format: 'pax', filter: 'lz4', extension: 'tar.lz4' },
|
||||
{ label: '7Z (.7z)', value: '7z', format: '7zip', filter: '', extension: '7z' },
|
||||
];
|
||||
|
||||
const archiveImportSuffixes = [
|
||||
'.tar.zstd', '.tar.zst', '.tar.gz', '.tgz', '.tar.xz', '.txz', '.tar.bz2', '.tbz2', '.tar.lz4',
|
||||
'.zip', '.7z', '.rar', '.tar', '.cpio', '.iso', '.xar'
|
||||
];
|
||||
|
||||
const archiveImportAccept = archiveImportSuffixes.join(',');
|
||||
|
||||
function getArchiveImportExtension(fileName: string) {
|
||||
const lowerName = fileName.toLowerCase();
|
||||
const suffix = archiveImportSuffixes.find(item => lowerName.endsWith(item));
|
||||
if (suffix) return suffix.slice(1);
|
||||
const dot = fileName.lastIndexOf('.');
|
||||
return dot >= 0 ? fileName.slice(dot + 1) : 'zip';
|
||||
}
|
||||
|
||||
function stripArchiveImportExtension(fileName: string) {
|
||||
const lowerName = fileName.toLowerCase();
|
||||
const suffix = archiveImportSuffixes.find(item => lowerName.endsWith(item));
|
||||
if (suffix) return fileName.slice(0, fileName.length - suffix.length);
|
||||
const dot = fileName.lastIndexOf('.');
|
||||
return dot > 0 ? fileName.slice(0, dot) : fileName;
|
||||
}
|
||||
|
||||
|
||||
export class Export extends enhance.Base {
|
||||
exporting = false; // 导出中
|
||||
exportElapsed = 0; // 已用时间(秒)
|
||||
exportTimer?: number; // 定时器句柄
|
||||
name : string;
|
||||
url : string;
|
||||
archiveFormat = archiveFormatOptions[0].value;
|
||||
versionName = "";
|
||||
allowVersionSelection = false;
|
||||
constructor(name: string, url: string, options: { allowVersionSelection?: boolean } = {}) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.allowVersionSelection = options.allowVersionSelection ?? false;
|
||||
}
|
||||
|
||||
selectedArchiveFormat() {
|
||||
return archiveFormatOptions.find(item => item.value === this.archiveFormat) ?? archiveFormatOptions[0];
|
||||
}
|
||||
|
||||
export() {
|
||||
if (this.exporting) return;
|
||||
|
||||
this.exporting = true;
|
||||
this.exportElapsed = 0;
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
// ⏱ 每秒更新时间
|
||||
this.exportTimer = window.setInterval(() => {
|
||||
this.exportElapsed = Math.floor((Date.now() - start) / 1000);
|
||||
this.flush();
|
||||
}, 1000);
|
||||
|
||||
this.flush();
|
||||
|
||||
const selectedArchive = this.selectedArchiveFormat();
|
||||
const payload: {
|
||||
version?: string;
|
||||
format: string;
|
||||
filter: string;
|
||||
extension: string;
|
||||
} = {
|
||||
format: selectedArchive.format,
|
||||
filter: selectedArchive.filter,
|
||||
extension: selectedArchive.extension,
|
||||
};
|
||||
const version = this.versionName.trim();
|
||||
if (this.allowVersionSelection && version.length > 0) {
|
||||
payload.version = version;
|
||||
}
|
||||
|
||||
axios.post(`${baseURL}/${this.url}`, payload, {
|
||||
responseType: 'blob'
|
||||
}).then(res => {
|
||||
|
||||
const ok = res.headers['ok'];
|
||||
|
||||
if(ok === "false"){
|
||||
res.data.text().then(text => {
|
||||
console.log(text)
|
||||
const jsonData = JSON.parse(text);
|
||||
message.error(jsonData.message);
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
const disposition = res.headers['content-disposition'];
|
||||
if (!disposition) {
|
||||
message.error('后端未返回 Content-Disposition,无法确定文件名');
|
||||
return;
|
||||
}
|
||||
|
||||
const match = disposition.match(/filename\*=UTF-8''([^;]+)|filename="([^"]+)"/);
|
||||
if (!match) {
|
||||
message.error('Content-Disposition 中未包含 filename');
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = decodeURIComponent(match[1] || match[2]);
|
||||
const blob = new Blob([res.data], {
|
||||
type: res.headers['content-type'] || 'application/octet-stream'
|
||||
});
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
message.success(`当前${this.name}已导出(耗时 ${this.exportElapsed}s)`);
|
||||
}).catch(() => {
|
||||
message.error(`${this.name}导出失败`);
|
||||
}).finally(() => {
|
||||
this.exporting = false;
|
||||
if (this.exportTimer) {
|
||||
clearInterval(this.exportTimer);
|
||||
this.exportTimer = undefined;
|
||||
}
|
||||
this.flush();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
<Space>
|
||||
|
||||
<Select
|
||||
value={this.archiveFormat}
|
||||
options={archiveFormatOptions.map(item => ({ label: item.label, value: item.value }))}
|
||||
style={{ width: 170 }}
|
||||
disabled={this.exporting}
|
||||
onChange={(value: string) => {
|
||||
this.archiveFormat = value;
|
||||
this.flush();
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={<DownloadOutlined />}
|
||||
loading={this.exporting}
|
||||
disabled={this.exporting}
|
||||
onClick={() => this.export()}
|
||||
>
|
||||
{this.exporting
|
||||
? `正在导出… ${this.exportElapsed}s`
|
||||
: `导出当前${this.name}`}
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Online_Upgrade extends enhance.Base {
|
||||
upgrading = false; // 导入中
|
||||
progress = 0;
|
||||
export_version2 : Export;
|
||||
export_logs : Export;
|
||||
constructor() {
|
||||
super();
|
||||
this.export_version2 = new Export("版本", "export_version", { allowVersionSelection: true });
|
||||
this.export_logs = new Export("日志", "export_logs");
|
||||
}
|
||||
|
||||
|
||||
/* ================= 导入版本 ================= */
|
||||
import_version(file: File) {
|
||||
if (this.upgrading) return false;
|
||||
|
||||
this.upgrading = true;
|
||||
this.progress = 0;
|
||||
this.flush();
|
||||
|
||||
const dir = stripArchiveImportExtension(file.name);
|
||||
const archiveExtension = getArchiveImportExtension(file.name);
|
||||
|
||||
// axios.post(
|
||||
// `${baseURL}/import_version`,
|
||||
// file,
|
||||
// {
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/zip',
|
||||
// 'dir': dir,
|
||||
// },
|
||||
// onUploadProgress: (e) => {
|
||||
// if (e.total) {
|
||||
// this.progress = Math.round((e.loaded / e.total) * 100);
|
||||
// this.flush();
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// ).then(() => {
|
||||
// message.success('版本导入完成');
|
||||
// }).catch(() => {
|
||||
// message.error('版本导入失败');
|
||||
// }).finally(() => {
|
||||
// this.upgrading = false;
|
||||
// this.flush();
|
||||
// });
|
||||
axios.post(
|
||||
`${baseURL}/import_version`,
|
||||
file,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'dir': dir,
|
||||
'archive-extension': archiveExtension,
|
||||
},
|
||||
validateStatus: () => true,
|
||||
transformRequest: [
|
||||
data => data,
|
||||
],
|
||||
onUploadProgress: (e) => {
|
||||
if (e.total) {
|
||||
this.progress = Math.round((e.loaded / e.total) * 100);
|
||||
this.flush();
|
||||
}
|
||||
},
|
||||
}
|
||||
).then((resp) => {
|
||||
const data = resp.data || {};
|
||||
|
||||
if (resp.status >= 200 && resp.status < 300 && data.ok === true) {
|
||||
message.success('版本导入完成');
|
||||
return;
|
||||
}
|
||||
|
||||
const reason =
|
||||
data.error ||
|
||||
data.message ||
|
||||
resp.statusText ||
|
||||
'未知错误';
|
||||
|
||||
if (resp.status === 400) {
|
||||
message.error(`版本导入失败:参数或压缩包无效:${reason}`);
|
||||
} else if (resp.status === 500) {
|
||||
message.error(`版本导入失败:服务器内部错误:${reason}`);
|
||||
} else {
|
||||
message.error(`版本导入失败:HTTP ${resp.status}:${reason}`);
|
||||
}
|
||||
|
||||
console.error('版本导入失败详情:', {
|
||||
status: resp.status,
|
||||
data,
|
||||
});
|
||||
}).catch((err) => {
|
||||
let reason = '未知错误';
|
||||
|
||||
if (err.response) {
|
||||
reason =
|
||||
err.response.data?.error ||
|
||||
err.response.data?.message ||
|
||||
err.response.statusText ||
|
||||
err.message;
|
||||
} else if (err.request) {
|
||||
reason = '服务器无响应,请检查地址、服务状态或跨域配置';
|
||||
} else if (err.message) {
|
||||
reason = err.message;
|
||||
}
|
||||
|
||||
message.error(`版本导入失败:${reason}`);
|
||||
console.error('版本导入异常:', err);
|
||||
}).finally(() => {
|
||||
this.upgrading = false;
|
||||
this.flush();
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={setting_style}>
|
||||
|
||||
<h2 style={row_style}>导入导出</h2>
|
||||
|
||||
<div style={col_style}>
|
||||
|
||||
<this.export_version2.x></this.export_version2.x> <br/>
|
||||
<this.export_logs.x></this.export_logs.x> <br/>
|
||||
|
||||
|
||||
|
||||
<Upload
|
||||
accept={archiveImportAccept}
|
||||
showUploadList={false}
|
||||
beforeUpload={(file) => this.import_version(file)}
|
||||
disabled={this.upgrading}
|
||||
>
|
||||
<Button
|
||||
danger
|
||||
icon={<UploadOutlined />}
|
||||
loading={this.upgrading}
|
||||
>
|
||||
导入版本
|
||||
</Button>
|
||||
</Upload>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
open={this.upgrading}
|
||||
footer={null}
|
||||
closable={false}
|
||||
title="正在导入版本"
|
||||
>
|
||||
<Progress percent={this.progress} />
|
||||
<div style={{ marginTop: 8 }}>
|
||||
版本导入中,请勿关闭页面
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
import enhance from "./core/enhance.tsx"
|
||||
import {Adminive_Settings} from "./Adminive/Adminive_Settings.tsx"
|
||||
import {PersistentScroll} from "./PersistentScroll.tsx"
|
||||
export class Settings extends enhance.Base {
|
||||
// 第一步:仅声明子组件属性,不即时实例化
|
||||
console: Console | null = null;
|
||||
data_source_config: Data_Source_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_model_settings: Cesium_Model_Settings | null = null;
|
||||
|
||||
isRebooting: boolean = false;
|
||||
|
||||
// 第二步:在构造函数中延迟初始化子组件
|
||||
constructor() {
|
||||
super();
|
||||
this.initSubComponents();
|
||||
}
|
||||
|
||||
// 新增:子组件初始化方法
|
||||
initSubComponents = () => {
|
||||
this.console = new Console();
|
||||
this.data_source_config = new Data_Source_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_model_settings = new Cesium_Model_Settings();
|
||||
};
|
||||
|
||||
render(props: any) {
|
||||
// 第三步:渲染前校验子组件是否初始化完成
|
||||
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>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PersistentScroll scrollKey="Settings" style={{
|
||||
gap: '16px',
|
||||
// display: 'flex',
|
||||
// flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
}}>
|
||||
|
||||
<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_model_settings.x></this.cesium_model_settings.x>
|
||||
|
||||
<br></br>
|
||||
<div style={setting_style}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={()=>{
|
||||
this.isRebooting = true;
|
||||
axios.post(`${baseURL}/restart_device`).then(res => {
|
||||
|
||||
});
|
||||
}}
|
||||
loading={this.isRebooting}
|
||||
disabled={this.isRebooting} // 禁用按钮,防止重复点击
|
||||
>
|
||||
重启fpga设备
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
height: "500px",
|
||||
}}></div>
|
||||
|
||||
|
||||
</PersistentScroll>
|
||||
</>
|
||||
)
|
||||
render() {
|
||||
return <PersistentScroll scrollKey="Settings" style={{gap: "16px"}}><Adminive_Settings /><div style={{height: 500}} /></PersistentScroll>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user