feat: 加载浮层+工作台改回下拉菜单

- load()增加模态加载浮层: 7步进度展示+进度条+逐步渲染
- 工作台切换从竖向平铺改回下拉菜单(带图标)
- 版本号 v1.0.0.39
This commit is contained in:
mac
2026-07-09 16:33:49 +08:00
parent c26cbb3cf4
commit c6c282537c
3 changed files with 113 additions and 11 deletions

View File

@@ -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 '<div class="flex flex-col items-center cursor-pointer rounded-lg py-1.5 px-1 w-14 transition-colors ' + (isActive ? 'bg-blue-600' : 'hover:bg-slate-800') + '" onclick="switchTenantFromMenu(\'' + t.replace(/'/g, "\\'") + '\')" title="' + esc(t) + '">' +
'<i data-lucide="' + icon + '" style="width:20px;height:20px;color:' + (isActive ? '#fff' : '#94a3b8') + '"></i>' +
'<span class="text-[10px] mt-1 max-w-[56px] truncate text-center ' + (isActive ? 'text-white' : 'text-slate-400') + '">' + esc(shortName) + '</span>' +
'</div>';
}).join('');
var currentShort = (state.tenant || '').replace("·无界", "");
var currentIcon = tenantIcons[state.tenant] || 'circle';
container.innerHTML = '<div class="flex flex-col items-center cursor-pointer hover:bg-slate-800 rounded-lg py-2 px-1 w-14 transition-colors" onclick="toggleTenantMenu(event)" title="切换工作台">' +
'<i data-lucide="' + currentIcon + '" style="width:20px;height:20px;color:#60a5fa"></i>' +
'<span class="text-[10px] mt-1 max-w-[56px] truncate text-center text-slate-300">' + esc(currentShort) + '</span>' +
'<i data-lucide="chevron-down" style="width:12px;height:12px;color:#64748b;margin-top:2px"></i>' +
'</div>';
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 = '<div class="px-4 py-2 border-b border-slate-100"><p class="text-xs font-semibold text-slate-500 uppercase tracking-wider">切换工作台</p></div>' +
tenants.map(t => {
const shortName = t.replace("·无界", "");
const isActive = t === state.tenant;
var icon = tenantIcons[t] || 'circle';
return '<button class="w-full text-left px-4 py-2.5 text-sm hover:bg-slate-50 transition-colors flex items-center gap-3 ' + (isActive ? 'text-blue-600 font-medium bg-blue-50' : 'text-slate-700') + '" onclick="switchTenantFromMenu(\'' + t.replace(/'/g, "\\'") + '\')">' +
'<i data-lucide="' + icon + '" style="width:16px;height:16px;color:' + (isActive ? '#2563eb' : '#94a3b8') + '"></i>' +
'<span class="flex-1">' + esc(shortName) + '</span>' +
(isActive ? '<i data-lucide="check" style="width:14px;height:14px;color:#2563eb"></i>' : '') +
'</button>';
}).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; }

View File

@@ -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 '<div id="_loadingStep_' + i + '" class="flex items-center gap-3 py-1.5 text-sm ' + (i === 0 ? 'text-blue-600 font-medium' : 'text-slate-400') + '">' +
'<span class="w-4 h-4 flex items-center justify-center">' + (i === 0 ? '<svg class="animate-spin" width="14" height="14" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25"/><path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>' : '<span class="w-2 h-2 rounded-full bg-slate-300"></span>') + '</span>' +
'<span>' + s.label + '</span>' +
'</div>';
}).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 = '<div class="bg-white rounded-2xl shadow-2xl px-8 py-6 w-full max-w-md">' +
'<h3 class="text-base font-bold text-slate-800 mb-1">正在加载工作台</h3>' +
'<p class="text-xs text-slate-400 mb-4">共 ' + steps.length + ' 个模块,请稍候…</p>' +
'<div id="_loadingSteps">' + stepsHtml + '</div>' +
'<div class="mt-4 h-1 bg-slate-100 rounded-full overflow-hidden"><div id="_loadingProgress" class="h-full bg-blue-600 transition-all duration-300" style="width:0%"></div></div>' +
'</div>';
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 = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
}
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 = '<svg class="animate-spin" width="14" height="14" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" opacity="0.25"/><path d="M12 2a10 10 0 0 1 10 10" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg>';
}
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) {

View File

@@ -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.0.0.38</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.0.0.39</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">