HEL-342: 全站切换霜曜日间与黑金夜间主题

按定稿把运行时 token 换成霜曜/黑金双主题,登录与壳层接入官方 Logo 与主题记忆,不改业务接口与金额计算。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
总工
2026-09-01 17:44:35 +08:00
co-authored by Cursor multica-agent
parent 2d0ae4d7d1
commit b905cba52c
15 changed files with 944 additions and 258 deletions
+66 -48
View File
@@ -39,49 +39,20 @@ const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
function animateView(view, { initial = false } = {}) {
if (!view || motionQuery.matches || typeof view.animate !== "function") return;
if (!initial) {
view.getAnimations().forEach((animation) => animation.cancel());
view.animate(
[{ opacity: 0.84, transform: "translateY(5px)" }, { opacity: 1, transform: "translateY(0)" }],
{ duration: 180, easing: "cubic-bezier(.22,1,.36,1)" },
if (!initial) return;
const rows = [...view.querySelectorAll(".ds-table tbody tr")].slice(0, 8);
rows.forEach((row, index) => {
row.getAnimations().forEach((animation) => animation.cancel());
row.animate(
[{ opacity: 0 }, { opacity: 1 }],
{ duration: 300, delay: index * 45, easing: "cubic-bezier(0.23, 1, 0.32, 1)", fill: "both" },
);
return;
}
const selectors = [
".page-head",
".stat-card",
".card",
".notice",
".list-row",
".filters",
];
const elements = [...new Set(selectors.flatMap((selector) => [...view.querySelectorAll(selector)]))];
elements.forEach((element, index) => {
element.getAnimations().forEach((animation) => animation.cancel());
const keyframes = [
{ opacity: 0, transform: `translateY(${initial ? 16 : 10}px)` },
{ opacity: 1, transform: "translateY(0)" },
];
element.animate(keyframes, {
duration: 440,
delay: Math.min(index * 38, 260),
easing: "cubic-bezier(.22,1,.36,1)",
fill: "both",
});
});
}
function initMotion() {
if (motionQuery.matches) return;
animateView($(".app-view.is-active"), { initial: true });
$$(".side-nav a", $("#sidebar") || document).forEach((item, index) => {
item.animate(
[{ opacity: 0, transform: "translateX(-8px)" }, { opacity: 1, transform: "translateX(0)" }],
{ duration: 360, delay: 90 + index * 28, easing: "cubic-bezier(.22,1,.36,1)", fill: "both" },
);
});
}
function recordStatus(status) {
@@ -139,11 +110,23 @@ function showToast(title, detail = "", kind = "info") {
}
toast.append(dot, body);
region.append(toast);
window.setTimeout(() => {
let remaining = 2600;
let started = Date.now();
let timer = window.setTimeout(dismiss, remaining);
function dismiss() {
toast.style.opacity = "0";
toast.style.transition = "opacity 0.2s ease";
window.setTimeout(() => toast.remove(), 200);
}, 4200);
toast.style.transition = "opacity 400ms cubic-bezier(0.32, 0.72, 0, 1), transform 400ms cubic-bezier(0.32, 0.72, 0, 1)";
toast.style.transform = "translateX(12px)";
window.setTimeout(() => toast.remove(), 400);
}
toast.addEventListener("mouseenter", () => {
window.clearTimeout(timer);
remaining -= Date.now() - started;
});
toast.addEventListener("mouseleave", () => {
started = Date.now();
timer = window.setTimeout(dismiss, Math.max(remaining, 0));
});
}
function toastIfLocked(result) {
@@ -188,7 +171,9 @@ function showView(view) {
closeNavigation({ restoreFocus: navigationWasOpen });
const activeView = $(`.app-view[data-page="${view}"]`);
requestAnimationFrame(() => animateView(activeView));
window.scrollTo({ top: 0, behavior: motionQuery.matches ? "auto" : "smooth" });
const shell = $(".main");
if (shell) shell.scrollTop = 0;
else window.scrollTo({ top: 0, behavior: motionQuery.matches ? "auto" : "smooth" });
if (portal === "company" && view === "transfers") {
if (state.transfersKeepDetail && state.transfersDetail?.company_id) {
showTransfersDetailLayer();
@@ -422,15 +407,19 @@ async function initAuthGuard() {
function applyCompanyIdentity(me) {
if (!me) return;
const row = $(".side-foot .user-row");
if (row) {
const foot = $(".side-foot") || $(".side-foot .user-row");
if (foot) {
const name = me.username || (portal === "company" ? (me.company_name || "公司用户") : "系统管理员");
const avatar = $(".avatar", row);
const avatar = $(".avatar", foot);
if (avatar) avatar.textContent = name.slice(0, 1);
const nameEl = $(".user-name", row);
const nameEl = $(".user-name", foot);
if (nameEl) nameEl.textContent = name;
const metaEl = $(".user-meta", row);
if (metaEl) metaEl.textContent = portal === "company" ? (me.company_name || "公司业务端") : "管理员";
const metaEl = $(".user-meta", foot);
if (metaEl) {
metaEl.textContent = portal === "company"
? `${me.company_name || "公司业务端"} · 仅本公司数据`
: "管理员 · 集团全量数据";
}
}
// The company portal always shows the session-bound company in page copy.
if (portal === "company" && me.company_name) {
@@ -1814,6 +1803,7 @@ async function loadAdminRemindersPending() {
if (empty) empty.style.display = "";
if (summary) summary.textContent = "0 家公司 · 0 项";
if (sendAll) { sendAll.disabled = true; sendAll.textContent = "全部一键发送"; }
syncPendingCheckAll();
return;
}
if (empty) empty.style.display = "none";
@@ -1831,7 +1821,7 @@ async function loadAdminRemindersPending() {
row.dataset.companyId = String(item.company_id);
const sentHint = item.send_count > 0 ? ` · 已提醒 ${item.send_count}` : "";
row.innerHTML =
'<label class="row" style="gap:6px;flex:none;"><input type="checkbox" class="pending-check" style="width:auto;" /></label>' +
'<label class="row" style="gap:6px;flex:none;"><input type="checkbox" class="pending-check ds-check" /></label>' +
`<span class="pill ${reminderTypePill(item.rule_key)}">${item.rule_label}</span>` +
`<div class="lr-main"><div class="lr-title">${item.title}</div>` +
`<div class="lr-sub"><span class="meta">${item.reason}${sentHint}</span></div></div>` +
@@ -1839,6 +1829,16 @@ async function loadAdminRemindersPending() {
`<div class="lr-side"><button type="button" class="btn btn-sm btn-primary pending-send-one">发送提醒</button></div>`;
list.append(row);
});
syncPendingCheckAll();
}
function syncPendingCheckAll() {
const master = $("#pending-check-all");
if (!master) return;
const boxes = $$(".pending-check");
const checked = boxes.filter((el) => el.checked).length;
master.checked = boxes.length > 0 && checked === boxes.length;
master.indeterminate = checked > 0 && checked < boxes.length;
}
async function loadAdminRemindersHistory(sourceFilter) {
@@ -1932,6 +1932,15 @@ function initAdminReminders() {
loadAdminRemindersPending();
loadAdminRemindersHistory("all");
$("#pending-check-all")?.addEventListener("change", (event) => {
const on = event.target.checked;
event.target.indeterminate = false;
$$(".pending-check").forEach((box) => { box.checked = on; });
});
document.addEventListener("change", (event) => {
if (event.target.classList?.contains("pending-check")) syncPendingCheckAll();
});
$("#reminder-scan-btn")?.addEventListener("click", async () => {
const response = await fetch("/api/admin/reminders/scan", { method: "POST" }).catch(() => null);
if (response?.status === 401) { window.location.href = "index.html"; return; }
@@ -4174,6 +4183,15 @@ function renderWorkspaceTransfersCard(summary) {
? `${formatWanHtml(pending.amount_total)}<span class="unit"> · ${pCount} 笔</span>`
: `0.00<span class="unit">万元 · 0 笔</span>`;
}
const confirmedSplit = $("#wsTfConfirmedSplit");
const pendingSplit = $("#wsTfPendingSplit");
if (confirmedSplit) confirmedSplit.innerHTML = formatWanHtml(confirmed.net_change, { signed: true });
if (pendingSplit) {
const pCount = Number(pending.count) || 0;
pendingSplit.innerHTML = pCount
? `${formatWanHtml(pending.amount_total)}<span class="unit"> · ${pCount} 笔</span>`
: `0.00<span class="unit">万元 · 0 笔</span>`;
}
}
async function loadTransfersSummary({ asOf } = {}) {