2026-10-28 11:14:17 +08:00

693 lines
44 KiB
HTML
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=1785481700">
<script src="/static/js/chart.umd.min.js"></script>
<style>
.nav-hidden { display: none !important; }
/* 左侧导航顶层菜单固定排序;子菜单及权限隐藏逻辑不受影响。 */
.sidebar-nav {
display: flex;
flex-direction: column;
}
.nav-order-1 { order: 1; }
.nav-order-2 { order: 2; }
.nav-order-3 { order: 3; }
.nav-order-4 { order: 4; }
/* flex order 只接受整数;呆滞库存管理紧跟采销协同之后。 */
.nav-order-5 { order: 5; }
.nav-order-6 { order: 6; }
.nav-order-7 { order: 7; }
.nav-order-8 { order: 8; }
.nav-order-9 { order: 9; }
.nav-order-10 { order: 10; }
.nav-order-11 { order: 11; }
.nav-order-12 { order: 12; }
/* 侧边栏折叠时,子菜单弹出面板 */
.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}
/* 产品详情明细弹窗 - 美化样式 */
.detail-two-col{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px}
.detail-col{display:flex;flex-direction:column;gap:6px}
.detail-pair{display:flex;border:1px solid #d0d0d0;border-radius:5px;overflow:hidden;min-height:36px;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,0.05)}
.detail-lbl{width:115px;min-width:115px;padding:6px 10px;background:#b0b0b0;color:#333;font-size:12px;font-weight:600;white-space:nowrap;display:flex;align-items:center;justify-content:flex-end;letter-spacing:0.3px;border-right:1px solid #999}
.detail-val-wrap{flex:1;padding:4px 10px;display:flex;align-items:center}
.detail-val{font-size:13px;color:#333;word-break:break-all;line-height:1.5}
.detail-input{width:100%;box-sizing:border-box;font-size:13px;border:1px solid #ccc;border-radius:3px;padding:5px 8px;color:#333;background:#fff;min-width:0;transition:border-color .2s,box-shadow .2s}
.detail-input:hover{border-color:#999}
.detail-input:focus{border-color:#1890ff;outline:none;box-shadow:0 0 0 2px rgba(24,144,255,0.15)}
.detail-select{appearance:auto;-webkit-appearance:auto;padding:5px 6px!important;cursor:pointer;background:#fff!important}
.detail-textarea:focus{border-color:#1890ff!important;outline:none;box-shadow:0 0 0 2px rgba(24,144,255,0.15)}
.orderer-select{background:#fff;cursor:pointer;min-height:28px}
.orderer-select option{padding:3px 6px}
.ac{width:auto;min-width:0}
.col-resize-handle{cursor:col-resize;transition:background .15s}
.col-resize-handle:hover{background:rgba(102,126,234,0.3)!important}
.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="/dashboard" class="nav-item nav-order-1 {% if page == 'dashboard' %}active{% endif %}" data-page="dashboard" data-roles="all">
<span class="nav-icon nav-icon-accent">
<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 nav-order-4" data-roles="all">
<a href="#" class="nav-item nav-group-header" data-group="collaboration" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
</span>
<span class="nav-text">采销协同</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-collaboration">
<a href="/collaboration/inventory-dashboard" class="nav-sub-item {% if page == 'collaboration_inventory_dashboard' %}active{% endif %}" data-page="collaboration_inventory_dashboard" data-roles="all"><span class="nav-sub-icon">&#9679;</span><span>库存数据看板</span></a>
<a href="/collaboration/key-sku-forecast" class="nav-sub-item {% if page == 'collaboration_key_sku_forecast' %}active{% endif %}" data-page="collaboration_key_sku_forecast" data-roles="all"><span class="nav-sub-icon">&#9679;</span><span>重点SKU预测</span></a>
<a href="/collaboration/sku-health-index" class="nav-sub-item {% if page == 'collaboration_sku_health_index' %}active{% endif %}" data-page="collaboration_sku_health_index" data-roles="all"><span class="nav-sub-icon">&#9679;</span><span>SKU库存健康指数</span></a>
<a href="/collaboration/slow-moving-sku" class="nav-sub-item {% if page == 'collaboration_slow_moving_sku' %}active{% endif %}" data-page="collaboration_slow_moving_sku" data-roles="all"><span class="nav-sub-icon">&#9679;</span><span>呆滞SKU清单</span></a>
<a href="/collaboration/sales-management" class="nav-sub-item {% if page == 'collaboration_sales_management' %}active{% endif %}" data-page="collaboration_sales_management" data-roles="all"><span class="nav-sub-icon">&#9679;</span><span>动销管理</span></a>
</div>
</div>
{# 基础数据(含子菜单): 所有非管理员也可查看 #}
<div class="nav-group nav-order-2" 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 nav-icon-accent">
<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="/brand-management" class="nav-sub-item {% if page == 'brand_management' %}active{% endif %}" data-page="brand_management">
<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>
{# 人员信息管理: 所有非管理员也可查看 #}
<a href="/personnel" class="nav-sub-item {% if page == 'personnel' %}active{% endif %}" data-page="personnel" data-roles="admin,super_admin,procurement_planner,procurement_follower,viewer,查看者">
<span class="nav-sub-icon">&#9679;</span>
<span>人员信息管理</span>
</a>
</div>
</div>
{# 库存与出库(含子菜单): 所有人 #}
<div class="nav-group nav-order-3" data-roles="all">
<a href="#" class="nav-item nav-group-header" data-group="inventory" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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>
<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 nav-order-5" data-roles="all">
<a href="#" class="nav-item nav-group-header" data-group="stagnant-inventory" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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="M3 6h18M6 6v14h12V6M9 10v6M12 10v6M15 10v6"/><path d="M9 6V3h6v3"/>
</svg>
</span>
<span class="nav-text">呆滞库存管理</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-stagnant-inventory">
<a href="/stagnant-inventory/dashboard" class="nav-sub-item {% if page == 'stagnant_inventory_dashboard' %}active{% endif %}" data-page="stagnant_inventory_dashboard"><span class="nav-sub-icon">&#9679;</span><span>清库存数据看板</span></a>
<a href="/stagnant-inventory/detail" class="nav-sub-item {% if page == 'stagnant_inventory_detail' %}active{% endif %}" data-page="stagnant_inventory_detail"><span class="nav-sub-icon">&#9679;</span><span>呆滞品数据明细</span></a>
<a href="/stagnant-inventory/plans" class="nav-sub-item {% if page == 'stagnant_inventory_plans' %}active{% endif %}" data-page="stagnant_inventory_plans"><span class="nav-sub-icon">&#9679;</span><span>清库存方案管理</span></a>
<a href="/stagnant-inventory/lists" class="nav-sub-item {% if page == 'stagnant_inventory_lists' %}active{% endif %}" data-page="stagnant_inventory_lists"><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 nav-order-7 {% if page == 'planning' %}active{% endif %}" data-page="planning">
<span class="nav-icon nav-icon-accent">
<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 nav-order-8" data-roles="admin,super_admin,procurement_planner,procurement_follower,product_developer,operator,viewer,查看者">
<a href="#" class="nav-item nav-group-header" data-group="spot" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>
</svg>
</span>
<span class="nav-text">现货采样管理</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-spot">
<a href="/spot/dashboard" class="nav-sub-item {% if page == 'spot_dashboard' %}active{% endif %}" data-page="spot_dashboard">
<span class="nav-sub-icon">&#9679;</span>
<span>现货数据看板</span>
</a>
<a href="/spot/application" class="nav-sub-item {% if page == 'spot_application' %}active{% endif %}" data-page="spot_application">
<span class="nav-sub-icon">&#9679;</span>
<span>现货采样申请</span>
</a>
<a href="/spot/confirmation" class="nav-sub-item {% if page == 'spot_confirmation' %}active{% endif %}" data-page="spot_confirmation">
<span class="nav-sub-icon">&#9679;</span>
<span>现货采样确认</span>
</a>
<a href="/spot/sales" class="nav-sub-item {% if page == 'spot_sales' %}active{% endif %}" data-page="spot_sales">
<span class="nav-sub-icon">&#9679;</span>
<span>现货销量管理</span>
</a>
<a href="/spot/authorization" class="nav-sub-item {% if page == 'spot_authorization' %}active{% endif %}" data-page="spot_authorization">
<span class="nav-sub-icon">&#9679;</span>
<span>授权资料管理</span>
</a>
</div>
</div>
{# 采购订单(含子菜单): 所有非管理员也可查看 #}
<div class="nav-group nav-order-9" 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 nav-icon-accent">
<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="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
</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="/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>
<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="/contract-templates" class="nav-sub-item {% if page == 'contract_templates' %}active{% endif %}" data-page="contract_templates">
<span class="nav-sub-icon">&#9679;</span>
<span>合同台账管理</span>
</a>
</div>
</div>
{# 产品下单(含子菜单): 所有用户可见 #}
<div class="nav-group nav-order-6" data-roles="all">
<a href="#" class="nav-item nav-item-accent nav-group-header" data-group="product-order" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/>
</svg>
</span>
<span class="nav-text">新品下单</span>
<span class="nav-arrow">&#9662;</span>
</a>
<div class="nav-submenu" id="submenu-product-order">
<a href="/product-new" class="nav-sub-item {% if page == 'product_new' %}active{% endif %}" data-page="product_new" data-roles="all">
<span class="nav-sub-icon">&#9679;</span>
<span>新品成品管理</span>
</a>
<a href="/product-packaging" class="nav-sub-item {% if page == 'product_packaging' %}active{% endif %}" data-page="product_packaging" data-roles="all">
<span class="nav-sub-icon">&#9679;</span>
<span>新品包材管理</span>
</a>
<a href="/product-category" class="nav-sub-item {% if page == 'product_category' %}active{% endif %}" data-page="product_category" data-roles="all">
<span class="nav-sub-icon">&#9679;</span>
<span>类目管理</span>
</a>
</div>
</div>
{# 知识库: 所有用户可见 #}
<a href="/knowledge" class="nav-item nav-order-10 {% if page == 'knowledge' %}active{% endif %}" data-page="knowledge" data-roles="all">
<span class="nav-icon nav-icon-accent">
<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>
{# AI分析: 所有用户可见 #}
<a href="/ai-analysis" class="nav-item nav-order-11 {% if page == 'ai_analysis' %}active{% endif %}" data-page="ai_analysis" data-roles="all">
<span class="nav-icon nav-icon-accent">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
</span>
<span class="nav-text">AI分析</span>
</a>
{# 系统设置(含子菜单): 仅管理员 #}
<div class="nav-group nav-order-12" data-roles="admin,super_admin">
<a href="#" class="nav-item nav-group-header" data-group="sysconfig" onclick="toggleSubmenu(event)">
<span class="nav-icon nav-icon-accent">
<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="/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="/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="/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>
</div>
</div>
</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', '') }}',
permissions: {{ _user_permissions_json|safe }}
};
</script>
<script src="/static/js/marked.min.js"></script>
<script src="/static/js/dompurify.min.js"></script>
<script src="/static/js/app.js?v={{ app_js_version }}"></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 === 'super_admin') return; // 超级管理员全可见
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 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 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 startPage = page;
if (page !== 'login' && window._currentUser && window._currentUser.role !== 'super_admin') {
var perms = window._currentUser.permissions || {};
var pagePerms = perms[page] || (page === 'product_packaging' ? (perms.product_new || {}) : {});
if (pagePerms['view'] === 0 || (page === 'contract_templates' && pagePerms['view'] !== 1)) {
// 找第一个有权限的页面
var accessiblePages = ['planning','purchase','products','bom','warehouse','suppliers','knowledge','users','permissions','logs','config','login_records','personnel','contract_templates','purchase_ready','orders_domestic','orders_overseas','orders_packaging','orders_finished','inventory_finished','inventory_packaging','data_summary','data_detail','product_new','product_packaging','product_category','brand_management'];
for (var pi = 0; pi < accessiblePages.length; pi++) {
var ap = accessiblePages[pi];
var apPerms = perms[ap] || {};
if (apPerms['view'] !== 0) {
startPage = ap;
break;
}
}
}
}
window._currentPage = startPage;
loadPage(startPage);
});
</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=21').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>
<div style="position:fixed;bottom:4px;right:8px;font-size:10px;color:#ccc;z-index:99999">v202607311130</div>
</body>
</html>