refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent cc5fb8d73e
commit e1e76cd51e
324 changed files with 63090 additions and 44743 deletions
File diff suppressed because it is too large Load Diff
+235
View File
@@ -0,0 +1,235 @@
<section id="limitPool" class="workspace-view page redesigned-pool-view">
<div class="section-toolbar lad-head redesigned-page-head pool-page-head">
<div class="section-title-group">
<h2>涨停池</h2>
<span id="limitPoolSubtitle" class="section-subtitle">0 只 · 数据日期 --</span>
<span id="resultCount" class="visually-hidden">0 只</span>
</div>
<div class="toolbar-controls">
<div class="segmented seg pool-filter-segments" role="group" aria-label="连板筛选">
<button type="button" class="segment active" data-filter="all">全部 <span id="limitAllCount">0</span></button>
<button type="button" class="segment" data-filter="1">首板 <span id="limitFirstCount">0</span></button>
<button type="button" class="segment" data-filter="2">2板 <span id="limitSecondCount">0</span></button>
<button type="button" class="segment" data-filter="3">3板+ <span id="limitThreePlusCount">0</span></button>
</div>
<label class="search search-field pool-search-field">
<span class="visually-hidden">搜索股票</span>
<i data-lucide="search" aria-hidden="true"></i>
<input id="stockSearch" type="search" placeholder="搜索代码 / 名称 / 板块">
</label>
<button id="exportButton" class="button btn" type="button">导出 CSV</button>
</div>
</div>
<div class="main-grid redesigned-pool-grid">
<div class="table-frame card tbl-wrap redesigned-card pool-table-card">
<table class="data-table tbl" id="limitTable">
<thead>
<tr>
<th class="row-number num" aria-label="序号">序号</th>
<th data-sort="name">股票</th>
<th class="number num sortable" data-sort="streak">连板<span class="arr"></span></th>
<th class="number num sortable" data-sort="change">涨幅(%<span class="arr"></span></th>
<th class="number num sortable" data-sort="price">价格(元)<span class="arr"></span></th>
<th data-sort="sector">所属板块</th>
<th class="number num sortable" data-sort="first_time">首封<span class="arr"></span></th>
<th class="number num sortable" data-sort="last_time">最后封板<span class="arr"></span></th>
<th class="number num sortable" data-sort="open_times">开板(次)<span class="arr"></span></th>
<th class="number num sortable" data-sort="turnover_rate">换手率(%<span class="arr"></span></th>
<th class="number num sortable" data-sort="amount_billion">成交额(亿)<span class="arr"></span></th>
<th class="number num sortable" data-sort="seal_amount_million">封单额(万)<span class="arr"></span></th>
<th class="reason-column">涨停原因</th>
</tr>
</thead>
<tbody id="limitTableBody"></tbody>
</table>
<div id="emptyState" class="empty-state" hidden>没有符合条件的股票</div>
</div>
<aside class="insight-rail pool-insight-rail" aria-label="连板与板块概况">
<section class="rail-section card redesigned-card">
<div class="rail-heading card-h redesigned-card-head">
<h3>连板高度</h3>
<span id="maxHeight">--</span>
</div>
<div id="ladderMini" class="ladder-mini pool-side-list"></div>
</section>
<section class="rail-section card redesigned-card">
<div class="rail-heading card-h redesigned-card-head">
<h3>热点板块</h3>
<button class="text-button" type="button" data-open-view="rotationView">查看轮动</button>
</div>
<div id="sectorMini" class="sector-mini pool-hot-list"></div>
</section>
</aside>
</div>
</section>
<section id="brokenView" class="workspace-view page redesigned-broken-view">
<div class="section-toolbar lad-head redesigned-page-head broken-page-head">
<div class="section-title-group">
<h2>炸板池</h2>
<span class="section-subtitle"><b id="brokenCount">0 只</b><span id="brokenMeta"> · 触及涨停后未能封住 · 数据日期 --</span></span>
</div>
<div class="toolbar-controls">
<label class="pool-search-field broken-search-field">
<i data-lucide="search" aria-hidden="true"></i>
<span class="visually-hidden">搜索炸板股票</span>
<input id="brokenSearch" type="search" placeholder="搜索代码/名称/板块" autocomplete="off">
</label>
<button id="brokenExportButton" class="button broken-export-button" type="button">导出 CSV</button>
</div>
</div>
<div class="table-frame card tbl-wrap phase-table-frame broken-table-card">
<table id="brokenTable" class="data-table tbl">
<thead>
<tr>
<th class="row-number num">序号</th>
<th>股票</th>
<th class="number num sortable" data-broken-sort="change">现价涨幅(%<span class="arr"></span></th>
<th class="number num sortable" data-auto-sort="true">距涨停(%<span class="arr"></span></th>
<th class="number num sortable" data-auto-sort="true">价格(元)<span class="arr"></span></th>
<th>所属板块</th>
<th class="number num sortable" data-auto-sort="true">首次触板<span class="arr"></span></th>
<th class="number num sortable" data-broken-sort="open_times">开板(次)<span class="arr"></span></th>
<th class="number num sortable" data-broken-sort="turnover_rate">换手率(%<span class="arr"></span></th>
<th class="number num sortable" data-broken-sort="amount_billion">成交额(亿)<span class="arr"></span></th>
<th class="reason-column">炸板原因</th>
</tr>
</thead>
<tbody id="brokenTableBody"></tbody>
</table>
<div id="brokenEmptyState" class="empty-state" hidden>无匹配标的</div>
</div>
</section>
<section id="downView" class="workspace-view page redesigned-down-view">
<div class="section-toolbar lad-head redesigned-page-head down-page-head">
<div class="section-title-group">
<h2>跌停板</h2>
<span class="section-subtitle"><b id="downCount">0 只</b><span id="downMeta"> · 观察退潮、高位风险与亏钱效应 · 数据日期 --</span></span>
</div>
<div class="toolbar-controls">
<span id="downSectorCluster" class="down-sector-cluster" hidden></span>
<label class="pool-search-field down-search-field">
<i data-lucide="search" aria-hidden="true"></i>
<span class="visually-hidden">搜索跌停股票</span>
<input id="downSearch" type="search" placeholder="搜索代码/名称/板块" autocomplete="off">
</label>
<button id="downExportButton" class="button down-export-button" type="button">导出 CSV</button>
</div>
</div>
<div class="table-frame card tbl-wrap down-table-card">
<table id="downTable" class="data-table tbl">
<thead>
<tr>
<th class="row-number num">序号</th>
<th>股票</th>
<th class="number num sortable" data-down-sort="change">跌幅(%<span class="arr"></span></th>
<th class="number num sortable" data-auto-sort="true">价格(元)<span class="arr"></span></th>
<th>所属板块</th>
<th class="number num sortable" data-down-sort="turnover_rate">换手率(%<span class="arr"></span></th>
<th class="number num sortable" data-down-sort="amount_billion">成交额(亿)<span class="arr"></span></th>
<th class="number num sortable" data-auto-sort="true">连续跌停(天)<span class="arr"></span></th>
<th class="reason-column">风险线索</th>
</tr>
</thead>
<tbody id="downTableBody"></tbody>
</table>
<div id="downEmptyState" class="empty-state" hidden>无匹配标的</div>
</div>
</section>
<section id="yesterdayView" class="workspace-view page redesigned-yesterday-view">
<div class="section-toolbar lad-head redesigned-page-head yesterday-page-head">
<div class="section-title-group">
<h2>昨日涨停表现</h2>
<span class="section-subtitle"><b id="yesterdayCount">0 只</b><span id="yesterdayMeta"> · 昨日 -- → 今日 --</span></span>
</div>
<div class="toolbar-controls">
<label class="pool-search-field yesterday-search-field">
<i data-lucide="search" aria-hidden="true"></i>
<span class="visually-hidden">搜索昨日涨停股票</span>
<input id="yesterdaySearch" type="search" placeholder="搜索代码/名称/板块" autocomplete="off">
</label>
<button id="yesterdayExportButton" class="button yesterday-export-button" type="button">导出 CSV</button>
</div>
</div>
<section class="yesterday-table-card">
<div id="yesterdayResultSummary" class="yesterday-result-summary" role="group" aria-label="昨日涨停结果筛选">
<button class="yesterday-summary-cell active" type="button" data-yesterday-filter="all" aria-pressed="true">
<span class="summary-label">全部</span><strong><b id="yesterdayAllCount">0</b> <small></small></strong><em>点击各格可筛选</em>
</button>
<button class="yesterday-summary-cell" type="button" data-yesterday-filter="advance" aria-pressed="false">
<span class="summary-label"><i class="yesterday-outcome-tag advance">晋级</i></span><strong class="up"><b id="yesterdayAdvanceCount">0</b> <small></small></strong><em id="yesterdayAdvanceRate">晋级率 0.0%</em>
</button>
<button class="yesterday-summary-cell" type="button" data-yesterday-filter="positive" aria-pressed="false">
<span class="summary-label"><i class="yesterday-outcome-tag positive">红盘</i></span><strong class="up"><b id="yesterdayPositiveCount">0</b> <small></small></strong><em id="yesterdayPositiveRate">兑现率 0.0%</em>
</button>
<button class="yesterday-summary-cell" type="button" data-yesterday-filter="fail" aria-pressed="false">
<span class="summary-label"><i class="yesterday-outcome-tag fail">断板</i></span><strong><b id="yesterdayFailCount">0</b> <small></small></strong><em id="yesterdayFailRate">占 0.0%</em>
</button>
<button class="yesterday-summary-cell" type="button" data-yesterday-filter="risk" aria-pressed="false">
<span class="summary-label"><i class="yesterday-outcome-tag broken">炸板</i><span>+</span><i class="yesterday-outcome-tag down">跌停</i></span><strong class="down"><b id="yesterdayRiskCount">0</b> <small></small></strong><em id="yesterdayRiskRate">亏钱效应 0.0%</em>
</button>
</div>
<div class="table-frame card tbl-wrap yesterday-table-scroll">
<table id="yesterdayTable" class="data-table tbl">
<thead>
<tr>
<th class="row-number num">序号</th>
<th>股票</th>
<th class="number num sortable" data-yesterday-sort="prior_streak">昨日高度(板)<span class="arr"></span></th>
<th class="number num sortable" data-yesterday-sort="current_change">今日涨幅(%<span class="arr"></span></th>
<th>今日结果</th>
<th class="number num sortable" data-auto-sort="true">当前高度(板)<span class="arr"></span></th>
<th>所属板块</th>
<th class="reason-column">涨停逻辑</th>
</tr>
</thead>
<tbody id="yesterdayTableBody"></tbody>
</table>
<div id="yesterdayEmptyState" class="empty-state" hidden>当前筛选下无标的</div>
</div>
</section>
</section>
<section id="performanceView" class="workspace-view page redesigned-performance-view">
<div class="section-toolbar lad-head redesigned-page-head performance-page-head">
<div class="section-title-group">
<h2>涨停表现</h2>
<span class="section-subtitle">昨日梯队今日晋级率 + 市场宽度 · <b id="performanceDateRange">--</b></span>
</div>
</div>
<div id="performanceCards" class="performance-cards perf-cards"></div>
<div class="performance-insight-grid">
<section class="performance-panel-card market-breadth-panel card" aria-labelledby="marketBreadthTitle">
<div class="performance-panel-head">
<h3 id="marketBreadthTitle">市场宽度</h3>
<span id="breadthDataTime" class="performance-date-tag">--</span>
</div>
<div class="performance-width-box">
<div class="performance-width-summary">
<span>上涨 <b id="breadthUpCount" class="up">--</b></span>
<span>红盘 <b id="breadthRatio" class="up">--</b></span>
<span>下跌 <b id="breadthDownCount" class="down">--</b></span>
</div>
<div class="performance-width-bar" aria-label="上涨、平盘与下跌家数分布">
<i id="breadthUpBar" class="breadth-up"></i><i id="breadthFlatBar" class="breadth-flat"></i><i id="breadthDownBar" class="breadth-down"></i>
</div>
<div class="performance-width-legend">
<span><i class="up-swatch"></i>上涨 <b id="breadthUpLegend">--</b></span>
<span id="breadthFlatLegendItem"><i class="flat-swatch"></i>平盘 <b id="breadthFlatLegend">--</b></span>
<span><i class="down-swatch"></i>下跌 <b id="breadthDownLegend">--</b></span>
<span id="breadthWarning" class="performance-width-warning">--</span>
</div>
</div>
</section>
<section class="performance-panel-card performance-conclusion-card card" aria-labelledby="performanceConclusionTitle">
<div class="performance-panel-head">
<h3 id="performanceConclusionTitle">今日结论</h3>
<span class="performance-date-tag">自动生成</span>
</div>
<div id="performanceConclusion" class="performance-conclusion"><div class="empty-state">暂无可用结论</div></div>
</section>
</div>
</section>
+91 -3
View File
@@ -4,9 +4,8 @@ window.XiaobaiPageModules.register("pools", [
"downView",
"yesterdayView",
"performanceView",
]);
], { bind: bindPoolEvents });
/* PRESERVATION-SOURCE-BEGIN app.js:1517-1915 */
function getVisibleStocks() {
if (!state.dashboard) return [];
let rows = [...(state.dashboard.limits || [])];
@@ -406,4 +405,93 @@ function renderPerformanceConclusion(rows) {
`;
}
/* PRESERVATION-SOURCE-END app.js:1517-1915 */
function changeSort(key) {
if (state.sortKey === key) state.sortDirection = state.sortDirection === "asc" ? "desc" : "asc";
else {
state.sortKey = key;
state.sortDirection = ["name", "code", "sector", "first_time", "last_time"].includes(key) ? "asc" : "desc";
}
renderLimitTable();
}
function compareRows(left, right) {
const leftValue = left[state.sortKey] ?? "";
const rightValue = right[state.sortKey] ?? "";
let result = typeof leftValue === "number" || typeof rightValue === "number"
? number(leftValue) - number(rightValue)
: String(leftValue).localeCompare(String(rightValue), "zh-CN", { numeric: true });
if (result === 0 && state.sortKey !== "first_time") result = String(left.first_time || "").localeCompare(String(right.first_time || ""));
return state.sortDirection === "asc" ? result : -result;
}
function updateSortHeaders() {
document.querySelectorAll("#limitTable th[data-sort]").forEach((header) => {
header.classList.remove("sort-asc", "sort-desc", "sorted");
const active = header.dataset.sort === state.sortKey;
if (active) header.classList.add(state.sortDirection === "asc" ? "sort-asc" : "sort-desc", "sorted");
const arrow = header.querySelector(".arr");
if (arrow) arrow.textContent = active ? (state.sortDirection === "asc" ? "▲" : "▼") : "↕";
});
}
function bindPoolEvents() {
document.querySelector("#stockSearch").addEventListener("input", (event) => {
state.query = event.target.value.trim().toLowerCase();
renderLimitTable();
});
document.querySelectorAll("[data-table-search]").forEach((input) => {
input.addEventListener("input", () => {
const query = input.value.trim().toLowerCase();
const body = document.querySelector(`#${CSS.escape(input.dataset.tableSearch)}`);
body?.querySelectorAll("tr").forEach((row) => {
row.hidden = Boolean(query) && !row.textContent.toLowerCase().includes(query);
});
});
});
document.querySelectorAll("[data-filter]").forEach((button) => {
button.addEventListener("click", () => {
document.querySelectorAll("[data-filter]").forEach((item) => item.classList.remove("active"));
button.classList.add("active");
state.filter = button.dataset.filter;
renderLimitTable();
});
});
document.querySelectorAll("#limitTable th[data-sort]").forEach((header) => {
header.addEventListener("click", () => changeSort(header.dataset.sort));
});
document.querySelector("#brokenSearch").addEventListener("input", (event) => {
state.brokenQuery = event.target.value.trim().toLowerCase();
renderBrokenTable(state.dashboard?.broken || []);
});
document.querySelectorAll("#brokenTable th[data-broken-sort]").forEach((header) => {
header.addEventListener("click", () => changeBrokenSort(header.dataset.brokenSort));
});
document.querySelector("#downSearch").addEventListener("input", (event) => {
state.downQuery = event.target.value.trim().toLowerCase();
renderDownTable(state.dashboard?.down_limits || []);
});
document.querySelectorAll("#downTable th[data-down-sort]").forEach((header) => {
header.addEventListener("click", () => changeDownSort(header.dataset.downSort));
});
document.querySelector("#yesterdaySearch").addEventListener("input", (event) => {
state.yesterdayQuery = event.target.value.trim().toLowerCase();
renderYesterdayTable(state.dashboard?.yesterday_limits || []);
});
document.querySelectorAll("[data-yesterday-filter]").forEach((button) => {
button.addEventListener("click", () => {
state.yesterdayFilter = button.dataset.yesterdayFilter;
renderYesterdayTable(state.dashboard?.yesterday_limits || []);
});
});
document.querySelectorAll("#yesterdayTable th[data-yesterday-sort]").forEach((header) => {
header.addEventListener("click", () => changeYesterdaySort(header.dataset.yesterdaySort));
});
document.querySelector("#exportButton").addEventListener("click", exportStocks);
document.querySelector("#brokenExportButton").addEventListener("click", exportBroken);
document.querySelector("#downExportButton").addEventListener("click", exportDown);
document.querySelector("#yesterdayExportButton").addEventListener("click", exportYesterday);
document.querySelector("#reasonForm").addEventListener("submit", saveReasonOverride);
}