HEL-144 返工A:四件事(改名/登录端标识/结账设置真保存/提醒回改)+ 迁移升为 7 + 移除废弃 B-44 前端样式测试

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
leefer
2026-08-25 18:41:01 +08:00
co-authored by multica-agent
parent 5816e8aa71
commit ece3e53472
17 changed files with 1142 additions and 1050 deletions
+211 -108
View File
@@ -316,7 +316,7 @@ function initEntry() {
function updateRole() {
const role = $('input[name="role"]:checked', form).value;
action.textContent = role === "admin" ? "进入总账管理端" : "进入公司业务端";
action.textContent = role === "admin" ? "进入管理端" : "进入公司业务端";
}
roleInputs.forEach((input) => input.addEventListener("change", updateRole));
@@ -408,7 +408,7 @@ function applyCompanyIdentity(me) {
const nameEl = $(".user-name", row);
if (nameEl) nameEl.textContent = name;
const metaEl = $(".user-meta", row);
if (metaEl) metaEl.textContent = portal === "company" ? (me.company_name || "公司业务端") : "集团总账会计";
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) {
@@ -692,7 +692,7 @@ function renderCompanyManualRecords() {
statusCell.innerHTML = `<span class="pill ${statusMeta.cls}">${statusMeta.label}</span>`;
const action = document.createElement("td");
if (record.status === "待总账复核") {
if (record.status === "待管理复核") {
action.innerHTML = '<button type="button" class="btn btn-sm btn-danger" data-action="withdraw">撤回</button>';
} else {
action.innerHTML = '<span class="muted">—</span>';
@@ -706,7 +706,7 @@ function renderCompanyManualRecords() {
function updateManualCounts() {
const records = readStoredRecords(storageKeys.manual).filter((record) => record.company === "A公司");
const pending = records.filter((record) => record.status === "待总账复核").length;
const pending = records.filter((record) => record.status === "待管理复核").length;
const pendingEl = $("#manualPendingStatus");
if (pendingEl) pendingEl.textContent = pending;
const foot = $("#manualFoot");
@@ -808,7 +808,7 @@ function appendAdminReviewRow(record, kind) {
row.dataset.evidence = isAccount
? "公司提交资料、开户行、账号、账户类型与启用日期"
: "公司手工记录、关联银行流水号、证明附件与提交说明";
if (statusLabel !== "待复核" && statusLabel !== "待总账复核") row.dataset.resolved = "true";
if (statusLabel !== "待复核" && statusLabel !== "待管理复核") row.dataset.resolved = "true";
if (isAccount) row.dataset.accountStatus = record.status;
const riskCell = document.createElement("td");
@@ -975,20 +975,11 @@ function fillCompanySelects(names) {
});
setSelectOptions($("#auditCompany"), names, { keepFirst: true });
setSelectOptions($("#flowCompany"), names, { keepFirst: true });
const reminderList = $("#reminderCompanyList");
if (reminderList) {
reminderList.replaceChildren(...names.map((name) => {
const label = document.createElement("label");
label.className = "row";
label.style.cssText = "gap:6px;font-size:13px;";
const input = document.createElement("input");
input.type = "checkbox";
input.name = "company";
input.value = name;
input.style.width = "auto";
label.append(input, document.createTextNode(name));
return label;
}));
const reminderSelect = $("#reminder-company");
if (reminderSelect) {
const kept = reminderSelect.querySelector('option[value=""]') ? [reminderSelect.options[0].cloneNode(true)] : [];
const companies = state.companies || [];
reminderSelect.replaceChildren(...kept, ...companies.map((company) => new Option(company.name, company.id)));
}
setSelectOptions($('#openingDialog [name="from"]'), names);
setSelectOptions($('#openingDialog [name="to"]'), names);
@@ -1227,16 +1218,74 @@ function initAdmin() {
remind?.addEventListener("change", renderSwitch);
renderSwitch();
$("#systemSettings")?.addEventListener("submit", (event) => {
function applySystemSettings(values) {
if (!values) return;
const start = $("#cs-start");
if (start && values.start_date) start.value = values.start_date;
const dayInput = $("#cs-day");
if (dayInput && values.closing_day) dayInput.value = values.closing_day;
const remindInput = $("#cs-remind");
if (remindInput && "auto_remind" in values) {
remindInput.checked = values.auto_remind === "1";
renderSwitch();
}
const daysSelect = $("#cs-remind-days");
if (daysSelect && values.remind_days) daysSelect.value = values.remind_days;
const closingDay = values.closing_day || "5";
const startDate = values.start_date || "2026-01-01";
const dash = $("#dashboardPeriodSub");
if (dash) dash.textContent = `每月 ${closingDay} 日结账 · 全局起算日 ${startDate}`;
const timeline = $("#timelineSub");
if (timeline) timeline.textContent = `全局起算日 ${startDate} 起,每月 ${closingDay} 日结账`;
const flows = $("#flowsRangeMeta");
if (flows) flows.textContent = `数据范围:${startDate} 起算 · 每月 ${closingDay} 日结账(7 月顺延至 08-29`;
const opening = $("#openingSub");
if (opening) opening.textContent = `${startDate} 起算的公司间往来期初数`;
}
async function loadSystemSettings() {
const response = await fetch("/api/admin/settings").catch(() => null);
if (response?.status === 401) {
window.location.href = "index.html";
return;
}
const result = await response?.json().catch(() => ({}));
if (response?.ok && result.settings) applySystemSettings(result.settings);
}
$("#systemSettings")?.addEventListener("submit", async (event) => {
event.preventDefault();
const day = parseInt($("#cs-day")?.value, 10);
const tip = $("#cs-save-tip");
if (day && (day < 1 || day > 28)) {
const day = parseInt($("#cs-day")?.value, 10);
if (!day || day < 1 || day > 28) {
if (tip) { tip.style.display = ""; tip.style.color = "var(--danger)"; tip.textContent = "结账日须为 1-28 之间的整数"; }
return;
}
const payload = {
closing_day: String(day),
start_date: $("#cs-start")?.value || "",
auto_remind: $("#cs-remind")?.checked ? "1" : "0",
remind_days: $("#cs-remind-days")?.value || "3",
};
const response = await fetch("/api/admin/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}).catch(() => null);
if (response?.status === 401) {
window.location.href = "index.html";
return;
}
const result = await response?.json().catch(() => ({}));
if (!response || !response.ok) {
const message = result?.message || "保存失败,请稍后重试";
if (tip) { tip.style.display = ""; tip.style.color = "var(--danger)"; tip.textContent = message; }
showToast("设置保存失败", message, "danger");
return;
}
if (tip) { tip.style.display = ""; tip.style.color = "var(--success)"; tip.textContent = "已保存 · 立即生效"; }
showToast("系统计算口径已保存", "正式系统将记录修改前后值与操作人", "success");
applySystemSettings(result.settings || {});
showToast("系统计算口径已保存", "修改前后值与操作人已留痕", "success");
});
$("#runClosingCheck")?.addEventListener("click", () => {
@@ -1309,111 +1358,164 @@ function initAdmin() {
showToast("期初余额已提交复核", "正式系统将保留录入依据与操作人", "success");
});
$("#reminderForm")?.addEventListener("submit", (event) => {
event.preventDefault();
const form = event.currentTarget;
const checked = Array.prototype.slice.call(form.querySelectorAll('input[name="company"]:checked'));
const type = $("#reminder-type").value;
const content = $("#reminder-content").value.trim();
const deadline = $("#reminder-deadline").value;
$("#company-error").style.display = checked.length ? "none" : "";
$("#content-error").style.display = content ? "none" : "";
if (!checked.length || !content) return;
const companies = checked.map((c) => c.value).join("、");
const summary = content.length > 46 ? content.slice(0, 46) + "…" : content;
const row = document.createElement("tr");
row.dataset.source = "manual";
row.innerHTML =
'<td class="cell-main">' + companies + '</td>' +
'<td><span class="tag">' + type + '</span></td>' +
'<td class="wrap">' + summary.replace(/</g, "&lt;") + '</td>' +
'<td class="meta">刚刚<span class="cell-sub">人工 · 系统管理员</span></td>' +
'<td class="meta">' + (deadline || "—") + '</td>' +
'<td><span class="pill pill-danger">未读</span></td>' +
'<td><div class="row" style="gap:6px;"><button type="button" class="btn btn-sm act-remind">再提醒</button><button type="button" class="btn btn-sm btn-ghost act-history" data-company="' + checked[0].value + '">查看历史</button></div></td>';
$("#reminder-tbody").insertBefore(row, $("#reminder-tbody").firstChild);
refreshReminderCounts();
form.querySelectorAll('input[name="company"]').forEach((c) => { c.checked = false; });
$("#reminder-content").value = "请于截止日期前完成 2026 年 7 月银行流水上传与待确认事项处理。";
$("#reminder-deadline").value = "2026-08-29";
const sendHint = $("#send-hint");
sendHint.style.display = "";
sendHint.textContent = "已发送给 " + companies + ",共 " + checked.length + " 家公司。";
clearTimeout(sendHint._t);
sendHint._t = setTimeout(() => { sendHint.style.display = "none"; }, 4000);
// ── 提醒管理:选公司 → 自动列出待提醒事项 → 一键发送 ──
const reminderSelect = $("#reminder-company");
const reminderItems = $("#reminder-items");
const reminderError = $("#reminder-error");
const companyError = $("#company-error");
const sendBtn = $("#send-btn");
const esc = (text) => String(text ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const fmtReminderTime = (iso) => String(iso || "").replace("T", " ").slice(5, 16);
let pendingReminderItems = [];
function renderPendingItems(items) {
pendingReminderItems = items || [];
if (!reminderItems) return;
if (!pendingReminderItems.length) {
reminderItems.innerHTML = '<div class="empty">该公司当前没有待提醒事项。</div>';
if (sendBtn) sendBtn.disabled = true;
return;
}
reminderItems.replaceChildren(...pendingReminderItems.map((item) => {
const row = document.createElement("div");
row.className = "list-row";
row.innerHTML =
'<span class="pill pill-warn" style="flex:none;">待处理</span>' +
'<div class="lr-main"><div class="lr-title">' + esc(item.kind) + '</div>' +
'<div class="lr-sub">' + esc(item.content) + '</div></div>';
return row;
}));
if (sendBtn) sendBtn.disabled = false;
}
async function loadPendingReminders(companyId) {
if (!companyId) { renderPendingItems([]); return; }
if (reminderItems) reminderItems.innerHTML = '<div class="loading-row">正在列出待提醒事项…</div>';
if (sendBtn) sendBtn.disabled = true;
const response = await fetch("/api/admin/reminders/pending?company_id=" + encodeURIComponent(companyId)).catch(() => null);
if (response?.status === 401) { window.location.href = "index.html"; return; }
const result = await response?.json().catch(() => ({}));
if (!response || !response.ok) { renderPendingItems([]); return; }
renderPendingItems(result.items || []);
}
reminderSelect?.addEventListener("change", () => {
if (reminderError) reminderError.style.display = "none";
loadPendingReminders(reminderSelect.value);
});
// 提醒历史 tabs / 计数
$("#reminderForm")?.addEventListener("submit", async (event) => {
event.preventDefault();
const companyId = reminderSelect?.value;
if (companyError) companyError.style.display = companyId ? "none" : "";
if (!companyId) return;
if (!pendingReminderItems.length) {
if (reminderError) reminderError.style.display = "";
return;
}
if (reminderError) reminderError.style.display = "none";
if (sendBtn) sendBtn.disabled = true;
const response = await fetch("/api/admin/reminders/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ company_id: Number(companyId) }),
}).catch(() => null);
if (response?.status === 401) { window.location.href = "index.html"; return; }
const result = await response?.json().catch(() => ({}));
if (!response || !response.ok) {
if (sendBtn) sendBtn.disabled = false;
showToast("发送失败", result?.message || "请稍后重试", "danger");
return;
}
const sendHint = $("#send-hint");
if (sendHint) {
sendHint.style.display = "";
sendHint.textContent = `已向 ${result.company_name} 发送 ${(result.reminders || []).length} 项提醒,截止 ${result.deadline || "—"}`;
clearTimeout(sendHint._t);
sendHint._t = setTimeout(() => { sendHint.style.display = "none"; }, 4000);
}
await loadReminderHistory();
await loadPendingReminders(companyId);
showToast("提醒已发送", `已生成 ${(result.reminders || []).length} 条提醒记录`, "success");
});
// ── 提醒历史(实时从后端读取) ──
let reminderFilter = "all";
function refreshReminderCounts() {
const rows = $$("#reminder-tbody tr");
let all = rows.length, sys = 0, man = 0;
rows.forEach((r) => { if (r.dataset.source === "system") sys++; else man++; });
$("#count-all").textContent = all;
$("#count-system").textContent = sys;
$("#count-manual").textContent = man;
$("#table-foot-count").textContent = "共 " + all + " 条提醒记录";
const countAll = $("#count-all"), countSys = $("#count-system"), countMan = $("#count-manual");
if (countAll) countAll.textContent = all;
if (countSys) countSys.textContent = sys;
if (countMan) countMan.textContent = man;
const foot = $("#table-foot-count");
if (foot) foot.textContent = "共 " + all + " 条提醒记录";
const state = $("#table-foot-state");
if (state) state.textContent = "未读 " + all + " · 处理中 0 · 已完成 0";
}
function applyReminderFilter() {
$$("#reminder-tbody tr").forEach((r) => {
r.style.display = (reminderFilter === "all" || r.dataset.source === reminderFilter) ? "" : "none";
});
}
async function loadReminderHistory() {
const tbody = $("#reminder-tbody");
if (!tbody) return;
const response = await fetch("/api/admin/reminders").catch(() => null);
if (response?.status === 401) { window.location.href = "index.html"; return; }
const result = await response?.json().catch(() => ({}));
const reminders = result?.reminders || [];
tbody.replaceChildren(...reminders.map((reminder) => {
const row = document.createElement("tr");
row.dataset.source = reminder.source;
row.innerHTML =
'<td class="cell-main">' + esc(reminder.company_name) + '</td>' +
'<td><span class="tag">' + esc(reminder.kind) + '</span></td>' +
'<td class="wrap">' + esc(reminder.content) + '</td>' +
'<td class="meta">' + fmtReminderTime(reminder.created_at) + '<span class="cell-sub">' + (reminder.source === "manual" ? "人工" : "系统") + (reminder.actor_username ? " · " + esc(reminder.actor_username) : "") + '</span></td>' +
'<td class="meta">' + (reminder.deadline || "—") + '</td>' +
'<td><span class="pill pill-danger">未读</span></td>' +
'<td><button type="button" class="btn btn-sm btn-ghost act-history" data-company-id="' + reminder.company_id + '" data-company="' + esc(reminder.company_name) + '">查看历史</button></td>';
return row;
}));
applyReminderFilter();
refreshReminderCounts();
}
$("#reminder-tabs")?.addEventListener("click", (event) => {
const btn = event.target.closest("button[data-filter]");
if (!btn) return;
$("#reminder-tabs").querySelectorAll("button").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
const filter = btn.dataset.filter;
$$("#reminder-tbody tr").forEach((r) => { r.style.display = (filter === "all" || r.dataset.source === filter) ? "" : "none"; });
reminderFilter = btn.dataset.filter;
applyReminderFilter();
});
$("#reminder-tbody")?.addEventListener("click", (event) => {
const remindBtn = event.target.closest(".act-remind");
if (remindBtn) {
const tr = remindBtn.closest("tr");
const timeCell = tr.children[3];
const sub = timeCell.querySelector(".cell-sub");
timeCell.firstChild.textContent = "刚刚";
if (sub) sub.textContent = sub.textContent.replace(/\s*·?\s*第 \d+ 次/, "") + " · 再提醒";
else timeCell.insertAdjacentHTML("beforeend", '<span class="cell-sub">再提醒</span>');
remindBtn.textContent = "已再提醒";
remindBtn.disabled = true;
return;
}
const historyBtn = event.target.closest(".act-history");
if (historyBtn) openReminderHistory(historyBtn.dataset.company);
if (historyBtn) openReminderHistory(historyBtn.dataset.companyId, historyBtn.dataset.company);
});
const reminderHISTORIES = {
"金牛农业": [
{ time: "2026-08-18 09:02", src: "系统 · 第 2 次", type: "流水未提交", status: "danger", statusText: "未读", body: "贵公司 7 月全部银行账户流水尚未提交,距 7 月结账顺延截止日仅剩 11 天,请尽快上传。" },
{ time: "2026-08-11 09:00", src: "系统 · 第 1 次", type: "流水未提交", status: "danger", statusText: "未读", body: "2026 年 7 月银行流水上传提醒:请于截止日前完成全部账户流水提交。" },
],
"金牛新能源": [
{ time: "2026-08-15 09:00", src: "系统", type: "单边待确认", status: "warn", statusText: "处理中", body: "与金牛贸易 3 笔单边流水合计 ¥4,860,000.00 待确认,请选择对方银行流水佐证。" },
],
"金牛置业": [
{ time: "2026-08-14 16:40", src: "人工 · 张维", type: "流水未提交", status: "warn", statusText: "处理中", body: "中行尾号 8821 账户 7 月 6—16 日流水断档,请补传该期间银行回单或对账单。" },
],
"金牛贸易": [
{ time: "2026-08-15 09:00", src: "系统", type: "单边待确认", status: "success", statusText: "已完成", body: "与金牛新能源往来 3 笔单边流水待确认,请核对 7 月销售回款记录。" },
],
"金牛煤业": [
{ time: "2026-08-12 09:00", src: "系统", type: "科目待确认", status: "warn", statusText: "处理中", body: "2 笔手工记录往来科目待确认(应收 / 其他应收),涉及煤炭运费结算。" },
],
"金牛物流": [
{ time: "2026-08-10 11:25", src: "人工 · 张维", type: "科目待确认", status: "success", statusText: "已完成", body: "1 笔运费结算手工记录科目待确认(其他应付待判定),请于结账前完成。" },
],
};
function openReminderHistory(company) {
$("#history-title").textContent = company + " · 提醒历史";
$("#history-sub").textContent = "2026 年 7 月账期以来的全部触达记录,按时间倒序。";
async function openReminderHistory(companyId, companyName) {
$("#history-title").textContent = (companyName || "") + " · 提醒历史";
$("#history-sub").textContent = "该公司的全部提醒触达记录,按时间倒序。";
const list = $("#history-list");
const items = reminderHISTORIES[company] || [];
list.innerHTML = '<div class="loading-row">加载中…</div>';
const response = await fetch("/api/admin/reminders?company_id=" + encodeURIComponent(companyId || "")).catch(() => null);
if (response?.status === 401) { window.location.href = "index.html"; return; }
const result = await response?.json().catch(() => ({}));
const items = result?.reminders || [];
if (!items.length) {
list.innerHTML = '<div class="empty"><div class="e-title">暂无历史记录</div>该公司在 7 月账期内尚未收到过提醒。</div>';
list.innerHTML = '<div class="empty"><div class="e-title">暂无历史记录</div>该公司尚未收到过提醒。</div>';
} else {
list.innerHTML = items.map((it) => {
return '<div class="list-row">' +
'<span class="tag">' + it.type + '</span>' +
'<div class="lr-main"><div class="lr-title">' + it.body + '</div><div class="lr-sub"><span class="meta">' + it.time + ' · ' + it.src + '</span></div></div>' +
'<div class="lr-side"><span class="pill pill-' + it.status + '">' + it.statusText + '</span></div></div>';
'<span class="tag">' + esc(it.kind) + '</span>' +
'<div class="lr-main"><div class="lr-title">' + esc(it.content) + '</div><div class="lr-sub"><span class="meta">' + fmtReminderTime(it.created_at) + ' · ' + (it.source === "manual" ? "人工" : "系统") + (it.actor_username ? " · " + esc(it.actor_username) : "") + '</span></div></div>' +
'<div class="lr-side"><span class="pill pill-danger">未读</span></div></div>';
}).join("");
}
openModal("history-modal");
@@ -1421,7 +1523,8 @@ function initAdmin() {
$("#history-close")?.addEventListener("click", () => closeModal("history-modal"));
$("#history-ok")?.addEventListener("click", () => closeModal("history-modal"));
refreshReminderCounts();
loadSystemSettings();
loadReminderHistory();
}
const FLOW_DEMO = [
@@ -2268,14 +2371,14 @@ function initCompany() {
remark: String(data.get("remark")).trim(),
bankReference: String(data.get("bankReference") || "").trim(),
evidenceName: evidence instanceof File ? evidence.name : "",
status: "待总账复核",
status: "待管理复核",
createdAt: new Date().toLocaleString("zh-CN", { hour12: false }),
};
records.push(record);
if (!writeStoredRecords(storageKeys.manual, records)) return;
renderCompanyManualRecords();
form.reset();
showToast("手工记录已提交", "总账复核前不会纳入公司间往来计算", "success");
showToast("手工记录已提交", "管理员复核前不会纳入公司间往来计算", "success");
});
let manualWithdrawRow = null;