修复bug
This commit is contained in:
@@ -18,7 +18,15 @@ export type Backend_Form_Section = {
|
|||||||
title?: string
|
title?: string
|
||||||
object_path?: string
|
object_path?: string
|
||||||
descriptor_path?: string
|
descriptor_path?: string
|
||||||
|
fields?: string[]
|
||||||
|
layout?: Backend_Field_Layout[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Backend_Field_Layout = {
|
||||||
|
kind: "cards" | "horizontal" | "vertical" | "list" | "grid" | string
|
||||||
fields: string[]
|
fields: string[]
|
||||||
|
columns?: number
|
||||||
|
minimum_width?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Backend_Disabled_Rule = {
|
export type Backend_Disabled_Rule = {
|
||||||
@@ -94,36 +102,67 @@ export function Backend_Fields(props: {
|
|||||||
const object = get_path(props.root, props.section.object_path) as Json_Object | undefined;
|
const object = get_path(props.root, props.section.object_path) as Json_Object | undefined;
|
||||||
const descriptor_path = props.section.descriptor_path ?? props.section.object_path;
|
const descriptor_path = props.section.descriptor_path ?? props.section.object_path;
|
||||||
const available_fields = fields_at_path(props.descriptor_fields, descriptor_path);
|
const available_fields = fields_at_path(props.descriptor_fields, descriptor_path);
|
||||||
const selected = props.section.fields
|
const layout = props.section.layout?.length
|
||||||
.map(name => available_fields.find(field => field.name === name))
|
? props.section.layout
|
||||||
.filter((field): field is Backend_Field_Descriptor => Boolean(field && field.readable !== false));
|
: [{kind: "vertical", fields: props.section.fields || []}];
|
||||||
|
const arranged_names = layout.flatMap(group => group.fields);
|
||||||
|
const remaining_names = (props.section.fields || []).filter(name => !arranged_names.includes(name));
|
||||||
|
const groups = remaining_names.length > 0
|
||||||
|
? [...layout, {kind: "vertical", fields: remaining_names}]
|
||||||
|
: layout;
|
||||||
|
const fields_by_name = new Map(available_fields
|
||||||
|
.filter(field => field.readable !== false)
|
||||||
|
.map(field => [field.name, field]));
|
||||||
const update = (field: Backend_Field_Descriptor, value: unknown) => {
|
const update = (field: Backend_Field_Descriptor, value: unknown) => {
|
||||||
if (!object) return;
|
if (!object) return;
|
||||||
object[field.name] = value;
|
object[field.name] = value;
|
||||||
const path = [props.section.object_path, field.name].filter(Boolean).join(".");
|
const path = [props.section.object_path, field.name].filter(Boolean).join(".");
|
||||||
props.onChange?.(path, value, field);
|
props.onChange?.(path, value, field);
|
||||||
};
|
};
|
||||||
const booleans = selected.filter(field => (field.presentation?.control || field.value_type) === "boolean");
|
const render_control = (field: Backend_Field_Descriptor) => (
|
||||||
const controls = selected.filter(field => !booleans.includes(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)}/>
|
||||||
|
);
|
||||||
|
const render_group = (group: Backend_Field_Layout, index: number) => {
|
||||||
|
const fields = group.fields.map(name => fields_by_name.get(name)).filter((field): field is Backend_Field_Descriptor => Boolean(field));
|
||||||
|
if (fields.length === 0) return null;
|
||||||
|
if (group.kind === "cards") {
|
||||||
|
return <div key={index} style={{width: "100%", minWidth: 0, contain: "inline-size"}}>
|
||||||
|
<div style={{display: "flex", flexWrap: "wrap", gap: 8, width: "100%"}}>
|
||||||
|
{fields.map(field => {
|
||||||
|
const active = Boolean(object?.[field.name]);
|
||||||
|
const disabled = field_disabled(props.root, field, props.rules || []);
|
||||||
|
return <Card key={field.name} size="small" styles={{body: {padding: "6px 10px"}}}
|
||||||
|
style={{
|
||||||
|
flex: "0 0 auto",
|
||||||
|
background: active ? "#e6f4ff" : "#fafafa",
|
||||||
|
borderColor: active ? "#91caff" : undefined,
|
||||||
|
opacity: disabled ? 0.62 : 1
|
||||||
|
}}>
|
||||||
|
<div style={{whiteSpace: "nowrap"}}>{render_control(field)}</div>
|
||||||
|
</Card>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
if (group.kind === "horizontal") {
|
||||||
|
return <div key={index} style={{display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8}}>
|
||||||
|
{fields.map(render_control)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
if (group.kind === "grid") {
|
||||||
|
const columns = group.columns ? `repeat(${group.columns}, minmax(0, 1fr))` : `repeat(auto-fit, minmax(${group.minimum_width || 280}px, 1fr))`;
|
||||||
|
return <div key={index} style={{display: "grid", gridTemplateColumns: columns, gap: 8, width: "100%"}}>
|
||||||
|
{fields.map(render_control)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
return <Space key={index} direction="vertical" size={8}>{fields.map(render_control)}</Space>;
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" size={8} style={{width: "100%"}}>
|
<Space direction="vertical" size={8} style={{width: "100%"}}>
|
||||||
{booleans.length > 0 && <div style={{display: "flex", flexWrap: "wrap", gap: 8}}>
|
{groups.map(render_group)}
|
||||||
{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 || [])}
|
|
||||||
onChange={value => update(field, value)}/>) }
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -64,7 +64,6 @@ export class Base_Drawer extends enhance.Base {
|
|||||||
const placement = props.placement || "right";
|
const placement = props.placement || "right";
|
||||||
const header = props.header;
|
const header = props.header;
|
||||||
const scrollKey = props.scrollKey || "Base_Drawer";
|
const scrollKey = props.scrollKey || "Base_Drawer";
|
||||||
const width = props.width || "auto";
|
|
||||||
|
|
||||||
// 按钮样式自动根据 placement 调整
|
// 按钮样式自动根据 placement 调整
|
||||||
const switchStyle = this.getSwitchStyle(placement, token);
|
const switchStyle = this.getSwitchStyle(placement, token);
|
||||||
@@ -84,7 +83,7 @@ export class Base_Drawer extends enhance.Base {
|
|||||||
mask={false}
|
mask={false}
|
||||||
closable={false}
|
closable={false}
|
||||||
getContainer={false}
|
getContainer={false}
|
||||||
width={width}
|
width="auto"
|
||||||
styles={header ? {body: {padding: 0}} : undefined}
|
styles={header ? {body: {padding: 0}} : undefined}
|
||||||
>
|
>
|
||||||
{header ? <div style={{height: "100%", display: "flex", flexDirection: "column"}}>
|
{header ? <div style={{height: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
|
|||||||
@@ -453,8 +453,7 @@ export class Data_Source_Show extends enhance.Base {
|
|||||||
{this.render_header_tabs(mode, enabled_list)}
|
{this.render_header_tabs(mode, enabled_list)}
|
||||||
</div>;
|
</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}`;
|
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)}
|
{this.render_tab_content(mode, active_ds)}
|
||||||
<div style={{
|
<div style={{
|
||||||
height: '100px',
|
height: '100px',
|
||||||
|
|||||||
+23
-19
@@ -15,6 +15,10 @@ type Aircraft_Column_Model = {
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: "index" | "timestamp" | "number" | "text" | "boolean" | string;
|
type: "index" | "timestamp" | "number" | "text" | "boolean" | string;
|
||||||
|
width?: number;
|
||||||
|
fixed?: "left" | "right";
|
||||||
|
sortable?: boolean;
|
||||||
|
filter?: "range" | "text" | "boolean" | string;
|
||||||
defaultSortOrder?: "ascend" | "descend";
|
defaultSortOrder?: "ascend" | "descend";
|
||||||
secondsKey?: string;
|
secondsKey?: string;
|
||||||
nanosecondsKey?: string;
|
nanosecondsKey?: string;
|
||||||
@@ -30,7 +34,7 @@ const onHeaderCell = (): HTMLProps<HTMLTableCellElement> => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function render_column_title(label: string) {
|
function render_column_title(label: string) {
|
||||||
return <div style={{whiteSpace: "nowrap"}}>{label.replaceAll("<", "").replaceAll(">", "")}</div>;
|
return <div style={{whiteSpace: "nowrap"}}>{label}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare_values(a: unknown, b: unknown): number {
|
function compare_values(a: unknown, b: unknown): number {
|
||||||
@@ -96,6 +100,14 @@ const text_filter = (field: string): TableColumnType<Aircraft_Row> => ({
|
|||||||
onFilter: (value, record) => String(record[field] ?? "").toLowerCase().includes(String(value).toLowerCase()),
|
onFilter: (value, record) => String(record[field] ?? "").toLowerCase().includes(String(value).toLowerCase()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const boolean_filter = (field: string): TableColumnType<Aircraft_Row> => ({
|
||||||
|
filters: [
|
||||||
|
{text: "是", value: "true"},
|
||||||
|
{text: "否", value: "false"},
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => Boolean(record[field]) === (value === "true"),
|
||||||
|
});
|
||||||
|
|
||||||
function format_timestamp(row: Record<string, unknown>, column: Aircraft_Column_Model): string {
|
function format_timestamp(row: Record<string, unknown>, column: Aircraft_Column_Model): string {
|
||||||
const seconds = Number(row[column.secondsKey ?? "uti"]);
|
const seconds = Number(row[column.secondsKey ?? "uti"]);
|
||||||
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
if (!Number.isFinite(seconds) || seconds <= 0) return "";
|
||||||
@@ -116,7 +128,8 @@ function create_columns(model: readonly Aircraft_Column_Model[]): TableColumnTyp
|
|||||||
return {
|
return {
|
||||||
title: render_column_title(column.label),
|
title: render_column_title(column.label),
|
||||||
key: column.key,
|
key: column.key,
|
||||||
width: 60,
|
width: column.width,
|
||||||
|
fixed: column.fixed,
|
||||||
align: "center",
|
align: "center",
|
||||||
render: (_value, _record, index) => index + 1,
|
render: (_value, _record, index) => index + 1,
|
||||||
};
|
};
|
||||||
@@ -127,33 +140,24 @@ function create_columns(model: readonly Aircraft_Column_Model[]): TableColumnTyp
|
|||||||
dataIndex: column.key,
|
dataIndex: column.key,
|
||||||
key: column.key,
|
key: column.key,
|
||||||
onHeaderCell,
|
onHeaderCell,
|
||||||
width: column.type === "timestamp" ? 190 : Math.max(88, column.label.length * 18 + 44),
|
width: column.width,
|
||||||
|
fixed: column.fixed,
|
||||||
defaultSortOrder: normalized_sort_order(column.defaultSortOrder),
|
defaultSortOrder: normalized_sort_order(column.defaultSortOrder),
|
||||||
sorter: (a, b) => compare_values(a[column.key], b[column.key]),
|
sorter: column.sortable ? (a, b) => compare_values(a[column.key], b[column.key]) : undefined,
|
||||||
render: value => <div style={{textAlign: "center", whiteSpace: column.type === "timestamp" ? "pre" : "nowrap"}}>
|
render: value => <div style={{textAlign: "center", whiteSpace: column.type === "timestamp" ? "pre" : "nowrap"}}>
|
||||||
{value == null ? "" : typeof value === "boolean" ? (value ? "是" : "否") : String(value)}
|
{value == null ? "" : typeof value === "boolean" ? (value ? "是" : "否") : String(value)}
|
||||||
</div>,
|
</div>,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
...(column.type === "number" ? range_filter(column.key) : text_filter(column.key)),
|
...(column.filter === "range" ? range_filter(column.key)
|
||||||
|
: column.filter === "text" ? text_filter(column.key)
|
||||||
|
: column.filter === "boolean" ? boolean_filter(column.key)
|
||||||
|
: {}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function fallback_model(rows: readonly Record<string, unknown>[]): Aircraft_Column_Model[] {
|
|
||||||
const first = rows[0];
|
|
||||||
if (!first) return [];
|
|
||||||
return [
|
|
||||||
{key: "index", label: "序号", type: "index"},
|
|
||||||
...Object.keys(first).map(key => ({
|
|
||||||
key,
|
|
||||||
label: key,
|
|
||||||
type: typeof first[key] === "number" ? "number" : "text",
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Aircraft_List extends enhance.Base {
|
export class Aircraft_List extends enhance.Base {
|
||||||
dataSource: Aircraft_Row[] = [];
|
dataSource: Aircraft_Row[] = [];
|
||||||
columns: TableColumnType<Aircraft_Row>[] = [];
|
columns: TableColumnType<Aircraft_Row>[] = [];
|
||||||
@@ -181,7 +185,7 @@ export class Aircraft_List extends enhance.Base {
|
|||||||
: Array.isArray(payload) ? payload : [];
|
: Array.isArray(payload) ? payload : [];
|
||||||
const model: Aircraft_Column_Model[] = Array.isArray(payload?.model?.columns)
|
const model: Aircraft_Column_Model[] = Array.isArray(payload?.model?.columns)
|
||||||
? payload.model.columns
|
? payload.model.columns
|
||||||
: fallback_model(raw_rows);
|
: [];
|
||||||
|
|
||||||
this.dataSource = raw_rows.map((item, index) => {
|
this.dataSource = raw_rows.map((item, index) => {
|
||||||
const row: Aircraft_Row = {...item, key: `${String(item.hex ?? "aircraft")}:${index}`};
|
const row: Aircraft_Row = {...item, key: `${String(item.hex ?? "aircraft")}:${index}`};
|
||||||
|
|||||||
Reference in New Issue
Block a user