feat(HEL-90): P2b 验收五条整改 + 夜间黑字排查

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
施工员
2026-08-24 20:13:39 +08:00
co-authored by multica-agent
parent 0e6c102ab2
commit 8237a9e6db
4 changed files with 111 additions and 39 deletions
+53 -20
View File
@@ -372,7 +372,7 @@
}
const head = ordered.map(function (col, i) {
const sortable = sortableColumn(col);
const sortable = !(opts && opts.noSort) && sortableColumn(col);
let extraCls = "";
let extraAttrs = "";
let indicator = "";
@@ -813,7 +813,7 @@
}
function sentimentTrendChart(rows) {
const W = 360, H = 240, padL = 8, padR = 30, padT = 12, padB = 22;
const W = 360, H = 168, padL = 8, padR = 30, padT = 10, padB = 20;
const pw = W - padL - padR;
const ph = H - padT - padB;
if (!rows.length) return emptyChart("暂无情绪历史数据");
@@ -927,7 +927,7 @@
return raw.slice().reverse().map(function (r) {
return Object.assign({}, r, { trade_date: displayCompactDate(r.trade_date) });
});
}, {});
}, { noSort: true });
}
function sentMetric(label, valueHtml, tone) {
@@ -951,6 +951,7 @@
function loadLadder() {
const seq = nextSeq();
Object.keys(expandedLadder).forEach(function (level) { delete expandedLadder[level]; });
const date = state.requestedDate;
global.MobileAPI.request("/api/dashboard?trade_date=" + encodeURIComponent(date)).then(function (payload) {
if (seq !== state.seq) return;
@@ -974,6 +975,7 @@
const rateHtml = level > 1 && count ? '<span class="m-ladder-rate">较' + (level - 1) + "板 " + rate + "%</span>" : "";
let stocksHtml = "";
let foldBtn = "";
if (gap) {
stocksHtml = '<div class="m-ladder-gap">' + (level >= maxLevel ? "断层 · " + escapeHtml(label) + "及以上空缺" : "该层暂时空缺") + "</div>";
} else {
@@ -995,15 +997,16 @@
"<small>" + escapeHtml(s.first_time && s.first_time !== "--" ? s.first_time : "时间待校正") + " · " + escapeHtml(amount) + "</small></span>" +
"</button>";
}).join("");
const remaining = !expanded && cap && stocks.length > cap ? stocks.length - cap : 0;
if (remaining) {
stocksHtml += '<button class="m-ladder-more" type="button" data-ladder-expand="' + level + '">展开剩余 ' + remaining + " 只</button>";
if (cap && stocks.length > cap) {
const remaining = stocks.length - cap;
foldBtn = '<button class="m-ladder-fold" type="button" data-ladder-expand="' + level + '">' +
(expanded ? "收起 \u25b4" : "展开剩余 " + remaining + " 只 \u25be") + "</button>";
}
}
return '<section class="m-ladder-tier' + (gap ? " is-gap" : "") + '">' +
'<div class="m-ladder-tier-head"><span class="m-ladder-tier-level" style="color:' + color + '">' + escapeHtml(label) + "</span>" +
'<span class="m-ladder-tier-count">' + count + " 只</span>" + rateHtml + "</div>" +
'<span class="m-ladder-tier-count">' + count + " 只</span>" + rateHtml + foldBtn + "</div>" +
'<div class="m-ladder-stocks">' + stocksHtml + "</div></section>";
}
@@ -1019,7 +1022,6 @@
ladders.forEach(function (g) { groupMap[number(g.level)] = g; });
const yesterday = dash.yesterday_limits || [];
const prevMax = yesterday.reduce(function (m, r) { return Math.max(m, number(r.prior_streak)); }, 0);
const spaceStocks = (groupMap[maxLevel] || {}).stocks || [];
const spaceChange = prevMax && maxLevel < prevMax ? "较昨日 " + prevMax + " 板 ↓ 压缩"
: prevMax && maxLevel > prevMax ? "较昨日 " + prevMax + " 板 ↑ 抬升" : "高度与昨日接近";
const spaceNote = maxLevel >= 5 ? "高位梯队仍有辨识度,重点看承接。"
@@ -1027,7 +1029,6 @@
const apexHtml = '<div class="m-apex">' +
'<div class="m-apex-head"><span>空间板</span><strong>' + (maxLevel ? maxLevel + " 板" : "--") + "</strong><em>" + escapeHtml(spaceChange) + "</em></div>" +
'<p>' + (spaceStocks.length ? spaceStocks.map(function (s) { return "<b>" + escapeHtml(s.name) + "</b>" + escapeHtml(s.sector || "其他") + ""; }).join(" · ") : "暂无空间板") + "</p>" +
"<small>" + escapeHtml(spaceNote) + "</small></div>";
const tiers = [];
@@ -1082,8 +1083,10 @@
const heat = strength >= 90 ? "strong" : strength >= 70 ? "warm" : "mild";
return '<button class="m-rotation-chip ' + heat + '" type="button" data-rotation-sector="' + escapeHtml(sector.name) + '" data-rotation-date="' + escapeHtml(day.trade_date) + '">' +
'<span class="m-rotation-rank rank-' + Math.min(number(sector.rank), 4) + '">' + number(sector.rank) + "</span>" +
'<span class="m-rotation-copy">' +
"<strong>" + escapeHtml(sector.name) + "</strong>" +
"<small>" + number(sector.count) + " 家 · " + formatNumber(number(sector.strength), 0) + "</small>" +
"</span>" +
"</button>";
}).join("");
return '<section class="m-rotation-day' + (day.trade_date === latestDate ? " is-latest" : "") + '">' +
@@ -1586,30 +1589,60 @@
function bindSheetDrag(root) {
const sheet = root.querySelector(".m-sheet");
const backdrop = root.querySelector(".m-sheet-backdrop");
const handle = root.querySelector(".m-sheet-handle");
if (!sheet || !handle) return;
const head = root.querySelector(".m-sheet-head");
if (!sheet) return;
let startY = 0;
let startT = 0;
let dragging = false;
handle.addEventListener("touchstart", function (event) {
function begin(event) {
if (event.target && event.target.closest(".m-sheet-close")) return;
dragging = true;
startY = event.touches[0].clientY;
startY = event.clientY;
startT = Date.now();
sheet.style.transition = "none";
}, { passive: true });
handle.addEventListener("touchmove", function (event) {
if (backdrop) backdrop.style.transition = "none";
if (event.currentTarget && event.currentTarget.setPointerCapture) {
try { event.currentTarget.setPointerCapture(event.pointerId); } catch (e) { /* noop */ }
}
}
function move(event) {
if (!dragging) return;
const dy = event.touches[0].clientY - startY;
if (dy > 0) sheet.style.transform = "translateY(" + dy + "px)";
}, { passive: true });
handle.addEventListener("touchend", function (event) {
const dy = event.clientY - startY;
if (dy > 0) {
sheet.style.transform = "translateY(" + dy + "px)";
if (backdrop) {
const ratio = Math.min(1, dy / sheet.offsetHeight);
backdrop.style.opacity = String(Math.max(0, 1 - ratio));
}
}
}
function end(event) {
if (!dragging) return;
dragging = false;
sheet.style.transition = "";
const dy = event.changedTouches[0].clientY - startY;
if (dy > sheet.offsetHeight / 3) {
if (backdrop) { backdrop.style.opacity = ""; backdrop.style.transition = ""; }
const dy = event.clientY - startY;
const dt = Date.now() - startT;
const velocity = dt > 0 ? dy / dt : 0;
if (dy >= sheet.offsetHeight * 0.25 || (velocity >= 0.5 && dy >= 40)) {
closeSheet();
} else {
sheet.style.transform = "";
}
}
[handle, head].forEach(function (grip) {
if (!grip) return;
grip.style.touchAction = "none";
grip.addEventListener("pointerdown", begin);
grip.addEventListener("pointermove", move);
grip.addEventListener("pointerup", end);
grip.addEventListener("pointercancel", end);
});
}