chore: complete stage 19 regression hardening

This commit is contained in:
leefer
2026-07-28 23:34:10 +08:00
parent 1cc80583b3
commit c30d2107b3
6 changed files with 179 additions and 41 deletions
+22 -14
View File
@@ -204,6 +204,14 @@ const elements = {
stockPreviewChart: document.querySelector("#stockPreviewChart"),
};
function openModalDialog(dialog) {
if (!(dialog instanceof HTMLDialogElement)) return;
document.querySelectorAll("dialog[open]").forEach((openDialog) => {
if (openDialog !== dialog) openDialog.close();
});
if (!dialog.open) dialog.showModal();
}
const metricAnimationFrames = new WeakMap();
const stockPreviewCache = new Map();
const STOCK_PREVIEW_DELAY = 380;
@@ -3076,7 +3084,7 @@ function openWatchlistDialog(item = null) {
document.querySelector("#watchlistSearchInput").value = "";
document.querySelector("#watchlistSearchResults").innerHTML = "";
syncWatchlistSelection(Boolean(item));
if (!elements.watchlistDialog.open) elements.watchlistDialog.showModal();
openModalDialog(elements.watchlistDialog);
requestAnimationFrame(() => (item ? document.querySelector("#watchlistRemark") : document.querySelector("#watchlistSearchInput")).focus());
}
@@ -3240,7 +3248,7 @@ function populateJournalForm() {
function openTradeLogDialog() {
resetTradeLogForm();
if (!elements.tradeLogDialog.open) elements.tradeLogDialog.showModal();
openModalDialog(elements.tradeLogDialog);
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
}
@@ -3311,7 +3319,7 @@ function editTradeLog(id) {
document.querySelector("#tradeLogExecution").value = item.execution || "";
setText("tradeLogDialogTitle", "编辑交易日志");
setText("saveTradeLog", "保存修改");
if (!elements.tradeLogDialog.open) elements.tradeLogDialog.showModal();
openModalDialog(elements.tradeLogDialog);
requestAnimationFrame(() => document.querySelector("#tradeLogCode").focus());
}
@@ -4067,7 +4075,7 @@ function renderScreenerProgress() {
function openStrategyDrawer(target = "editor") {
const drawer = document.querySelector("#strategyDrawer");
if (!drawer.open) drawer.showModal();
openModalDialog(drawer);
requestAnimationFrame(() => {
const focusTarget = target === "library"
? document.querySelector("#strategyList .strategy-item.active") || document.querySelector("#strategyList .strategy-item")
@@ -5889,7 +5897,7 @@ function openHeavenReading(mode, options = {}) {
? Boolean(options.loading)
: false;
renderHeavenReadingDialog();
if (!elements.heavenReadingDialog.open) elements.heavenReadingDialog.showModal();
openModalDialog(elements.heavenReadingDialog);
requestAnimationFrame(() => {
syncHeavenReadingAnimation();
document.querySelector("#closeHeavenReadingDialog").focus();
@@ -5901,7 +5909,7 @@ async function openHeavenHistory(mode) {
state.heavenReadingTab = "history";
state.heavenReadingSelectedId = 0;
renderHeavenReadingDialog();
if (!elements.heavenReadingDialog.open) elements.heavenReadingDialog.showModal();
openModalDialog(elements.heavenReadingDialog);
await loadHeavenReadingHistory(mode);
}
@@ -7570,7 +7578,7 @@ async function loadAlerts(openDialog = false) {
state.alerts = payload.items || [];
state.alertUnreadCount = number(payload.unread_count);
renderAlerts();
if (openDialog && !elements.alertsDialog.open) elements.alertsDialog.showModal();
if (openDialog) openModalDialog(elements.alertsDialog);
} catch (error) {
if (openDialog) showToast(error.message || "提醒加载失败");
}
@@ -7580,7 +7588,7 @@ function openAlerts() {
toggleHeaderCommandMenu(false);
toggleAccountDropdown(false);
document.querySelector("#alertDate").value ||= todayString();
elements.alertsDialog.showModal();
openModalDialog(elements.alertsDialog);
loadAlerts();
}
@@ -7688,7 +7696,7 @@ function renderAlerts() {
async function openReviewAssistant() {
toggleHeaderCommandMenu(false);
toggleAccountDropdown(false);
if (!elements.assistantDialog.open) elements.assistantDialog.showModal();
openModalDialog(elements.assistantDialog);
updateAssistantControls();
if (!hasMemberAccess()) {
document.querySelector("#closeAssistantDialog").focus();
@@ -7841,7 +7849,7 @@ function updateAssistantControls() {
function openGlobalSearch() {
if (!state.user) return;
toggleHeaderCommandMenu(false);
if (!elements.globalSearchDialog.open) elements.globalSearchDialog.showModal();
openModalDialog(elements.globalSearchDialog);
requestAnimationFrame(() => {
elements.globalSearchInput.focus();
elements.globalSearchInput.select();
@@ -7984,7 +7992,7 @@ async function openEntityDetail(item) {
setText("entityDetailDate", "正在加载行情");
document.querySelector("#entityDetailChange").className = "";
document.querySelector("#entityDetailMetrics").innerHTML = '<div class="empty-state">正在加载交易数据</div>';
if (!elements.entityDetailDialog.open) elements.entityDetailDialog.showModal();
openModalDialog(elements.entityDetailDialog);
clearEntityDetailChart("正在加载日 K 数据");
try {
const params = new URLSearchParams({ type: item.type, id: item.id, trade_date: elements.tradeDate.value });
@@ -8209,7 +8217,7 @@ async function openStock(code, fallback = null) {
document.querySelector("#stockNotePlan").value = "";
document.querySelector("#stockNotes").innerHTML = '<div class="empty-state">正在加载笔记</div>';
updateWatchButton();
if (!elements.stockDialog.open) elements.stockDialog.showModal();
openModalDialog(elements.stockDialog);
clearPriceChart("正在加载日 K 数据");
try {
const query = new URLSearchParams({ trade_date: elements.tradeDate.value });
@@ -8508,7 +8516,7 @@ async function openSettings(panel = "profile") {
const status = document.querySelector("#connectionStatus");
status.className = "connection-status";
status.textContent = "正在读取账号状态";
if (!elements.settingsDialog.open) elements.settingsDialog.showModal();
openModalDialog(elements.settingsDialog);
try {
const payload = await apiRequest("/api/account/status");
const access = payload.llm_access || {};
@@ -8587,7 +8595,7 @@ async function switchAccount() {
async function openAdminSettings(refreshOnly = false) {
if (state.user?.role !== "admin") return;
if (!refreshOnly && !elements.adminDialog.open) elements.adminDialog.showModal();
if (!refreshOnly) openModalDialog(elements.adminDialog);
const status = document.querySelector("#adminConnectionStatus");
status.textContent = "正在读取系统状态";
try {
-4
View File
@@ -30,8 +30,6 @@
--right-rail-wide:372px;
--pool-table-max-height:calc(var(--content-height) - var(--topbar-height) - var(--page-pad-y) - var(--page-pad-y) - var(--card-gap));
--sentiment-history-max-height:510px;
--sentiment-chart-compact-height:clamp(180px,20dvh,250px);
--sentiment-chart-compact-canvas-height:calc(var(--sentiment-chart-compact-height) - 18px);
--sentiment-history-min-height:220px;
--primary-share:1.45fr;
--secondary-share:.75fr;
@@ -912,8 +910,6 @@ tbody tr.clickable{cursor:pointer}
}
@media (min-width:721px) and (max-height:1100px){
#sentimentCycleView .sentiment-chart-shell{height:var(--sentiment-chart-compact-height)}
#sentimentCycleView .sentiment-chart-shell canvas{height:var(--sentiment-chart-compact-canvas-height)}
#sentimentCycleView .sentiment-phase-block{gap:12px;padding:10px 12px}
#sentimentCycleView .sentiment-current-phase-badge{padding:8px 12px}
#sentimentCycleView .sentiment-phase-advice{margin-top:4px;padding:4px 8px;line-height:1.4}
+1 -1
View File
@@ -996,7 +996,7 @@ button { cursor: pointer; }
.heart-incense::after { content: "一炷香"; display: block; position: absolute; top: calc(100% + 14px); left: 50%; color: var(--wt-faint); font-family: var(--heaven-serif); font-size: 10px; letter-spacing: .22em; white-space: nowrap; transform: translateX(-50%); }
.heart-incense i { width: 10px; height: 10px; position: absolute; top: 0; left: 50%; margin: -5px 0 0 -5px; border-radius: 50%; background: radial-gradient(circle,#ffd9a0 0,#e08840 45%,transparent 75%); box-shadow: 0 0 14px 4px rgba(255,180,90,.35); transform: none; }
.heart-incense i::after { content: ""; width: 8px; height: 22px; position: absolute; bottom: 5px; left: 50%; border-radius: 50%; background: rgba(216,210,189,.18); filter: blur(4px); opacity: 0; transform: translateX(-50%); }
.heart-incense i.is-burning { animation: heart-incense-glow 1.8s ease-in-out infinite; }
.heart-incense i.is-burning { animation: heart-incense-burn 45s linear 1s forwards,heart-incense-glow 1.8s ease-in-out infinite; }
.heart-incense i.is-burning::after { animation: heart-incense-smoke 2.2s ease-out infinite; }
@keyframes heart-incense-glow { 0%,100% { box-shadow: 0 0 10px 3px rgba(255,180,90,.26); } 50% { box-shadow: 0 0 18px 6px rgba(255,180,90,.5); } }
@keyframes heart-incense-smoke { 0% { opacity: 0; transform: translate(-50%,0) scale(.65); } 28% { opacity: .55; } 100% { opacity: 0; transform: translate(-70%,-28px) scale(1.2); } }