This commit is contained in:
@@ -3,7 +3,6 @@ import {
|
||||
Banner,
|
||||
Button,
|
||||
Checkbox,
|
||||
Descriptions,
|
||||
Empty,
|
||||
Divider,
|
||||
Input,
|
||||
@@ -31,6 +30,7 @@ import type {
|
||||
Research,
|
||||
ResearchState,
|
||||
} from "../types";
|
||||
import { DetailFieldGrid } from "./DetailFieldGrid";
|
||||
import { PnlChart } from "./PnlChart";
|
||||
import { SelfCorrelationPanel } from "./SelfCorrelationPanel";
|
||||
import { SubmissionPanel } from "./SubmissionPanel";
|
||||
@@ -231,19 +231,10 @@ export function AlphaDetail({
|
||||
>
|
||||
<TabPane tab="概览与表达式" itemKey="overview">
|
||||
<div className="detail-section">
|
||||
<Descriptions
|
||||
size="small"
|
||||
align="left"
|
||||
layout="horizontal"
|
||||
column={3}
|
||||
<DetailFieldGrid
|
||||
data={["region", "universe", "stage"].map((key) => ({
|
||||
key,
|
||||
value: (
|
||||
<DescriptionValue
|
||||
field={key}
|
||||
value={detail[key as keyof Detail]}
|
||||
/>
|
||||
),
|
||||
value: detail[key as keyof Detail],
|
||||
}))}
|
||||
/>
|
||||
<p className="muted">
|
||||
@@ -266,14 +257,10 @@ export function AlphaDetail({
|
||||
)}
|
||||
<Divider />
|
||||
<h3>回测设置</h3>
|
||||
<Descriptions
|
||||
size="small"
|
||||
align="left"
|
||||
layout="horizontal"
|
||||
column={3}
|
||||
<DetailFieldGrid
|
||||
data={Object.entries(detail.settings).map(([key, value]) => ({
|
||||
key,
|
||||
value: <DescriptionValue field={key} value={value} />,
|
||||
value,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
@@ -494,15 +481,8 @@ function MetricTable({
|
||||
<div className="detail-section">
|
||||
<h3>{title}</h3>
|
||||
{entries.length ? (
|
||||
<Descriptions
|
||||
size="small"
|
||||
align="left"
|
||||
layout="horizontal"
|
||||
column={3}
|
||||
data={entries.map(([key, value]) => ({
|
||||
key,
|
||||
value: <DescriptionValue field={key} value={value} />,
|
||||
}))}
|
||||
<DetailFieldGrid
|
||||
data={entries.map(([key, value]) => ({ key, value }))}
|
||||
/>
|
||||
) : (
|
||||
<p className="muted">未提供</p>
|
||||
@@ -510,57 +490,3 @@ function MetricTable({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Render structured metric values without flattening their field hierarchy. */
|
||||
function DescriptionValue({ field, value }: { field: string; value: unknown }) {
|
||||
if (value !== null && typeof value === "object") {
|
||||
const entries = Object.entries(value);
|
||||
return entries.length ? (
|
||||
<Descriptions
|
||||
className="nested-descriptions"
|
||||
size="small"
|
||||
align="left"
|
||||
data={entries.map(([key, child]) => ({
|
||||
key,
|
||||
value: <DescriptionValue field={key} value={child} />,
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<span>{Array.isArray(value) ? "[]" : "{}"}</span>
|
||||
);
|
||||
}
|
||||
// Use field semantics for states; numbers and ordinary text remain plain values.
|
||||
const stateField = /(?:^|_)(status|state|stage|result|verdict)$/.test(
|
||||
field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(),
|
||||
);
|
||||
const state = typeof value === "string" ? value.toUpperCase() : "";
|
||||
if (
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
value !== "" &&
|
||||
(typeof value === "boolean" ||
|
||||
stateField ||
|
||||
state === "ON" ||
|
||||
state === "OFF")
|
||||
) {
|
||||
const color =
|
||||
value === true ||
|
||||
["PASS", "PASSED", "SUCCESS", "COMPLETED", "ON"].includes(state)
|
||||
? "green"
|
||||
: ["FAIL", "FAILED", "ERROR", "BLOCK"].includes(state)
|
||||
? "red"
|
||||
: ["WARNING", "PENDING", "PARTIAL", "REVIEW"].includes(state)
|
||||
? "orange"
|
||||
: "grey";
|
||||
return (
|
||||
<Tag size="small" color={color}>
|
||||
{typeof value === "boolean"
|
||||
? value
|
||||
? "是"
|
||||
: "否"
|
||||
: displayValue(value)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
return <span>{displayValue(value)}</span>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Tag } from "@douyinfe/semi-ui-19";
|
||||
import { displayValue } from "../api";
|
||||
|
||||
/** Keep structured values on their own row at every nesting level. */
|
||||
export function DetailFieldGrid({
|
||||
data,
|
||||
}: {
|
||||
data: { key: string; value: unknown }[];
|
||||
}) {
|
||||
return (
|
||||
<div className="detail-field-container">
|
||||
<dl className="detail-field-grid">
|
||||
{data.map(({ key, value }) => {
|
||||
const structured = value !== null && typeof value === "object";
|
||||
return (
|
||||
<div
|
||||
className={structured ? "detail-field-group" : "detail-field"}
|
||||
key={key}
|
||||
>
|
||||
<dt>{key}</dt>
|
||||
<dd>
|
||||
{structured ? (
|
||||
Object.keys(value).length ? (
|
||||
<DetailFieldGrid
|
||||
data={Object.entries(value).map(([name, child]) => ({
|
||||
key: Array.isArray(value) ? `[${name}]` : name,
|
||||
value: child,
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<span className="muted">
|
||||
{Array.isArray(value) ? "[]" : "{}"}
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<FieldValue field={key} value={value} />
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FieldValue({ field, value }: { field: string; value: unknown }) {
|
||||
// Use field semantics for states; numbers and ordinary text remain plain values.
|
||||
const stateField = /(?:^|_)(status|state|stage|result|verdict)$/.test(
|
||||
field.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(),
|
||||
);
|
||||
const state = typeof value === "string" ? value.toUpperCase() : "";
|
||||
if (
|
||||
value !== null &&
|
||||
value !== undefined &&
|
||||
value !== "" &&
|
||||
(typeof value === "boolean" ||
|
||||
stateField ||
|
||||
state === "ON" ||
|
||||
state === "OFF")
|
||||
) {
|
||||
const color =
|
||||
value === true ||
|
||||
["PASS", "PASSED", "SUCCESS", "COMPLETED", "ON"].includes(state)
|
||||
? "green"
|
||||
: ["FAIL", "FAILED", "ERROR", "BLOCK"].includes(state)
|
||||
? "red"
|
||||
: ["WARNING", "PENDING", "PARTIAL", "REVIEW"].includes(state)
|
||||
? "orange"
|
||||
: "grey";
|
||||
return (
|
||||
<Tag size="small" color={color}>
|
||||
{typeof value === "boolean"
|
||||
? value
|
||||
? "是"
|
||||
: "否"
|
||||
: displayValue(value)}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
return <span>{displayValue(value)}</span>;
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useState } from "react";
|
||||
import {
|
||||
Banner,
|
||||
Button,
|
||||
Descriptions,
|
||||
Empty,
|
||||
Spin,
|
||||
Table,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
Toast,
|
||||
} from "@douyinfe/semi-ui-19";
|
||||
import { api, correlationLabels, formatNumber, formatTime, post } from "../api";
|
||||
import { DetailFieldGrid } from "./DetailFieldGrid";
|
||||
import type { CorrelationResult } from "../types";
|
||||
|
||||
export function SelfCorrelationPanel({
|
||||
@@ -108,11 +108,7 @@ export function SelfCorrelationPanel({
|
||||
计算于 {formatTime(result.calculated_at, timezone)}
|
||||
</span>
|
||||
</div>
|
||||
<Descriptions
|
||||
size="small"
|
||||
align="left"
|
||||
layout="horizontal"
|
||||
column={3}
|
||||
<DetailFieldGrid
|
||||
data={[
|
||||
{
|
||||
key: "最大相关系数",
|
||||
|
||||
Reference in New Issue
Block a user