From 629f933cf921ed70b877eadaa4acd5b3e93ee9bd Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Wed, 2 Sep 2026 18:26:57 +0800 Subject: [PATCH] refactor(selection): update component descriptions and improve layout for stock detail panel --- .../pages/components-preview-page.tsx | 2 +- .../sector-radar/pages/sector-radar-page.tsx | 2 +- .../selection/components/pattern-score.tsx | 42 +---- .../selection/components/selection-chart.tsx | 7 +- .../components/selection-presentation.ts | 36 +++- .../selection-results-workbench.tsx | 4 +- .../components/signal-detail-panel.tsx | 174 ++++++++---------- .../pages/selection-results-page.test.tsx | 26 ++- zhixing-web/src/shared/ui/select.tsx | 6 +- 9 files changed, 149 insertions(+), 150 deletions(-) diff --git a/zhixing-web/src/features/components/pages/components-preview-page.tsx b/zhixing-web/src/features/components/pages/components-preview-page.tsx index 6fd6413..9759030 100644 --- a/zhixing-web/src/features/components/pages/components-preview-page.tsx +++ b/zhixing-web/src/features/components/pages/components-preview-page.tsx @@ -85,7 +85,7 @@ export function ComponentsPreviewPage() {
diff --git a/zhixing-web/src/features/sector-radar/pages/sector-radar-page.tsx b/zhixing-web/src/features/sector-radar/pages/sector-radar-page.tsx index 84aca9e..0e78337 100644 --- a/zhixing-web/src/features/sector-radar/pages/sector-radar-page.tsx +++ b/zhixing-web/src/features/sector-radar/pages/sector-radar-page.tsx @@ -312,7 +312,7 @@ function FilterSelect({ - + {options.map((option) => ( diff --git a/zhixing-web/src/features/selection/components/pattern-score.tsx b/zhixing-web/src/features/selection/components/pattern-score.tsx index b0b5428..0f370ab 100644 --- a/zhixing-web/src/features/selection/components/pattern-score.tsx +++ b/zhixing-web/src/features/selection/components/pattern-score.tsx @@ -6,13 +6,6 @@ interface PatternScoreProps { score: SelectionPatternScore | null } -const breakdownLabels = { - trend_structure: "趋势", - kdj_state: "KDJ", - volume_pattern: "量能", - price_shape: "价格形态", -} as const - export function PatternScoreSummary({ score }: PatternScoreProps) { if (!score) return 未评分 if (score.status === "failed") { @@ -47,34 +40,13 @@ export function PatternScoreDetails({ score }: PatternScoreProps) { ) } return ( -
-
-
-

- {formatScore(score.value)} 分 -

-

- 最佳案例:{score.case?.name ?? "未知"} -

-
- - {score.status === "matched" ? "已匹配" : "低于匹配阈值"} - -
- {score.breakdown ? ( -
- {Object.entries(breakdownLabels).map(([key, label]) => ( -
-
{label}
-
- {formatScore( - score.breakdown?.[key as keyof typeof breakdownLabels], - )} -
-
- ))} -
- ) : null} +
+

+ {formatScore(score.value)} 分 +

+ + {score.status === "matched" ? "已匹配" : "低于匹配阈值"} +
) } diff --git a/zhixing-web/src/features/selection/components/selection-chart.tsx b/zhixing-web/src/features/selection/components/selection-chart.tsx index cddad85..3c8b4fd 100644 --- a/zhixing-web/src/features/selection/components/selection-chart.tsx +++ b/zhixing-web/src/features/selection/components/selection-chart.tsx @@ -11,6 +11,8 @@ import { import * as echarts from "echarts/core" import { CanvasRenderer } from "echarts/renderers" +import { cn } from "@/shared/ui/utils" + import type { SelectionChart as SelectionChartData } from "../api/selection.types" import { buildSelectionChartOption } from "./selection-chart-option" @@ -29,9 +31,10 @@ echarts.use([ interface SelectionChartProps { chart: SelectionChartData + className?: string } -export function SelectionChart({ chart }: SelectionChartProps) { +export function SelectionChart({ chart, className }: SelectionChartProps) { const containerRef = useRef(null) const instanceRef = useRef(null) @@ -62,7 +65,7 @@ export function SelectionChart({ chart }: SelectionChartProps) { return (
diff --git a/zhixing-web/src/features/selection/components/selection-presentation.ts b/zhixing-web/src/features/selection/components/selection-presentation.ts index f88c6aa..27b798d 100644 --- a/zhixing-web/src/features/selection/components/selection-presentation.ts +++ b/zhixing-web/src/features/selection/components/selection-presentation.ts @@ -86,16 +86,44 @@ export function getSignalDetailItems(details: SelectionSignal["details"]) { })) } +export const stockBasicMetricKeys = [ + "j", + "rsi", + "daily_change", + "daily_amplitude", +] as const + export function getSignalJValue(details: SelectionSignal["details"]) { return formatSignalDetailValue("j", details.j) } export function getStockJValue(stock: SelectionStockResult) { - const signal = stock.signals.find( - ({ details }) => - details.j !== null && details.j !== undefined && details.j !== "", + return getStockDetailValue(stock, "j") +} + +/** + * Read one stock-level metric from the first signal that carries it. + * + * Zhixing B1 writes the same target-row metrics onto every matched category, + * so the detail pane must not group these values by signal. + */ +export function getStockDetailValue(stock: SelectionStockResult, key: string) { + const signal = stock.signals.find(({ details }) => + hasDetailValue(details[key]), ) - return getSignalJValue(signal?.details ?? {}) + return formatSignalDetailValue(key, signal?.details[key]) +} + +export function getStockBasicMetrics(stock: SelectionStockResult) { + return stockBasicMetricKeys.map((key) => ({ + key, + label: detailLabels[key], + value: getStockDetailValue(stock, key), + })) +} + +function hasDetailValue(value: number | string | null | undefined) { + return value !== null && value !== undefined && value !== "" } function formatSignalDetailValue( diff --git a/zhixing-web/src/features/selection/components/selection-results-workbench.tsx b/zhixing-web/src/features/selection/components/selection-results-workbench.tsx index 9bb998b..128a5dc 100644 --- a/zhixing-web/src/features/selection/components/selection-results-workbench.tsx +++ b/zhixing-web/src/features/selection/components/selection-results-workbench.tsx @@ -131,7 +131,7 @@ export function SelectionResultsWorkbench({ > - + {signalCategoryOptions.map((option) => ( @@ -156,7 +156,7 @@ export function SelectionResultsWorkbench({ > - + {SCORE_SORT_OPTIONS.map((option) => ( diff --git a/zhixing-web/src/features/selection/components/signal-detail-panel.tsx b/zhixing-web/src/features/selection/components/signal-detail-panel.tsx index d9e42cb..383edff 100644 --- a/zhixing-web/src/features/selection/components/signal-detail-panel.tsx +++ b/zhixing-web/src/features/selection/components/signal-detail-panel.tsx @@ -1,15 +1,13 @@ import { lazy, Suspense } from "react" -import { Database, Search } from "lucide-react" +import { Search } from "lucide-react" import { useSelectionChart } from "../api/selection.query" -import { Badge } from "@/shared/ui/badge" import { Card } from "@/shared/ui/card" import type { SelectionStockResult } from "../api/selection.types" import { PatternScoreDetails } from "./pattern-score" -import { categoryToneClass, getCategoryLabel } from "./selection-presentation" -import { SignalDetails } from "./signal-details" import { PatternCaseImage } from "./pattern-case-image" +import { getStockBasicMetrics } from "./selection-presentation" const SelectionChart = lazy(() => import("./selection-chart").then((module) => ({ @@ -44,111 +42,95 @@ export function SignalDetailPanel({ stock }: SignalDetailPanelProps) { ) } + const metricRows = [ + ...getStockBasicMetrics(stock), + { + key: "target_trade_date", + label: "目标交易日", + value: stock.target_trade_date, + }, + { key: "strategy", label: "策略", value: "知行 B1" }, + ] + return ( ) diff --git a/zhixing-web/src/features/selection/pages/selection-results-page.test.tsx b/zhixing-web/src/features/selection/pages/selection-results-page.test.tsx index 3b2242c..4e71d33 100644 --- a/zhixing-web/src/features/selection/pages/selection-results-page.test.tsx +++ b/zhixing-web/src/features/selection/pages/selection-results-page.test.tsx @@ -90,7 +90,12 @@ const selectedResult: SelectionResults = { { category: "zhixing_b1_original_b1", close: 10.5, - details: { j: 12, rsi: 20 }, + details: { + daily_amplitude: 3.4, + daily_change: 1.2, + j: 12, + rsi: 20, + }, name: "平安银行", strategy: "zhixing_b1", target_trade_date: "2026-08-08", @@ -199,17 +204,22 @@ describe("SelectionResultsPage", () => { const detailPanel = screen.getByRole("complementary", { name: "当前股票详情", }) - expect(within(detailPanel).getByText("关键指标")).toBeInTheDocument() + expect(within(detailPanel).queryByText("关键指标")).not.toBeInTheDocument() expect(within(detailPanel).getByText("图形相似度评分")).toBeInTheDocument() expect( within(detailPanel).getByText("暂无可用行情数据。"), ).toBeInTheDocument() expect( - within(detailPanel).getByText("最佳案例:华纳药厂"), + within(detailPanel).getByText("最接近案例:华纳药厂"), ).toBeInTheDocument() - expect(within(detailPanel).getByText("价格形态")).toBeInTheDocument() - expect(within(detailPanel).getAllByText("J 值")).toHaveLength(2) + expect(within(detailPanel).queryByText("价格形态")).not.toBeInTheDocument() + expect(within(detailPanel).getByText("J 值")).toBeInTheDocument() expect(within(detailPanel).getByText("RSI")).toBeInTheDocument() + expect(within(detailPanel).getByText("日涨跌幅")).toBeInTheDocument() + expect(within(detailPanel).getByText("1.2%")).toBeInTheDocument() + expect(within(detailPanel).getByText("日振幅")).toBeInTheDocument() + expect(within(detailPanel).getByText("3.4%")).toBeInTheDocument() + expect(within(detailPanel).getByText("20")).toBeInTheDocument() const caseImages = within(detailPanel).getAllByRole("img", { name: "华纳药厂 B1 参考案例图", }) @@ -590,8 +600,10 @@ describe("SelectionResultsPage", () => { name: "当前股票详情", }) expect(within(detailPanel).getByText("招商银行")).toBeInTheDocument() - expect(within(detailPanel).getByText("MACD")).toBeInTheDocument() - expect(within(detailPanel).getByText("3.2")).toBeInTheDocument() + expect(within(detailPanel).queryByText("MACD")).not.toBeInTheDocument() + expect(within(detailPanel).queryByText("3.2")).not.toBeInTheDocument() + expect(within(detailPanel).getByText("J 值")).toBeInTheDocument() + expect(within(detailPanel).getByText("RSI")).toBeInTheDocument() expect( screen.queryByRole("button", { name: "查看详情" }), ).not.toBeInTheDocument() diff --git a/zhixing-web/src/shared/ui/select.tsx b/zhixing-web/src/shared/ui/select.tsx index 26a3819..79058a2 100644 --- a/zhixing-web/src/shared/ui/select.tsx +++ b/zhixing-web/src/shared/ui/select.tsx @@ -75,8 +75,10 @@ function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { } function SelectContent({ - align = "center", - alignItemWithTrigger = true, + // Project default: conventional dropdown below the trigger. Call sites should + // not re-declare align / alignItemWithTrigger for ordinary form and filter use. + align = "start", + alignItemWithTrigger = false, alignOffset = 0, className, children, -- 2.52.0