diff --git a/backend/routes.py b/backend/routes.py index 6e33122..d9b4821 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -92,6 +92,30 @@ def auth_logout(): return jsonify({"ok": True}) +@bp.route("/api/auth/change-password", methods=["POST"]) +@login_required +def auth_change_password(): + data = request.get_json(force=True) or {} + old_password = data.get("old_password", "") + new_password = data.get("new_password", "") + if not old_password or not new_password: + return jsonify({"error": "旧密码和新密码不能为空"}), 400 + if len(new_password) < 6: + return jsonify({"error": "新密码至少需要6位"}), 400 + user_id = session.get("user_id") + conn = db() + try: + user = one(conn, "SELECT * FROM users WHERE id=?", (user_id,)) + if not user or not check_password_hash(user["password_hash"], old_password): + return jsonify({"error": "旧密码错误"}), 401 + new_hash = generate_password_hash(new_password) + _exec(conn, "UPDATE users SET password_hash=? WHERE id=?", (new_hash, user_id)) + conn.commit() + return jsonify({"ok": True}) + finally: + conn.close() + + @bp.route("/api/auth/me") def auth_me(): if "user_id" not in session: diff --git a/static/modules/admin.js b/static/modules/admin.js index b2dbc32..a7ec86b 100644 --- a/static/modules/admin.js +++ b/static/modules/admin.js @@ -166,3 +166,89 @@ window.deleteUser = async (uid, username) => { toast("删除失败:" + e.message, "error"); } }; + +// 修改密码弹窗 +window.openChangePwdModal = () => { + const overlay = document.createElement("div"); + overlay.id = "changePwdOverlay"; + overlay.className = "fixed inset-0 bg-black/40 z-[9999] flex items-center justify-center p-4"; + overlay.innerHTML = ` +
+
+

修改密码

+ +
+
+ + + +
+ + +
+
+
`; + overlay.addEventListener("click", (e) => { if (e.target === overlay) closeChangePwdModal(); }); + document.body.appendChild(overlay); + if (window.lucide) lucide.createIcons(); +}; + +window.togglePwdEye = (btn) => { + const input = btn.parentElement.querySelector("input"); + const icon = btn.querySelector("i"); + if (input.type === "password") { + input.type = "text"; + icon.setAttribute("data-lucide", "eye"); + } else { + input.type = "password"; + icon.setAttribute("data-lucide", "eye-off"); + } + if (window.lucide) lucide.createIcons({ elements: [icon] }); +}; + +window.closeChangePwdModal = () => { + const el = document.getElementById("changePwdOverlay"); + if (el) el.remove(); +}; + +window.doChangePwd = async (e) => { + e.preventDefault(); + const form = e.target; + const fd = new FormData(form); + const oldPwd = fd.get("old_password"); + const newPwd = fd.get("new_password"); + const confirmPwd = fd.get("confirm_password"); + if (newPwd !== confirmPwd) { toast("两次输入的新密码不一致", "error"); return; } + if (newPwd.length < 6) { toast("新密码至少需要6位", "error"); return; } + try { + const resp = await fetch("/api/auth/change-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ old_password: oldPwd, new_password: newPwd }), + }); + const data = await resp.json(); + if (!resp.ok) { toast(data.error || "修改失败", "error"); return; } + closeChangePwdModal(); + toast("密码修改成功", "success"); + } catch (err) { + toast("请求失败:" + err.message, "error"); + } +}; diff --git a/static/modules/finance.js b/static/modules/finance.js index c2462de..7845112 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -116,7 +116,7 @@ function renderFinance() { const finHeaderBase = `视图:${toolDateSelect}`; const finAddBtn = ``; - const finFilterTabs = `
`; + const finFilterTabs = `
`; document.querySelector("#finance").innerHTML = `
${finFilterTabs} @@ -246,6 +246,7 @@ function renderFinance() { return `
`; } const isUnsigned = state.finFilter === '待签约'; + const filterPfs = state.finFilter === 'all' ? pfs : pfs.filter(x => x.status === state.finFilter); const METRIC_CARDS = [ { label: '签约金额', value: ctx => moneyWan(ctx.signTotal), color: 'text-slate-700' }, { label: '已确收', hideUnsigned: true, value: ctx => moneyWan(ctx.sumRev), color: 'text-blue-700' }, @@ -331,7 +332,7 @@ function renderFinance() { }; if (state.finView === 'monthly') { - const allPfs = pfs.filter(x => x.status === state.finFilter); + const allPfs = filterPfs; const now = new Date(); const defaultMonth = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0"); if (!state.finMonth) state.finMonth = defaultMonth; @@ -358,7 +359,7 @@ function renderFinance() { } if (state.finView === 'quarterly') { - const allPfs = pfs.filter(x => x.status === state.finFilter); + const allPfs = filterPfs; const qRanges = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]; const now = new Date(); if (state.finQuarter === undefined) { @@ -404,7 +405,7 @@ function renderFinance() { expense.forEach(e => { cost += parseFloat(e.cost || 0) || 0; paid += parseFloat(e.paid || 0) || 0; }); return { rev: Math.round(rev), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid), gross: Math.round(gross) }; }; - const allPfs = pfs.filter(x => x.status === state.finFilter); + const allPfs = filterPfs; let sumRev = 0, sumPay = 0, sumCost = 0, sumPaid = 0, sumGross = 0; const rows = []; allPfs.forEach(pf => { diff --git a/static/modules/projects.js b/static/modules/projects.js index 0ecafc6..3905bde 100644 --- a/static/modules/projects.js +++ b/static/modules/projects.js @@ -82,6 +82,9 @@ window.toggleUserMenu = (user) => { ${user.role === 'admin' ? `` : ''} + `; diff --git a/templates/index.html b/templates/index.html index 45c01b8..5568bfe 100644 --- a/templates/index.html +++ b/templates/index.html @@ -76,7 +76,7 @@
-

OPC Manager v1.0.0.14

+

OPC Manager v1.0.0.15

科普 OPC 工作台