f8e760961e
Also expand account-risk-cooldown docs with countdown format, API fields, and frontend assets. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
3.5 KiB
JavaScript
110 lines
3.5 KiB
JavaScript
/**
|
|
* 账户风控徽章倒计时 — 四所实例 + 中控共用。
|
|
*/
|
|
(function (global) {
|
|
"use strict";
|
|
|
|
function formatRemaining(totalSec) {
|
|
const sec = Math.max(0, Math.floor(Number(totalSec) || 0));
|
|
if (sec <= 0) return "";
|
|
const h = Math.floor(sec / 3600);
|
|
const m = Math.floor((sec % 3600) / 60);
|
|
const s = sec % 60;
|
|
if (h > 0) return `${h}h ${String(m).padStart(2, "0")}m`;
|
|
if (m > 0) return `${m}m ${String(s).padStart(2, "0")}s`;
|
|
return `${s}s`;
|
|
}
|
|
|
|
function baseLabel(riskStatus, el) {
|
|
if (riskStatus && riskStatus.status_label) return String(riskStatus.status_label);
|
|
if (el && el.dataset && el.dataset.statusLabel) return String(el.dataset.statusLabel);
|
|
return "正常";
|
|
}
|
|
|
|
function badgeText(riskStatus) {
|
|
const label = baseLabel(riskStatus, null);
|
|
const until = Number(riskStatus && riskStatus.freeze_until_ms);
|
|
if (!Number.isFinite(until) || until <= Date.now()) return label;
|
|
const cd = formatRemaining((until - Date.now()) / 1000);
|
|
return cd ? `${label} · ${cd}` : label;
|
|
}
|
|
|
|
function setNormalBadge(el) {
|
|
el.className = "risk-status-badge risk-status-normal";
|
|
el.dataset.statusLabel = "正常";
|
|
el.textContent = "正常";
|
|
el.title = "";
|
|
if (el.dataset) delete el.dataset.freezeUntilMs;
|
|
}
|
|
|
|
function refreshElement(el) {
|
|
if (!el) return;
|
|
const label = baseLabel(null, el);
|
|
const until = Number(el.dataset && el.dataset.freezeUntilMs);
|
|
if (!Number.isFinite(until) || until <= Date.now()) {
|
|
if (el.dataset && el.dataset.freezeUntilMs) {
|
|
setNormalBadge(el);
|
|
} else {
|
|
el.textContent = label;
|
|
}
|
|
return;
|
|
}
|
|
const cd = formatRemaining((until - Date.now()) / 1000);
|
|
el.textContent = cd ? `${label} · ${cd}` : label;
|
|
}
|
|
|
|
function applyToElement(el, riskStatus) {
|
|
if (!el || !riskStatus) return;
|
|
const st = riskStatus.status || "normal";
|
|
el.className = "risk-status-badge risk-status-" + st;
|
|
el.dataset.statusLabel = baseLabel(riskStatus, el);
|
|
if (riskStatus.freeze_until_ms != null && riskStatus.freeze_until_ms !== "") {
|
|
el.dataset.freezeUntilMs = String(riskStatus.freeze_until_ms);
|
|
} else if (el.dataset) {
|
|
delete el.dataset.freezeUntilMs;
|
|
}
|
|
el.textContent = badgeText(riskStatus);
|
|
el.title = riskStatus.reason || "";
|
|
}
|
|
|
|
function formatBadgeHtml(riskStatus, esc) {
|
|
if (!riskStatus || typeof riskStatus !== "object") return "";
|
|
const safe = typeof esc === "function" ? esc : (s) => String(s);
|
|
const st = riskStatus.status || "normal";
|
|
const label = safe(riskStatus.status_label || "正常");
|
|
const title = safe(riskStatus.reason || "");
|
|
const text = safe(badgeText(riskStatus));
|
|
const until = riskStatus.freeze_until_ms;
|
|
const untilAttr =
|
|
until != null && until !== ""
|
|
? ` data-freeze-until-ms="${safe(String(until))}"`
|
|
: "";
|
|
return (
|
|
`<span class="risk-status-badge risk-status-${safe(st)}" role="status"` +
|
|
` title="${title}" data-status-label="${label}"${untilAttr}>${text}</span>`
|
|
);
|
|
}
|
|
|
|
function tickAll(root) {
|
|
const scope = root || document;
|
|
scope.querySelectorAll(".risk-status-badge[data-freeze-until-ms]").forEach(refreshElement);
|
|
}
|
|
|
|
let timer = null;
|
|
function startTicker() {
|
|
if (timer) return;
|
|
tickAll();
|
|
timer = setInterval(() => tickAll(), 1000);
|
|
}
|
|
|
|
global.AccountRiskBadge = {
|
|
formatRemaining,
|
|
badgeText,
|
|
refreshElement,
|
|
applyToElement,
|
|
formatBadgeHtml,
|
|
tickAll,
|
|
startTicker,
|
|
};
|
|
})(typeof window !== "undefined" ? window : globalThis);
|