diff --git a/app/frontend/m/config/nav.config.js b/app/frontend/m/config/nav.config.js index 043887c..ed6d3b9 100644 --- a/app/frontend/m/config/nav.config.js +++ b/app/frontend/m/config/nav.config.js @@ -211,6 +211,89 @@ ] } } + }, + + "market/sentiment/history": { + frozenColumns: [ + { key: "trade_date", label: "日期", type: "text", width: 88 } + ], + primaryColumns: [ + { key: "score", label: "温度", type: "int" }, + { key: "phase", label: "阶段", type: "text" }, + { key: "direction", label: "方向", type: "text" } + ], + scrollColumns: [ + { key: "limit_up_count", label: "涨停", type: "int" }, + { key: "first_board_count", label: "首板", type: "int" }, + { key: "second_board_count", label: "二板", type: "int" }, + { key: "three_plus_count", label: "三板+", type: "int" }, + { key: "max_height", label: "高度", type: "int" }, + { key: "broken_count", label: "炸板", type: "int" }, + { key: "limit_down_count", label: "跌停", type: "int" }, + { key: "seal_rate", label: "封板率%", type: "rate" }, + { key: "previous_limit_count", label: "昨涨停", type: "int" }, + { key: "previous_positive_rate", label: "昨红率%", type: "rate" } + ] + }, + + "market/auction": { + frozenColumns: [ + { key: "stock", label: "股票", type: "stock", width: 96 } + ], + primaryColumns: [ + { key: "attention_score", label: "关注分", type: "score" }, + { key: "change", label: "竞价涨幅%", type: "change" }, + { key: "expectation", label: "预期", type: "expectation" } + ], + scrollColumns: [ + { key: "sector", label: "方向", type: "text" }, + { key: "amount_million", label: "竞价额百万", type: "money" }, + { key: "volume_ratio", label: "量比", type: "rate" } + ] + }, + + "market/rotation/members": { + frozenColumns: [ + { key: "stock", label: "股票", type: "stock", width: 96 } + ], + primaryColumns: [ + { key: "change", label: "涨跌幅%", type: "change" }, + { key: "close", label: "收盘", type: "price" } + ], + scrollColumns: [ + { key: "open", label: "开盘", type: "price" }, + { key: "amount_billion", label: "成交额亿", type: "money" } + ] + }, + + "market/themes/members": { + frozenColumns: [ + { key: "stock", label: "股票", type: "stock", width: 96 } + ], + primaryColumns: [ + { key: "change", label: "涨跌幅%", type: "change" }, + { key: "price", label: "现价", type: "price" } + ], + scrollColumns: [ + { key: "amount_billion", label: "成交额亿", type: "money" } + ] + }, + + "market/dragon/operations": { + frozenColumns: [ + { key: "stock", label: "股票", type: "stock", width: 96 } + ], + primaryColumns: [ + { key: "direction", label: "方向", type: "direction" }, + { key: "change", label: "涨幅%", type: "change" } + ], + scrollColumns: [ + { key: "buy_million", label: "买入百万", type: "money" }, + { key: "sell_million", label: "卖出百万", type: "money" }, + { key: "net_buy_million", label: "净额百万", type: "change" }, + { key: "seat_name", label: "席位", type: "text", wide: true }, + { key: "reason", label: "上榜原因", type: "text", wide: true } + ] } } }; diff --git a/app/frontend/m/css/features.css b/app/frontend/m/css/features.css index 0258783..ccd6488 100644 --- a/app/frontend/m/css/features.css +++ b/app/frontend/m/css/features.css @@ -1033,3 +1033,1077 @@ transform: none; } } + +/* ===== 复杂行情页(P2b):情绪周期 / 天梯 / 轮动 / 竞价 / 题材库 / 龙虎榜 ===== */ + +.m-scroll { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + background: var(--canvas); +} + +.m-table-wrap { + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + touch-action: pan-x pan-y; +} + +.m-section-title { + display: flex; + align-items: center; + justify-content: space-between; + margin: 14px 12px 8px; + font-size: var(--font-size-label); + font-weight: 600; + color: var(--text-secondary); +} + +.m-card, +.m-chart-card, +.m-sent-card, +.m-apex { + margin: 0 12px; + padding: 14px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); +} + +.m-chart-card { + padding: 8px 6px; + height: var(--mobile-chart-height); +} + +.m-chart-card svg { + width: 100%; + height: 100%; + display: block; +} + +.m-inline-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + min-height: 60px; + font-size: var(--font-size-label); + color: var(--text-tertiary); +} + +/* 情绪周期 */ +.m-sent-score-row { + display: flex; + align-items: center; + gap: 10px; +} + +.m-sent-score { + font-size: 40px; + font-weight: 700; + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.m-sent-score-side { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; +} + +.m-phase { + font-size: 11px; + font-weight: 600; + padding: 2px 8px; + border-radius: 999px; +} + +.m-phase--ice { background: var(--action-soft); color: var(--action); } +.m-phase--repair { background: var(--market-down-soft); color: var(--market-down); } +.m-phase--fermentation { background: var(--warning-soft); color: var(--warning); } +.m-phase--climax { background: var(--market-up-soft); color: var(--market-up); } +.m-phase--divergence { background: var(--surface-muted); color: var(--text-secondary); } +.m-phase--retreat { background: var(--surface-muted); color: var(--text-tertiary); } + +.m-sent-direction { + font-size: 12px; + font-weight: 600; +} + +.m-sent-dir--up { color: var(--market-up); } +.m-sent-dir--down { color: var(--market-down); } +.m-sent-dir--new { color: var(--action); } +.m-sent-dir--flat { color: var(--text-secondary); } + +.m-sent-label { + margin-top: 6px; + font-size: var(--font-size-card-title); + color: var(--text-secondary); +} + +.m-sent-metrics { + display: flex; + gap: 8px; + margin-top: 12px; + flex-wrap: wrap; +} + +.m-sent-metric { + flex: 1; + min-width: 72px; + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px; + border-radius: 6px; + background: var(--surface-muted); +} + +.m-sent-metric span { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-sent-metric b { + font-size: var(--font-size-card-title); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-sent-up b { color: var(--market-up); } +.m-sent-warn b { color: var(--warning); } +.m-sent-down b { color: var(--market-down); } + +.m-sent-advice { + margin-top: 10px; + font-size: var(--font-size-label); + color: var(--text-secondary); + line-height: 1.5; +} + +.m-sentiment-line { + fill: none; + stroke: var(--action); + stroke-width: 2; +} + +.m-sentiment-area { + fill: var(--action-soft); + stroke: none; +} + +.m-components { + margin: 0 12px; + padding: 4px 14px 14px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); +} + +.m-component { + padding: 12px 0; + border-bottom: 1px solid var(--border-subtle); +} + +.m-component:last-child { + border-bottom: 0; +} + +.m-component-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.m-component-head span { + font-size: var(--font-size-label); + font-weight: 500; +} + +.m-component-head b { + font-size: var(--font-size-card-title); + font-variant-numeric: tabular-nums; +} + +.m-component-track { + height: 6px; + margin: 8px 0 6px; + border-radius: 3px; + background: var(--surface-muted); + overflow: hidden; +} + +.m-component-track i { + display: block; + height: 100%; + border-radius: 3px; + background: var(--action); +} + +.m-component small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-range-tabs { + display: inline-flex; + gap: 4px; +} + +.m-range-tab { + height: 26px; + padding: 0 10px; + border: 1px solid var(--border-strong); + border-radius: 999px; + background: var(--surface); + color: var(--text-secondary); + font-size: var(--font-size-aux); + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.m-range-tab.active { + background: var(--action-soft); + border-color: var(--action); + color: var(--action); + font-weight: 600; +} + +/* 市场天梯 */ +.m-apex-head { + display: flex; + align-items: baseline; + gap: 8px; +} + +.m-apex-head span { + font-size: var(--font-size-label); + color: var(--text-secondary); +} + +.m-apex-head strong { + font-size: 24px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.m-apex-head em { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + font-style: normal; +} + +.m-apex p { + margin: 8px 0 4px; + font-size: var(--font-size-label); + line-height: 1.5; +} + +.m-apex p b { + color: var(--text-primary); +} + +.m-apex small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-ladder-tier { + margin: 8px 12px 0; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); + overflow: hidden; +} + +.m-ladder-tier-head { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border-bottom: 1px solid var(--border-subtle); +} + +.m-ladder-tier-level { + font-size: var(--font-size-card-title); + font-weight: 600; +} + +.m-ladder-tier-count { + font-size: var(--font-size-label); + color: var(--text-secondary); +} + +.m-ladder-rate { + margin-left: auto; + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-ladder-stocks { + padding: 4px 0; +} + +.m-ladder-stock { + display: flex; + flex-direction: column; + gap: 2px; + width: 100%; + padding: 10px 14px; + border: 0; + background: transparent; + text-align: left; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: background-color var(--motion-press-release) var(--ease-press); +} + +.m-ladder-stock:active { + background: var(--surface-hover); +} + +.m-ladder-stock-main { + display: flex; + align-items: center; + gap: 6px; +} + +.m-ladder-stock-main strong { + font-size: var(--font-size-table); + font-weight: 500; +} + +.m-ladder-stock-main small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + font-variant-numeric: tabular-nums; +} + +.m-ladder-tag { + font-style: normal; + font-size: 10px; + padding: 1px 4px; + border-radius: 3px; + background: var(--action-soft); + color: var(--action); +} + +.m-ladder-tag.is-broken { + background: var(--warning-soft); + color: var(--warning); +} + +.m-ladder-stock-sub { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.m-ladder-stock-sub b { + font-size: var(--font-size-label); + font-weight: 400; + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-ladder-stock-sub small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + flex: 0 0 auto; +} + +.m-ladder-gap { + padding: 12px 14px; + font-size: var(--font-size-label); + color: var(--text-tertiary); +} + +.m-ladder-more { + width: 100%; + height: 40px; + border: 0; + border-top: 1px solid var(--border-subtle); + background: transparent; + color: var(--action); + font-size: var(--font-size-label); + cursor: pointer; +} + +.m-rate-list { + margin: 0 12px; + padding: 12px 14px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); +} + +.m-rate-row { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 0; +} + +.m-rate-row span { + width: 76px; + flex: 0 0 auto; + font-size: var(--font-size-label); + color: var(--text-secondary); +} + +.m-rate-row i { + flex: 1; + height: 8px; + border-radius: 4px; + background: var(--surface-muted); + overflow: hidden; +} + +.m-rate-row i b { + display: block; + height: 100%; + background: var(--action); +} + +.m-rate-row strong { + width: 52px; + flex: 0 0 auto; + text-align: right; + font-size: var(--font-size-label); + font-variant-numeric: tabular-nums; +} + +/* 主题轮动 */ +.m-rotation-day { + margin: 0 12px 10px; + padding: 10px 12px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); +} + +.m-rotation-day.is-latest { + border: 1px solid var(--action-soft); +} + +.m-rotation-day header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.m-rotation-day header time { + font-size: var(--font-size-card-title); + font-weight: 600; +} + +.m-rotation-day header span { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-rotation-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.m-rotation-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 8px; + border: 1px solid var(--border-strong); + background: var(--surface); + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--motion-press-release) var(--ease-press), + background-color var(--motion-press-release) var(--ease-press); +} + +.m-rotation-chip:active { + transform: scale(0.96); + background: var(--surface-hover); +} + +.m-rotation-chip strong { + font-size: var(--font-size-label); + font-weight: 500; +} + +.m-rotation-chip small { + font-size: var(--font-size-aux); + color: var(--text-secondary); +} + +.m-rotation-chip.heat-strong { + border-color: var(--market-up); +} + +.m-rotation-chip.heat-warm { + border-color: var(--warning); +} + +.m-rotation-rank { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 4px; + font-size: 10px; + font-weight: 600; + background: var(--surface-muted); + color: var(--text-secondary); +} + +.m-rotation-rank.rank-1 { background: var(--market-up-soft); color: var(--market-up); } +.m-rotation-rank.rank-2 { background: var(--warning-soft); color: var(--warning); } +.m-rotation-rank.rank-3 { background: var(--market-down-soft); color: var(--market-down); } +.m-rotation-rank.rank-4 { background: var(--action-soft); color: var(--action); } + +/* 竞价 */ +.m-phase-notice { + margin: 10px 12px; + padding: 10px 12px; + border-radius: 8px; + background: var(--action-soft); + display: flex; + flex-direction: column; + gap: 2px; +} + +.m-phase-notice strong { + font-size: var(--font-size-label); + color: var(--action); +} + +.m-phase-notice span { + font-size: var(--font-size-aux); + color: var(--text-secondary); +} + +.m-phase-notice--selection { + background: var(--warning-soft); +} + +.m-phase-notice--selection strong { + color: var(--warning); +} + +.m-sum-strip { + display: flex; + margin: 10px 12px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); + overflow: hidden; +} + +.m-sum-metric { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + padding: 10px 2px; + text-align: center; +} + +.m-sum-metric + .m-sum-metric { + border-left: 1px solid var(--border); +} + +.m-sum-metric span { + font-size: var(--font-size-aux); + color: var(--text-secondary); + white-space: nowrap; +} + +.m-sum-metric strong { + font-size: var(--font-size-card-title); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-sum-metric strong small { + font-size: 10px; + font-weight: 400; + color: var(--text-tertiary); +} + +.m-sum-up strong { color: var(--market-up); } +.m-sum-down strong { color: var(--market-down); } +.m-sum-warn strong { color: var(--warning); } + +.m-mini-bars { + display: flex; + align-items: flex-end; + gap: 4px; + height: 100%; + padding: 0 6px; +} + +.m-mini-bar { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + height: 100%; + gap: 3px; +} + +.m-mini-bar span { + width: 100%; + max-width: 18px; + min-height: 2px; + border-radius: 3px 3px 0 0; + background: var(--action-soft); +} + +.m-mini-bar.current span { + background: var(--action); +} + +.m-mini-bar small { + font-size: 9px; + color: var(--text-tertiary); + white-space: nowrap; +} + +.m-carry-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--border-subtle); +} + +.m-carry-row:last-child { + border-bottom: 0; +} + +.m-carry-name { + flex: 1; + min-width: 0; + font-size: var(--font-size-label); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-carry-leader { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + flex: 0 0 auto; +} + +.m-carry-status { + font-size: 10px; + padding: 1px 6px; + border-radius: 4px; + flex: 0 0 auto; +} + +.m-carry-status.strong { background: var(--market-up-soft); color: var(--market-up); } +.m-carry-status.steady { background: var(--action-soft); color: var(--action); } +.m-carry-status.mixed { background: var(--warning-soft); color: var(--warning); } +.m-carry-status.weak { background: var(--surface-muted); color: var(--text-secondary); } + +.m-carry-median { + flex: 0 0 auto; + font-size: var(--font-size-label); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-carry-median.up { color: var(--market-up); } +.m-carry-median.down { color: var(--market-down); } + +/* 题材库 */ +.m-theme-list { + margin: 0 12px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); + overflow: hidden; +} + +.m-theme-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 12px 14px; + border: 0; + border-bottom: 1px solid var(--border-subtle); + background: transparent; + text-align: left; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: background-color var(--motion-press-release) var(--ease-press); +} + +.m-theme-item:last-child { + border-bottom: 0; +} + +.m-theme-item:active { + background: var(--surface-hover); +} + +.m-theme-rank { + flex: 0 0 auto; + width: 22px; + text-align: center; + font-size: var(--font-size-aux); + color: var(--text-tertiary); + font-variant-numeric: tabular-nums; +} + +.m-theme-item:nth-child(-n+3) .m-theme-rank { + color: var(--warning); + font-weight: 600; +} + +.m-theme-copy { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} + +.m-theme-copy strong { + font-size: var(--font-size-table); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-theme-copy small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-theme-item > b { + flex: 0 0 auto; + font-size: var(--font-size-label); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-theme-item > b.up { color: var(--market-up); } +.m-theme-item > b.down { color: var(--market-down); } + +.m-theme-detail-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 8px; +} + +.m-theme-detail-head strong { + font-size: var(--font-size-page-title); + font-weight: 600; +} + +.m-theme-detail-head b { + font-size: var(--font-size-card-title); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-theme-detail-head b.up { color: var(--market-up); } +.m-theme-detail-head b.down { color: var(--market-down); } + +.m-theme-detail-code { + margin-top: 2px; + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-sheet-meta { + margin-bottom: 8px; + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +/* 龙虎榜 */ +.m-dragon-list { + margin: 0 12px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.m-dragon-card { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 14px; + border: 0; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); + text-align: left; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--motion-press-release) var(--ease-press); +} + +.m-dragon-card:active { + transform: scale(0.98); +} + +.m-dragon-card-rank { + flex: 0 0 auto; + font-size: var(--font-size-aux); + color: var(--text-tertiary); + font-variant-numeric: tabular-nums; +} + +.m-dragon-card-mono { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background: var(--action-soft); + color: var(--action); + font-size: 13px; + font-weight: 600; +} + +.m-dragon-card-copy { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} + +.m-dragon-card-copy strong { + font-size: var(--font-size-table); + font-weight: 500; +} + +.m-dragon-card-copy small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-dragon-card-stats { + flex: 0 0 auto; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 1px; +} + +.m-dragon-card-stats small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-dragon-card-stats b { + font-size: var(--font-size-label); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-dragon-card-stats b.up { color: var(--market-up); } +.m-dragon-card-stats b.down { color: var(--market-down); } + +.m-dragon-unclassified { + margin: 8px 12px 0; + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-dragon-trader-totals { + display: flex; + gap: 8px; + margin-bottom: 10px; +} + +.m-total { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px; + border-radius: 6px; + background: var(--surface-muted); +} + +.m-total span { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-total b { + font-size: var(--font-size-label); + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.m-total b.up { color: var(--market-up); } +.m-total b.down { color: var(--market-down); } + +.m-profile-list { + margin: 0 12px; + border-radius: 8px; + background: var(--surface); + box-shadow: var(--elevation-card); + overflow: hidden; +} + +.m-profile-row { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 12px 14px; + border: 0; + border-bottom: 1px solid var(--border-subtle); + background: transparent; + text-align: left; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.m-profile-row:last-child { + border-bottom: 0; +} + +.m-profile-row:active { + background: var(--surface-hover); +} + +.m-profile-mono { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 8px; + background: var(--action-soft); + color: var(--action); + font-size: 13px; + font-weight: 600; +} + +.m-profile-copy { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} + +.m-profile-copy strong { + font-size: var(--font-size-table); + font-weight: 500; +} + +.m-profile-copy small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.m-profile-seat { + flex: 0 0 auto; + font-size: var(--font-size-aux); + color: var(--text-secondary); +} + +.m-profile-detail-head { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; +} + +.m-profile-detail-head .m-profile-mono { + width: 44px; + height: 44px; + font-size: 16px; +} + +.m-profile-detail-head strong { + font-size: var(--font-size-page-title); +} + +.m-profile-detail-head small { + font-size: var(--font-size-aux); + color: var(--text-tertiary); +} + +.m-profile-desc { + font-size: var(--font-size-label); + color: var(--text-secondary); + line-height: 1.6; +} + +.m-profile-orgs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.m-profile-orgs span { + font-size: var(--font-size-label); + padding: 6px 10px; + border-radius: 6px; + background: var(--surface-muted); + color: var(--text-primary); +} + +.m-profile-notice { + margin-top: 12px; + font-size: var(--font-size-aux); + color: var(--warning); +} + +.m-tag--exp-above { background: var(--market-up-soft); color: var(--market-up); } +.m-tag--exp-matched { background: var(--surface-muted); color: var(--text-secondary); } +.m-tag--exp-below { background: var(--market-down-soft); color: var(--market-down); } +.m-tag--exp-one { background: var(--warning-soft); color: var(--warning); } + +.m-tag--dir-up { background: var(--market-up-soft); color: var(--market-up); } +.m-tag--dir-down { background: var(--market-down-soft); color: var(--market-down); } +.m-tag--dir-flat { background: var(--surface-muted); color: var(--text-secondary); } + +@media (prefers-reduced-motion: reduce) { + .m-rotation-chip:active, + .m-dragon-card:active, + .m-range-tab:active { + transform: none; + } +} diff --git a/app/frontend/m/js/pages.js b/app/frontend/m/js/pages.js index d018ba0..b6a8fda 100644 --- a/app/frontend/m/js/pages.js +++ b/app/frontend/m/js/pages.js @@ -110,6 +110,21 @@ calCursor: null, sort: { key: "", dir: null }, detail: null, + sentimentHistory: null, + sentimentRange: 20, + rotation: null, + rotationSelectedSector: "", + rotationSelectedDate: "", + rotationMembers: null, + auction: null, + auctionDataset: "focus", + themes: null, + themesDetail: null, + themesSelectedCode: "", + dragon: null, + dragonProfiles: null, + dragonViewMode: "daily", + dragonSelectedTrader: "", }; let sheetToken = 0; @@ -137,7 +152,7 @@ } function isNumericType(type) { - return ["rank", "change", "price", "rate", "money", "gap", "int", "streak", "advance", "height", "move"].indexOf(type) >= 0; + return ["rank", "change", "price", "rate", "money", "gap", "int", "streak", "advance", "height", "move", "score"].indexOf(type) >= 0; } function sortableColumn(col) { @@ -203,7 +218,7 @@ function colAlign(col) { if (col.type === "rank") return "m-align-center"; - if (["change", "price", "rate", "money", "gap", "int", "streak", "advance", "height", "move"].indexOf(col.type) >= 0) return "m-align-right"; + if (["change", "price", "rate", "money", "gap", "int", "streak", "advance", "height", "move", "score"].indexOf(col.type) >= 0) return "m-align-right"; return "m-align-left"; } @@ -289,6 +304,23 @@ return '' + escapeHtml(text) + ""; } + function scoreCell(value) { + if (value == null || value === "") return ''; + return '' + formatNumber(number(value), 1) + ""; + } + + function expectationCell(value) { + if (value == null || value === "") return ""; + const map = { "超预期": "above", "符合预期": "matched", "低于预期": "below", "竞价一字": "one" }; + return '' + escapeHtml(value) + ""; + } + + function directionCell(value) { + if (value == null || value === "") return ""; + const map = { "买入": "up", "卖出": "down", "持平": "flat" }; + return '' + escapeHtml(value) + ""; + } + function cellHtml(col, row, index) { const value = row[col.key]; switch (col.type) { @@ -307,6 +339,9 @@ case "move": return moveCell(value); case "concepts": return conceptsCell(value); case "dual": return dualCell(value); + case "score": return scoreCell(value); + case "expectation": return expectationCell(value); + case "direction": return directionCell(value); case "text": default: return textCell(value); } @@ -314,7 +349,7 @@ /* ---------------------------------------------------------------- table */ - function buildTable(cols, rows) { + function buildTable(cols, rows, opts) { const frozen = cols.frozenColumns || []; const primary = cols.primaryColumns || []; const scroll = cols.scrollColumns || []; @@ -354,7 +389,8 @@ const cells = ordered.map(function (col, i) { return cellOpen(col, i, false) + cellHtml(col, row, rIndex) + ""; }).join(""); - const code = /^\d{6}$/.test(String(row.code || "")) ? ' data-code="' + escapeHtml(row.code) + '"' : ""; + const linkable = !(opts && opts.noLink); + const code = linkable && /^\d{6}$/.test(String(row.code || "")) ? ' data-code="' + escapeHtml(row.code) + '"' : ""; return "
' + escapeHtml(sentimentAdvice(latest.phase)) + "
" + + "' + (spaceStocks.length ? spaceStocks.map(function (s) { return "" + escapeHtml(s.name) + "(" + escapeHtml(s.sector || "其他") + ")"; }).join(" · ") : "暂无空间板") + "
" + + "" + escapeHtml(spaceNote) + "" + escapeHtml(unavailable ? "龙虎榜数据暂不可用" : payload.meta.trade_date + " 暂无龙虎榜明细") + "
" + + "龙虎榜明细通常在盘后陆续披露,可稍后刷新或查看前一交易日。' + escapeHtml(profile.description || "名录暂未收录该游资的公开简介。") + "
" + + (orgs.length ? '' + escapeHtml(meta.notice) + "
" : ""; + } + + /* ----- complex page registry ----- */ + + function setupComplexPage(key) { + resetComplexState(key); + document.getElementById("m-view").classList.add("m-view-feature"); + updateHeader(findLabel(key) || key, state.requestedDate); + document.getElementById("m-view").innerHTML = complexFrame(key, complexScroll(skeletonHtml(8))); + } + + const COMPLEX_PAGES = { + "market/sentiment": setupComplexPage, + "market/ladder": setupComplexPage, + "market/rotation": setupComplexPage, + "market/auction": setupComplexPage, + "market/themes": setupComplexPage, + "market/dragon": setupComplexPage, + }; + + const COMPLEX_LOADERS = { + "market/sentiment": loadSentiment, + "market/ladder": loadLadder, + "market/rotation": loadRotation, + "market/auction": loadAuction, + "market/themes": loadThemes, + "market/dragon": loadDragon, + }; + + function isComplexPage(key) { + return Boolean(COMPLEX_PAGES[key]); + } + /* ---------------------------------------------------------------- sheets */ function ensureSheetRoot() { @@ -744,7 +1602,7 @@ if (!dateStr) return; state.requestedDate = dateStr; closeSheet(); - load(); + reloadCurrent(); } function openDateSheet() { @@ -1153,11 +2011,72 @@ if (dateBtn) { openDateSheet(); return; } const retry = event.target.closest("[data-action=retry]"); - if (retry) { load(); return; } + if (retry) { reloadCurrent(); return; } const sortHead = event.target.closest("[data-sort-key]"); if (sortHead) { toggleSort(sortHead.dataset.sortKey); return; } + const rangeTab = event.target.closest("[data-sentiment-range]"); + if (rangeTab) { + state.sentimentRange = number(rangeTab.dataset.sentimentRange) || 20; + document.querySelectorAll("[data-sentiment-range]").forEach(function (tab) { + const active = number(tab.dataset.sentimentRange) === state.sentimentRange; + tab.classList.toggle("active", active); + tab.setAttribute("aria-pressed", String(active)); + }); + loadSentiment(); + return; + } + + const auctionDataset = event.target.closest("[data-action=auction-dataset]"); + if (auctionDataset) { + state.auctionDataset = auctionDataset.dataset.dataset || "focus"; + document.querySelectorAll("[data-action=auction-dataset]").forEach(function (tab) { + const active = tab.dataset.dataset === state.auctionDataset; + tab.classList.toggle("active", active); + tab.setAttribute("aria-selected", String(active)); + }); + renderAuctionTable(); + return; + } + + const dragonMode = event.target.closest("[data-action=dragon-mode]"); + if (dragonMode) { + const mode = dragonMode.dataset.mode; + state.dragonViewMode = mode === "profiles" ? "profiles" : "daily"; + if (mode === "profiles") loadDragonProfiles(); + else if (state.dragon) renderDragon(); + else loadDragon(); + return; + } + + const ladderStock = event.target.closest("[data-ladder-stock]"); + if (ladderStock) { openDetailSheet(ladderStock.dataset.ladderStock); return; } + + const ladderExpand = event.target.closest("[data-ladder-expand]"); + if (ladderExpand) { + const level = number(ladderExpand.dataset.ladderExpand); + if (expandedLadder[level]) delete expandedLadder[level]; + else expandedLadder[level] = true; + renderLadder(); + return; + } + + const rotationSector = event.target.closest("[data-rotation-sector]"); + if (rotationSector) { + openRotationMembersSheet(rotationSector.dataset.rotationSector, rotationSector.dataset.rotationDate); + return; + } + + const themeCode = event.target.closest("[data-theme-code]"); + if (themeCode) { openThemeDetailSheet(themeCode.dataset.themeCode); return; } + + const dragonTrader = event.target.closest("[data-dragon-trader]"); + if (dragonTrader) { openDragonTraderSheet(dragonTrader.dataset.dragonTrader); return; } + + const profileId = event.target.closest("[data-profile-id]"); + if (profileId) { openProfileSheet(profileId.dataset.profileId); return; } + const watchBtn = event.target.closest("[data-action=watch]"); if (watchBtn) { toggleWatch(); return; } @@ -1216,6 +2135,6 @@ global.MobilePages = { render: renderPage, - has: function (key) { return Boolean(pageConfig(key)); }, + has: function (key) { return Boolean(pageConfig(key) || isComplexPage(key)); }, }; })(window);