dy-dashboard/templates/base.html.backup-0630
2026-10-28 11:14:17 +08:00

562 lines
33 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>蝶雅采购智能体平台</title>
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#1677ff">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="蝶雅数据">
<link rel="icon" type="image/png" href="/static/logo-small.png">
<link rel="apple-touch-icon" href="/static/logo-small.png">
<link rel="stylesheet" href="/static/css/style.css?v=307">
<script src="/static/js/chart.umd.min.js"></script>
<style>
.nav-hidden { display: none !important; }
/* 侧边栏折叠时,子菜单弹出面板 */
.sidebar.collapsed .nav-group {
position: relative;
}
.sidebar-popup-menu {
display: none;
position: fixed;
left: 60px;
min-width: 180px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 6px 16px rgba(0,0,0,0.12);
z-index: 99999;
padding: 6px 0;
}
.sidebar-popup-menu.open {
display: block;
}
.sidebar-popup-menu a {
display: block;
padding: 10px 16px;
font-size: 14px;
color: #333;
text-decoration: none;
}
.sidebar-popup-menu a:hover {
background: #f5f5f5;
color: #1677ff;
}
/* BOM树形表格 */
#bomTable .bom-product-row td { border-bottom: 1px solid var(--border); }
#bomTable .bom-material-row td { border-bottom: 1px dashed #e0e0e0; font-size: 13px; }
#bomTable .bom-material-row:last-child td { border-bottom: 1px solid var(--border); }
#bomTable .bom-product-row:hover { background: #e8f0fe !important; }
#bomTable .bom-material-row:hover { background: #f0f4f8 !important; }
.filter-wrap .filter-clear{position:absolute;right:5px;top:50%;transform:translateY(-50%);cursor:pointer;color:#bfbfbf;font-size:13px;display:none;z-index:2;padding:1px 4px;border-radius:3px;line-height:1;background:var(--bg,#fff)}
.filter-wrap.has-value:hover .filter-clear{display:block}
.filter-clear:hover{color:#cf1322;background:#eee}
.filter-wrap.has-value::after{content:"▾";position:absolute;right:5px;top:50%;transform:translateY(-50%);font-size:11px;color:#999;pointer-events:none;transition:opacity .15s}
.filter-wrap.has-value:hover::after{opacity:0}
.pd-fields-2col{display:grid;grid-template-columns:1fr 1fr;gap:0}
.pd-fields-3col{display:grid;grid-template-columns:1fr 1fr 1fr;gap:0}
.pd-fields-4col{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:0}
.pd-row-2col{border-bottom:1px solid #f0f0f0}
.pd-row-3col{border-bottom:1px solid #f0f0f0}
.pd-row-4col{border-bottom:1px solid #f0f0f0}
.filter-wrap{position:relative;display:inline-block;vertical-align:middle}
.filter-wrap select{appearance:none;-webkit-appearance:none;-moz-appearance:none;padding-right:22px!important}
.filter-wrap::after{content:"\25be";position:absolute;right:6px;top:50%;transform:translateY(-50%);font-size:11px;color:#999;pointer-events:none;transition:opacity .15s}
.filter-wrap.has-value:hover::after{opacity:0}
.filter-wrap .filter-clear{position:absolute;right:4px;top:50%;transform:translateY(-50%);cursor:pointer;color:#999;font-size:14px;display:none;z-index:3;line-height:1;padding:0 3px}
.filter-wrap.has-value:hover .filter-clear{display:block}
.filter-clear:hover{color:#cf1322}
</style>
</head>
<body>
<!-- 侧边栏 -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-logo">
<img src="/static/logo-small.png" alt="Logo" style="width:32px;height:32px;border-radius:6px;">
</div>
<span class="sidebar-title">蝶雅采购智能体平台</span>
<button class="sidebar-toggle" id="sidebarToggle" onclick="toggleSidebar()" aria-label="切换菜单">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
</div>
<nav class="sidebar-nav">
{# 所有用户可见 #}
<a href="/" class="nav-item {% if page == 'dashboard' %}active{% endif %}" data-page="dashboard" data-roles="all">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>
</svg>
</span>
<span class="nav-text">数据概览</span>
</a>
{# 出库数据(含子菜单): 所有非管理员也可查看 #}
<div class="nav-group" data-roles="admin,super_admin,procurement_follower,operator,viewer,查看者">
<a href="#" class="nav-item nav-group-header" data-group="outbound" onclick="toggleSubmenu(event)">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
</svg>
</span>
<span class="nav-text">出库数据</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-outbound">
<a href="/data/summary" class="nav-sub-item {% if page == 'data_summary' %}active{% endif %}" data-page="data_summary">
<span class="nav-sub-icon">&#9679;</span>
<span>出库数据汇总</span>
</a>
<a href="/data/detail" class="nav-sub-item {% if page == 'data_detail' %}active{% endif %}" data-page="data_detail">
<span class="nav-sub-icon">&#9679;</span>
<span>出库数据明细</span>
</a>
</div>
</div>
{# 库存信息(含子菜单): 所有人 #}
<div class="nav-group" data-roles="all">
<a href="#" class="nav-item nav-group-header" data-group="inventory" onclick="toggleSubmenu(event)">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
</svg>
</span>
<span class="nav-text">库存信息</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-inventory">
<a href="/inventory/finished" class="nav-sub-item {% if page == 'inventory_finished' %}active{% endif %}" data-page="inventory_finished">
<span class="nav-sub-icon">&#9679;</span>
<span>成品库存</span>
</a>
<a href="/inventory/packaging" class="nav-sub-item {% if page == 'inventory_packaging' %}active{% endif %}" data-page="inventory_packaging">
<span class="nav-sub-icon">&#9679;</span>
<span>包材库存</span>
</a>
</div>
</div>
{# 计划预测: 所有非管理员也可查看 #}
<a href="/planning" data-roles="admin,super_admin,procurement_planner,procurement_follower,product_developer,operator,viewer,查看者" class="nav-item {% if page == 'planning' %}active{% endif %}" data-page="planning">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
</svg>
</span>
<span class="nav-text">计划预测</span>
</a>
{# 采购订单(含子菜单): 所有非管理员也可查看 #}
<div class="nav-group" data-roles="admin,super_admin,procurement_planner,procurement_follower,viewer,查看者">
<a href="#" class="nav-item nav-group-header" data-group="purchase" onclick="toggleSubmenu(event)">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
</svg>
</span>
<span class="nav-text">采购订单</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-purchase">
<a href="/purchase" class="nav-sub-item {% if page == 'purchase' %}active{% endif %}" data-page="purchase">
<span class="nav-sub-icon">&#9679;</span>
<span>待下单管理</span>
</a>
<a href="/purchase/ready" class="nav-sub-item {% if page == 'purchase_ready' %}active{% endif %}" data-page="purchase_ready">
<span class="nav-sub-icon">&#9679;</span>
<span>下单备份管理</span>
</a>
<a href="/orders/domestic" class="nav-sub-item {% if page == 'orders_domestic' %}active{% endif %}" data-page="orders_domestic">
<span class="nav-sub-icon">&#9679;</span>
<span>国内订单管理</span>
</a>
<a href="/orders/overseas" class="nav-sub-item {% if page == 'orders_overseas' %}active{% endif %}" data-page="orders_overseas">
<span class="nav-sub-icon">&#9679;</span>
<span>海外订单管理</span>
</a>
<a href="/orders/packaging" class="nav-sub-item {% if page == 'orders_packaging' %}active{% endif %}" data-page="orders_packaging">
<span class="nav-sub-icon">&#9679;</span>
<span>包材订单管理</span>
</a>
<a href="/orders/finished" class="nav-sub-item {% if page == 'orders_finished' %}active{% endif %}" data-page="orders_finished">
<span class="nav-sub-icon">&#9679;</span>
<span>成品提货表</span>
</a>
</div>
</div>
{# 基础信息(含子菜单): 所有非管理员也可查看 #}
<div class="nav-group" data-roles="admin,super_admin,procurement_planner,procurement_follower,product_developer,operator,viewer,查看者">
<a href="#" class="nav-item nav-group-header" data-group="basic" onclick="toggleSubmenu(event)">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>
</svg>
</span>
<span class="nav-text">基础信息</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-basic">
<a href="/products" class="nav-sub-item {% if page == 'products' %}active{% endif %}" data-page="products">
<span class="nav-sub-icon">&#9679;</span>
<span>产品信息管理</span>
</a>
<a href="/bom" class="nav-sub-item {% if page == 'bom' %}active{% endif %}" data-page="bom">
<span class="nav-sub-icon">&#9679;</span>
<span>物料清单管理</span>
</a>
{# 仓库管理: 所有非管理员也可查看 #}
<a href="/warehouse" class="nav-sub-item {% if page == 'warehouse' %}active{% endif %}" data-page="warehouse" data-roles="admin,super_admin,viewer,查看者">
<span class="nav-sub-icon">&#9679;</span>
<span>仓库信息管理</span>
</a>
{# 供应商管理: 所有非管理员也可查看 #}
<a href="/suppliers" class="nav-sub-item {% if page == 'suppliers' %}active{% endif %}" data-page="suppliers" data-roles="admin,super_admin,procurement_planner,procurement_follower,viewer,查看者">
<span class="nav-sub-icon">&#9679;</span>
<span>供应商管理</span>
</a>
</div>
</div>
{# 知识库: 所有用户可见 #}
<a href="/knowledge" class="nav-item {% if page == 'knowledge' %}active{% endif %}" data-page="knowledge" data-roles="all">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/><path d="M12 6v6l2-1 2 1V6"/>
</svg>
</span>
<span class="nav-text">知识库</span>
</a>
{# 测试问题: 所有用户可见 #}
<a href="/feedback" class="nav-item {% if page == 'feedback' %}active{% endif %}" data-page="feedback" data-roles="all">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
</svg>
</span>
<span class="nav-text">测试问题</span>
</a>
{# 系统设置(含子菜单): 仅管理员 #}
<div class="nav-group" data-roles="admin,super_admin">
<a href="#" class="nav-item nav-group-header" data-group="sysconfig" onclick="toggleSubmenu(event)">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</span>
<span class="nav-text">系统设置</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-sysconfig">
<a href="/users" class="nav-sub-item {% if page == 'users' %}active{% endif %}" data-page="users">
<span class="nav-sub-icon">&#9679;</span>
<span>用户管理</span>
</a>
<a href="/permissions" class="nav-sub-item {% if page == 'permissions' %}active{% endif %}" data-page="permissions">
<span class="nav-sub-icon">&#9679;</span>
<span>权限配置</span>
</a>
<a href="/login-records" class="nav-sub-item {% if page == 'login_records' %}active{% endif %}" data-page="login_records">
<span class="nav-sub-icon">&#9679;</span>
<span>访问记录</span>
</a>
<a href="/logs" class="nav-sub-item {% if page == 'logs' %}active{% endif %}" data-page="logs">
<span class="nav-sub-icon">&#9679;</span>
<span>操作日志</span>
</a>
<a href="/config" class="nav-sub-item {% if page == 'config' %}active{% endif %}" data-page="config">
<span class="nav-sub-icon">&#9679;</span>
<span>参数配置</span>
</a>
<a href="/config-calibrate" class="nav-sub-item {% if page == 'config_calibrate' %}active{% endif %}" data-page="config_calibrate">
<span class="nav-sub-icon">&#9679;</span>
<span>参数校准</span>
</a>
</div>
</div>
{# 合同模板: 所有用户可见 #}
<a href="/contract-templates" class="nav-item {% if page == 'contract_templates' %}active{% endif %}" data-page="contract_templates" data-roles="all">
<span class="nav-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>
</svg>
</span>
<span class="nav-text">合同模板</span>
</a>
</nav>
<!-- 底部用户信息 -->
<div class="sidebar-footer">
<div class="sidebar-user">
<div class="sidebar-avatar">
<img src="/static/logo-small.png" alt="avatar" style="width:32px;height:32px;border-radius:50%">
</div>
<div class="sidebar-user-info" style="flex:1;min-width:0">
<div style="font-size:13px;font-weight:600;color:rgba(255,255,255,0.85);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">{{ session.get('real_name', session.get('username', '用户')) }}</div>
<div style="font-size:11px;color:rgba(255,255,255,0.45)">
{% set role = session.get('role', '') %}
{% set role_names = {'admin':'管理员','procurement_planner':'采购计划','procurement_follower':'采购跟单','product_developer':'产品开发','operator':'运营专员'} %}
{{ role_names.get(role, '') }}
{% set level = session.get('user_level', '') %}
{% set level_names = {'employee':'','leader':'·组长','director':'·主管'} %}
{{ level_names.get(level, '') }}
</div>
</div>
<div onclick="handleLogout()" title="退出登录" style="cursor:pointer;color:rgba(255,255,255,0.5);padding:4px;border-radius:4px;display:flex;align-items:center" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='rgba(255,255,255,0.5)'">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
</svg>
</div>
</div>
</div>
</aside>
<div class="sidebar-overlay" id="sidebarOverlay" onclick="toggleSidebar()"></div>
<!-- 主内容区 -->
<main class="main-content" id="mainContent">
<div class="page-loading" id="pageLoading">
<div class="loading-spinner"></div>
</div>
</main>
<script>
window._currentRole = '{{ session.get('role', '') }}';
window._currentUserLevel = '{{ session.get('user_level', 'employee') }}';
window._currentUser = {
user_id: {{ session.get('user_id', 0) }},
username: '{{ session.get('username', '') }}',
role: '{{ session.get('role', '') }}'
};
</script>
<script src="/static/js/marked.min.js"></script>
<script src="/static/js/app.js?v=350"></script>
<script src="/static/js/knowledge-actions.js?v=1"></script>
<script>
// 根据角色隐藏无权限的菜单
(function() {
var role = window._currentRole || '';
document.querySelectorAll('[data-roles]').forEach(function(el) {
var roles = el.getAttribute('data-roles');
if (roles === 'all') return;
var allowed = roles.split(',').map(function(r) { return r.trim(); });
if (role === 'admin' || role === 'super_admin') return; // admin 全可见
if (allowed.indexOf(role) === -1) {
el.classList.add('nav-hidden');
}
});
})();
function handleLogout() {
fetch('/api/logout', {method: 'POST'})
.then(function() { window.location.href = '/login'; });
}
function toggleSidebar() {
var sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('collapsed');
var overlay = document.getElementById('sidebarOverlay');
if (sidebar.classList.contains('collapsed')) {
overlay.style.display = 'none';
// 侧边栏缩进后,给图标添加title提示
sidebar.querySelectorAll('.nav-item').forEach(function(item) {
var textEl = item.querySelector('.nav-text');
if (textEl && textEl.textContent.trim()) {
item.setAttribute('title', textEl.textContent.trim());
}
});
sidebar.querySelectorAll('.nav-sub-item').forEach(function(item) {
var textEl = item.querySelector('span:not(.nav-sub-icon)');
if (textEl && textEl.textContent.trim()) {
item.setAttribute('title', textEl.textContent.trim());
}
});
} else {
// 展开后移除title
sidebar.querySelectorAll('.nav-item, .nav-sub-item').forEach(function(item) {
item.removeAttribute('title');
});
if (window.innerWidth <= 768) {
overlay.style.display = 'block';
}
}
}
function toggleSubmenu(event) {
event.preventDefault();
var header = event.currentTarget;
var group = header.dataset.group;
var submenu = document.getElementById('submenu-' + group);
var arrow = header.querySelector('.nav-arrow');
if (!submenu) return;
var sidebar = document.getElementById('sidebar');
if (sidebar && sidebar.classList.contains('collapsed')) {
// 折叠状态:直接打开独立的弹出面板
var popup = document.querySelector('.sidebar-popup-menu');
if (popup && popup.dataset.group === group) {
popup.remove();
return;
}
if (popup) popup.remove();
// 收集子菜单链接
var itemsHtml = '';
submenu.querySelectorAll('a').forEach(function(a) {
var href = a.getAttribute('href') || '#';
var text = a.textContent.trim();
var isActive = window.location.pathname === href;
itemsHtml += '<a href="' + href + '" style="display:block;padding:10px 16px;font-size:14px;text-decoration:none;' + (isActive ? 'color:#1677ff;font-weight:600;background:#e6f7ff' : 'color:#333') + '">' + text + '</a>';
});
popup = document.createElement('div');
popup.className = 'sidebar-popup-menu';
popup.dataset.group = group;
popup.innerHTML = itemsHtml;
var rect = header.getBoundingClientRect();
popup.style.top = (rect.top - 4) + 'px';
document.body.appendChild(popup);
// 延迟后再添加 open 类确保过渡效果
setTimeout(function() { popup.classList.add('open'); }, 10);
// SPA导航
popup.addEventListener('click', function(e) {
var link = e.target.closest('a');
if (link) {
e.preventDefault();
popup.remove();
var href = link.getAttribute('href');
if (href && typeof navigateTo === 'function') navigateTo(href);
else window.location.href = href;
}
});
// 点击外部关闭
setTimeout(function() {
document.addEventListener('click', function popupOutside(e) {
if (!popup.contains(e.target) && e.target !== header && !header.contains(e.target)) {
if (popup.parentNode) popup.remove();
document.removeEventListener('click', popupOutside);
}
});
}, 50);
} else {
submenu.classList.toggle('open');
if (arrow) arrow.classList.toggle('rotated');
}
}
// 页面加载时检查侧边栏状态
function updateSidebarTitles() {
var sidebar = document.getElementById('sidebar');
if (!sidebar) return;
if (sidebar.classList.contains('collapsed')) {
sidebar.querySelectorAll('.nav-item').forEach(function(item) {
var textEl = item.querySelector('.nav-text');
if (textEl && textEl.textContent.trim()) {
item.setAttribute('title', textEl.textContent.trim());
}
});
sidebar.querySelectorAll('.nav-sub-item').forEach(function(item) {
var textEl = item.querySelector('span:not(.nav-sub-icon)');
if (textEl && textEl.textContent.trim()) {
item.setAttribute('title', textEl.textContent.trim());
}
});
}
}
document.addEventListener('DOMContentLoaded', function() {
updateSidebarTitles();
var page = '{{ page }}';
// 自动展开当前页面对应的子菜单
var activeSubItem = document.querySelector('.nav-sub-item.active');
if (activeSubItem) {
var submenu = activeSubItem.closest('.nav-submenu');
if (submenu) {
submenu.classList.add('open');
var arrow = submenu.closest('.nav-group') ? submenu.closest('.nav-group').querySelector('.nav-arrow') : null;
if (arrow) arrow.classList.add('rotated');
}
}
// 展开基础信息子菜单
var basicActive = document.querySelector('#submenu-basic .nav-sub-item.active');
if (basicActive) {
var bMenu = document.getElementById('submenu-basic');
if (bMenu) bMenu.classList.add('open');
var bArrow = document.querySelector('.nav-group-header[data-group="basic"] .nav-arrow');
if (bArrow) bArrow.classList.add('rotated');
}
// 展开库存子菜单
var invActive = document.querySelector('#submenu-inventory .nav-sub-item.active');
if (invActive) {
var iMenu = document.getElementById('submenu-inventory');
if (iMenu) iMenu.classList.add('open');
var iArrow = document.querySelector('.nav-group-header[data-group="inventory"] .nav-arrow');
if (iArrow) iArrow.classList.add('rotated');
}
// 如果当前在高权限页面,展开系统设置菜单
var sysConfigActive = document.querySelector('#submenu-sysconfig .nav-sub-item.active');
if (sysConfigActive) {
var sysMenu = document.getElementById('submenu-sysconfig');
if (sysMenu) sysMenu.classList.add('open');
var sysArrow = document.querySelector('.nav-group-header[data-group="sysconfig"] .nav-arrow');
if (sysArrow) sysArrow.classList.add('rotated');
}
// 展开采购订单菜单
var purchaseActive = document.querySelector('#submenu-purchase .nav-sub-item.active');
if (purchaseActive) {
var pMenu = document.getElementById('submenu-purchase');
if (pMenu) pMenu.classList.add('open');
var pArrow = document.querySelector('.nav-group-header[data-group="purchase"] .nav-arrow');
if (pArrow) pArrow.classList.add('rotated');
}
// 展开出库数据菜单
var outboundActive = document.querySelector('#submenu-outbound .nav-sub-item.active');
if (outboundActive) {
var oMenu = document.getElementById('submenu-outbound');
if (oMenu) oMenu.classList.add('open');
var oArrow = document.querySelector('.nav-group-header[data-group="outbound"] .nav-arrow');
if (oArrow) oArrow.classList.add('rotated');
}
// 当前页面不在侧边栏中(无权限),跳转到数据概览
var currentNav = document.querySelector('.nav-item.active');
if (!currentNav && page !== 'login') {
// 页面有权限但菜单不显示或不存在,由后端拦截
}
window._currentPage = page;
loadPage(page);
});
</script>
<script>
if ('serviceWorker' in navigator) {
// 先清除所有旧 SW 缓存
if (caches) {
caches.keys().then(function(keys) {
keys.forEach(function(k) { if (k.startsWith('dy-dashboard-')) caches.delete(k); });
});
}
window.addEventListener('load', function() {
navigator.serviceWorker.register('/static/sw.js?v=7').then(function(reg) {
reg.update();
if (reg.installing) {
reg.installing.addEventListener('statechange', function() {
if (this.state === 'activated') location.reload();
});
}
}).catch(function(err) {
console.log('SW failed:', err);
});
});
}
</script>
</body>
</html>