feat(HEL-566): 去掉主站行情管理入口并修好模型列表收起
主站行情管理并入数据中枢
- 数据中枢数据源配置页新增「主站行情任务」卡:交易时段后台刷新开关、
手动后台刷新、历史区间回补(任务提交 + 轮询主站 job 记录亮灯),
数据与调度仍归主站,控制台只经桥接代操作
- 新增桥接端点 /api/hub-admin/market/{refresh,backfill};回补改为
market.backfill 后台任务(jobs.config.json 注册,锁独立,超时 30 分钟),
桥接调用秒回,不再阻塞
- 主站桌面端删除顶栏「行情管理」按钮与整个行情管理对话框,清理随之
失效的 CSS;移动端删除 system/admin 页与入口,管理员专区只留数据中枢
- Tushare Token / iFinD 凭证编辑沿用数据源页既有凭证区,无功能缺失
模型池收起修复
- 拉出模型清单后按钮切换为「收起列表」,收起只留一行摘要;再次点击
重新拉取并展开;勾选添加完成后清单自动收起(原逻辑保留)
- 交互全部沿用 HEL-558 已确认样式的既有按钮与提示组件,未新增视觉
自测
- verify_baseline 通过;pytest 492 项通过;数据中枢 240 项通过
- verify_datahub_console 新增 [11b] 行情任务桥接端到端段;UI 自测新增
行情任务卡开关往返、模型清单展开/收起/再展开/添加自动收起,日夜主题
与 1030 窄屏复验通过
- Playwright e2e 102/103:唯一失败项在基线提交上同样失败(本机字体度量
导致的头部溢出,与本卡无关)
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -13,10 +13,6 @@ async function initialize() {
|
||||
document.querySelector("#journalDate").max = todayString();
|
||||
document.querySelector("#tradeLogDate").value = elements.tradeDate.value;
|
||||
document.querySelector("#tradeLogDate").max = todayString();
|
||||
document.querySelector("#backfillStart").value = todayString();
|
||||
document.querySelector("#backfillEnd").value = todayString();
|
||||
document.querySelector("#backfillStart").max = todayString();
|
||||
document.querySelector("#backfillEnd").max = todayString();
|
||||
document.querySelector("#qiObservationDate").value = elements.tradeDate.value;
|
||||
document.querySelector("#qiObservationDate").max = todayString();
|
||||
document.querySelector("#accountBirthDate").max = todayString();
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
</div>
|
||||
<button id="refreshButton" class="button command-button" type="button" title="刷新"><i data-lucide="refresh-cw"></i><span>刷新</span></button>
|
||||
<button id="syncButton" class="button primary command-button" type="button" title="后台刷新" hidden><i data-lucide="cloud-download"></i><span>后台刷新</span></button>
|
||||
<button id="marketAdminButton" class="button command-button" type="button" title="行情管理" hidden><i data-lucide="settings-2"></i><span>行情管理</span></button>
|
||||
<button id="settingsButton" class="button command-button" type="button" title="数据中枢" hidden><i data-lucide="database"></i><span>数据中枢</span></button>
|
||||
<div class="account-menu-shell">
|
||||
<div id="accountRoleBadges" class="account-role-badges" aria-label="账号身份">
|
||||
@@ -592,35 +591,6 @@
|
||||
</section>
|
||||
</dialog>
|
||||
|
||||
<dialog id="adminDialog" class="settings-dialog admin-dialog" aria-labelledby="adminDialogTitle">
|
||||
<div class="dialog-header">
|
||||
<div><span class="dialog-eyebrow">管理员</span><h2 id="adminDialogTitle">行情管理</h2></div>
|
||||
<button id="closeAdminDialog" class="icon-button" type="button" aria-label="关闭" title="关闭"><i data-lucide="x"></i></button>
|
||||
</div>
|
||||
<div id="adminConnectionStatus" class="connection-status" aria-live="polite">正在读取系统状态</div>
|
||||
<p class="form-hint">模型池、会员与邀请码已统一在数据中枢管理,点击顶栏「数据中枢」进入。</p>
|
||||
<div class="admin-panel" data-admin-panel="market">
|
||||
<form id="systemMarketForm" class="settings-section">
|
||||
<div class="settings-section-heading"><h3>公共行情</h3><span id="systemDataStatus">待检查</span></div>
|
||||
<label class="form-field"><span>行情来源凭据</span><input id="systemTokenInput" type="text" value="请在数据中枢后台统一管理" disabled></label>
|
||||
<label class="form-field"><span>实时来源凭据</span><input id="systemIfindTokenInput" type="text" value="请在数据中枢后台统一管理" disabled></label>
|
||||
<label class="switch-control"><input id="systemBackgroundRefresh" type="checkbox"><span>启用交易时段后台刷新</span></label>
|
||||
<p class="form-hint">所有用户读取同一份后台快照,页面不会随后台任务自动重绘。</p>
|
||||
<div id="datahubRouteStatus" class="admin-refresh-status" data-tone="idle" role="status" aria-live="polite"><i data-lucide="database"></i><span>数据中枢线路待检查</span></div>
|
||||
<div id="adminRefreshStatus" class="admin-refresh-status" data-tone="idle" role="status" aria-live="polite"><i data-lucide="circle-dot"></i><span>尚未手动刷新</span></div>
|
||||
<div class="dialog-actions admin-inline-actions"><button id="adminRefreshButton" class="button" type="button"><i data-lucide="refresh-cw"></i>立即后台刷新</button><button class="button primary" type="submit">保存刷新设置</button></div>
|
||||
</form>
|
||||
<section class="settings-section">
|
||||
<div class="settings-section-heading"><h3>历史数据回补</h3><span>管理员任务</span></div>
|
||||
<div class="backfill-controls">
|
||||
<label class="form-field"><span>开始日期</span><input id="backfillStart" type="date"></label>
|
||||
<label class="form-field"><span>结束日期</span><input id="backfillEnd" type="date"></label>
|
||||
</div>
|
||||
<div class="dialog-actions"><button id="backfillButton" class="button" type="button">开始回补</button></div>
|
||||
</section>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<div id="loadingOverlay" class="loading-overlay" hidden>
|
||||
<div class="loading-box">
|
||||
<span class="spinner" aria-hidden="true"></span>
|
||||
|
||||
@@ -51,8 +51,7 @@
|
||||
items: [
|
||||
{ key: "system/profile", label: "账号资料", icon: "user" },
|
||||
{ key: "system/password", label: "修改密码", icon: "lock" },
|
||||
{ key: "system/membership", label: "会员状态", icon: "gem" },
|
||||
{ key: "system/admin", label: "行情管理", icon: "sliders-horizontal", adminOnly: true }
|
||||
{ key: "system/membership", label: "会员状态", icon: "gem" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3407,44 +3407,6 @@
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.m-sys-status-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.m-sys-status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
font-size: var(--font-size-body);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.m-sys-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.m-sys-dot--ok {
|
||||
background: var(--market-down);
|
||||
}
|
||||
|
||||
.m-sys-switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.m-sys-switch-row strong {
|
||||
display: block;
|
||||
font-size: var(--font-size-body);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.m-btn-outline,
|
||||
.m-btn-outline-danger {
|
||||
width: 100%;
|
||||
|
||||
+6
-142
@@ -3132,7 +3132,6 @@
|
||||
"system/profile": setupSystemPage,
|
||||
"system/password": setupSystemPage,
|
||||
"system/membership": setupSystemPage,
|
||||
"system/admin": setupSystemPage,
|
||||
};
|
||||
|
||||
const COMPLEX_LOADERS = {
|
||||
@@ -3154,7 +3153,6 @@
|
||||
"system/profile": loadSystem,
|
||||
"system/password": loadSystem,
|
||||
"system/membership": loadSystem,
|
||||
"system/admin": loadSystem,
|
||||
};
|
||||
|
||||
function isComplexPage(key) {
|
||||
@@ -4928,14 +4926,6 @@
|
||||
return String(key || state.key || "").indexOf("system/") === 0;
|
||||
}
|
||||
|
||||
function systemForbiddenHtml() {
|
||||
return '<div class="m-state m-motion-rise-in" data-system-page="forbidden">' +
|
||||
'<span class="m-state-icon">' + icon("lock", 26) + "</span>" +
|
||||
"<p>仅管理员可访问</p>" +
|
||||
"<small>行情管理与数据中枢需要管理员权限。</small>" +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
function systemFill(html) {
|
||||
const scroll = document.getElementById("m-scroll");
|
||||
if (scroll) scroll.innerHTML = html;
|
||||
@@ -4975,26 +4965,17 @@
|
||||
|
||||
function loadSystem() {
|
||||
const key = state.key;
|
||||
if (key === "system/admin" && !global.MobileSession.isAdmin()) {
|
||||
systemFill(systemForbiddenHtml());
|
||||
return;
|
||||
}
|
||||
if (key === "system/password") {
|
||||
renderSystemPassword();
|
||||
return;
|
||||
}
|
||||
const seq = nextSeq();
|
||||
const url = key === "system/admin" ? "/api/admin/settings" : "/api/account/status";
|
||||
const url = "/api/account/status";
|
||||
return global.MobileAPI.request(url).then(function (payload) {
|
||||
if (seq !== state.seq || state.key !== key) return;
|
||||
if (key === "system/admin") {
|
||||
state.system.admin = payload || {};
|
||||
renderSystemAdmin();
|
||||
} else {
|
||||
state.system.account = payload || {};
|
||||
if (key === "system/profile") renderSystemProfile();
|
||||
else renderSystemMembership();
|
||||
}
|
||||
state.system.account = payload || {};
|
||||
if (key === "system/profile") renderSystemProfile();
|
||||
else renderSystemMembership();
|
||||
}).catch(function (error) {
|
||||
if (seq !== state.seq || state.key !== key) return;
|
||||
systemFill(errorHtml(error && error.message ? error.message : "加载失败"));
|
||||
@@ -5078,9 +5059,6 @@
|
||||
{ key: "system/password", icon: "lock", label: "修改密码", hint: "建议定期更换" },
|
||||
{ key: "system/membership", icon: "gem", label: "会员状态", hint: "有效期与智能分析额度" }
|
||||
];
|
||||
const adminRows = [
|
||||
{ key: "system/admin", icon: "sliders-horizontal", label: "行情管理", hint: "数据源状态 · 后台刷新 · 历史回补" }
|
||||
];
|
||||
const html =
|
||||
'<div class="m-sys-home" data-system-page="home">' +
|
||||
'<div class="m-card m-sys-profile-card"><span class="m-sys-avatar">' + escapeHtml(avatar) + "</span><div>" +
|
||||
@@ -5093,7 +5071,7 @@
|
||||
'<h3 class="m-sys-group-title">偏好</h3>' +
|
||||
'<div class="m-card m-sys-list">' + systemThemeRowHtml() + "</div>" +
|
||||
(global.MobileSession.isAdmin()
|
||||
? '<h3 class="m-sys-group-title">管理员专区</h3><div class="m-card m-sys-list">' + adminRows.map(systemRowHtml).join("") + dataHubRowHtml() + "</div>"
|
||||
? '<h3 class="m-sys-group-title">管理员专区</h3><div class="m-card m-sys-list">' + dataHubRowHtml() + "</div>"
|
||||
: "") +
|
||||
'<h3 class="m-sys-group-title">其他</h3>' +
|
||||
'<div class="m-card m-sys-list">' +
|
||||
@@ -5106,7 +5084,7 @@
|
||||
'<span class="m-sys-row-body"><strong>退出登录</strong><small>退出后需要重新登录</small></span>' +
|
||||
'<span class="m-sys-row-chevron">' + icon("chevron-right", 16) + "</span></button>" +
|
||||
"</div>" +
|
||||
'<p class="m-sys-foot">' + (global.MobileSession.isAdmin() ? "小白复盘 · 内网个人版" : "行情管理与数据中枢仅管理员可见") + "</p>" +
|
||||
'<p class="m-sys-foot">' + (global.MobileSession.isAdmin() ? "小白复盘 · 内网个人版" : "数据中枢仅管理员可见") + "</p>" +
|
||||
"</div>";
|
||||
document.getElementById("m-view").innerHTML = html;
|
||||
}
|
||||
@@ -5196,55 +5174,6 @@
|
||||
systemFill(html);
|
||||
}
|
||||
|
||||
function statusDot(ok) {
|
||||
return '<span class="m-sys-dot' + (ok ? " m-sys-dot--ok" : "") + '"></span>';
|
||||
}
|
||||
|
||||
function datahubStatusText(hub) {
|
||||
const enabled = number(hub.enabled_reads);
|
||||
const total = number(hub.total_reads) || enabled;
|
||||
const fallbacks = hub.fallback_labels || [];
|
||||
if (fallbacks.length) return " 备用 " + fallbacks.join("、");
|
||||
if (hub.configured) return " 主线路 " + enabled + "/" + total;
|
||||
return " 未配置";
|
||||
}
|
||||
|
||||
function renderSystemAdmin() {
|
||||
const payload = state.system.admin || {};
|
||||
const data = payload.data || {};
|
||||
const ifind = data.ifind || {};
|
||||
const marketHtml =
|
||||
'<div class="m-sys-body" data-system-admin-panel="market">' +
|
||||
'<div class="m-card m-sys-section"><strong>数据源状态</strong>' +
|
||||
'<div class="m-sys-status-list">' +
|
||||
'<div class="m-sys-status-item"><span>数据中枢</span><span>' + statusDot(data.configured) + (data.configured ? " 已连接" : " 未连接") + "</span></div>" +
|
||||
'<div class="m-sys-status-item"><span>iFinD</span><span>' + statusDot(ifind.configured) + (ifind.configured ? " 已配置" : " 未配置") + "</span></div>" +
|
||||
'<div class="m-sys-status-item"><span>行情快照</span><strong>' + number(data.snapshot_dates) + " 个交易日</strong></div>" +
|
||||
'<div class="m-sys-status-item"><span>后台刷新</span><span>' + statusDot(data.background_refresh_enabled) + (data.background_refresh_enabled ? " 已启用" : " 已暂停") + "</span></div>" +
|
||||
'<div class="m-sys-status-item"><span>数据中枢</span><span>' + statusDot(Boolean((data.datahub || {}).configured) && !((data.datahub || {}).fallback_count)) + datahubStatusText(data.datahub || {}) + "</span></div>" +
|
||||
"</div></div>" +
|
||||
'<div class="m-card m-sys-section"><strong>数据源密钥</strong>' +
|
||||
formFieldHtml("行情来源凭据", '<input id="m-sys-token" type="text" value="请在数据中枢后台统一管理" disabled>', false) +
|
||||
formFieldHtml("实时来源凭据", '<input id="m-sys-ifind" type="text" value="请在数据中枢后台统一管理" disabled>', false) +
|
||||
'<button class="m-btn-primary" type="button" data-system-save-market>刷新状态</button></div>' +
|
||||
'<div class="m-card m-sys-section"><strong>后台刷新</strong>' +
|
||||
'<div class="m-sys-switch-row"><div><strong>交易时段自动刷新</strong><p class="m-sys-hint">开启后后台定时更新快照</p></div>' +
|
||||
'<button class="m-theme-switch" type="button" data-system-toggle-refresh role="switch" aria-checked="' + (data.background_refresh_enabled ? "true" : "false") + '" aria-label="交易时段自动刷新"><span class="m-theme-switch-thumb"></span></button></div>' +
|
||||
'<input id="m-sys-bg-refresh" type="checkbox"' + (data.background_refresh_enabled ? " checked" : "") + ' hidden>' +
|
||||
'<button class="m-btn-outline" type="button" data-system-refresh>立即刷新一次</button>' +
|
||||
'<p class="m-sys-hint">所有用户读取同一份快照,刷新不影响当前页面内容。</p></div>' +
|
||||
'<div class="m-card m-sys-section"><strong>历史数据回补</strong>' +
|
||||
formFieldHtml("开始日期", dateInputHtml("m-sys-backfill-start", ""), false) +
|
||||
formFieldHtml("结束日期", dateInputHtml("m-sys-backfill-end", ""), false) +
|
||||
'<button class="m-btn-outline" type="button" data-system-backfill>开始回补</button>' +
|
||||
'<p class="m-sys-hint">回补用于补齐缺失的历史行情,开始前会再次确认;回补期间页面可正常使用。</p></div>' +
|
||||
'<div class="m-card m-sys-section"><strong>模型池 · 会员 · 邀请码</strong>' +
|
||||
'<p class="m-sys-hint">这些配置已统一在数据中枢管理。</p>' +
|
||||
'<button class="m-btn-outline" type="button" data-system-datahub>打开数据中枢</button></div>' +
|
||||
"</div>";
|
||||
systemFill(marketHtml);
|
||||
}
|
||||
|
||||
function dataHubConsoleUrl() {
|
||||
if (global.XIAOBAI_DATAHUB_URL) return String(global.XIAOBAI_DATAHUB_URL);
|
||||
// HEL-564: 固定内网地址,见 frontend/shared/admin.js 同名函数。
|
||||
@@ -5373,67 +5302,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function saveSystemMarket() {
|
||||
const button = document.querySelector("[data-system-save-market]");
|
||||
if (button) button.disabled = true;
|
||||
loadSystem().then(function () {
|
||||
showToast("数据中枢状态已刷新");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "数据中枢状态读取失败");
|
||||
}).then(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSystemRefresh() {
|
||||
const enabled = !Boolean((state.system.admin && state.system.admin.data && state.system.admin.data.background_refresh_enabled));
|
||||
global.MobileAPI.request("/api/admin/settings", "POST", {
|
||||
background_refresh_enabled: enabled,
|
||||
}).then(function () {
|
||||
showToast(enabled ? "后台刷新已启用" : "后台刷新已暂停");
|
||||
loadSystem();
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "后台刷新设置失败");
|
||||
});
|
||||
}
|
||||
|
||||
function startSystemRefresh() {
|
||||
const button = document.querySelector("[data-system-refresh]");
|
||||
if (button) button.disabled = true;
|
||||
global.MobileAPI.request("/api/admin/refresh", "POST", { trade_date: todayString() }).then(function (payload) {
|
||||
showToast((payload && payload.message) || "后台刷新已提交");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "后台刷新启动失败");
|
||||
}).then(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function startSystemBackfill() {
|
||||
const startDate = (document.getElementById("m-sys-backfill-start") || {}).value;
|
||||
const endDate = (document.getElementById("m-sys-backfill-end") || {}).value;
|
||||
openConfirmSheet("开始历史回补?", "将按选定日期补齐缺失行情,回补期间页面仍可使用。", {
|
||||
confirmLabel: "开始回补",
|
||||
centered: true,
|
||||
onConfirm: function () {
|
||||
const button = document.querySelector("[data-system-backfill]");
|
||||
if (button) button.disabled = true;
|
||||
global.MobileAPI.request("/api/backfill", "POST", {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
}).then(function (payload) {
|
||||
const count = payload && payload.results ? payload.results.length : 0;
|
||||
showToast("历史回补完成,共处理 " + count + " 个工作日");
|
||||
loadSystem();
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "回补失败");
|
||||
}).then(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
window.addEventListener("hashchange", closeSheet);
|
||||
|
||||
@@ -5566,10 +5434,6 @@
|
||||
if (event.target.closest("[data-system-save-password]")) { saveSystemPassword(); return; }
|
||||
if (event.target.closest("[data-system-switch]")) { switchSystemAccount(); return; }
|
||||
if (event.target.closest("[data-system-logout]")) { logoutSystemAccount(); return; }
|
||||
if (event.target.closest("[data-system-save-market]")) { saveSystemMarket(); return; }
|
||||
if (event.target.closest("[data-system-toggle-refresh]")) { toggleSystemRefresh(); return; }
|
||||
if (event.target.closest("[data-system-refresh]")) { startSystemRefresh(); return; }
|
||||
if (event.target.closest("[data-system-backfill]")) { startSystemBackfill(); return; }
|
||||
if (event.target.closest("[data-system-datahub]")) { openDataHubConsole(); return; }
|
||||
|
||||
// 我的复盘:新增/编辑/删除/筛选/提交等操作
|
||||
|
||||
@@ -1,54 +1,3 @@
|
||||
async function backfillData() {
|
||||
const button = document.querySelector("#backfillButton");
|
||||
button.disabled = true;
|
||||
setLoading(true, "正在回补历史交易日");
|
||||
try {
|
||||
const payload = await apiRequest("/api/backfill", "POST", {
|
||||
start_date: document.querySelector("#backfillStart").value,
|
||||
end_date: document.querySelector("#backfillEnd").value,
|
||||
});
|
||||
const failed = (payload.failed_count || 0);
|
||||
const skipped = (payload.skipped_non_trading_days || []).length;
|
||||
const suffix = failed
|
||||
? `,失败 ${failed} 个`
|
||||
: skipped
|
||||
? `,跳过 ${skipped} 个非交易日`
|
||||
: "";
|
||||
showToast(`历史回补完成,共处理 ${payload.results.length} 个交易日${suffix}`);
|
||||
state.sentimentHistory = null;
|
||||
state.sentimentHistoryKey = "";
|
||||
if (state.activeView === "sentimentCycleView") {
|
||||
await loadSentimentHistory(true);
|
||||
}
|
||||
await openAdminSettings(true);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function openAdminSettings(refreshOnly = false) {
|
||||
if (state.user?.role !== "admin") return;
|
||||
if (!refreshOnly) openModalDialog(elements.adminDialog);
|
||||
const status = document.querySelector("#adminConnectionStatus");
|
||||
status.textContent = "正在读取系统状态";
|
||||
try {
|
||||
const payload = await apiRequest("/api/admin/settings");
|
||||
const data = payload.data || {};
|
||||
const ifind = data.ifind || {};
|
||||
status.textContent = `数据中枢 ${data.configured ? "已连接" : "未连接"} · iFinD ${ifind.configured ? "已配置" : "未配置"} · ${number(data.snapshot_dates)} 个交易日`;
|
||||
status.classList.toggle("connected", Boolean(data.configured));
|
||||
setText("systemDataStatus", data.background_refresh_enabled ? "后台刷新已启用" : "后台刷新已暂停");
|
||||
renderDatahubRouteStatus(data.datahub || {});
|
||||
document.querySelector("#systemBackgroundRefresh").checked = Boolean(data.background_refresh_enabled);
|
||||
} catch (error) {
|
||||
status.textContent = error.message || "系统配置读取失败";
|
||||
}
|
||||
}
|
||||
|
||||
function dataHubConsoleUrl() {
|
||||
if (window.XIAOBAI_DATAHUB_URL) return String(window.XIAOBAI_DATAHUB_URL);
|
||||
// HEL-564: 固定内网地址。域名只反代 8765,按主机名推导会拼出打不开的
|
||||
@@ -61,49 +10,9 @@ function openDataHubConsole() {
|
||||
window.open(dataHubConsoleUrl(), "_blank", "noopener");
|
||||
}
|
||||
|
||||
function renderDatahubRouteStatus(hub) {
|
||||
const box = document.querySelector("#datahubRouteStatus");
|
||||
if (!box) return;
|
||||
const label = box.querySelector("span");
|
||||
const enabled = number(hub.enabled_reads);
|
||||
const total = number(hub.total_reads) || enabled;
|
||||
const fallbacks = hub.fallback_labels || [];
|
||||
if (fallbacks.length) {
|
||||
box.dataset.tone = "warning";
|
||||
if (label) label.textContent = `数据中枢主线路 ${enabled}/${total} · 备用 ${fallbacks.length} 类:${fallbacks.join("、")}`;
|
||||
return;
|
||||
}
|
||||
box.dataset.tone = hub.configured ? "success" : "idle";
|
||||
if (label) {
|
||||
label.textContent = hub.configured
|
||||
? `数据中枢主线路 ${enabled}/${total},当前无备用`
|
||||
: "数据中枢未配置,网站只保留已有真实快照";
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMarketSettings(event) {
|
||||
event.preventDefault();
|
||||
const button = event.currentTarget.querySelector("button[type='submit']");
|
||||
button.disabled = true;
|
||||
try {
|
||||
await apiRequest("/api/admin/settings", "POST", {
|
||||
background_refresh_enabled: document.querySelector("#systemBackgroundRefresh").checked,
|
||||
});
|
||||
showToast("行情刷新设置已保存");
|
||||
await openAdminSettings(true);
|
||||
} catch (error) {
|
||||
showToast(error.message || "系统配置保存失败");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* HEL-566: 主站「行情管理」已整体并入数据中枢(数据源配置页的
|
||||
主站行情任务卡:后台刷新开关 / 手动刷新 / 历史回补),主站只保留
|
||||
顶栏「数据中枢」入口。 */
|
||||
function bindAdminEvents() {
|
||||
document.querySelector("#settingsButton").addEventListener("click", openDataHubConsole);
|
||||
document.querySelector("#marketAdminButton").addEventListener("click", () => openAdminSettings());
|
||||
document.querySelector("#closeAdminDialog").addEventListener("click", () => elements.adminDialog.close());
|
||||
document.querySelector("#backfillButton").addEventListener("click", backfillData);
|
||||
document.querySelector("#systemMarketForm").addEventListener("submit", saveMarketSettings);
|
||||
document.querySelector("#adminRefreshButton").addEventListener("click", startAdminRefresh);
|
||||
}
|
||||
|
||||
+2
-109
@@ -121,23 +121,6 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.admin-dialog {
|
||||
width: min(1060px, -24px + 100vw);
|
||||
}
|
||||
|
||||
.admin-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-inline-actions {
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.admin-dialog > form,
|
||||
.settings-section > form {
|
||||
padding: 0px;
|
||||
}
|
||||
@@ -160,22 +143,6 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.switch-control {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 9px;
|
||||
|
||||
margin-top: 13px;
|
||||
|
||||
color: var(--text-secondary);
|
||||
|
||||
font-size: 13px;
|
||||
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
margin: 18px 18px 0px;
|
||||
|
||||
@@ -200,8 +167,7 @@
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.settings-dialog .settings-section > form,
|
||||
.settings-dialog.admin-dialog > form {
|
||||
.settings-dialog .settings-section > form {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@@ -245,22 +211,6 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.backfill-controls {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.admin-inline-actions {
|
||||
align-items: stretch;
|
||||
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
margin: 10px 0px 0px;
|
||||
|
||||
@@ -271,32 +221,6 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.admin-refresh-status {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: var(--surface-muted);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.admin-refresh-status svg {
|
||||
flex: 0 0 auto;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.admin-refresh-status[data-tone="running"] { color: var(--primary); }
|
||||
.admin-refresh-status[data-tone="success"] { color: var(--down); }
|
||||
.admin-refresh-status[data-tone="warning"] { color: var(--warning); }
|
||||
.admin-refresh-status[data-tone="failure"] { color: var(--up); }
|
||||
|
||||
.account-button > span {
|
||||
flex: 0 0 auto;
|
||||
|
||||
@@ -359,8 +283,7 @@
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.settings-dialog .settings-section > form,
|
||||
.settings-dialog.admin-dialog > form {
|
||||
.settings-dialog .settings-section > form {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@@ -368,10 +291,6 @@
|
||||
.settings-dialog .form-hint {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.backfill-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.account-menu-chevron {
|
||||
@@ -1297,32 +1216,6 @@ button.account-role-badge:focus-visible {
|
||||
max-width: 460px;
|
||||
}
|
||||
|
||||
.settings-dialog.admin-dialog {
|
||||
width: min(900px, -28px + 100vw);
|
||||
}
|
||||
|
||||
.admin-dialog .connection-status {
|
||||
margin: 12px 20px 0px;
|
||||
|
||||
padding: 8px 10px;
|
||||
|
||||
border-radius: 7px;
|
||||
|
||||
color: var(--text-secondary);
|
||||
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.admin-dialog .admin-panel {
|
||||
max-width: 860px;
|
||||
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.admin-dialog .settings-section + .settings-section {
|
||||
border-top: 1px solid var(--dialog-line);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.account-settings-dialog .membership-status-grid {
|
||||
grid-template-columns: repeat(2, minmax(0px, 1fr));
|
||||
|
||||
@@ -212,7 +212,6 @@ const elements = {
|
||||
entityDetailDialog: document.querySelector("#entityDetailDialog"),
|
||||
entityDetailChart: document.querySelector("#entityDetailChart"),
|
||||
settingsDialog: document.querySelector("#settingsDialog"),
|
||||
adminDialog: document.querySelector("#adminDialog"),
|
||||
priceChart: document.querySelector("#priceChart"),
|
||||
stockPreview: document.querySelector("#stockPreview"),
|
||||
stockPreviewBackdrop: document.querySelector("#stockPreviewBackdrop"),
|
||||
|
||||
@@ -38,22 +38,18 @@ async function loadDashboard(force = false, background = false, showOverlay = tr
|
||||
}
|
||||
|
||||
async function startAdminRefresh() {
|
||||
const buttons = [document.querySelector("#syncButton"), document.querySelector("#adminRefreshButton")].filter(Boolean);
|
||||
const buttons = [document.querySelector("#syncButton")].filter(Boolean);
|
||||
buttons.forEach((button) => { button.disabled = true; });
|
||||
const requestedDate = elements.tradeDate.value;
|
||||
setAdminRefreshStatus("running", `正在刷新 ${requestedDate} 的行情,请稍候…`, "loader-circle");
|
||||
try {
|
||||
const payload = await apiRequest("/api/admin/refresh", "POST", { trade_date: requestedDate });
|
||||
if (!payload.started || !payload.job_key) {
|
||||
setAdminRefreshStatus("warning", "已有刷新任务正在运行,请稍后再试。", "clock-3");
|
||||
showToast(payload.message || "已有后台刷新任务正在运行");
|
||||
return;
|
||||
}
|
||||
setStatus(`正在刷新 ${requestedDate} 的行情`);
|
||||
const job = await waitForAdminRefresh(payload.job_key);
|
||||
if (job.status === "failed") {
|
||||
const reason = job.message || job.error_code || "数据源未返回结果";
|
||||
setAdminRefreshStatus("failure", `刷新失败:${reason}`, "circle-x");
|
||||
setStatus("后台刷新失败");
|
||||
showToast("后台刷新失败");
|
||||
return;
|
||||
@@ -68,25 +64,20 @@ async function startAdminRefresh() {
|
||||
const updated = formatTimestamp(meta.updated_at);
|
||||
const freshness = dashboardFreshnessMessage(meta);
|
||||
if (meta.realtime && actualCompact === requestedCompact && !meta.carried_forward) {
|
||||
setAdminRefreshStatus("success", `刷新成功:已获取 ${actualDate} 的盘中行情,更新时间 ${updated}`, "circle-check");
|
||||
showToast(`刷新成功:已获取 ${actualDate} 的盘中行情`);
|
||||
return;
|
||||
}
|
||||
if (freshness || actualCompact !== requestedCompact || meta.carried_forward || meta.limit_data_source === "derived") {
|
||||
setAdminRefreshStatus("warning", freshness || `部分正式数据尚未到齐,当前展示 ${actualDate || "最近可用数据"}`, "triangle-alert");
|
||||
setStatus(freshness || "部分正式数据尚未到齐,当前展示最近可用数据");
|
||||
return;
|
||||
}
|
||||
if (meta.notice) {
|
||||
setAdminRefreshStatus("warning", `已刷新到 ${actualDate}(${updated}),但数据源提示:${meta.notice}`, "triangle-alert");
|
||||
showToast(`已刷新到 ${actualDate},请留意数据源提示`);
|
||||
} else {
|
||||
setAdminRefreshStatus("success", `刷新成功:已获取 ${actualDate} 的最新行情,更新时间 ${updated}`, "circle-check");
|
||||
showToast(`刷新成功:已获取 ${actualDate} 的最新行情`);
|
||||
showToast(`刷新成功:已获取 ${actualDate} 的最新行情,更新时间 ${updated}`);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.message || "后台刷新失败";
|
||||
setAdminRefreshStatus("failure", `刷新失败:${message}`, "circle-x");
|
||||
setStatus("后台刷新失败");
|
||||
showToast(message);
|
||||
} finally {
|
||||
@@ -94,14 +85,6 @@ async function startAdminRefresh() {
|
||||
}
|
||||
}
|
||||
|
||||
function setAdminRefreshStatus(tone, message, icon = "circle-dot") {
|
||||
const status = document.querySelector("#adminRefreshStatus");
|
||||
if (!status) return;
|
||||
status.dataset.tone = tone;
|
||||
status.innerHTML = `<i data-lucide="${icon}"></i><span>${escapeHtml(message)}</span>`;
|
||||
refreshIcons();
|
||||
}
|
||||
|
||||
async function waitForAdminRefresh(jobKey) {
|
||||
for (let attempt = 0; attempt < 120; attempt += 1) {
|
||||
const payload = await apiRequest("/api/admin/settings");
|
||||
|
||||
@@ -48,7 +48,6 @@ async function applyAuthenticatedSession(session) {
|
||||
const isAdmin = session.user?.role === "admin";
|
||||
updateAccountIdentityBadges(session.user?.membership || {});
|
||||
document.querySelector("#settingsButton").hidden = !isAdmin;
|
||||
document.querySelector("#marketAdminButton").hidden = !isAdmin;
|
||||
document.querySelector("#syncButton").hidden = !isAdmin;
|
||||
document.querySelector("#reasonForm").hidden = !isAdmin;
|
||||
document.querySelector("#sectorPhaseManager").hidden = !isAdmin;
|
||||
|
||||
Reference in New Issue
Block a user