diff --git a/tools/portal.py b/tools/portal.py
index fac6d14..4486431 100644
--- a/tools/portal.py
+++ b/tools/portal.py
@@ -38,6 +38,7 @@ from __future__ import annotations
import argparse
import sys
import uuid
+from datetime import datetime
from pathlib import Path
from typing import Any
@@ -552,6 +553,7 @@ PAGE = r"""
+
@@ -583,8 +585,11 @@ async function jpost(url, body) {
17- 文档明确要求:不使用浏览器原生 alert/prompt 作为正式交互方案,
不能只用控制台日志代替用户提示。这里用 Toast + 模态框替代。 */
+const PAGE_BUILD = "__BUILD__";
+
function toast(text, kind) {
const box = $('toasts');
+ if (!box) { console.error('[portal] #toasts 缺失,退回原生提示'); (kind === 'bad' ? console.error : console.log)(text); return; }
const el = document.createElement('div');
el.className = 'toast ' + (kind || '');
el.textContent = text;
@@ -598,19 +603,29 @@ function bad(text) { toast(text, 'bad'); }
function warn(text) { toast(text, 'warn'); }
function openModal(title, bodyHtml, footHtml) {
+ const back = $('modal-back');
+ if (!back) {
+ // 兜底:容器缺失(多半是浏览器缓存了旧页面)时不要静默失败
+ console.error('[portal] #modal-back 缺失:页面可能是旧版本,请硬刷新(Ctrl+F5)');
+ window.alert(String(title) + '\n\n页面组件缺失,请硬刷新后再试(Ctrl+F5)');
+ return false;
+ }
$('modal-title').textContent = title;
$('modal-body').innerHTML = bodyHtml;
$('modal-foot').innerHTML = footHtml || '';
- $('modal-back').classList.add('on');
+ back.classList.add('on');
+ return true;
}
function closeModal() { $('modal-back').classList.remove('on'); }
/** 替代 confirm:返回 Promise。 */
function askConfirm(title, text, okLabel) {
return new Promise((resolve) => {
- openModal(title, `${esc(text)}
`,
+ const opened = openModal(title, `${esc(text)}
`,
`
`);
+ // 模态打不开时必须**立刻 resolve**,否则 await 永远挂着 —— 表现就是"点了没反应"
+ if (!opened) { resolve(false); return; }
window.__ask = resolve;
});
}
@@ -621,21 +636,23 @@ function askText(title, label, options) {
if (opts.choices) {
return new Promise((resolve) => {
const sel = opts.choices.map((c) => ``).join('');
- openModal(title,
+ const opened = openModal(title,
`
`,
`
`);
+ if (!opened) { resolve(null); return; }
window.__ask = resolve;
});
}
return new Promise((resolve) => {
- openModal(title,
+ const opened = openModal(title,
`
${esc(opts.hint || '')}
`,
`
`);
+ if (!opened) { resolve(null); return; }
window.__ask = resolve;
});
}
@@ -735,6 +752,7 @@ function showApp() {
$('who').textContent = ME.username + '(' + ME.user_id + ')';
$('role').textContent = ME.roles.join(' / ') || '无角色';
$('env').textContent = (ME.environment?.mode || '') + ' · ' + (ME.environment?.mysql || ME.environment?.target || '');
+ if ($('build')) $('build').textContent = 'build ' + PAGE_BUILD;
VIEW = ME.view;
const tabs = [{ id: VIEW, label: ME.view_label }];
// 多角色时允许手动切到其他已具备的界面,便于一次演示
@@ -1848,6 +1866,11 @@ boot();