This commit is contained in:
@@ -1,26 +1,13 @@
|
|||||||
// app.js — 入口文件(加载模块 + 初始化)
|
// 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");
|
const savedTenant = localStorage.getItem("opc-active-tenant");
|
||||||
if (savedTenant) {
|
if (savedTenant) {
|
||||||
state.tenant = savedTenant;
|
state.tenant = savedTenant;
|
||||||
document.querySelectorAll(".workspace-nav-item").forEach(el => el.classList.toggle("active", el.dataset.tenant === savedTenant));
|
|
||||||
const label = savedTenant.replace("·无界", "");
|
const label = savedTenant.replace("·无界", "");
|
||||||
document.querySelector("#workspaceTitle").textContent = label + " OPC 工作台";
|
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");
|
const savedTab = localStorage.getItem("opc-active-tab");
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,57 @@ function applyUserTenants() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleUserMenu(user);
|
toggleUserMenu(user);
|
||||||
});
|
});
|
||||||
const allowedTenants = data.tenants || [];
|
// 缓存可用工作台列表,供下拉菜单使用
|
||||||
document.querySelectorAll(".workspace-nav-item").forEach(el => {
|
state.allowedTenants = data.tenants || [];
|
||||||
el.style.display = allowedTenants.includes(el.dataset.tenant) ? "" : "none";
|
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) => {
|
window.toggleUserMenu = (user) => {
|
||||||
let menu = document.getElementById("userMenu");
|
let menu = document.getElementById("userMenu");
|
||||||
if (menu) { menu.remove(); return; }
|
if (menu) { menu.remove(); return; }
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ async function load() {
|
|||||||
function switchTab(tab) {
|
function switchTab(tab) {
|
||||||
state.active = tab;
|
state.active = tab;
|
||||||
localStorage.setItem("opc-active-tab", 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));
|
document.querySelectorAll(".panel").forEach((panel) => panel.classList.toggle("active", panel.id === tab));
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,8 @@ window.switchTenant = (tenant) => {
|
|||||||
state.selectedProject = null;
|
state.selectedProject = null;
|
||||||
localStorage.setItem("opc-active-tenant", tenant);
|
localStorage.setItem("opc-active-tenant", tenant);
|
||||||
document.querySelector("#workspaceTitle").textContent = tenant.replace("·无界", "") + " OPC 工作台";
|
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();
|
load();
|
||||||
};
|
};
|
||||||
window.doLogout = async () => {
|
window.doLogout = async () => {
|
||||||
|
|||||||
@@ -32,25 +32,30 @@ body {
|
|||||||
background: rgba(96,165,250,0.15);
|
background: rgba(96,165,250,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.sidebar-tab {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
flex-direction: column;
|
||||||
}
|
|
||||||
|
|
||||||
.tabs button {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 2px solid transparent;
|
padding: 8px 4px;
|
||||||
color: #64748b;
|
border-radius: 8px;
|
||||||
display: inline-flex;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
color: #94a3b8;
|
||||||
font-weight: 600;
|
transition: all 0.15s ease;
|
||||||
gap: 8px;
|
width: 100%;
|
||||||
padding: 14px 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs button.active {
|
.sidebar-tab:hover {
|
||||||
border-bottom-color: #1d4ed8;
|
background: #1e293b;
|
||||||
color: #1d4ed8;
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab.active {
|
||||||
|
background: #1e293b;
|
||||||
|
color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab.active i {
|
||||||
|
color: #60a5fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
|
|||||||
@@ -29,29 +29,42 @@
|
|||||||
<div class="flex min-h-screen">
|
<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">
|
<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>
|
<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>
|
<span class="text-[10px] text-slate-400 mt-1.5 max-w-[64px] truncate text-center" id="userDisplayName" title=""></span>
|
||||||
</div>
|
</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>
|
<div class="w-8 h-px bg-slate-700 my-2"></div>
|
||||||
<span class="text-[10px] mt-1">科普</span>
|
<!-- 工作台切换按钮 -->
|
||||||
|
<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>
|
||||||
<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>
|
<div class="w-8 h-px bg-slate-700 my-2"></div>
|
||||||
<span class="text-[10px] mt-1">科研</span>
|
<!-- 导航 Tab 图标 -->
|
||||||
</div>
|
<div class="flex flex-col items-center gap-1 w-14" id="sidebarTabs">
|
||||||
<div class="workspace-nav-item" data-tenant="医患·无界" onclick="switchTenant('医患·无界')" title="医患·无界">
|
<div class="sidebar-tab active" data-tab="home" onclick="switchTab('home')" 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>
|
<i data-lucide="home" style="width:20px;height:20px"></i>
|
||||||
<span class="text-[10px] mt-1">医患</span>
|
<span class="text-[10px] mt-1">首页</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-nav-item" data-tenant="MCN·无界" onclick="switchTenant('MCN·无界')" title="MCN·无界">
|
<div class="sidebar-tab" data-tab="finance" onclick="switchTab('finance')" 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"><rect x="2" y="7" width="20" height="15" rx="2"/><path d="M17 2l-5 5-5-5"/></svg>
|
<i data-lucide="briefcase-business" style="width:20px;height:20px"></i>
|
||||||
<span class="text-[10px] mt-1">MCN</span>
|
<span class="text-[10px] mt-1">财务</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-nav-item" data-tenant="无界·无界" onclick="switchTenant('无界·无界')" title="无界·无界">
|
<div class="sidebar-tab" data-tab="projects" onclick="switchTab('projects')" 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>
|
<i data-lucide="file-text" 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="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>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<!-- 主内容区 -->
|
<!-- 主内容区 -->
|
||||||
@@ -67,14 +80,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<main class="px-8 py-6">
|
||||||
<section id="home" class="panel active"></section>
|
<section id="home" class="panel active"></section>
|
||||||
<section id="projects" class="panel"></section>
|
<section id="projects" class="panel"></section>
|
||||||
|
|||||||
Reference in New Issue
Block a user