fix: merge B-157 and B-158 shell review blockers
Keep 1024-1439 broken/seal metrics in the detail panel, fold desktop admin actions into the ellipsis menu, and fit the 13-column limit pool at 1600 without hiding the data date. Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
e778c883db
commit
bb67085dd4
@@ -451,8 +451,8 @@
|
||||
},
|
||||
{
|
||||
"path": "frontend/shared/shell.css",
|
||||
"bytes": 60932,
|
||||
"lines": 3677
|
||||
"bytes": 62110,
|
||||
"lines": 3706
|
||||
},
|
||||
{
|
||||
"path": "backend/features/heaven/engine.py",
|
||||
@@ -461,8 +461,8 @@
|
||||
},
|
||||
{
|
||||
"path": "frontend/index.html",
|
||||
"bytes": 46449,
|
||||
"lines": 642
|
||||
"bytes": 47039,
|
||||
"lines": 646
|
||||
},
|
||||
{
|
||||
"path": "backend/features/screener/catalog.py",
|
||||
@@ -574,6 +574,11 @@
|
||||
"bytes": 10539,
|
||||
"lines": 244
|
||||
},
|
||||
{
|
||||
"path": "frontend/shared/dashboard.js",
|
||||
"bytes": 9993,
|
||||
"lines": 220
|
||||
},
|
||||
{
|
||||
"path": "backend/data/providers/tushare_sectors.py",
|
||||
"bytes": 9876,
|
||||
@@ -584,11 +589,6 @@
|
||||
"bytes": 9348,
|
||||
"lines": 222
|
||||
},
|
||||
{
|
||||
"path": "frontend/shared/dashboard.js",
|
||||
"bytes": 9323,
|
||||
"lines": 210
|
||||
},
|
||||
{
|
||||
"path": "frontend/pages/market/entity-detail.js",
|
||||
"bytes": 9119,
|
||||
|
||||
@@ -107,11 +107,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="tape-detail">
|
||||
<div class="tape-detail-item market-item"><span>市场情绪</span><strong class="tape-metric-value"><b id="detailSentimentScore">--</b><em id="detailSentimentText" class="sentiment-text">等待数据</em></strong></div>
|
||||
<div class="tape-detail-item market-item"><span>上涨家数</span><strong id="tapeUp" class="up">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>下跌家数</span><strong id="tapeDown" class="down">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>涨停 / 跌停</span><strong id="tapeLimit">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>两市成交额</span><strong id="tapeAmount">--</strong></div>
|
||||
<div class="tape-detail-item market-item metric-wide"><span>数据日期</span><strong id="dataDateMetric" class="metric-value small">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>涨停</span><strong id="tapeLimit" class="up">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>跌停</span><strong id="tapeLimitDown" class="down">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>炸板</span><strong id="detailBroken" class="warning">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>封板率</span><strong id="detailSealRate">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>两市成交</span><strong id="tapeAmount">--</strong></div>
|
||||
<div class="tape-detail-item market-item"><span>数据日期</span><strong id="dataDateMetric" class="metric-value small">--</strong></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
+15
-4
@@ -1,5 +1,16 @@
|
||||
/* Canonical CSS owner: mentor. Historical layers consolidated 2026-08-02. */
|
||||
|
||||
body[data-active-view="mentorView"] .app-page-context {
|
||||
max-width: 420px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
body[data-active-view="mentorView"] .app-page-context span {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
text-overflow: unset;
|
||||
}
|
||||
|
||||
#mentorView.workspace-view {
|
||||
--qp-bg-header: #F5F6F7;
|
||||
--qp-bg-sidebar: #FFFFFF;
|
||||
@@ -334,7 +345,7 @@
|
||||
|
||||
#mentorView .mentor-option {
|
||||
position: relative;
|
||||
height: 75px;
|
||||
height: 62px;
|
||||
margin: 0 10px 0 8px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
@@ -1061,11 +1072,11 @@
|
||||
|
||||
#mentorView .mentor-send-button {
|
||||
width: 71px;
|
||||
height: 31px;
|
||||
min-height: 31px;
|
||||
height: 30px;
|
||||
min-height: 30px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
background: var(--qp-accent);
|
||||
color: #FFFFFF;
|
||||
font-size: 13px;
|
||||
|
||||
+33
-18
@@ -892,24 +892,6 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.pool-streak-tag {
|
||||
display: inline-block;
|
||||
|
||||
padding: 1.5px 7px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
|
||||
border-radius: 5px;
|
||||
|
||||
font-size: 11px;
|
||||
|
||||
line-height: 1.6;
|
||||
|
||||
background: var(--r2-up-soft);
|
||||
|
||||
color: var(--r2-up);
|
||||
}
|
||||
|
||||
.pool-state-tag.one-word {
|
||||
background: rgb(255, 243, 217);
|
||||
|
||||
@@ -2225,6 +2207,39 @@
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
#limitPool #limitTable {
|
||||
min-width: 0;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#limitPool.redesigned-pool-view .pool-table-card.tbl-wrap {
|
||||
overflow-x: hidden;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#limitPool #limitTable.tbl thead th,
|
||||
#limitPool #limitTable.tbl tbody td,
|
||||
#limitPool .pool-table-card .data-table thead th,
|
||||
#limitPool .pool-table-card .data-table tbody td {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
#limitPool #limitTable .reason-column {
|
||||
min-width: 4.5em;
|
||||
|
||||
overflow: visible;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#limitPool #limitTable .pool-reason-cell {
|
||||
overflow: visible;
|
||||
|
||||
text-overflow: unset;
|
||||
}
|
||||
|
||||
#brokenTable,
|
||||
#downTable,
|
||||
#limitTable,
|
||||
|
||||
@@ -30,7 +30,7 @@ function renderLimitTable() {
|
||||
<tr data-code="${escapeHtml(row.code)}">
|
||||
<td class="row-number muted">${index + 1}</td>
|
||||
<td><span class="stock-cell"><strong class="stock-name sname">${escapeHtml(row.name)}</strong><small class="stock-code scode">${escapeHtml(row.code)}</small></span></td>
|
||||
<td class="number num"><span class="pool-streak-tag tag red">${streakLabel(row.streak)}</span></td>
|
||||
<td class="number num"><span class="streak-pill${number(row.streak) >= 4 ? " high" : ""}">${streakLabel(row.streak)}</span></td>
|
||||
<td class="number num up">${signed(row.change)}</td>
|
||||
<td class="number num">${formatNumber(row.price, 2)}</td>
|
||||
<td>${escapeHtml(row.sector || "其他")}</td>
|
||||
|
||||
+4
-18
@@ -313,24 +313,10 @@
|
||||
background: var(--table-header);
|
||||
}
|
||||
|
||||
.sentiment-history-table tbody tr:nth-child(2n) td {
|
||||
background: var(--surface-subtle);
|
||||
}
|
||||
|
||||
.sentiment-history-table tbody tr:hover td {
|
||||
background: var(--table-hover);
|
||||
}
|
||||
|
||||
.sentiment-history-table tbody tr.latest-row td {
|
||||
background: var(--table-selected);
|
||||
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.sentiment-history-table tbody tr.latest-row td:first-child {
|
||||
box-shadow: inset 3px 0 0 var(--action);
|
||||
}
|
||||
|
||||
.sentiment-score-cell {
|
||||
font-weight: 750;
|
||||
}
|
||||
@@ -383,9 +369,9 @@
|
||||
}
|
||||
|
||||
.sentiment-phase-badge.phase-fermentation {
|
||||
background: rgb(237, 246, 234);
|
||||
background: var(--up-soft);
|
||||
|
||||
color: rgb(61, 113, 51);
|
||||
color: var(--up);
|
||||
}
|
||||
|
||||
.sentiment-phase-badge.phase-climax {
|
||||
@@ -1640,9 +1626,9 @@
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #sentimentCycleView .sentiment-phase-badge.phase-fermentation {
|
||||
background: var(--market-down-soft);
|
||||
background: var(--market-up-soft);
|
||||
|
||||
color: var(--market-down);
|
||||
color: var(--market-up);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] #sentimentCycleView .sentiment-phase-badge.phase-climax {
|
||||
|
||||
@@ -42,7 +42,7 @@ function renderSentimentHistory() {
|
||||
empty.hidden = rows.length > 0;
|
||||
body.innerHTML = [...rows].reverse().map((row) => {
|
||||
return `
|
||||
<tr class="${row.trade_date === latest?.trade_date ? "latest-row" : ""}">
|
||||
<tr>
|
||||
<td class="sentiment-date-cell">${escapeHtml(displayCompactDate(row.trade_date))}</td>
|
||||
<td class="number sentiment-score-cell ${sentimentScoreClass(row.score)}">${number(row.score)}</td>
|
||||
<td><span class="sentiment-phase-badge ${sentimentPhaseClass(row.phase)}">${escapeHtml(row.phase)}</span></td>
|
||||
|
||||
@@ -1178,14 +1178,14 @@ button.account-role-badge:focus-visible {
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) and (max-width: 1510px) {
|
||||
.account-role-badge span {
|
||||
display: none;
|
||||
.app-header .header-command-group .account-role-badge span {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.account-role-badge {
|
||||
min-width: 27px;
|
||||
.app-header .header-command-group .account-role-badge {
|
||||
min-width: 0;
|
||||
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
/* Canonical CSS owner: cards. Historical layers consolidated 2026-08-02. */
|
||||
|
||||
.stock-name {
|
||||
font-weight: 700;
|
||||
font-size: var(--fs-table);
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.stock-code {
|
||||
color: var(--text-muted);
|
||||
color: var(--text-3);
|
||||
|
||||
font-size: var(--fs-aux);
|
||||
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@@ -49,25 +53,37 @@
|
||||
}
|
||||
|
||||
.streak-pill {
|
||||
background: var(--coral-soft);
|
||||
|
||||
color: var(--coral);
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
display: inline-flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
min-height: 24px;
|
||||
justify-content: center;
|
||||
|
||||
padding: 2px 8px;
|
||||
min-width: 30px;
|
||||
|
||||
border-radius: 4px;
|
||||
height: 20px;
|
||||
|
||||
padding: 0 7px;
|
||||
|
||||
border-radius: 999px;
|
||||
|
||||
background: var(--up-soft);
|
||||
|
||||
color: var(--up);
|
||||
|
||||
font-size: var(--fs-caption);
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.streak-pill.high {
|
||||
background: var(--up);
|
||||
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.outcome-tag {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -253,21 +269,29 @@
|
||||
}
|
||||
|
||||
.sname {
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
|
||||
font-size: 13px;
|
||||
font-size: var(--fs-table);
|
||||
}
|
||||
|
||||
.scode {
|
||||
font-size: 11px;
|
||||
font-size: var(--fs-aux);
|
||||
|
||||
color: var(--faint);
|
||||
color: var(--text-3);
|
||||
|
||||
margin-left: 6px;
|
||||
margin-left: 0;
|
||||
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.stock-cell {
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--faint);
|
||||
}
|
||||
|
||||
@@ -1010,7 +1010,7 @@ textarea {
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.header-menu-button {
|
||||
display: none;
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
.data-table th.sort-asc::after {
|
||||
content: " ↑";
|
||||
|
||||
color: var(--blue);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.data-table th.sort-desc::after {
|
||||
content: " ↓";
|
||||
|
||||
color: var(--blue);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.data-table tbody tr td {
|
||||
@@ -114,9 +114,9 @@
|
||||
|
||||
text-align: left;
|
||||
|
||||
height: 42px;
|
||||
height: 40px;
|
||||
|
||||
padding: 8px 11px;
|
||||
padding: 0 12px;
|
||||
|
||||
border-right: 0px;
|
||||
|
||||
@@ -214,7 +214,9 @@
|
||||
.data-table .stock-name {
|
||||
color: var(--text-primary);
|
||||
|
||||
font-weight: 700;
|
||||
font-size: var(--fs-table);
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.main-grid > .table-frame {
|
||||
@@ -278,9 +280,9 @@
|
||||
}
|
||||
|
||||
.main-grid .data-table thead th {
|
||||
height: 34px;
|
||||
height: 36px;
|
||||
|
||||
padding: 6px 10px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.main-grid .data-table tbody td {
|
||||
@@ -339,8 +341,16 @@
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.tbl thead th.sorted .arr {
|
||||
color: var(--blue);
|
||||
.tbl thead th.sorted,
|
||||
.data-table thead th.sorted,
|
||||
.data-table thead th.sort-asc,
|
||||
.data-table thead th.sort-desc {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tbl thead th.sorted .arr,
|
||||
.data-table thead th.sorted .arr {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tbl tbody td {
|
||||
|
||||
@@ -89,7 +89,10 @@ function renderDashboard() {
|
||||
const { meta, overview, ladders, sectors } = state.dashboard;
|
||||
animateMetric("tapeUp", overview.up_count, (value) => Math.round(value));
|
||||
animateMetric("tapeDown", overview.down_count, (value) => Math.round(value));
|
||||
setText("tapeLimit", `${overview.limit_up_count} / 跌停 ${overview.limit_down_count}`);
|
||||
animateMetric("tapeLimit", overview.limit_up_count, (value) => `${Math.round(value)} 家`);
|
||||
animateMetric("tapeLimitDown", overview.limit_down_count, (value) => `${Math.round(value)} 家`);
|
||||
animateMetric("detailBroken", overview.broken_count, (value) => `${Math.round(value)} 家`);
|
||||
animateMetric("detailSealRate", overview.seal_rate, (value) => `${formatNumber(value, 1)}%`);
|
||||
animateMetric("tapeAmount", overview.amount_billion, (value) => `${formatNumber(value, 1)} 亿`);
|
||||
animateMetric("limitUpMetric", overview.limit_up_count, (value) => `${Math.round(value)} 家`);
|
||||
animateMetric("limitDownMetric", overview.limit_down_count, (value) => `${Math.round(value)} 家`);
|
||||
@@ -98,23 +101,30 @@ function renderDashboard() {
|
||||
animateMetric("amountMetric", overview.amount_billion, (value) => `${formatNumber(value, 1)} 亿`);
|
||||
setText("dataDateMetric", dashboardDataTimestamp(meta));
|
||||
animateMetric("sentimentScore", overview.sentiment_score, (value) => Math.round(value));
|
||||
setText("sentimentText", sentimentLabel(overview.sentiment_score));
|
||||
const sentimentChip = document.querySelector("#sentimentText");
|
||||
if (sentimentChip) {
|
||||
const label = sentimentLabel(overview.sentiment_score);
|
||||
sentimentChip.classList.toggle("is-hot", label === "情绪高涨");
|
||||
sentimentChip.classList.toggle("is-strong", label === "情绪偏强");
|
||||
sentimentChip.classList.toggle("is-cold", label === "情绪冰点" || label === "情绪偏弱");
|
||||
}
|
||||
animateMetric("detailSentimentScore", overview.sentiment_score, (value) => Math.round(value));
|
||||
const moodLabel = sentimentLabel(overview.sentiment_score);
|
||||
setText("sentimentText", moodLabel);
|
||||
setText("detailSentimentText", moodLabel);
|
||||
document.querySelectorAll("#sentimentText, #detailSentimentText").forEach((sentimentChip) => {
|
||||
sentimentChip.classList.toggle("is-hot", moodLabel === "情绪高涨");
|
||||
sentimentChip.classList.toggle("is-strong", moodLabel === "情绪偏强");
|
||||
sentimentChip.classList.toggle("is-cold", moodLabel === "情绪冰点" || moodLabel === "情绪偏弱");
|
||||
});
|
||||
const pageSubtitle = document.querySelector("#currentPageSubtitle");
|
||||
const activePage = window.XiaobaiPages?.get(state.activeView);
|
||||
if (pageSubtitle && activePage?.id !== "mentorView") {
|
||||
if (pageSubtitle) {
|
||||
const dateText = displayCompactDate(meta.trade_date);
|
||||
if (activePage?.id === "mentorView") {
|
||||
pageSubtitle.textContent = dateText === "--"
|
||||
? "与不同交易思维模型持续对话 · 数据日期 --"
|
||||
: `与不同交易思维模型持续对话 · 数据日期 ${dateText}`;
|
||||
} else {
|
||||
const groupLabel = activePage?.group === "market"
|
||||
? "市场复盘"
|
||||
: activePage?.group === "personal" ? "个人" : "智能工具";
|
||||
const dateText = displayCompactDate(meta.trade_date);
|
||||
pageSubtitle.textContent = dateText === "--" ? groupLabel : `${groupLabel} · ${dateText}`;
|
||||
}
|
||||
}
|
||||
updateSentimentGauge(overview.sentiment_score);
|
||||
setText("updatedAt", `${dashboardSourceLabel(meta)} · 更新 ${formatTimestamp(meta.updated_at)}`);
|
||||
|
||||
|
||||
@@ -187,12 +187,6 @@ body.sidebar-collapsed .nav-group + .nav-group {
|
||||
.metric {
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.metric-wide {
|
||||
display: flex;
|
||||
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
@@ -228,10 +222,6 @@ body.sidebar-collapsed .nav-group + .nav-group {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.metric-wide {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -262,10 +252,6 @@ body.sidebar-collapsed .nav-group + .nav-group {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.metric-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.section-title-group {
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -311,10 +297,6 @@ body.sidebar-collapsed .nav-group + .nav-group {
|
||||
.overview-strip {
|
||||
grid-template-columns: minmax(210px, 1.3fr) repeat(5, minmax(90px, 0.7fr));
|
||||
}
|
||||
|
||||
.overview-strip .metric-wide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@@ -644,10 +626,6 @@ body.sidebar-collapsed .app-main {
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.overview-strip .metric-wide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overview-strip .metric:nth-of-type(n+4) {
|
||||
display: none;
|
||||
}
|
||||
@@ -662,14 +640,6 @@ body.sidebar-collapsed .app-main {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.overview-strip[data-overview-expanded="true"] .metric-wide {
|
||||
min-height: 54px;
|
||||
|
||||
display: flex;
|
||||
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.overview-strip[data-overview-expanded="true"] .metric:nth-of-type(n) {
|
||||
min-height: 54px;
|
||||
|
||||
@@ -770,10 +740,6 @@ body.sidebar-collapsed .app-main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.overview-strip .metric-wide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overview-strip .metric:nth-of-type(n+5) {
|
||||
display: none;
|
||||
}
|
||||
@@ -892,7 +858,7 @@ body.sidebar-collapsed .app-main {
|
||||
|
||||
padding: 0px 10px;
|
||||
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
font-size: 12.5px;
|
||||
|
||||
@@ -1452,7 +1418,7 @@ body.sidebar-collapsed .module-nav .nav-group + .nav-group {
|
||||
|
||||
border: 0px;
|
||||
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
background: transparent;
|
||||
|
||||
@@ -2719,10 +2685,6 @@ body.sidebar-collapsed .status-bar {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.overview-strip .metric-wide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overview-strip .metric:nth-of-type(n+4) {
|
||||
display: none;
|
||||
}
|
||||
@@ -3216,11 +3178,6 @@ body.sidebar-collapsed .status-bar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
body.mobile-shell .overview-strip .metric-wide,
|
||||
body.mobile-shell .overview-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.mobile-shell .workspace-view,
|
||||
body.mobile-shell .workspace-view.page:not(#heavenView),
|
||||
body.mobile-shell:is([data-active-view]) .workspace-view.active-view {
|
||||
@@ -3367,7 +3324,8 @@ body.sidebar-collapsed .status-bar {
|
||||
color: var(--down);
|
||||
}
|
||||
|
||||
.tape-metric .metric-value.warning {
|
||||
.tape-metric .metric-value.warning,
|
||||
.tape-detail-item .warning {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
@@ -3380,7 +3338,8 @@ body.sidebar-collapsed .status-bar {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tape-sentiment .sentiment-text {
|
||||
.tape-sentiment .sentiment-text,
|
||||
.tape-detail-item .sentiment-text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 18px;
|
||||
@@ -3394,17 +3353,20 @@ body.sidebar-collapsed .status-bar {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.tape-sentiment .sentiment-text.is-hot {
|
||||
.tape-sentiment .sentiment-text.is-hot,
|
||||
.tape-detail-item .sentiment-text.is-hot {
|
||||
background: var(--up-soft);
|
||||
color: var(--up);
|
||||
}
|
||||
|
||||
.tape-sentiment .sentiment-text.is-strong {
|
||||
.tape-sentiment .sentiment-text.is-strong,
|
||||
.tape-detail-item .sentiment-text.is-strong {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tape-sentiment .sentiment-text.is-cold {
|
||||
.tape-sentiment .sentiment-text.is-cold,
|
||||
.tape-detail-item .sentiment-text.is-cold {
|
||||
background: var(--down-soft);
|
||||
color: var(--down);
|
||||
}
|
||||
@@ -3479,11 +3441,13 @@ body.sidebar-collapsed .status-bar {
|
||||
}
|
||||
|
||||
.app-header .header-actions {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-left: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.app-header .header-date-group {
|
||||
@@ -3675,3 +3639,68 @@ body.sidebar-collapsed .status-bar {
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.app-header .header-menu-button {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.app-header .header-command-group {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 6px;
|
||||
width: 240px;
|
||||
padding: 8px;
|
||||
overflow: visible;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow-float);
|
||||
}
|
||||
|
||||
.app-header .header-command-group.is-open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.app-header .header-command-group .command-button,
|
||||
.app-header .header-command-group .account-button {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.app-header .header-command-group .account-menu-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-header .header-command-group .account-role-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.app-header .header-command-group .account-dropdown {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
body[data-active-view="heavenView"] .app-header .overview-strip,
|
||||
body[data-active-view="heavenView"] .app-header .overview-strip[data-overview-expanded="true"],
|
||||
body.mobile-shell[data-active-view="heavenView"] .app-header .overview-strip,
|
||||
body.mobile-shell[data-active-view="heavenView"] .app-header .overview-strip[data-overview-expanded="true"] {
|
||||
display: none;
|
||||
flex: 0 0 0;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
height: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,11 +56,32 @@
|
||||
updateSidebarControl();
|
||||
}
|
||||
|
||||
function pageGroupLabel(page) {
|
||||
return page?.group === "market"
|
||||
? "市场复盘"
|
||||
: page?.group === "personal" ? "个人" : "智能工具";
|
||||
}
|
||||
|
||||
function writePageSubtitle(viewId) {
|
||||
const pageSubtitle = document.querySelector("#currentPageSubtitle");
|
||||
if (!pageSubtitle) return;
|
||||
const page = registry.get(viewId);
|
||||
const dateText = options.tradeDate?.() || "";
|
||||
const hasDate = Boolean(dateText) && dateText !== "--";
|
||||
if (viewId === "mentorView") {
|
||||
pageSubtitle.textContent = hasDate
|
||||
? `与不同交易思维模型持续对话 · 数据日期 ${dateText}`
|
||||
: "与不同交易思维模型持续对话 · 数据日期 --";
|
||||
return;
|
||||
}
|
||||
const groupLabel = pageGroupLabel(page);
|
||||
pageSubtitle.textContent = hasDate ? `${groupLabel} · ${dateText}` : groupLabel;
|
||||
}
|
||||
|
||||
function syncNavigation(viewId) {
|
||||
const page = registry.get(viewId);
|
||||
const navigationId = page?.navigation_alias || viewId;
|
||||
const marketView = page?.group === "market";
|
||||
const previousActiveView = document.body.dataset.activeView;
|
||||
document.body.dataset.activeView = viewId;
|
||||
const mobileGroup = document.querySelector("#mobilePageGroup");
|
||||
const mobileTitle = document.querySelector("#mobilePageTitle");
|
||||
@@ -72,13 +93,7 @@
|
||||
if (mobileTitle) mobileTitle.textContent = page?.title || "小白复盘";
|
||||
const pageTitle = document.querySelector("#currentPageTitle");
|
||||
if (pageTitle) pageTitle.textContent = page?.title || "小白复盘";
|
||||
const pageSubtitle = document.querySelector("#currentPageSubtitle");
|
||||
if (pageSubtitle && previousActiveView !== viewId) {
|
||||
const groupLabel = page?.group === "market"
|
||||
? "市场复盘"
|
||||
: page?.group === "personal" ? "个人" : "智能工具";
|
||||
pageSubtitle.textContent = groupLabel;
|
||||
}
|
||||
writePageSubtitle(viewId);
|
||||
document.querySelectorAll(".module-tab").forEach((button) => {
|
||||
button.classList.toggle("active", button.dataset.view === navigationId);
|
||||
button.classList.toggle(
|
||||
@@ -216,7 +231,7 @@
|
||||
if (expanded) toggleHeaderCommandMenu(false);
|
||||
});
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!event.target.closest(".header-actions")) toggleHeaderCommandMenu(false);
|
||||
if (!event.target.closest("#headerCommandGroup, #headerMenuButton")) toggleHeaderCommandMenu(false);
|
||||
if (!event.target.closest(".overview-strip")) setOverviewExpanded(false);
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
|
||||
@@ -510,12 +510,21 @@ async function mockApplication(page, authSession = session(), options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
async function openHeaderCommandMenu(page) {
|
||||
const menu = page.locator("#headerCommandGroup");
|
||||
if (await menu.isVisible()) return;
|
||||
await page.locator("#headerMenuButton").click();
|
||||
await expect(menu).toBeVisible();
|
||||
}
|
||||
|
||||
test("admin shell opens every primary workspace and global search", async ({ page }) => {
|
||||
await mockApplication(page);
|
||||
await page.goto("/index.html");
|
||||
await expect(page.locator("#authGate")).toBeHidden();
|
||||
await openHeaderCommandMenu(page);
|
||||
await expect(page.locator("#settingsButton")).toBeVisible();
|
||||
await expect(page.locator("#syncButton")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await page.locator("#alertButton").click();
|
||||
await expect(page.locator("#alertsDialog")).toBeVisible();
|
||||
await page.locator("#closeAlertsDialog").click();
|
||||
@@ -589,6 +598,7 @@ test("manual refresh stays in place without reopening the full-page loader", asy
|
||||
await page.goto("/index.html");
|
||||
await expect(page.locator("#loadingOverlay")).toBeHidden();
|
||||
|
||||
await openHeaderCommandMenu(page);
|
||||
await page.locator("#refreshButton").click();
|
||||
|
||||
await expect(page.locator("#refreshButton")).toBeDisabled();
|
||||
@@ -3134,6 +3144,7 @@ test("global dialogs share the stage 18 geometry without changing account or adm
|
||||
expect(assistantGeometry.contentDisplay).toBe("flex");
|
||||
await page.locator("#closeAssistantDialog").click();
|
||||
|
||||
await openHeaderCommandMenu(page);
|
||||
await page.locator("#accountButton").click();
|
||||
await page.locator('[data-account-panel="membership"]').click();
|
||||
await expect(page.locator("#settingsDialog")).toHaveAttribute("aria-labelledby", "accountDialogTitle");
|
||||
@@ -3144,6 +3155,7 @@ test("global dialogs share the stage 18 geometry without changing account or adm
|
||||
expect(Math.abs(accountBox.y + accountBox.height / 2 - 450)).toBeLessThanOrEqual(2);
|
||||
await page.locator("#closeSettingsDialog").click();
|
||||
|
||||
await openHeaderCommandMenu(page);
|
||||
await page.locator("#settingsButton").click();
|
||||
await expect(page.locator("#adminDialog")).toHaveAttribute("aria-labelledby", "adminDialogTitle");
|
||||
await expect(page.locator("#adminSectionSelect")).toBeVisible();
|
||||
@@ -3163,3 +3175,63 @@ test("global dialogs share the stage 18 geometry without changing account or adm
|
||||
expect(mobileGeometry.width).toBeLessThanOrEqual(375);
|
||||
expect(mobileGeometry.documentOverflow).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("merged header keeps date, detail fields and 13 pool columns after dual-review rework", async ({ page }) => {
|
||||
await mockApplication(page, session("admin", true));
|
||||
await page.goto("/index.html");
|
||||
await page.waitForTimeout(400);
|
||||
await expect(page.locator("#currentPageSubtitle")).toContainText("市场复盘 ·");
|
||||
await expect(page.locator("#headerMenuButton")).toBeVisible();
|
||||
await expect(page.locator("#headerCommandGroup")).toBeHidden();
|
||||
await expect(page.locator("#themeToggle")).toBeVisible();
|
||||
await expect(page.locator("#refreshButton")).toBeHidden();
|
||||
|
||||
await page.locator('[data-view="limitPool"]').first().click();
|
||||
await expect(page.locator("#currentPageSubtitle")).toContainText("市场复盘 ·");
|
||||
await expect(page.locator("#currentPageSubtitle")).not.toHaveText("市场复盘");
|
||||
|
||||
await page.setViewportSize({ width: 1280, height: 800 });
|
||||
await expect(page.locator(".tape-optional").first()).toBeHidden();
|
||||
await page.locator("#overviewToggle").click();
|
||||
const detailText = await page.locator(".tape-detail").innerText();
|
||||
for (const label of ["市场情绪", "上涨家数", "下跌家数", "涨停", "跌停", "炸板", "封板率", "两市成交", "数据日期"]) {
|
||||
expect(detailText).toContain(label);
|
||||
}
|
||||
expect(detailText).not.toContain("涨停 / 跌停");
|
||||
await expect(page.locator("#tapeLimitDown")).toBeVisible();
|
||||
await expect(page.locator("#detailBroken")).toBeVisible();
|
||||
await expect(page.locator("#dataDateMetric")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.setViewportSize({ width: 1600, height: 1000 });
|
||||
await page.locator('[data-view="limitPool"]').first().click();
|
||||
const tableFit = await page.evaluate(() => {
|
||||
const card = document.querySelector("#limitPool .pool-table-card");
|
||||
const reason = document.querySelector("#limitTable .reason-column");
|
||||
const header = document.querySelector(".app-header");
|
||||
return {
|
||||
cardOverflow: card.scrollWidth - card.clientWidth,
|
||||
reasonVisible: reason.getBoundingClientRect().right <= card.getBoundingClientRect().right + 1,
|
||||
reasonText: reason.textContent.trim(),
|
||||
headerOverflow: header.scrollWidth - header.clientWidth,
|
||||
};
|
||||
});
|
||||
expect(tableFit.reasonText).toContain("涨停原因");
|
||||
expect(tableFit.reasonVisible).toBe(true);
|
||||
expect(tableFit.cardOverflow).toBeLessThanOrEqual(1);
|
||||
expect(tableFit.headerOverflow).toBeLessThanOrEqual(1);
|
||||
|
||||
await page.locator('[data-view="heavenView"]').first().click();
|
||||
await expect(page.locator(".app-header .overview-strip")).toBeHidden();
|
||||
await expect(page.locator("#overviewToggle")).toBeHidden();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.locator('[data-view="limitPool"]').first().click({ force: true });
|
||||
await page.locator("#mobileMarketViewSelect").selectOption("sentimentCycleView");
|
||||
await page.locator("#overviewToggle").click();
|
||||
const mobileDetail = await page.locator(".tape-detail").innerText();
|
||||
for (const label of ["市场情绪", "上涨家数", "下跌家数", "涨停", "跌停", "炸板", "封板率", "两市成交", "数据日期"]) {
|
||||
expect(mobileDetail).toContain(label);
|
||||
}
|
||||
await expect(page.locator("#dataDateMetric")).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user