diff --git a/static/modules/projects.js b/static/modules/projects.js index e3ad0b4..36a401c 100644 --- a/static/modules/projects.js +++ b/static/modules/projects.js @@ -67,19 +67,58 @@ function updateTenantLabel() { }; const container = document.querySelector("#tenantList"); if (container) { - container.innerHTML = tenants.map(t => { - const shortName = t.replace("·无界", ""); - const isActive = t === state.tenant; - var icon = tenantIcons[t] || 'circle'; - return '
' + - '' + - '' + esc(shortName) + '' + - '
'; - }).join(''); + var currentShort = (state.tenant || '').replace("·无界", ""); + var currentIcon = tenantIcons[state.tenant] || 'circle'; + container.innerHTML = '
' + + '' + + '' + esc(currentShort) + '' + + '' + + '
'; if (window.lucide) window.lucide.createIcons(); } } +window.toggleTenantMenu = (event) => { + event.stopPropagation(); + let menu = document.getElementById("tenantMenu"); + if (menu) { menu.remove(); return; } + const btn = event.currentTarget; + const rect = btn.getBoundingClientRect(); + const tenants = (state.allowedTenants || []).slice().sort(function(a, b) { var order = ['总览','学术·无界','MCN·无界','科普·无界','医患·无界','科研·无界']; var ai = order.indexOf(a); var bi = order.indexOf(b); if (ai < 0) ai = 99; if (bi < 0) bi = 99; return ai - bi; }); + var tenantIcons = { + '学术·无界': 'graduation-cap', + 'MCN·无界': 'video', + '科普·无界': 'flask-conical', + '医患·无界': 'stethoscope', + '科研·无界': 'microscope', + '总览': 'layout-dashboard' + }; + menu = document.createElement("div"); + menu.id = "tenantMenu"; + menu.className = "fixed bg-white rounded-lg shadow-xl border border-slate-200 py-1 min-w-[180px] z-[9999]"; + menu.style.left = Math.min(rect.left - 8, window.innerWidth - 200) + "px"; + menu.style.top = rect.bottom + 6 + "px"; + menu.innerHTML = '

切换工作台

' + + tenants.map(t => { + const shortName = t.replace("·无界", ""); + const isActive = t === state.tenant; + var icon = tenantIcons[t] || 'circle'; + return ''; + }).join(''); + document.body.appendChild(menu); + if (window.lucide) window.lucide.createIcons(); + setTimeout(() => { + document.addEventListener("click", function closeMenu() { + menu.remove(); + document.removeEventListener("click", closeMenu); + }, { once: true }); + }, 10); +}; + window.toggleUserMenu = (user) => { let menu = document.getElementById("userMenu"); if (menu) { menu.remove(); return; } diff --git a/static/modules/utils.js b/static/modules/utils.js index b5c8267..6f462d2 100644 --- a/static/modules/utils.js +++ b/static/modules/utils.js @@ -115,18 +115,81 @@ function renderTable(headers, rows, rowClicks) { `); } +var _loadingSteps = [ + { fn: 'renderHome', label: '正在加载首页仪表盘(指标卡片+图表)' }, + { fn: 'renderProjects', label: '正在加载业务机会列表' }, + { fn: 'renderProposals', label: '正在加载业务方案列表' }, + { fn: 'renderProducts', label: '正在加载产品迭代列表' }, + { fn: 'renderPerformance', label: '正在加载绩效管理' }, + { fn: 'renderFinance', label: '正在加载财务模块(表格+总览+图表)' }, + { fn: 'renderPlan', label: '正在加载计划预算' }, +]; + +function showLoadingOverlay(steps) { + var existing = document.getElementById('_loadingOverlay'); + if (existing) existing.remove(); + var stepsHtml = steps.map(function(s, i) { + return '
' + + '' + (i === 0 ? '' : '') + '' + + '' + s.label + '' + + '
'; + }).join(''); + var overlay = document.createElement('div'); + overlay.id = '_loadingOverlay'; + overlay.className = 'fixed inset-0 z-[99999] flex items-center justify-center bg-black/50'; + overlay.innerHTML = '
' + + '

正在加载工作台

' + + '

共 ' + steps.length + ' 个模块,请稍候…

' + + '
' + stepsHtml + '
' + + '
' + + '
'; + document.body.appendChild(overlay); +} + +function updateLoadingStep(idx, total) { + var stepEl = document.getElementById('_loadingStep_' + idx); + if (stepEl) { + stepEl.className = 'flex items-center gap-3 py-1.5 text-sm text-green-600 font-medium'; + stepEl.querySelector('span:first-child').innerHTML = ''; + } + var nextEl = document.getElementById('_loadingStep_' + (idx + 1)); + if (nextEl) { + nextEl.className = 'flex items-center gap-3 py-1.5 text-sm text-blue-600 font-medium'; + nextEl.querySelector('span:first-child').innerHTML = ''; + } + var progress = document.getElementById('_loadingProgress'); + if (progress) progress.style.width = Math.round((idx + 1) / total * 100) + '%'; +} + +function hideLoadingOverlay() { + var overlay = document.getElementById('_loadingOverlay'); + if (overlay) overlay.remove(); +} + async function load() { + showLoadingOverlay(_loadingSteps); state.data = await api(`/api/bootstrap?tenant=${encodeURIComponent(state.tenant)}`); // 首次加载时确保标准资料库 7 项已初始化 if (typeof ensureStandardProposals === "function") { const existing = (state.data.proposals || []).filter(p => p.proposal_type === "标准资料"); const missingCount = 7 - existing.length; if (missingCount > 0) { + hideLoadingOverlay(); await ensureStandardProposals(); return; // ensureStandardProposals 内部会再次 render } } - render(); + // 逐步渲染 + for (var i = 0; i < _loadingSteps.length; i++) { + var step = _loadingSteps[i]; + if (typeof window[step.fn] === 'function') { + window[step.fn](); + } + updateLoadingStep(i, _loadingSteps.length); + await new Promise(function(r) { setTimeout(r, 50); }); + } + if (window.lucide) window.lucide.createIcons(); + setTimeout(hideLoadingOverlay, 300); } function switchTab(tab) { diff --git a/templates/index.html b/templates/index.html index bf4de69..7495998 100644 --- a/templates/index.html +++ b/templates/index.html @@ -76,7 +76,7 @@
-

OPC Manager v1.0.0.38

+

OPC Manager v1.0.0.39

科普 OPC 工作台