feat(selection): show stock industry and concept boards in detail panel
Add point-in-time stock membership lookup to the sector radar module
(GET /sector-radar/stocks/{ts_code}/membership) reading the existing
dc_index/dc_member snapshots, and surface industries plus concept chips
in the selection signal detail panel with graceful no-data hiding.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { lazy, Suspense } from "react"
|
||||
import { Search } from "lucide-react"
|
||||
import { useStockSectorMembership } from "@/features/sector-radar/api/sector-radar.query"
|
||||
import { useSelectionChart } from "../api/selection.query"
|
||||
|
||||
import { Card } from "@/shared/ui/card"
|
||||
@@ -18,6 +19,8 @@ const SelectionChart = lazy(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const displayedConceptCount = 8
|
||||
|
||||
interface SignalDetailPanelProps {
|
||||
stock: SelectionStockResult | undefined
|
||||
}
|
||||
@@ -28,6 +31,10 @@ export function SignalDetailPanel({ stock }: SignalDetailPanelProps) {
|
||||
stock?.target_trade_date,
|
||||
stock?.strategy,
|
||||
)
|
||||
const membershipQuery = useStockSectorMembership(
|
||||
stock?.ts_code,
|
||||
stock?.target_trade_date,
|
||||
)
|
||||
|
||||
if (!stock) {
|
||||
return (
|
||||
@@ -63,6 +70,19 @@ export function SignalDetailPanel({ stock }: SignalDetailPanelProps) {
|
||||
},
|
||||
]
|
||||
|
||||
const membership =
|
||||
membershipQuery.data?.status === "success" ? membershipQuery.data : undefined
|
||||
const industries = membership?.industries ?? []
|
||||
const concepts = membership?.concepts ?? []
|
||||
const extraConceptCount = Math.max(
|
||||
0,
|
||||
concepts.length - displayedConceptCount,
|
||||
)
|
||||
const sectorSnapshotTitle =
|
||||
membership?.trade_date != null
|
||||
? `板块成分截至 ${membership.trade_date}`
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<aside
|
||||
aria-label="当前股票详情"
|
||||
@@ -117,6 +137,33 @@ export function SignalDetailPanel({ stock }: SignalDetailPanelProps) {
|
||||
¥ {stock.close.toFixed(2)}
|
||||
</p>
|
||||
|
||||
{industries.length > 0 && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
行业:{industries.map((industry) => industry.name).join("、")}
|
||||
</p>
|
||||
)}
|
||||
{concepts.length > 0 && (
|
||||
<div
|
||||
className="mt-2 flex flex-wrap items-center gap-1"
|
||||
title={sectorSnapshotTitle}
|
||||
>
|
||||
<span className="text-xs text-muted-foreground">板块</span>
|
||||
{concepts.slice(0, displayedConceptCount).map((concept) => (
|
||||
<span
|
||||
className="rounded bg-muted px-1.5 py-0.5 text-xs"
|
||||
key={concept.code}
|
||||
>
|
||||
{concept.name}
|
||||
</span>
|
||||
))}
|
||||
{extraConceptCount > 0 && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
等 {concepts.length} 个
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<dl className="mt-3 divide-y divide-border/60 border-y border-border/60 text-xs">
|
||||
{metricRows.map((row) => (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user