Compare commits

...

2 Commits

Author SHA1 Message Date
mac
f6792cad39 Merge branch 'dev'
All checks were successful
Deploy / deploy (push) Successful in 10s
2026-06-24 12:42:56 +08:00
mac
33f47acc55 左侧菜单改版:工作台下拉 + 5个图标导航
- 工作台改为下拉菜单(layout-grid + chevron-down 图标)
- 顶部 tabs 移到左侧 sidebar,5 个图标导航(首页/财务/台账/方案/产品)
- 头像与工作台间、工作台与导航间各加分隔线
- 经营管理 tab 短名改为'财务'
- 移除 .tabs 样式,新增 .sidebar-tab 样式
2026-06-24 12:42:56 +08:00
5 changed files with 105 additions and 63 deletions

View File

@@ -1,26 +1,13 @@
// app.js — 入口文件(加载模块 + 初始化)
// 所有业务逻辑已拆分到 modules/ 目录:
// utils.js — 共享状态、工具函数、API 封装
// home.js — 首页 + 财务趋势图
// projects.js — 重点工作与台账(项目+任务+拖拽)
// proposals.js — 业务方案 + 文件管理
// products.js — 产品迭代
// finance.js — 经营管理(财务)
// drawer.js — 详情抽屉 + 评论 + 转移
// Tab 点击委托
document.querySelector("#tabs").addEventListener("click", (event) => {
const button = event.target.closest("button[data-tab]");
if (button) switchTab(button.dataset.tab);
});
// 恢复上次的工作台和标签页
const savedTenant = localStorage.getItem("opc-active-tenant");
if (savedTenant) {
state.tenant = savedTenant;
document.querySelectorAll(".workspace-nav-item").forEach(el => el.classList.toggle("active", el.dataset.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");

View File

@@ -13,13 +13,57 @@ function applyUserTenants() {
e.stopPropagation();
toggleUserMenu(user);
});
const allowedTenants = data.tenants || [];
document.querySelectorAll(".workspace-nav-item").forEach(el => {
el.style.display = allowedTenants.includes(el.dataset.tenant) ? "" : "none";
});
// 缓存可用工作台列表,供下拉菜单使用
state.allowedTenants = data.tenants || [];
updateTenantLabel();
});
}
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 || [];
menu = document.createElement("div");
menu.id = "tenantMenu";
menu.className = "fixed bg-white rounded-lg shadow-xl border border-slate-200 py-1 min-w-[160px] z-[9999]";
menu.style.left = Math.min(rect.left - 8, window.innerWidth - 180) + "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 => `
<button class="w-full text-left px-4 py-2 text-sm hover:bg-slate-50 transition-colors flex items-center justify-between gap-2 ${t === state.tenant ? 'text-blue-600 font-medium' : 'text-slate-700'}" onclick="switchTenantFromMenu('${t.replace(/'/g, "\\'")}')">
<span>${esc(t)}</span>
${t === state.tenant ? '<i data-lucide="check" style="width:14px;height:14px"></i>' : ''}
</button>
`).join('')}`;
document.body.appendChild(menu);
if (window.lucide) lucide.createIcons();
setTimeout(() => {
document.addEventListener("click", function closeMenu() {
menu.remove();
document.removeEventListener("click", closeMenu);
}, { once: true });
}, 10);
};
window.switchTenantFromMenu = (tenant) => {
document.getElementById("tenantMenu")?.remove();
switchTenant(tenant);
};
function updateTenantLabel() {
const label = document.querySelector("#currentTenantLabel");
if (label) {
label.textContent = state.tenant.replace("·无界", "") || "工作台";
label.title = state.tenant;
}
}
window.toggleUserMenu = (user) => {
let menu = document.getElementById("userMenu");
if (menu) { menu.remove(); return; }

View File

@@ -123,7 +123,7 @@ async function load() {
function switchTab(tab) {
state.active = tab;
localStorage.setItem("opc-active-tab", tab);
document.querySelectorAll("#tabs button").forEach((btn) => btn.classList.toggle("active", btn.dataset.tab === tab));
document.querySelectorAll(".sidebar-tab").forEach((btn) => btn.classList.toggle("active", btn.dataset.tab === tab));
document.querySelectorAll(".panel").forEach((panel) => panel.classList.toggle("active", panel.id === tab));
render();
}
@@ -179,7 +179,8 @@ window.switchTenant = (tenant) => {
state.selectedProject = null;
localStorage.setItem("opc-active-tenant", tenant);
document.querySelector("#workspaceTitle").textContent = tenant.replace("·无界", "") + " OPC 工作台";
document.querySelectorAll(".workspace-nav-item").forEach((el) => el.classList.toggle("active", el.dataset.tenant === tenant));
const label = document.querySelector("#currentTenantLabel");
if (label) { label.textContent = tenant.replace("·无界", "") || "工作台"; label.title = tenant; }
load();
};
window.doLogout = async () => {

View File

@@ -32,25 +32,30 @@ body {
background: rgba(96,165,250,0.15);
}
.tabs {
.sidebar-tab {
display: flex;
gap: 4px;
}
.tabs button {
flex-direction: column;
align-items: center;
border-bottom: 2px solid transparent;
color: #64748b;
display: inline-flex;
font-size: 14px;
font-weight: 600;
gap: 8px;
padding: 14px 16px;
padding: 8px 4px;
border-radius: 8px;
cursor: pointer;
color: #94a3b8;
transition: all 0.15s ease;
width: 100%;
}
.tabs button.active {
border-bottom-color: #1d4ed8;
color: #1d4ed8;
.sidebar-tab:hover {
background: #1e293b;
color: #cbd5e1;
}
.sidebar-tab.active {
background: #1e293b;
color: #60a5fa;
}
.sidebar-tab.active i {
color: #60a5fa;
}
.panel {

View File

@@ -29,29 +29,42 @@
<div class="flex min-h-screen">
<!-- 左侧工作台切换栏 -->
<aside class="w-[72px] bg-slate-900 flex flex-col items-center py-6 gap-1 shrink-0 sticky top-0 h-screen overflow-y-auto" id="workspaceSidebar">
<div class="flex flex-col items-center mb-6 cursor-pointer" onclick="document.getElementById('userAvatar').click()">
<div class="flex flex-col items-center mb-2 cursor-pointer" onclick="document.getElementById('userAvatar').click()">
<div class="w-10 h-10 rounded-full bg-slate-700 flex items-center justify-center text-white font-bold text-sm hover:ring-2 hover:ring-blue-400 transition-all" id="userAvatar" title=""></div>
<span class="text-[10px] text-slate-400 mt-1.5 max-w-[64px] truncate text-center" id="userDisplayName" title=""></span>
</div>
<div class="workspace-nav-item active" data-tenant="科普·无界" onclick="switchTenant('科普·无界')" title="科普·无界">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 12h8"/><path d="M12 8v8"/></svg>
<span class="text-[10px] mt-1">科普</span>
<!-- 分隔线 -->
<div class="w-8 h-px bg-slate-700 my-2"></div>
<!-- 工作台切换按钮 -->
<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)">
<i data-lucide="layout-grid" style="width:18px;height:18px;color:#94a3b8"></i>
<span class="text-[10px] text-slate-400 mt-1 max-w-[56px] truncate text-center" id="currentTenantLabel">工作台</span>
<i data-lucide="chevron-down" style="width:12px;height:12px;color:#64748b;margin-top:2px"></i>
</div>
<div class="workspace-nav-item" data-tenant="科研·无界" onclick="switchTenant('科研·无界')" title="科研·无界">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
<span class="text-[10px] mt-1">科研</span>
</div>
<div class="workspace-nav-item" data-tenant="医患·无界" onclick="switchTenant('医患·无界')" title="医患·无界">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
<span class="text-[10px] mt-1">医患</span>
</div>
<div class="workspace-nav-item" data-tenant="MCN·无界" onclick="switchTenant('MCN·无界')" title="MCN·无界">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="15" rx="2"/><path d="M17 2l-5 5-5-5"/></svg>
<span class="text-[10px] mt-1">MCN</span>
</div>
<div class="workspace-nav-item" data-tenant="无界·无界" onclick="switchTenant('无界·无界')" title="无界·无界">
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<span class="text-[10px] mt-1">无界</span>
<!-- 分隔线 -->
<div class="w-8 h-px bg-slate-700 my-2"></div>
<!-- 导航 Tab 图标 -->
<div class="flex flex-col items-center gap-1 w-14" id="sidebarTabs">
<div class="sidebar-tab active" data-tab="home" onclick="switchTab('home')" title="首页">
<i data-lucide="home" 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="经营管理">
<i data-lucide="briefcase-business" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">财务</span>
</div>
<div class="sidebar-tab" data-tab="projects" onclick="switchTab('projects')" title="重点工作与台账">
<i data-lucide="file-text" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">台账</span>
</div>
<div class="sidebar-tab" data-tab="proposals" onclick="switchTab('proposals')" title="业务方案">
<i data-lucide="package" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">方案</span>
</div>
<div class="sidebar-tab" data-tab="products" onclick="switchTab('products')" title="产品迭代">
<i data-lucide="wallet-cards" style="width:20px;height:20px"></i>
<span class="text-[10px] mt-1">产品</span>
</div>
</div>
</aside>
<!-- 主内容区 -->
@@ -67,14 +80,6 @@
</div>
</header>
<nav class="tabs border-b border-slate-200 bg-white px-8" id="tabs">
<button class="active" data-tab="home"><i data-lucide="home"></i>首页</button>
<button data-tab="finance"><i data-lucide="briefcase-business"></i>经营管理</button>
<button data-tab="projects"><i data-lucide="file-text"></i>重点工作与台账</button>
<button data-tab="proposals"><i data-lucide="package"></i>业务方案</button>
<button data-tab="products"><i data-lucide="wallet-cards"></i>产品迭代</button>
</nav>
<main class="px-8 py-6">
<section id="home" class="panel active"></section>
<section id="projects" class="panel"></section>