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:
MS-01-Codex
2026-08-20 02:19:04 +08:00
co-authored by Cursor multica-agent
parent e778c883db
commit bb67085dd4
15 changed files with 334 additions and 158 deletions
+9 -9
View File
@@ -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,
+7 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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>
+5 -5
View File
@@ -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;
}
}
+40 -16
View File
@@ -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);
}
+1 -1
View File
@@ -1010,7 +1010,7 @@ textarea {
@media (min-width: 768px) {
.header-menu-button {
display: none;
display: inline-flex;
}
}
+19 -9
View File
@@ -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 {
+24 -14
View File
@@ -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,22 +101,29 @@ 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") {
const groupLabel = activePage?.group === "market"
? "市场复盘"
: activePage?.group === "personal" ? "个人" : "智能工具";
if (pageSubtitle) {
const dateText = displayCompactDate(meta.trade_date);
pageSubtitle.textContent = dateText === "--" ? groupLabel : `${groupLabel} · ${dateText}`;
if (activePage?.id === "mentorView") {
pageSubtitle.textContent = dateText === "--"
? "与不同交易思维模型持续对话 · 数据日期 --"
: `与不同交易思维模型持续对话 · 数据日期 ${dateText}`;
} else {
const groupLabel = activePage?.group === "market"
? "市场复盘"
: activePage?.group === "personal" ? "个人" : "智能工具";
pageSubtitle.textContent = dateText === "--" ? groupLabel : `${groupLabel} · ${dateText}`;
}
}
updateSentimentGauge(overview.sentiment_score);
setText("updatedAt", `${dashboardSourceLabel(meta)} · 更新 ${formatTimestamp(meta.updated_at)}`);
+79 -50
View File
@@ -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;
}
+24 -9
View File
@@ -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) => {
+72
View File
@@ -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();
});