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