Compare commits

...

2 Commits

Author SHA1 Message Date
mac
ceb54e29ec fix: 展开明细合并月份+tab选中态+默认排序+已发生改名
- 展开明细合并budget_data和expense_data月份(修复只有费用没预算时看不到)
- switchFinanceTab选择器限定#financeModal(修复弹窗tab误伤顶部筛选tab)
- 项目管理表格默认按确收金额从高到低排序
- 执行→已发生, tip改淡黄色
- 登录后默认选权限范围内第一个工作台
- 版本号 v1.2.0.10
2026-07-10 19:11:32 +08:00
mac
9ca5b11e20 feat: 执行→已发生+默认工作台+tip改色
- 执行模块改名为已发生
- tip改为淡黄色+新文案(只记录已发生的确收/毛利/回款/成本/支出)
- 登录后默认选择权限范围内第一个工作台
- applyUserTenants返回Promise,app.js先await再load
- 去除总览工作台残留
- 版本号 v1.2.0.9
2026-07-10 18:22:54 +08:00
6 changed files with 56 additions and 42 deletions

View File

@@ -1,23 +1,12 @@
// app.js — 入口文件(加载模块 + 初始化)
// 恢复上次的工作台和标签页
const savedTenant = localStorage.getItem("opc-active-tenant");
if (savedTenant) {
state.tenant = savedTenant;
const label = savedTenant.replace("·无界", "");
document.querySelector("#workspaceTitle").textContent = label + " OPC 工作台";
const tLabel = document.querySelector("#currentTenantLabel");
if (tLabel) { tLabel.textContent = label || "工作台"; tLabel.title = savedTenant; }
}
const savedTab = localStorage.getItem("opc-active-tab");
// 初始化
applyUserTenants();
updateSidebarTabs();
load().then(() => {
if (state.tenant === "总览") {
switchTab("home");
} else if (savedTab && savedTab !== "home") {
applyUserTenants().then(() => {
updateSidebarTabs();
return load();
}).then(() => {
const savedTab = localStorage.getItem("opc-active-tab");
if (savedTab && savedTab !== "home") {
switchTab(savedTab);
}
}).catch((error) => {

View File

@@ -119,9 +119,9 @@ function renderFinance() {
document.querySelector("#finance").innerHTML = `<div class="grid gap-2">
${finFilterTabs}
${isOverviewMode ? '' : `<div class="flex items-center gap-3 px-4 py-2 rounded-lg border" style="background:linear-gradient(135deg,#dbeafe,#bfdbfe);border-color:#3b82f6">
<i data-lucide="lightbulb" class="text-blue-600 flex-shrink-0" style="width:18px;height:18px"></i>
<span class="text-sm text-blue-900"><strong>执行提示</strong>先关注确收,毛利确保业务运营,再关注回款,回款率,最后逐步优化成本支出</span>
${isOverviewMode ? '' : `<div class="flex items-center gap-3 px-4 py-2 rounded-lg border" style="background:linear-gradient(135deg,#fef9c3,#fde047);border-color:#eab308">
<i data-lucide="lightbulb" class="text-yellow-700 flex-shrink-0" style="width:18px;height:18px"></i>
<span class="text-sm text-yellow-900"><strong>已发生模块</strong>只记录,已经发生的确收,毛利,回款,成本支出</span>
</div>`}
${state.finFilter !== 'expense' && state.finFilter !== 'overview' && state.finFilter !== 'quarterlyOverview' ? card(`<div class="flex justify-between items-center"><div class="flex items-center gap-2">${finHeaderBase}</div>${finAddBtn}</div>`, "py-2 px-4") : ''}
<div id="financeModal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/40" onclick="closeFinanceModal()"><div class="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 max-h-[92vh] overflow-y-auto" onclick="event.stopPropagation()"><div class="sticky top-0 z-10 bg-white/95 backdrop-blur border-b border-slate-100 px-8 py-5 flex items-center justify-between"><div><h3 class="text-xl font-bold text-slate-800" id="financeModalTitle">新增项目财务</h3><p class="text-xs text-slate-400 mt-0.5">填写项目财务信息与月度预算</p></div><div class="flex items-center gap-2"><button class="btn btn-ghost btn-sm text-red-600 hidden" id="financeDeleteBtn" onclick="deleteFinanceItem()"><i data-lucide="trash-2"></i>删除</button><button class="btn btn-ghost btn-sm rounded-full w-8 h-8 p-0" onclick="closeFinanceModal()"><i data-lucide="x"></i></button></div></div>
@@ -266,10 +266,14 @@ function renderFinance() {
{ label: '付款率', hideUnsigned: true, value: ctx => ctx.sumCost && ctx.sumPaid ? Math.round(ctx.sumPaid / ctx.sumCost * 100) + '%' : '—', color: 'text-slate-500' },
].filter(c => true);
// 列排序
if (!state.finSort) state.finSort = { key: null, asc: true };
// 列排序(默认按确收金额从高到低)
if (!state.finSort) state.finSort = { key: 'sig|rev', asc: false };
const sortData = (data, key) => {
if (!key || !state.finSort.key) return data;
if (!key) return data;
if (!state.finSort.key) {
// 默认按确收金额从高到低
return data.slice().sort(function(a, b) { return (parseFloat(b.rev) || 0) - (parseFloat(a.rev) || 0); });
}
var asc = state.finSort.asc ? 1 : -1;
return data.slice().sort(function(a, b) {
var va = a[key], vb = b[key];
@@ -336,14 +340,19 @@ function renderFinance() {
var expenseArr = [];
try { budgetArr = JSON.parse(pf.budget_data || "[]"); } catch(e) {}
try { expenseArr = JSON.parse(pf.expense_data || "[]"); } catch(e) {}
var detailRows = budgetArr.filter(function(b) {
var exp = expenseArr.find(function(e) { return e.month === (b.month || ''); }) || {};
// 合并 budget 和 expense 的所有月份
var allMonths = {};
budgetArr.forEach(function(b) { allMonths[b.month || ''] = true; });
expenseArr.forEach(function(e) { allMonths[e.month || ''] = true; });
var detailRows = Object.keys(allMonths).filter(function(m) {
var b = budgetArr.find(function(x) { return x.month === m; }) || {};
var exp = expenseArr.find(function(x) { return x.month === m; }) || {};
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
return bRev || bGross || bPay || eCost || ePaid;
}).map(function(b) {
var m = b.month || '';
var exp = expenseArr.find(function(e) { return e.month === m; }) || {};
}).sort().map(function(m) {
var b = budgetArr.find(function(x) { return x.month === m; }) || {};
var exp = expenseArr.find(function(x) { return x.month === m; }) || {};
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
var bCf = bPay - ePaid;
@@ -707,7 +716,7 @@ window.editPfSignMonth = (event, pfId) => {
};
window.switchFinanceTab = (tab) => {
document.querySelectorAll(".finance-tab").forEach(b => b.classList.toggle("active", b.dataset.tab === tab));
document.querySelectorAll("#financeModal .finance-tab").forEach(b => b.classList.toggle("active", b.dataset.tab === tab));
document.querySelector("#financeTabInfo").classList.toggle("hidden", tab !== "info");
document.querySelector("#financeTabRevpay").classList.toggle("hidden", tab !== "revpay");
document.querySelector("#financeTabCost").classList.toggle("hidden", tab !== "cost");

View File

@@ -264,10 +264,14 @@ function renderPlan() {
{ label: '付款率', hideUnsigned: true, value: ctx => ctx.sumCost && ctx.sumPaid ? Math.round(ctx.sumPaid / ctx.sumCost * 100) + '%' : '—', color: 'text-slate-500' },
].filter(c => true);
// 列排序
if (!state.planSort) state.planSort = { key: null, asc: true };
// 列排序(默认按确收金额从高到低)
if (!state.planSort) state.planSort = { key: 'sig|rev', asc: false };
const sortData = (data, key) => {
if (!key || !state.planSort.key) return data;
if (!key) return data;
if (!state.planSort.key) {
// 默认按确收金额从高到低
return data.slice().sort(function(a, b) { return (parseFloat(b.rev) || 0) - (parseFloat(a.rev) || 0); });
}
var asc = state.planSort.asc ? 1 : -1;
return data.slice().sort(function(a, b) {
var va = a[key], vb = b[key];
@@ -351,14 +355,19 @@ function renderPlan() {
var expenseArr = [];
try { budgetArr = JSON.parse(pf.budget_data || "[]"); } catch(e) {}
try { expenseArr = JSON.parse(pf.expense_data || "[]"); } catch(e) {}
var detailRows = budgetArr.filter(function(b) {
var exp = expenseArr.find(function(e) { return e.month === (b.month || ''); }) || {};
// 合并 budget 和 expense 的所有月份
var allMonths = {};
budgetArr.forEach(function(b) { allMonths[b.month || ''] = true; });
expenseArr.forEach(function(e) { allMonths[e.month || ''] = true; });
var detailRows = Object.keys(allMonths).filter(function(m) {
var b = budgetArr.find(function(x) { return x.month === m; }) || {};
var exp = expenseArr.find(function(x) { return x.month === m; }) || {};
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
return bRev || bGross || bPay || eCost || ePaid;
}).map(function(b) {
var m = b.month || '';
var exp = expenseArr.find(function(e) { return e.month === m; }) || {};
}).sort().map(function(m) {
var b = budgetArr.find(function(x) { return x.month === m; }) || {};
var exp = expenseArr.find(function(x) { return x.month === m; }) || {};
var bRev = parseFloat(b.rev || 0), bGross = parseFloat(b.gross || 0), bPay = parseFloat(b.payment || 0);
var eCost = parseFloat(exp.cost || 0), ePaid = parseFloat(exp.paid || 0);
var bCf = bPay - ePaid;

View File

@@ -1,7 +1,7 @@
// projects.js — 重点工作与台账(项目管理 + 任务管理)
function applyUserTenants() {
fetch("/api/auth/me").then(r => r.json()).then(data => {
return fetch("/api/auth/me").then(r => r.json()).then(data => {
if (!data.logged_in) { window.location.href = "/login"; return; }
const user = data.user;
const avatar = document.querySelector("#userAvatar");
@@ -15,6 +15,13 @@ function applyUserTenants() {
});
// 缓存可用工作台列表,供下拉菜单使用
state.allowedTenants = data.tenants || [];
// 如果当前工作台不在权限范围内,选择第一个
if (state.allowedTenants.length > 0 && state.allowedTenants.indexOf(state.tenant) < 0) {
state.tenant = state.allowedTenants[0];
localStorage.setItem("opc-active-tenant", state.tenant);
var label0 = state.tenant.replace("·无界", "");
document.querySelector("#workspaceTitle").textContent = label0 + " OPC 工作台";
}
updateTenantLabel();
});
}

View File

@@ -123,7 +123,7 @@ var _loadingSteps = [
{ fn: 'renderProposals', label: '正在加载业务方案列表' },
{ fn: 'renderProducts', label: '正在加载产品迭代列表' },
{ fn: 'renderPerformance', label: '正在加载绩效管理' },
{ fn: 'renderFinance', label: '正在加载执行模块(表格+总览+图表)' },
{ fn: 'renderFinance', label: '正在加载已发生模块(表格+总览+图表)' },
{ fn: 'renderPlan', label: '正在加载预算模块' },
];

View File

@@ -49,9 +49,9 @@
<i data-lucide="calendar-range" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">预算</span>
</div>
<div class="sidebar-tab" data-tab="finance" onclick="switchTab('finance')" title="执行">
<div class="sidebar-tab" data-tab="finance" onclick="switchTab('finance')" title="已发生">
<i data-lucide="folder-open-dot" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">执行</span>
<span class="text-[10px] mt-1">已发生</span>
</div>
<div class="sidebar-tab" data-tab="projects" onclick="switchTab('projects')" title="Focus">
<i data-lucide="file-text" style="width:20px;height:20px"></i>
@@ -76,7 +76,7 @@
<header class="topbar border-b border-slate-200 bg-white px-8 py-5">
<div class="flex items-center gap-3 w-full">
<div class="flex-1">
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.2.0.8</span></p>
<p class="eyebrow text-xs font-semibold uppercase tracking-[0.18em] text-blue-700">OPC Manager <span class="ml-2 text-xs font-normal text-slate-400 tracking-normal">v1.2.0.10</span></p>
<div class="flex items-center gap-4 mt-1">
<h1 class="text-2xl font-semibold" id="workspaceTitle">科普 OPC 工作台</h1>
<div class="hidden sm:flex items-center gap-1.5 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-full px-4 py-1.5">