562 lines
33 KiB
Plaintext
562 lines
33 KiB
Plaintext
<!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">▾</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">●</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">●</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">▾</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">●</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">●</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">▾</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">●</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">●</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">●</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">●</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">●</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">●</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">▾</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">●</span>
|
||
<span>产品信息管理</span>
|
||
</a>
|
||
<a href="/bom" class="nav-sub-item {% if page == 'bom' %}active{% endif %}" data-page="bom">
|
||
<span class="nav-sub-icon">●</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">●</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">●</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">▾</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">●</span>
|
||
<span>用户管理</span>
|
||
</a>
|
||
<a href="/permissions" class="nav-sub-item {% if page == 'permissions' %}active{% endif %}" data-page="permissions">
|
||
<span class="nav-sub-icon">●</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">●</span>
|
||
<span>访问记录</span>
|
||
</a>
|
||
<a href="/logs" class="nav-sub-item {% if page == 'logs' %}active{% endif %}" data-page="logs">
|
||
<span class="nav-sub-icon">●</span>
|
||
<span>操作日志</span>
|
||
</a>
|
||
<a href="/config" class="nav-sub-item {% if page == 'config' %}active{% endif %}" data-page="config">
|
||
<span class="nav-sub-icon">●</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">●</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>
|