修复bug

This commit is contained in:
2026-08-09 18:06:08 +08:00
parent 37d10dbb98
commit ed2b0ffa56
8 changed files with 152 additions and 47 deletions
+2
View File
@@ -3,6 +3,7 @@ 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"
import {BackendConfig, type Backend_Config_Schema} from "./BackendConfig.tsx"
type Composition_Node = {
kind: string
slot?: string
@@ -40,6 +41,7 @@ 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} />
if (slot.component_kind === "ecap_backend_config") return <BackendConfig schema={slot.schema as unknown as Backend_Config_Schema} />
return <div style={{padding: 16, color: "#ff4d4f"}}> Adminive {name} ({slot.component_kind})</div>
}
export function Adminive_Settings() {
+4 -4
View File
@@ -94,11 +94,11 @@ export function ArchiveOperations({schema}: {schema: Archive_Operations_Schema})
}
return false
}
return <div style={{padding: 16}}>
<h2>{schema.title ?? "导入导出"}</h2>
<Space direction="vertical" size={12} style={{width: "100%"}}>
return <div style={{padding: 16, width: "100%", display: "flex", flexDirection: "column", alignItems: "center"}}>
<h2 style={{textAlign: "center"}}>{schema.title ?? "导入导出"}</h2>
<Space direction="vertical" align="center" size={12} style={{width: "100%"}}>
<Select value={format} options={schema.formats.map(item => ({label: item.label, value: item.value}))} onChange={set_format} style={{width: 200}} />
{schema.exports.map(item => <Space key={item.name} wrap>
{schema.exports.map(item => <Space key={item.name} wrap style={{justifyContent: "center"}}>
{item.allow_version_selection && <Input value={version} onChange={event => set_version(event.target.value)} placeholder="指定版本,留空导出当前版本" style={{width: 260}} />}
<Button icon={<DownloadOutlined />} loading={busy === `export:${item.name}`} disabled={Boolean(busy)} onClick={() => void export_archive(item)}>{item.name}</Button>
</Space>)}
+73
View File
@@ -0,0 +1,73 @@
import {Card, Spin, Tree, Typography} from "antd"
import type {DataNode} from "antd/es/tree"
import axios from "axios"
import {useEffect, useMemo, useState} from "react"
export type Backend_Config_Schema = {
title?: string
data_api: string
}
type Adminive_Response<T> = {
status: number
msg: string
data: T
}
function value_text(value: unknown): string {
if (typeof value === "string") return value
if (value === null) return "null"
return JSON.stringify(value)
}
function tree_nodes(value: unknown, parent: string): DataNode[] {
if (Array.isArray(value)) {
return value.map((item, index) => tree_node(`[${index}]`, item, `${parent}.${index}`))
}
if (typeof value === "object" && value !== null) {
return Object.entries(value).map(([key, item]) => tree_node(key, item, `${parent}.${key}`))
}
return []
}
function tree_node(name: string, value: unknown, key: string): DataNode {
const children = tree_nodes(value, key)
return {
key,
title: children.length > 0
? name
: <span><Typography.Text strong>{name}</Typography.Text><Typography.Text type="secondary">{value_text(value)}</Typography.Text></span>,
children: children.length > 0 ? children : undefined
}
}
export function BackendConfig({schema}: {schema: Backend_Config_Schema}) {
const [config, set_config] = useState<Record<string, unknown> | null>(null)
const [error, set_error] = useState("")
useEffect(() => {
set_config(null)
set_error("")
axios.get<Adminive_Response<Record<string, unknown>>>(schema.data_api).then(response => {
set_config(response.data.data)
}).catch(reason => {
set_error(reason instanceof Error ? reason.message : String(reason))
})
}, [schema.data_api])
const sections = useMemo(() => Object.entries(config ?? {}), [config])
if (error) return <div style={{padding: 16, color: "#ff4d4f"}}>{error}</div>
if (!config) return <div style={{padding: 24, textAlign: "center"}}><Spin /></div>
return (
<div style={{padding: 16, width: "100%"}}>
<h2 style={{textAlign: "center"}}>{schema.title ?? "后端完整配置(只读)"}</h2>
<div style={{display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 420px), 1fr))", gap: 12}}>
{sections.map(([name, value]) => (
<Card key={name} size="small" title={name} styles={{body: {overflowX: "auto"}}}>
<Tree showLine treeData={tree_nodes(value, name)} selectable={false} />
</Card>
))}
</div>
</div>
)
}
+14 -7
View File
@@ -1,5 +1,5 @@
import React from "react";
import {Checkbox, ColorPicker, Input, InputNumber, Select, Space} from "antd";
import {Card, Checkbox, ColorPicker, Input, InputNumber, Select, Space} from "antd";
export type Backend_Field_Descriptor = {
name: string
@@ -107,12 +107,19 @@ export function Backend_Fields(props: {
const controls = selected.filter(field => !booleans.includes(field));
return (
<Space direction="vertical" size={8} style={{width: "100%"}}>
{booleans.length > 0 && <Space wrap>
{booleans.map(field => <Backend_Field_Control key={field.name} descriptor={field}
value={object?.[field.name]}
disabled={field_disabled(props.root, field, props.rules || [])}
onChange={value => update(field, value)}/>) }
</Space>}
{booleans.length > 0 && <div style={{display: "flex", flexWrap: "wrap", gap: 8}}>
{booleans.map(field => (
<Card key={field.name} size="small" styles={{body: {padding: "6px 10px"}}}
style={{flex: "0 0 auto", background: "#fafafa"}}>
<div style={{whiteSpace: "nowrap"}}>
<Backend_Field_Control descriptor={field}
value={object?.[field.name]}
disabled={field_disabled(props.root, field, props.rules || [])}
onChange={value => update(field, value)}/>
</div>
</Card>
))}
</div>}
{controls.map(field => <Backend_Field_Control key={field.name} descriptor={field}
value={object?.[field.name]}
disabled={field_disabled(props.root, field, props.rules || [])}
+2 -1
View File
@@ -64,6 +64,7 @@ export class Base_Drawer extends enhance.Base {
const placement = props.placement || "right";
const header = props.header;
const scrollKey = props.scrollKey || "Base_Drawer";
const width = props.width || "auto";
// 按钮样式自动根据 placement 调整
const switchStyle = this.getSwitchStyle(placement, token);
@@ -83,7 +84,7 @@ export class Base_Drawer extends enhance.Base {
mask={false}
closable={false}
getContainer={false}
width="auto"
width={width}
styles={header ? {body: {padding: 0}} : undefined}
>
{header ? <div style={{height: "100%", display: "flex", flexDirection: "column"}}>
+7 -6
View File
@@ -411,15 +411,15 @@ export class Data_Source_Show extends enhance.Base {
<Title level={5}>{schema.title ?? "数据源配置"}{ds.key}</Title>
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
section={schema.common} rules={schema.rules} onChange={refresh}/>
<div style={{marginTop: 12, paddingTop: 8, borderTop: "1px solid #eee"}}>
<div style={{marginTop: 12}}>
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
section={schema.position} rules={schema.rules} onChange={refresh}/>
</div>
<div style={{marginTop: 18, paddingTop: 12, borderTop: "1px solid #eee"}}>
<Title level={5}>{schema.display[mode].title}</Title>
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
section={schema.display[mode]} rules={schema.rules} onChange={refresh}/>
</div>
<div style={{marginTop: 16}}>
<Backend_Fields root={ds as any} descriptor_fields={config.config_descriptor_fields}
section={schema.position} rules={schema.rules} onChange={refresh}/>
</div>
{this.render_aircraft_monitor_config(ds, mode)}
<br/>
<Button type="primary" autoInsertSpace onClick={() => {
@@ -453,7 +453,8 @@ export class Data_Source_Show extends enhance.Base {
{this.render_header_tabs(mode, enabled_list)}
</div>;
const scroll_key = this.active_tab_key === "source" ? `Data_Source_Show:source:${this.active_data_source_key}` : `Data_Source_Show:${mode}:${this.active_tab_key}`;
return <this.drawer.x header={header} scrollKey={scroll_key}>
return <this.drawer.x header={header} scrollKey={scroll_key}
width="min(640px, calc(100vw - 24px))">
{this.render_tab_content(mode, active_ds)}
<div style={{
height: '100px',
+18 -19
View File
@@ -22,19 +22,15 @@ type Aircraft_Column_Model = {
const onHeaderCell = (): HTMLProps<HTMLTableCellElement> => ({
style: {
writingMode: "vertical-lr",
textOrientation: "upright",
paddingLeft: 0,
paddingRight: 0,
writingMode: "horizontal-tb",
whiteSpace: "nowrap",
paddingLeft: 8,
paddingRight: 8,
},
});
function render_column_title(label: string) {
return <div>{label.split(/(<[^>]+>)/g).filter(Boolean).map((part, index) =>
part.startsWith("<") && part.endsWith(">")
? <span style={{writingMode: "horizontal-tb"}} key={index}>{part.slice(1, -1)}</span>
: <span key={index}>{part}</span>
)}</div>;
return <div style={{whiteSpace: "nowrap"}}>{label.replaceAll("<", "").replaceAll(">", "")}</div>;
}
function compare_values(a: unknown, b: unknown): number {
@@ -131,9 +127,10 @@ function create_columns(model: readonly Aircraft_Column_Model[]): TableColumnTyp
dataIndex: column.key,
key: column.key,
onHeaderCell,
width: column.type === "timestamp" ? 190 : Math.max(88, column.label.length * 18 + 44),
defaultSortOrder: normalized_sort_order(column.defaultSortOrder),
sorter: (a, b) => compare_values(a[column.key], b[column.key]),
render: value => <div style={{textAlign: "center", whiteSpace: column.type === "timestamp" ? "pre-line" : undefined}}>
render: value => <div style={{textAlign: "center", whiteSpace: column.type === "timestamp" ? "pre" : "nowrap"}}>
{value == null ? "" : typeof value === "boolean" ? (value ? "是" : "否") : String(value)}
</div>,
};
@@ -222,8 +219,8 @@ export class Aircraft_List extends enhance.Base {
}
render() {
return <Layout style={{height: "100%"}}>
<Flex gap="middle" vertical>
return <Layout style={{height: "100%", minWidth: 0}}>
<Flex gap="middle" vertical style={{width: "100%", minWidth: 0}}>
<Flex gap="middle">
<Select prefix={<Prefix label="数据来源"/>}
placeholder="请选择数据来源"
@@ -238,13 +235,15 @@ export class Aircraft_List extends enhance.Base {
}}/>
<this.refresh_btn.x/>
</Flex>
<Table<Aircraft_Row> dataSource={this.dataSource}
columns={this.columns}
showSorterTooltip={false}
size="small"
scroll={{x: "max-content", y: "calc(100vh - 300px)"}}
pagination={false}
style={{overflowX: "auto"}}/>
<div style={{width: "100%", minWidth: 0, overflowX: "auto"}}>
<Table<Aircraft_Row> dataSource={this.dataSource}
columns={this.columns}
showSorterTooltip={false}
size="small"
scroll={{x: "max-content", y: "calc(100vh - 300px)"}}
pagination={false}
style={{width: "100%"}}/>
</div>
</Flex>
</Layout>;
}
+32 -10
View File
@@ -1,5 +1,5 @@
import React from "react";
import {Button, message, Space, Upload} from "antd";
import {Button, Card, message, Select, Space, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons";
import enhance from "../core/enhance.tsx";
import {col_style, row_style, setting_style} from "../Global.tsx";
@@ -46,6 +46,7 @@ export type Cesium_Model_Settings_Schema = {
export class Cesium_Model_Settings extends enhance.Base {
config: Map_Model_Config = empty_map_model_config;
schema: Cesium_Model_Settings_Schema;
selected_collection_items: Record<string, string> = {};
constructor(schema: Cesium_Model_Settings_Schema) {
super();
@@ -96,21 +97,42 @@ export class Cesium_Model_Settings extends enhance.Base {
);
}
render_collection(collection: Model_Collection_Schema) {
const available = collection.items.filter(item => get_path(this.config, `${collection.path}.${item.key}`));
const selected = available.some(item => item.key === this.selected_collection_items[collection.path])
? this.selected_collection_items[collection.path]
: available[0]?.key;
if (!selected) return null;
const item = available.find(value => value.key === selected)!;
return (
<Card key={collection.path} size="small" title={collection.title} style={{width: "100%"}}>
<Space direction="vertical" size={12} style={{width: "100%"}}>
<Select
value={selected}
options={available.map(value => ({value: value.key, label: value.label}))}
style={{width: "100%", minWidth: 240}}
onChange={value => {
this.selected_collection_items[collection.path] = value;
this.flush();
}}/>
{this.render_model(item.label, `${collection.path}.${item.key}`, {
model_type: collection.upload_model_type,
label: collection.upload_label || "上传模型",
success_message: collection.upload_success_message
}, item.key)}
</Space>
</Card>
);
}
render(props: any) {
return (
<div style={setting_style}>
<h2 style={row_style}>{this.schema.title ?? "Cesium模型设置"}</h2>
<Space direction="vertical" size={12} style={col_style}>
<Space direction="vertical" size={12} style={{...col_style, width: "min(100%, 640px)"}}>
{(this.schema.groups || []).map(group =>
this.render_model(group.title, group.path, group.upload))}
{(this.schema.collections || []).flatMap(collection => [
<h3 key={`${collection.path}:title`} style={row_style}>{collection.title}</h3>,
...collection.items.map(item => this.render_model(item.label, `${collection.path}.${item.key}`, {
model_type: collection.upload_model_type,
label: collection.upload_label || "上传模型",
success_message: collection.upload_success_message
}, item.key))
])}
{(this.schema.collections || []).map(collection => this.render_collection(collection))}
<Button type="primary" onClick={() => this.save()}></Button>
</Space>
</div>