From 34786ba9e5dd9318a267609071e37d9505c5e0c5 Mon Sep 17 00:00:00 2001 From: mac Date: Thu, 2 Jul 2026 17:55:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A7=E5=93=81=E8=BF=AD=E4=BB=A3=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E5=8C=96=20+=20=E8=B4=A2=E5=8A=A1=E6=80=BB=E8=A7=86?= =?UTF-8?q?=E5=9B=BE/=E6=9C=88=E5=BA=A6=E8=A7=86=E5=9B=BE=20+=20=E6=80=BB?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 产品迭代: - 卡片改表格(10列),5个日期内联编辑,后端日期校验 - 表头排序,新增未开始状态,详情页耗时统计 - 删除 owner/platform/feature_list 字段 财务: - 新增总视图和月度视图,去除确收/毛利和回款/应付视图 - 月度流水加已付列,费用改应付 - 月份选择器,表格居中对齐 - 去除流程项目/流程金额卡片 总工作台: - 聚合所有工作台首页数据 - 只显示首页tab,隐藏4个模块卡片 --- backend/flask_app.py | 90 ++++++++++++++++++++++++++++-- backend/migrations/columns.py | 8 +++ static/app.js | 7 ++- static/modules/finance.js | 101 +++++++++++++++++++++++++++------- static/modules/home.js | 4 +- static/modules/utils.js | 14 +++++ 6 files changed, 197 insertions(+), 27 deletions(-) diff --git a/backend/flask_app.py b/backend/flask_app.py index 8637b6f..1fcfc21 100644 --- a/backend/flask_app.py +++ b/backend/flask_app.py @@ -67,7 +67,7 @@ def admin_required(f): return decorated -ALL_TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"] +ALL_TENANTS = ["总工作台", "科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"] @app.route("/login") def login_page(): @@ -90,7 +90,7 @@ def auth_login(): session["role"] = user["role"] # 管理员可看所有工作台,OPC负责人看分配的工作台 if user["role"] == "admin": - session["tenants"] = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"] + session["tenants"] = ["总工作台", "科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"] else: ut = rows(conn, "SELECT tenant FROM user_tenants WHERE user_id=?", (user["id"],)) session["tenants"] = [x["tenant"] for x in ut] @@ -113,10 +113,14 @@ def auth_logout(): def auth_me(): if "user_id" not in session: return jsonify({"logged_in": False}) + tenants = session.get("tenants", []) + # 确保总工作台始终在列表最前 + if "总工作台" not in tenants: + tenants = ["总工作台"] + tenants return jsonify({ "logged_in": True, "user": {"id": session["user_id"], "username": session["username"], "display_name": session["display_name"], "role": session["role"]}, - "tenants": session.get("tenants", []), + "tenants": tenants, }) @@ -748,10 +752,88 @@ def bootstrap(): tenant = request.args.get("tenant", session.get("tenants", ["科普·无界"])[0]) # 验证用户是否有权限访问该 workbench allowed = session.get("tenants", []) + if "总工作台" not in allowed: + allowed = ["总工作台"] + allowed if tenant not in allowed: tenant = allowed[0] conn = db() try: + # 总工作台:聚合所有工作台的首页数据 + if tenant == "总工作台": + real_tenants = [t for t in allowed if t != "总工作台"] + all_metrics = [] + all_monthly = [] + all_recent = [] + for t in real_tenants: + t_pfs = rows(conn, "SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", [t]) + t_ops = attach_common(conn, "operations", rows(conn, "SELECT * FROM operation_projects WHERE tenant=? ORDER BY id ASC", [t])) + t_sales = attach_common(conn, "sales", rows(conn, "SELECT * FROM sales_leads WHERE tenant=? ORDER BY id DESC", [t])) + t_products = attach_common(conn, "products", rows(conn, "SELECT * FROM product_versions WHERE tenant=? ORDER BY id DESC", [t])) + t_proposals = attach_common(conn, "proposals", rows(conn, "SELECT * FROM business_proposals WHERE tenant=? ORDER BY id DESC", [t])) + t_signed_pfs = [x for x in t_pfs if x["status"] == "已签约"] + def t_parse_budget(pf): + try: + budget = json.loads(pf.get("budget_data") or "[]") + except (json.JSONDecodeError, TypeError): + budget = [] + return {(b.get("month") or "").replace("-", "_"): b for b in budget} + t_bm = {pf["id"]: t_parse_budget(pf) for pf in t_pfs} + def t_sum_budget(field, months_range): + total = 0 + for pf in t_pfs: + bm = t_bm.get(pf["id"], {}) + for m in months_range: + b = bm.get(f"2026_{m:02d}") + if b: + total += float(b.get(field) or 0) + return total + _now_month = date.today().month + _q_start = ((_now_month - 1) // 3) * 3 + 1 + _q_range = range(_q_start, _q_start + 3) + _q_months = [f"2026-{m:02d}" for m in _q_range] + all_metrics.append({ + "total_projects": len(t_signed_pfs), + "total_proposals": len(t_ops), + "total_products": len(t_proposals), + "upcoming_products": len(t_products), + "signed_amount": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约"), + "signed_annual": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约"), + "signed_q2": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约" and (x.get("sign_month") or "")[:7] in _q_months), + "signed_month": sum(x["sign_amount"] or 0 for x in t_pfs if x["status"] == "已签约" and (x.get("sign_month") or "")[:7] == f"2026-{_now_month:02d}"), + "revenue_annual": t_sum_budget("rev", range(1, 13)), + "revenue_q2": t_sum_budget("rev", _q_range), + "monthly_revenue": t_sum_budget("rev", [_now_month]), + "gross_annual": t_sum_budget("gross", range(1, 13)), + "gross_q2": t_sum_budget("gross", _q_range), + "monthly_net_profit": t_sum_budget("gross", [_now_month]), + "payment_annual": t_sum_budget("payment", range(1, 13)), + "payment_q2": t_sum_budget("payment", _q_range), + "payment_month": t_sum_budget("payment", [_now_month]), + "cost_annual": t_sum_budget("cost", range(1, 13)), + "cost_q2": t_sum_budget("cost", _q_range), + "cost_month": t_sum_budget("cost", [_now_month]), + }) + all_monthly.append(monthly_finance(conn, t)) + all_recent.extend(rows(conn, "SELECT * FROM follow_up_records WHERE tenant=? ORDER BY id DESC LIMIT 4", [t])) + # 合并 metrics + agg = {} + for key in ["total_projects","total_proposals","total_products","upcoming_products","signed_amount","signed_annual","signed_q2","signed_month","revenue_annual","revenue_q2","monthly_revenue","gross_annual","gross_q2","monthly_net_profit","payment_annual","payment_q2","payment_month","cost_annual","cost_q2","cost_month"]: + agg[key] = sum(m.get(key, 0) for m in all_metrics) + # 合并 monthly finance(按月累加) + merged_monthly = [] + for i in range(12): + m = {"month": all_monthly[0][i]["month"] if all_monthly and len(all_monthly[0]) > i else f"2026-{i+1:02d}"} + for field in ["revenue","gross","payment","cost","sign"]: + m[field] = sum(tl[i][field] if i < len(tl) else 0 for tl in all_monthly) + merged_monthly.append(m) + summary = { + "project_name": "总工作台", + "metrics": agg, + "recent": sorted(all_recent, key=lambda x: x.get("id", 0), reverse=True)[:8], + "risks": [], + } + return jsonify({"summary": summary, "sales": [], "proposals": [], "operations": [], "products": [], "finance": [], "projectFinances": [], "financeMonthly": merged_monthly, "tasks": [], "tenant": tenant, "tenants": allowed}) + def q(sql, *args): return rows(conn, sql, args) sales = attach_common(conn, "sales", q("SELECT * FROM sales_leads WHERE tenant=? ORDER BY id DESC", tenant)) @@ -859,7 +941,7 @@ TABLES = { "products": ("product_versions", ["product_name", "version", "version_goal", "priority", "start_date", "plan_date", "dev_done_date", "test_date", "launch_date", "status", "notes", "tenant"]), "finance": ("finance_records", ["month", "project_name", "record_type", "category", "amount", "occurred_date", "notes", "tenant"]), "tasks": ("project_tasks", ["project_id", "phase", "milestone", "task", "owner", "due_date", "blockers", "notes", "status", "sort_order", "priority", "tenant"]), - "projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "budget_data"]), + "projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "total_payment", "total_cost", "total_paid", "budget_data"]), } diff --git a/backend/migrations/columns.py b/backend/migrations/columns.py index e11b67c..8587db9 100644 --- a/backend/migrations/columns.py +++ b/backend/migrations/columns.py @@ -67,6 +67,14 @@ def migrate_add_columns(): _add_column_if_missing(conn, "project_finances", col, f"ALTER TABLE project_finances ADD COLUMN {col} DOUBLE NOT NULL DEFAULT 0") + # project_finances 总视图字段:已回款 / 应付 / 已付 + _add_column_if_missing(conn, "project_finances", "total_payment", + "ALTER TABLE project_finances ADD COLUMN total_payment DOUBLE NOT NULL DEFAULT 0") + _add_column_if_missing(conn, "project_finances", "total_cost", + "ALTER TABLE project_finances ADD COLUMN total_cost DOUBLE NOT NULL DEFAULT 0") + _add_column_if_missing(conn, "project_finances", "total_paid", + "ALTER TABLE project_finances ADD COLUMN total_paid DOUBLE NOT NULL DEFAULT 0") + conn.commit() print("[migrate] 加列迁移完成") finally: diff --git a/static/app.js b/static/app.js index 6bd8d5e..195bc3f 100644 --- a/static/app.js +++ b/static/app.js @@ -13,8 +13,13 @@ const savedTab = localStorage.getItem("opc-active-tab"); // 初始化 applyUserTenants(); +updateSidebarTabs(); load().then(() => { - if (savedTab && savedTab !== "home") switchTab(savedTab); + if (state.tenant === "总工作台") { + switchTab("home"); + } else if (savedTab && savedTab !== "home") { + switchTab(savedTab); + } }).catch((error) => { document.querySelector("main").innerHTML = `
加载失败:${esc(error.message)}
`; }); diff --git a/static/modules/finance.js b/static/modules/finance.js index bd14922..b588ed0 100644 --- a/static/modules/finance.js +++ b/static/modules/finance.js @@ -73,47 +73,47 @@ function renderFinance() { const budget = JSON.parse(pf.budget_data || "[]"); budget.forEach(b => { budgetMap[(b.month || "").replace("-", "_")] = b; }); } catch (e) {} - const isRevView = state.finView !== "cashflow"; + const isRevView = state.finView !== "cashflow" && state.finView !== "overview" && state.finView !== "monthly"; const mCols = months.map(m => { const b = budgetMap[m] || {}; if (isRevView) { const rev = b.rev || 0; const gross = b.gross || 0; - return `${rev ? money(rev) : '—'}
${gross ? money(gross) : '—'}`; + return `${rev ? money(rev) : '—'}
${gross ? money(gross) : '—'}`; } else { const payment = b.payment || 0; const cost = b.cost || 0; - return `${payment ? money(payment) : '—'}
${cost ? money(cost) : '—'}`; + return `${payment ? money(payment) : '—'}
${cost ? money(cost) : '—'}`; } }).join(""); const totalCol = (() => { if (isRevView) { const totalRev = pf.total_rev || 0; const totalGross = pf.total_gross || 0; - return `${totalRev ? money(totalRev) : '—'}
${totalGross ? money(totalGross) : '—'}`; + return `${totalRev ? money(totalRev) : '—'}
${totalGross ? money(totalGross) : '—'}`; } else { let totalPayment = 0, totalCost = 0; try { JSON.parse(pf.budget_data || "[]").forEach(b => { totalPayment += parseFloat(b.payment||0)||0; totalCost += parseFloat(b.cost||0)||0; }); } catch (e) {} - return `${totalPayment ? money(totalPayment) : '—'}
${totalCost ? money(totalCost) : '—'}`; + return `${totalPayment ? money(totalPayment) : '—'}
${totalCost ? money(totalCost) : '—'}`; } })(); const sm = pf.sign_month || ""; const signMonthCell = `${sm || '—'}`; - return `${esc(pf.customer_name)}${esc(pf.business_type)}${pf.status === "已签约" ? badge("已签约") : pf.status === "流程中" ? badge("流程中","blue") : badge("待签约","amber")}${signMonthCell}${money(pf.sign_amount)}${mCols}${totalCol}${esc(pf.sales_person) || "—"}${esc(pf.owner) || "—"}`; + return `${esc(pf.customer_name)}${esc(pf.business_type)}${pf.status === "已签约" ? badge("已签约") : pf.status === "流程中" ? badge("流程中","blue") : badge("待签约","amber")}${signMonthCell}${money(pf.sign_amount)}${mCols}${totalCol}`; }; document.querySelector("#finance").innerHTML = `
-
- ${[["已签项目","" + signed.length,"file-check-2"],["签约金额",moneyInt(sumSign),"coins"],["流程项目","" + inContract.length,"file-clock"],["流程金额",moneyInt(sumContract),"clock"],["待签项目","" + pending.length,"file-question"],["待签金额",moneyInt(sumPending),"hourglass"]].map(([l,v,icon]) => `
${l}${v}
`).join("")} +
+ ${[["已签项目","" + signed.length,"file-check-2"],["签约金额",moneyInt(sumSign),"coins"],["待签项目","" + pending.length,"file-question"],["待签金额",moneyInt(sumPending),"hourglass"]].map(([l,v,icon]) => `
${l}${v}
`).join("")}
- ${[["本月确收",moneyInt(thisMonthRev),"trending-up"],["本月毛利",moneyInt(thisMonthGross),"percent"],["本月回款",moneyInt(monthPayment),"wallet"],["本月费用",moneyInt(monthCost),"receipt"],["本月现金流",moneyInt(monthCashflow),"repeat"]].map(([l,v,icon]) => `
${l}${v}
`).join("")} + ${[["本月确收",moneyInt(thisMonthRev),"trending-up"],["本月毛利",moneyInt(thisMonthGross),"percent"],["本月回款",moneyInt(monthPayment),"wallet"],["本月应付",moneyInt(monthCost),"receipt"],["本月现金流",moneyInt(monthCashflow),"repeat"]].map(([l,v,icon]) => `
${l}${v}
`).join("")}
-
+
- ${card(`

项目明细 (${pfs.length})

${[["已签约","已签约"],["流程中","流程中"],["待签约","待签约"]].map(([k,v]) => ``).join("")}
${monthLabels.map(l => ``).join("")}${pfs.filter(x => x.status === state.finFilter).map(renderPfRow).join("")}
项目名称类型状态签约月份签约金额${l}
${state.finView !== 'cashflow' ? '确收/毛利' : '回款/费用'}
总计
${state.finView !== 'cashflow' ? '确收/毛利' : '回款/费用'}
商务负责人经营负责人
`, "p-4")} + ${state.finView === 'monthly' ? (() => { + const allPfs = pfs.filter(x => x.status === state.finFilter); + const now = new Date(); + const defaultMonth = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,"0"); + if (!state.finMonth) state.finMonth = defaultMonth; + const selMonth = state.finMonth; + // 收集所有有数据的月份 + 当前月 + const monthSet = new Set([defaultMonth]); + allPfs.forEach(pf => { let bd = []; try { bd = JSON.parse(pf.budget_data || "[]"); } catch(e) {} bd.forEach(b => { if (b.month) monthSet.add(b.month); }); }); + const sortedMonths = [...monthSet].sort().reverse(); + const monthOpts = sortedMonths.map(m => ``).join(""); + const fmt = (v) => v ? `${money(v)}` : ''; + const fmtDiff = (v) => { if (!v) return ''; return `${money(Math.abs(v))}`; }; + const rows = []; + let sumRev=0, sumPay=0, sumCost=0, sumPaid=0; + allPfs.forEach(pf => { + let bd = []; try { bd = JSON.parse(pf.budget_data || "[]"); } catch(e) {} + const b = bd.find(x => (x.month||"") === selMonth) || {}; + const rev = Math.round(parseFloat(b.rev||0)||0); + const payment = Math.round(parseFloat(b.payment||0)||0); + const cost = Math.round(parseFloat(b.cost||0)||0); + const paid = Math.round(parseFloat(b.paid||0)||0); + if (!rev && !payment && !cost && !paid) return; + const payDiff = rev - payment; + const costDiff = cost - paid; + const cashflow = payment - paid; + sumRev+=rev; sumPay+=payment; sumCost+=cost; sumPaid+=paid; + rows.push(`${esc(pf.customer_name)}${esc(pf.business_type)}${pf.status === '已签约' ? badge('已签约') : pf.status === '流程中' ? badge('流程中','blue') : badge('待签约','amber')}${fmt(rev)}${fmt(payment)}${fmtDiff(payDiff)}${fmt(cost)}${fmt(paid)}${fmtDiff(costDiff)}${cashflow ? money(cashflow) : ''}`); + }); + return card(`

月度视图 (${allPfs.length} 项目)

${[["已签约","已签约"],["流程中","流程中"],["待签约","待签约"]].map(([k,v]) => ``).join("")}
${rows.length ? rows.join("") : ''}
项目名称类型状态已确收已回款回款差额应付已付应付差额现金流
该月份暂无数据
合计${money(sumRev)}${money(sumPay)}${fmtDiff(sumRev - sumPay)}${money(sumCost)}${money(sumPaid)}${fmtDiff(sumCost - sumPaid)}${money(sumPay - sumPaid)}
`, "p-4"); + })() : (() => { + const calcTotals = (pf) => { + let budget = []; try { budget = JSON.parse(pf.budget_data || "[]"); } catch (e) {} + let rev = 0, payment = 0, cost = 0; + budget.forEach(b => { rev += parseFloat(b.rev||0)||0; payment += parseFloat(b.payment||0)||0; cost += parseFloat(b.cost||0)||0; }); + const paid = parseFloat(pf.total_paid) || 0; + return { rev: Math.round(rev), payment: Math.round(payment), cost: Math.round(cost), paid: Math.round(paid) }; + }; + const allPfs = pfs.filter(x => x.status === state.finFilter); + let sumRev=0, sumPay=0, sumCost=0, sumPaid=0; + allPfs.forEach(pf => { const t = calcTotals(pf); sumRev+=t.rev; sumPay+=t.payment; sumCost+=t.cost; sumPaid+=t.paid; }); + const fmt = (v) => v ? `${money(v)}` : ''; + const fmtDiff = (v) => { if (!v) return ''; return `${money(Math.abs(v))}`; }; + return card(`

总视图 (${allPfs.length})

${[["已签约","已签约"],["流程中","流程中"],["待签约","待签约"]].map(([k,v]) => ``).join("")}
${allPfs.map(pf => { const t = calcTotals(pf); const payDiff = t.rev - t.payment; const costDiff = t.cost - t.paid; const cashflow = t.payment - t.paid; return ``; }).join("")}
项目名称类型状态已确收已回款回款差额应付已付应付差额现金流
${esc(pf.customer_name)}${esc(pf.business_type)}${pf.status === '已签约' ? badge('已签约') : pf.status === '流程中' ? badge('流程中','blue') : badge('待签约','amber')}${fmt(t.rev)}${fmt(t.payment)}${fmtDiff(payDiff)}${fmt(t.cost)}${fmt(t.paid)}${fmtDiff(costDiff)}${cashflow ? money(cashflow) : ''}
合计${money(sumRev)}${money(sumPay)}${fmtDiff(sumRev - sumPay)}${money(sumCost)}${money(sumPaid)}${fmtDiff(sumCost - sumPaid)}${money(sumPay - sumPaid)}
`, "p-4"); + })()}
`; if (window.lucide) window.lucide.createIcons(); } @@ -187,7 +235,7 @@ window.openFinanceModal = () => { modal.classList.remove("hidden"); }; -window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '') => { +window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '', paid = '') => { const tbody = document.querySelector("#budgetTbody"); if (!tbody) return; const row = document.createElement("tr"); @@ -196,6 +244,7 @@ window.addBudgetRow = (month = '', rev = '', gross = '', payment = '', cost = '' + `; tbody.appendChild(row); if (window.lucide) window.lucide.createIcons(); @@ -206,20 +255,24 @@ window.updateBudgetSummary = () => { const grossEl = document.querySelector("#budgetTotalGross"); const paymentEl = document.querySelector("#budgetTotalPayment"); const costEl = document.querySelector("#budgetTotalCost"); + const paidEl = document.querySelector("#budgetTotalPaid"); if (!revEl || !grossEl) return; const revInputs = document.querySelectorAll('[name="budget_rev[]"]'); const grossInputs = document.querySelectorAll('[name="budget_gross[]"]'); const paymentInputs = document.querySelectorAll('[name="budget_payment[]"]'); const costInputs = document.querySelectorAll('[name="budget_cost[]"]'); - let totalRev = 0, totalGross = 0, totalPayment = 0, totalCost = 0; + const paidInputs = document.querySelectorAll('[name="budget_paid[]"]'); + let totalRev = 0, totalGross = 0, totalPayment = 0, totalCost = 0, totalPaid = 0; revInputs.forEach(el => { totalRev += parseFloat(el.value) || 0; }); grossInputs.forEach(el => { totalGross += parseFloat(el.value) || 0; }); paymentInputs.forEach(el => { totalPayment += parseFloat(el.value) || 0; }); costInputs.forEach(el => { totalCost += parseFloat(el.value) || 0; }); + paidInputs.forEach(el => { totalPaid += parseFloat(el.value) || 0; }); revEl.textContent = money(totalRev); grossEl.textContent = money(totalGross); if (paymentEl) paymentEl.textContent = money(totalPayment); if (costEl) costEl.textContent = money(totalCost); + if (paidEl) paidEl.textContent = money(totalPaid); }; window.initBudgetTable = (budgetData) => { @@ -227,7 +280,7 @@ window.initBudgetTable = (budgetData) => { if (!tbody) return; tbody.innerHTML = ""; const rows = budgetData || []; - rows.forEach(r => addBudgetRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.cost || '')); + rows.forEach(r => addBudgetRow(r.month || '', r.rev || '', r.gross || '', r.payment || '', r.cost || '', r.paid || '')); setTimeout(() => updateBudgetSummary(), 50); }; @@ -315,8 +368,9 @@ window.createFinance = async (event) => { const grosses = form.querySelectorAll('[name="budget_gross[]"]'); const payments = form.querySelectorAll('[name="budget_payment[]"]'); const costs = form.querySelectorAll('[name="budget_cost[]"]'); + const paids = form.querySelectorAll('[name="budget_paid[]"]'); const budgetRows = []; - let totalRev = 0, totalGross = 0; + let totalRev = 0, totalGross = 0, totalPaidFromBudget = 0; for (let i = 0; i < months.length; i++) { const m = months[i].value.trim(); if (!m) continue; @@ -324,13 +378,20 @@ window.createFinance = async (event) => { const gross = parseFloat(grosses[i].value) || 0; const payment = parseFloat(payments[i].value) || 0; const cost = parseFloat(costs[i].value) || 0; - budgetRows.push({ month: m, rev, gross, payment, cost }); + const paid = parseFloat(paids[i].value) || 0; + budgetRows.push({ month: m, rev, gross, payment, cost, paid }); totalRev += rev; totalGross += gross; + totalPaidFromBudget += paid; } data.budget_data = JSON.stringify(budgetRows); data.total_rev = totalRev; data.total_gross = totalGross; + data.total_paid = totalPaidFromBudget; + let totalPayment = 0, totalCost = 0; + for (const r of budgetRows) { totalPayment += r.payment; totalCost += r.cost; } + data.total_payment = totalPayment; + data.total_cost = totalCost; const pfId = data.pf_id; delete data.pf_id; try { diff --git a/static/modules/home.js b/static/modules/home.js index f809dea..fdd4a6c 100644 --- a/static/modules/home.js +++ b/static/modules/home.js @@ -32,14 +32,14 @@ function renderHome() { const tblCard = (title, rows) => card(`

${title}

${rows.map(([label, value]) => ``).join("")}
${label}${value}
`, "p-4"); document.querySelector("#home").innerHTML = `
-
+ ${state.tenant === "总工作台" ? "" : `
${[ ["经营管理", m.total_projects, "finance"], ["重点工作与台账", m.total_proposals, "projects"], ["业务方案", m.total_products, "proposals"], ["产品迭代", m.upcoming_products, "products"], ].map(([label, value, tab]) => ``).join("")} -
+
`}
${tblCard("合同金额", rows1)}${tblCard("确收金额", rows2)}${tblCard("确收毛利", rows3)}${tblCard("回款金额", rows4)}${tblCard("费用金额", rows5)}
${card(`

月度签约趋势

2026
`, "p-4")} diff --git a/static/modules/utils.js b/static/modules/utils.js index 031df3c..7d727d5 100644 --- a/static/modules/utils.js +++ b/static/modules/utils.js @@ -128,6 +128,18 @@ function switchTab(tab) { render(); } +function updateSidebarTabs() { + const isOverview = state.tenant === "总工作台"; + document.querySelectorAll(".sidebar-tab").forEach((btn) => { + const tab = btn.dataset.tab; + if (isOverview && tab !== "home") { + btn.style.display = "none"; + } else { + btn.style.display = ""; + } + }); +} + function render() { if (!state.data) return; renderHome(); @@ -181,6 +193,8 @@ window.switchTenant = (tenant) => { document.querySelector("#workspaceTitle").textContent = tenant.replace("·无界", "") + " OPC 工作台"; const label = document.querySelector("#currentTenantLabel"); if (label) { label.textContent = tenant.replace("·无界", "") || "工作台"; label.title = tenant; } + updateSidebarTabs(); + if (tenant === "总工作台") switchTab("home"); load(); }; window.doLogout = async () => {