rebuild(stage-7): deliver ladder and sector rotation

This commit is contained in:
leefer
2026-07-30 03:28:24 +08:00
parent 31a53de890
commit a76d344a98
28 changed files with 1161 additions and 13 deletions
+15
View File
@@ -67,6 +67,16 @@ export type MarketWorkspaceData = {
items?: Record<string, unknown>[];
};
export type RotationMembersData = {
trade_date: string;
sector_name: string;
sector_code: string;
member_count: number;
quoted_count: number;
coverage: number;
items: Record<string, unknown>[];
};
export const marketApi = {
summary(date?: string): Promise<MarketSummary> {
const query = date ? `?date=${encodeURIComponent(date)}` : "";
@@ -88,4 +98,9 @@ export const marketApi = {
syncSnapshot(date: string): Promise<{ trade_date: string; temperature: number }> {
return api.post(`/market/snapshot-sync?date=${encodeURIComponent(date)}`);
},
rotationMembers(sector: string, date: string): Promise<RotationMembersData> {
return api.get<RotationMembersData>(
`/market/rotation-members?sector=${encodeURIComponent(sector)}&date=${encodeURIComponent(date)}`,
);
},
};
@@ -116,3 +116,300 @@
.breadth-up { background: var(--color-up); }
.breadth-flat { background: var(--color-text-faint); }
.breadth-down { background: var(--color-down); }
.page-actions {
display: flex;
align-items: center;
gap: var(--s-8);
}
.page-actions .seg-control {
margin-left: 0;
}
.ladder-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) var(--s-320);
gap: var(--layout-gap);
align-items: start;
}
.ladder-board {
overflow: hidden;
}
.ladder-level {
display: grid;
grid-template-columns: var(--s-200) minmax(0, 1fr);
border-bottom: var(--s-1) solid var(--color-divider);
}
.ladder-level:last-child {
border-bottom: 0;
}
.ladder-level > header {
display: grid;
align-content: center;
gap: var(--s-6);
padding: var(--s-12) var(--s-14);
border-right: var(--s-1) solid var(--color-divider);
background: var(--color-primary-soft);
}
.ladder-level.is-gap > header,
.ladder-level.is-gap .ladder-stocks {
background: var(--color-surface-muted);
}
.ladder-level > header div {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--s-8);
}
.ladder-level > header strong {
color: var(--color-primary);
font-size: var(--font-15);
}
.ladder-level > header span,
.ladder-level > header small {
color: var(--color-text-secondary);
font-size: var(--font-11);
}
.ladder-stocks {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(var(--s-200), 1fr));
gap: var(--s-8);
padding: var(--s-10);
}
.ladder-stock {
min-width: 0;
display: grid;
gap: var(--s-6);
padding: var(--s-9);
border: var(--s-1) solid var(--color-border);
border-radius: var(--control-radius);
background: var(--color-surface);
transition: border-color var(--duration-fast) var(--ease-standard),
background var(--duration-fast) var(--ease-standard),
transform var(--duration-fast) var(--ease-standard);
}
.ladder-stock:hover {
border-color: var(--color-primary-border);
background: var(--color-primary-soft);
transform: translateY(calc(var(--s-1) * -1));
}
.ladder-stock > div,
.ladder-stock > p {
min-width: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s-8);
}
.ladder-stock strong,
.ladder-stock b {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ladder-stock span,
.ladder-stock small {
color: var(--color-text-secondary);
font-size: var(--font-11);
}
.ladder-gap-note {
align-self: center;
color: var(--color-text-faint);
}
.ladder-expand {
min-height: var(--s-44);
border: var(--s-1) dashed var(--color-border);
border-radius: var(--control-radius);
color: var(--color-primary);
background: var(--color-surface-muted);
}
.ladder-insights {
display: grid;
gap: var(--layout-gap);
}
.ladder-insight-body {
display: grid;
gap: var(--s-10);
padding: var(--s-14);
}
.ladder-insight-body > strong {
color: var(--color-primary);
font-size: var(--font-18);
}
.ladder-insight-body p {
color: var(--color-text-secondary);
font-size: var(--font-12);
line-height: var(--s-18);
}
.ladder-rate-list {
display: grid;
gap: var(--s-9);
padding: var(--s-12) var(--s-14);
}
.ladder-rate-list > div {
display: grid;
grid-template-columns: var(--s-64) minmax(0, 1fr) var(--s-44);
align-items: center;
gap: var(--s-8);
color: var(--color-text-secondary);
font-size: var(--font-11);
}
.ladder-rate-list strong {
text-align: right;
font-variant-numeric: tabular-nums;
}
.rotation-trajectory,
.rotation-members {
min-width: 0;
overflow: hidden;
}
.rotation-top-tag {
margin-left: auto;
}
.rotation-legend {
min-height: var(--s-34);
display: flex;
align-items: center;
gap: var(--s-16);
padding: var(--s-6) var(--s-14);
border-bottom: var(--s-1) solid var(--color-divider);
color: var(--color-text-secondary);
font-size: var(--font-11);
}
.rotation-legend span {
display: inline-flex;
align-items: center;
gap: var(--s-4);
}
.rotation-legend i {
width: var(--s-14);
height: var(--s-9);
border-radius: var(--radius-4);
}
.rotation-tracker {
min-height: var(--s-46);
display: flex;
align-items: center;
gap: var(--s-14);
padding: var(--s-7) var(--s-14);
border-bottom: var(--s-1) solid var(--color-primary-border);
background: var(--color-primary-soft);
}
.rotation-tracker strong {
color: var(--color-primary);
}
.rotation-tracker span {
flex: 1;
color: var(--color-text-secondary);
font-size: var(--font-11);
}
.rotation-days {
display: grid;
grid-template-columns: repeat(9, minmax(0, 1fr));
}
.rotation-day {
min-width: 0;
border-right: var(--s-1) solid var(--color-divider);
}
.rotation-day:last-child {
border-right: 0;
}
.rotation-day > header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--s-4);
padding: var(--s-8);
border-bottom: var(--s-1) solid var(--color-divider);
background: var(--color-surface-muted);
}
.rotation-day > header time {
font-weight: var(--weight-700);
}
.rotation-day > header span {
color: var(--color-text-faint);
font-size: var(--font-10-5);
}
.rotation-day > div {
display: grid;
}
.rotation-sector {
min-width: 0;
display: grid;
grid-template-columns: var(--s-20) minmax(0, 1fr);
gap: var(--s-2) var(--s-6);
padding: var(--s-7) var(--s-8);
border-bottom: var(--s-1) solid var(--color-divider);
text-align: left;
transition: opacity var(--duration-fast) var(--ease-standard);
}
.rotation-sector > span {
grid-row: span 2;
align-self: center;
color: var(--color-text-secondary);
font-variant-numeric: tabular-nums;
}
.rotation-sector strong {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--font-11);
}
.rotation-sector small {
color: var(--color-text-secondary);
font-size: var(--font-10-5);
}
.rotation-sector.selected {
color: var(--color-primary);
outline: var(--s-1) solid var(--color-primary);
outline-offset: calc(var(--s-1) * -1);
opacity: 1;
}
.heat-high { background: var(--color-up-soft); }
.heat-medium { background: var(--color-warning-soft); }
.heat-low { background: var(--color-primary-soft); }
@@ -197,4 +197,51 @@
.performance-conclusion {
position: static;
}
.market-page-header .page-actions {
width: 100%;
align-items: stretch;
flex-direction: column;
}
.market-page-header .page-actions .seg-control {
width: 100%;
}
.market-page-header .page-actions .seg-control button {
flex: 1;
}
.ladder-layout,
.ladder-level {
grid-template-columns: minmax(0, 1fr);
}
.ladder-level > header {
border-right: 0;
border-bottom: var(--s-1) solid var(--color-divider);
}
.ladder-stocks {
grid-template-columns: minmax(0, 1fr);
}
.rotation-legend {
align-items: flex-start;
flex-direction: column;
}
.rotation-tracker {
align-items: stretch;
flex-direction: column;
}
.rotation-days {
grid-template-columns: minmax(0, 1fr);
}
.rotation-day {
border-right: 0;
border-bottom: var(--s-1) solid var(--color-divider);
}
}