```html
<!DOCTYPE html>
<html lang="zh-CN" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业审批矩阵配置平台 · BPM</title>
<script src="https://cdn.tailwindcss.com">
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.c…;
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9…; rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js">
</script>
<style>
/* ===== Theme Variables ===== */
:root {
--bg-body: #080c18;
--bg-sidebar: #0d1326;
--bg-card: rgba(18, 28, 56, 0.85);
--bg-card-solid: #111c36;
--bg-hover: rgba(79, 107, 240, 0.08);
--border-color: rgba(79, 107, 240, 0.15);
--text-primary: #eef2ff;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--accent: #4f6bf0;
--accent-glow: rgba(79, 107, 240, 0.3);
--gold: #f0b34b;
--gold-glow: rgba(240, 179, 75, 0.25);
--success: #22c55e;
--danger: #ef4444;
--sidebar-width: 260px;
--header-height: 68px;
--radius: 12px;
--radius-sm: 8px;
--transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.light {
--bg-body: #f0f4fe;
--bg-sidebar: #ffffff;
--bg-card: rgba(255, 255, 255, 0.92);
--bg-card-solid: #ffffff;
--bg-hover: rgba(79, 107, 240, 0.05);
--border-color: rgba(79, 107, 240, 0.12);
--text-primary: #0f172a;
--text-secondary: #475569;
--text-muted: #94a3b8;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'DM Sans', sans-serif;
background: var(--bg-body);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
transition: background var(--transition), color var(--transition);
}
.font-heading {
font-family: 'Plus Jakarta Sans', sans-serif;
}
/* ===== Scrollbar ===== */
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--accent);
border-radius: 4px;
}
/* ===== Layout ===== */
#app {
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/* ===== Sidebar ===== */
.sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
transition: transform var(--transition), background var(--transition), border-color var(--transition);
z-index: 50;
position: relative;
}
.sidebar::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(79, 107, 240, 0.03) 0%, transparent 60%);
pointer-events: none;
}
.sidebar-logo {
padding: 20px 20px 16px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 12px;
}
.sidebar-logo .logo-icon {
width: 38px;
height: 38px;
border-radius: 10px;
background: linear-gradient(135deg, var(--accent), #7c3aed);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #fff;
flex-shrink: 0;
box-shadow: 0 4px 16px var(--accent-glow);
}
.sidebar-logo .logo-text {
font-family: 'Plus Jakarta Sans', sans-serif;
font-weight: 700;
font-size: 15px;
letter-spacing: -0.3px;
line-height: 1.2;
color: var(--text-primary);
}
.sidebar-logo .logo-text small {
display: block;
font-weight: 400;
font-size: 10px;
letter-spacing: 1.5px;
text-transform: uppercase;
color: var(--gold);
opacity: 0.8;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 12px 10px;
}
.nav-section-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.8px;
color: var(--text-muted);
padding: 16px 14px 6px;
font-weight: 600;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: var(--radius-sm);
color: var(--text-secondary);
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
font-weight: 500;
position: relative;
}
.nav-item:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.nav-item.active {
background: rgba(79, 107, 240, 0.12);
color: var(--accent);
box-shadow: inset 3px 0 0 var(--accent);
}
.nav-item i {
width: 20px;
text-align: center;
font-size: 15px;
}
.nav-item .badge {
margin-left: auto;
background: rgba(240, 179, 75, 0.15);
color: var(--gold);
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
font-weight: 600;
}
.sidebar-footer {
padding: 14px 16px;
border-top: 1px solid var(--border-color);
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
}
.sidebar-footer .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent), var(--gold));
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #fff;
font-weight: 700;
flex-shrink: 0;
}
/* ===== Header ===== */
.header {
height: var(--header-height);
background: var(--bg-sidebar);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
padding: 0 24px;
gap: 16px;
transition: background var(--transition), border-color var(--transition);
flex-shrink: 0;
}
.header .breadcrumb {
font-size: 13px;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 6px;
}
.header .breadcrumb span {
color: var(--text-primary);
font-weight: 500;
}
.header .breadcrumb i {
font-size: 10px;
}
.header-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 12px;
}
.header-btn {
width: 36px;
height: 36px;
border-radius: 10px;
border: 1px solid var(--border-color);
background: transparent;
color: var(--text-secondary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
font-size: 15px;
}
.header-btn:hover {
background: var(--bg-hover);
color: var(--accent);
border-color: var(--accent);
}
/* ===== Main Content ===== */
.main-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
.main-content {
flex: 1;
overflow-y: auto;
padding: 24px;
position: relative;
}
/* ===== Page Transition ===== */
.page {
display: none;
animation: fadeIn 0.3s ease;
}
.page.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ===== Cards ===== */
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 20px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
transition: background var(--transition), border-color var(--transition), box-shadow 0.3s;
}
.card:hover {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.card-solid {
background: var(--bg-card-solid);
}
/* ===== Stat Card ===== */
.stat-card {
padding: 20px 24px;
border-radius: var(--radius);
background: var(--bg-card);
border: 1px solid var(--border-color);
display: flex;
align-items: flex-start;
gap: 16px;
transition: all 0.3s;
}
.stat-card:hover {
border-color: var(--accent);
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(79, 107, 240, 0.1);
}
.stat-card .stat-icon {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.stat-card .stat-value {
font-family: 'Plus Jakarta Sans', sans-serif;
font-size: 26px;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.5px;
}
.stat-card .stat-label {
font-size: 13px;
color: var(--text-secondary);
margin-top: 2px;
}
.stat-card .stat-change {
font-size: 12px;
font-weight: 600;
padding: 1px 8px;
border-radius: 6px;
margin-left: auto;
}
/* ===== Table ===== */
.table-wrap {
overflow-x: auto;
}
table.mat-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
table.mat-table thead th {
text-align: left;
padding: 10px 14px;
font-weight: 600;
color: var(--text-muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.8px;
border-bottom: 1px solid var(--border-color);
background: transparent;
}
table.mat-table tbody td {
padding: 12px 14px;
border-bottom: 1px solid var(--border-color);
color: var(--text-secondary);
transition: background 0.2s;
}
table.mat-table tbody tr:hover td {
background: var(--bg-hover);
}
table.mat-table .status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 10px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
}
/* ===== Modal ===== */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease;
}
.modal-overlay.open {
display: flex;
}
.modal-box {
background: var(--bg-card-solid);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 28px;
width: 90%;
max-width: 520px;
max-height: 85vh;
overflow-y: auto;
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.4);
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* ===== Form Elements ===== */
.form-input {
width: 100%;
padding: 10px 14px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 14px;
font-family: 'DM Sans', sans-serif;
transition: border 0.2s;
outline: none;
}
.form-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-glow);
}
.form-input::placeholder {
color: var(--text-muted);
}
.light .form-input {
background: rgba(0, 0, 0, 0.02);
}
.form-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 6px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 20px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 600;
font-family: 'DM Sans', sans-serif;
border: none;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, var(--accent), #6d28d9);
color: #fff;
box-shadow: 0 4px 16px var(--accent-glow);
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 8px 24px var(--accent-glow);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.btn-ghost:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.btn-gold {
background: linear-gradient(135deg, var(--gold), #d97706);
color: #0f172a;
box-shadow: 0 4px 16px var(--gold-glow);
}
.btn-gold:hover {
transform: translateY(-1px);
box-shadow: 0 8px 24px var(--gold-glow);
}
.btn-danger {
background: var(--danger);
color: #fff;
}
.btn-danger:hover {
background: #dc2626;
transform: translateY(-1px);
}
.btn-sm {
padding: 6px 14px;
font-size: 12px;
}
.btn-xs {
padding: 4px 10px;
font-size: 11px;
border-radius: 6px;
}
/* ===== Tree ===== */
.tree-node {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
font-size: 13px;
color: var(--text-secondary);
}
.tree-node:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.tree-node .toggle {
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
color: var(--text-muted);
transition: transform 0.2s;
font-size: 10px;
}
.tree-node .toggle.open {
transform: rotate(90deg);
}
.tree-children {
padding-left: 28px;
display: none;
}
.tree-children.open {
display: block;
}
/* ===== Toggle Switch ===== */
.toggle-switch {
width: 40px;
height: 22px;
border-radius: 11px;
background: var(--border-color);
position: relative;
cursor: pointer;
transition: background 0.3s;
flex-shrink: 0;
}
.toggle-switch.active {
background: var(--accent);
}
.toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.toggle-switch.active::after {
transform: translateX(18px);
}
/* ===== Hamburger ===== */
.hamburger {
display: none;
width: 36px;
height: 36px;
border-radius: 10px;
border: 1px solid var(--border-color);
background: transparent;
color: var(--text-secondary);
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 16px;
}
/* ===== Responsive ===== */
@media (max-width: 767px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
transform: translateX(-100%);
z-index: 100;
box-shadow: 4px 0 32px rgba(0, 0, 0, 0.4);
}
.sidebar.open {
transform: translateX(0);
}
.hamburger {
display: flex;
}
.sidebar-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 99;
display: none;
}
.sidebar-overlay.open {
display: block;
}
.main-content {
padding: 16px;
}
.header {
padding: 0 16px;
}
.stat-card .stat-value {
font-size: 20px;
}
}
@media (min-width: 768px) {
.sidebar-overlay {
display: none !important;
}
}
/* ===== Extra ===== */
.glow-text {
text-shadow: 0 0 40px rgba(79, 107, 240, 0.15);
}
.gold-glow {
box-shadow: 0 0 24px var(--gold-glow);
}
.accent-border-left {
border-left: 3px solid var(--accent);
}
.gold-border-left {
border-left: 3px solid var(--gold);
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-muted);
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.3;
}
.tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 500;
background: rgba(79, 107, 240, 0.1);
color: var(--accent);
}
.tag-gold {
background: rgba(240, 179, 75, 0.12);
color: var(--gold);
}
.tag-success {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.tag-danger {
background: rgba(239, 68, 68, 0.1);
color: var(--danger);
}
/* loading shimmer */
.shimmer {
background: linear-gradient(90deg, var(--bg-card) 25%, var(--bg-hover) 50%, var(--bg-card) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 4px;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* ===== Login Page Styles ===== */
.login-bg {
background: radial-gradient(ellipse at 20% 50%, rgba(79, 107, 240, 0.08) 0%, transparent 60%),
radial-gradient(ellipse at 80% 20%, rgba(240, 179, 75, 0.05) 0%, transparent 50%),
var(--bg-body);
}
.light .login-bg {
background: radial-gradient(ellipse at 20% 50%, rgba(79, 107, 240, 0.04) 0%, transparent 60%),
radial-gradient(ellipse at 80% 20%, rgba(240, 179, 75, 0.03) 0%, transparent 50%),
var(--bg-body);
}
/* permission checkbox group */
.perm-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 8px;
}
.perm-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
cursor: pointer;
transition: all 0.2s;
font-size: 13px;
color: var(--text-secondary);
}
.perm-item:hover {
border-color: var(--accent);
}
.perm-item.checked {
border-color: var(--accent);
background: rgba(79, 107, 240, 0.08);
color: var(--accent);
}
.perm-item input[type="checkbox"] {
accent-color: var(--accent);
}
/* Matrix grid visualization */
.matrix-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
}
.matrix-cell {
aspect-ratio: 1;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
transition: all 0.2s;
}
.matrix-cell:hover {
transform: scale(1.05);
}
</style>
</head>
<body>
<!-- ==================== APP ROOT ==================== -->
<div id="app">
<!-- Sidebar Overlay (mobile) -->
<div id="sidebarOverlay" class="sidebar-overlay" onclick="App.toggleSidebar()"></div>
<!-- ===== SIDEBAR ===== -->
<aside id="sidebar" class="sidebar">
<div class="sidebar-logo">
<div class="logo-icon"><i class="fa-solid fa-cubes"></i></div>
<div class="logo-text">
审批矩阵
<small>Configuration Platform</small>
</div>
</div>
<nav class="sidebar-nav">
<div class="nav-section-label">核心功能</div>
<a class="nav-item active" data-route="/dashboard" onclick="App.navigate('/dashboard')">
<i class="fa-solid fa-chart-pie"></i> 仪表盘
</a>
<a class="nav-item" data-route="/org" onclick="App.navigate('/org')">
<i class="fa-solid fa-sitemap"></i> 组织管理
</a>
<a class="nav-item" data-route="/user" onclick="App.navigate('/user')">
<i class="fa-solid fa-users"></i> 用户管理
<span class="badge">24</span>
</a>
<a class="nav-item" data-route="/role" onclick="App.navigate('/role')">
<i class="fa-solid fa-shield-halved"></i> 角色权限
</a>
<div class="nav-section-label" style="margin-top:8px;">审批配置</div>
<a class="nav-item" data-route="/matrix" onclick="App.navigate('/matrix')">
<i class="fa-solid fa-table-cells-large"></i> 审批矩阵
</a>
<a class="nav-item" data-route="/audit" onclick="App.navigate('/audit')">
<i class="fa-solid fa-clock-rotate-left"></i> 审计日志
</a>
<div class="nav-section-label" style="margin-top:8px;">系统</div>
<a class="nav-item" data-route="/settings" onclick="App.navigate('/settings')">
<i class="fa-solid fa-gear"></i> 系统设置
</a>
</nav>
<div class="sidebar-footer">
<div class="avatar">张</div>
<div style="flex:1;min-width:0;">
<div style="font-weight:600;font-size:13px;color:var(--text-primary);">张明远</div>
<div style="font-size:11px;color:var(--text-muted);">超级管理员</div>
</div>
<button onclick="App.logout()" class="header-btn" style="width:30px;height:30px;border-radius:8px;" title="退出登录">
<i class="fa-solid fa-right-from-bracket" style="font-size:13px;"></i>
</button>
</div>
</aside>
<!-- ===== MAIN AREA ===== -->
<div class="main-area">
<!-- Header -->
<header id="mainHeader" class="header">
<button class="hamburger" onclick="App.toggleSidebar()">
<i class="fa-solid fa-bars"></i>
</button>
<div class="breadcrumb">
<i class="fa-solid fa-house"></i>
<i class="fa-solid fa-chevron-right"></i>
<span id="pageTitle">仪表盘</span>
</div>
<div class="header-right">
<button class="header-btn" onclick="App.toggleTheme()" title="切换主题">
<i id="themeIcon" class="fa-solid fa-moon"></i>
</button>
<button class="header-btn" title="通知">
<i class="fa-solid fa-bell"></i>
</button>
<button class="header-btn" title="帮助">
<i class="fa-solid fa-circle-question"></i>
</button>
</div>
</header>
<!-- Content -->
<main class="main-content" id="mainContent">
<!-- ===== PAGE: LOGIN ===== -->
<div id="page-login" class="page" style="display:none;">
<div class="login-bg" style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;">
<div class="card" style="max-width:400px;width:100%;padding:36px 32px;">
<div style="text-align:center;margin-bottom:28px;">
<div style="width:56px;height:56px;border-radius:16px;background:linear-gradient(135deg,var(--accent),#7c3aed);display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:26px;color:#fff;box-shadow:0 8px 32px var(--accent-glow);">
<i class="fa-solid fa-cubes"></i>
</div>
<h2 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">企业审批矩阵</h2>
<p style="color:var(--text-muted);font-size:13px;margin-top:4px;">请登录以继续使用配置平台</p>
</div>
<form onsubmit="App.handleLogin(event)">
<div style="margin-bottom:16px;">
<label class="form-label">用户名</label>
<input type="text" class="form-input" id="loginUser" value="admin" placeholder="输入用户名" required>
</div>
<div style="margin-bottom:20px;">
<label class="form-label">密码</label>
<input type="password" class="form-input" id="loginPass" value="123456" placeholder="输入密码" required>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;justify-content:center;padding:12px;">
<i class="fa-solid fa-arrow-right-to-bracket"></i> 登录系统
</button>
<p style="text-align:center;margin-top:16px;font-size:12px;color:var(--text-muted);">
<i class="fa-solid fa-lock-open" style="margin-right:4px;"></i>
演示账号: admin / 123456
</p>
</form>
</div>
</div>
</div>
<!-- ===== PAGE: DASHBOARD ===== -->
<div id="page-dashboard" class="page active">
<div style="margin-bottom:24px;">
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">审批矩阵概览</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">实时监控企业审批流程与权限配置状态</p>
</div>
<!-- Stat Cards -->
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:24px;">
<div class="stat-card">
<div class="stat-icon" style="background:rgba(79,107,240,0.12);color:var(--accent);">
<i class="fa-solid fa-route"></i>
</div>
<div style="flex:1;">
<div class="stat-value">1,284</div>
<div class="stat-label">审批流程总数</div>
</div>
<span class="stat-change" style="background:rgba(34,197,94,0.1);color:var(--success);">+12.5%</span>
</div>
<div class="stat-card">
<div class="stat-icon" style="background:rgba(240,179,75,0.12);color:var(--gold);">
<i class="fa-solid fa-layer-group"></i>
</div>
<div style="flex:1;">
<div class="stat-value">36</div>
<div class="stat-label">审批矩阵规则</div>
</div>
<span class="stat-change" style="background:rgba(79,107,240,0.1);color:var(--accent);">+3</span>
</div>
<div class="stat-card">
<div class="stat-icon" style="background:rgba(34,197,94,0.1);color:var(--success);">
<i class="fa-solid fa-user-check"></i>
</div>
<div style="flex:1;">
<div class="stat-value">142</div>
<div class="stat-label">审批人员</div>
</div>
<span class="stat-change" style="background:rgba(34,197,94,0.1);color:var(--success);">+8</span>
</div>
<div class="stat-card">
<div class="stat-icon" style="background:rgba(239,68,68,0.1);color:var(--danger);">
<i class="fa-solid fa-clock"></i>
</div>
<div style="flex:1;">
<div class="stat-value">23</div>
<div class="stat-label">待处理审批</div>
</div>
<span class="stat-change" style="background:rgba(239,68,68,0.1);color:var(--danger);">+5</span>
</div>
</div>
<!-- Charts Row -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:24px;">
<div class="card" style="padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;">审批趋势(近7日)</h3>
<span class="tag">周环比 +8.2%</span>
</div>
<div id="chartTrend" style="height:240px;"></div>
</div>
<div class="card" style="padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;">审批类型分布</h3>
<span class="tag-gold tag">按数量</span>
</div>
<div id="chartPie" style="height:240px;"></div>
</div>
</div>
<!-- Matrix Heatmap + Activity -->
<div style="display:grid;grid-template-columns:1.6fr 1fr;gap:18px;">
<div class="card" style="padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;">审批金额矩阵</h3>
<span class="tag">单位:万元</span>
</div>
<div id="chartMatrix" style="height:220px;"></div>
</div>
<div class="card" style="padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;">最近操作</h3>
<a href="#" onclick="App.navigate('/audit');return false;" style="font-size:12px;color:var(--accent);">查看全部</a>
</div>
<div style="display:flex;flex-direction:column;gap:10px;">
<div style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-hover);">
<div style="width:28px;height:28px;border-radius:8px;background:rgba(79,107,240,0.12);display:flex;align-items:center;justify-content:center;color:var(--accent);font-size:12px;"><i class="fa-solid fa-pen-to-square"></i></div>
<div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:500;color:var(--text-primary);">修改了「采购合同」审批金额上限</div>
<div style="font-size:11px;color:var(--text-muted);">张明远 · 12 分钟前</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-hover);">
<div style="width:28px;height:28px;border-radius:8px;background:rgba(240,179,75,0.12);display:flex;align-items:center;justify-content:center;color:var(--gold);font-size:12px;"><i class="fa-solid fa-user-plus"></i></div>
<div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:500;color:var(--text-primary);">新增审批人员「李雪婷」</div>
<div style="font-size:11px;color:var(--text-muted);">王磊 · 1 小时前</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-hover);">
<div style="width:28px;height:28px;border-radius:8px;background:rgba(34,197,94,0.1);display:flex;align-items:center;justify-content:center;color:var(--success);font-size:12px;"><i class="fa-solid fa-check"></i></div>
<div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:500;color:var(--text-primary);">审批矩阵 v2.3 配置已生效</div>
<div style="font-size:11px;color:var(--text-muted);">系统 · 3 小时前</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;background:var(--bg-hover);">
<div style="width:28px;height:28px;border-radius:8px;background:rgba(239,68,68,0.1);display:flex;align-items:center;justify-content:center;color:var(--danger);font-size:12px;"><i class="fa-solid fa-trash-can"></i></div>
<div style="flex:1;min-width:0;">
<div style="font-size:13px;font-weight:500;color:var(--text-primary);">删除过期审批规则 #A-0341</div>
<div style="font-size:11px;color:var(--text-muted);">赵刚 · 5 小时前</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ===== PAGE: ORG ===== -->
<div id="page-org" class="page">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
<div>
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">组织管理</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">管理企业组织架构,支持拖拽调整层级关系</p>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-ghost btn-sm" onclick="alert('导出功能开发中')"><i class="fa-solid fa-download"></i> 导出</button>
<button class="btn btn-primary btn-sm" onclick="alert('新增部门功能开发中')"><i class="fa-solid fa-plus"></i> 新增部门</button>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1.6fr;gap:18px;">
<div class="card" style="padding:16px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-sitemap" style="color:var(--accent);margin-right:8px;"></i>组织树
</h3>
<div id="orgTree" style="min-height:360px;">
<!-- rendered by JS -->
</div>
</div>
<div class="card" style="padding:16px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-circle-info" style="color:var(--gold);margin-right:8px;"></i>部门详情
</h3>
<div id="orgDetail">
<div style="color:var(--text-muted);text-align:center;padding:60px 0;">
<i class="fa-solid fa-hand-pointer" style="font-size:32px;opacity:0.3;display:block;margin-bottom:12px;"></i>
请点击左侧组织节点查看详情
</div>
</div>
</div>
</div>
<div class="card" style="margin-top:18px;padding:16px;">
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<span style="font-size:13px;color:var(--text-secondary);"><i class="fa-solid fa-circle-info" style="color:var(--accent);margin-right:4px;"></i> 拖拽提示:</span>
<span style="font-size:12px;color:var(--text-muted);">选中节点后拖拽到目标父节点即可完成层级变更,系统将自动保存。</span>
<span class="tag tag-success"><i class="fa-solid fa-check"></i> 已启用实时保存</span>
</div>
</div>
</div>
<!-- ===== PAGE: USER ===== -->
<div id="page-user" class="page">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
<div>
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">用户管理</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">管理审批系统用户账号与基本信息</p>
</div>
<div style="display:flex;gap:8px;">
<button class="btn btn-ghost btn-sm" onclick="alert('批量导入功能开发中')"><i class="fa-solid fa-upload"></i> 批量导入</button>
<button class="btn btn-primary btn-sm" onclick="App.openUserModal()"><i class="fa-solid fa-plus"></i> 新建用户</button>
</div>
</div>
<!-- Search -->
<div class="card" style="padding:14px 16px;margin-bottom:16px;">
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<div style="flex:1;min-width:160px;position:relative;">
<i class="fa-solid fa-search" style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:13px;"></i>
<input type="text" class="form-input" id="userSearch" placeholder="搜索用户名、邮箱、手机..." style="padding-left:34px;" oninput="App.filterUsers()">
</div>
<select class="form-input" style="width:auto;min-width:120px;" onchange="App.filterUsers()">
<option value="">全部状态</option>
<option value="active">启用</option>
<option value="disabled">禁用</option>
</select>
<select class="form-input" style="width:auto;min-width:120px;" onchange="App.filterUsers()">
<option value="">全部部门</option>
<option value="技术部">技术部</option>
<option value="财务部">财务部</option>
<option value="人事部">人事部</option>
<option value="市场部">市场部</option>
<option value="运营部">运营部</option>
</select>
</div>
</div>
<!-- Table -->
<div class="card" style="padding:0;overflow:hidden;">
<div class="table-wrap">
<table class="mat-table" id="userTable">
<thead>
<tr>
<th style="width:40px;"><input type="checkbox" style="accent-color:var(--accent);"></th>
<th>用户名</th>
<th>姓名</th>
<th>部门</th>
<th>角色</th>
<th>状态</th>
<th>最后登录</th>
<th style="text-align:right;">操作</th>
</tr>
</thead>
<tbody id="userTableBody">
<!-- rendered by JS -->
</tbody>
</table>
</div>
<!-- Pagination -->
<div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-top:1px solid var(--border-color);flex-wrap:wrap;gap:8px;">
<span style="font-size:12px;color:var(--text-muted);">共 <strong id="userTotal" style="color:var(--text-primary);">24</strong> 条记录</span>
<div style="display:flex;gap:4px;">
<button class="btn btn-ghost btn-xs"><i class="fa-solid fa-chevron-left"></i></button>
<button class="btn btn-primary btn-xs" style="background:var(--accent);color:#fff;">1</button>
<button class="btn btn-ghost btn-xs">2</button>
<button class="btn btn-ghost btn-xs">3</button>
<button class="btn btn-ghost btn-xs"><i class="fa-solid fa-chevron-right"></i></button>
</div>
</div>
</div>
</div>
<!-- ===== PAGE: ROLE ===== -->
<div id="page-role" class="page">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
<div>
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">角色权限</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">基于RBAC模型管理角色与菜单按钮级权限</p>
</div>
<button class="btn btn-primary btn-sm" onclick="alert('新增角色功能开发中')"><i class="fa-solid fa-plus"></i> 新增角色</button>
</div>
<div style="display:grid;grid-template-columns:1fr 2fr;gap:18px;">
<!-- Role List -->
<div class="card" style="padding:16px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-shield-halved" style="color:var(--accent);margin-right:8px;"></i>角色列表
</h3>
<div id="roleList" style="display:flex;flex-direction:column;gap:4px;">
<!-- rendered by JS -->
</div>
</div>
<!-- Permission Editor -->
<div class="card" style="padding:16px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;">
<i class="fa-solid fa-lock-open" style="color:var(--gold);margin-right:8px;"></i>
<span id="permRoleTitle">超级管理员</span>
<span style="font-size:12px;font-weight:400;color:var(--text-muted);margin-left:8px;">权限配置</span>
</h3>
<button class="btn btn-gold btn-xs" onclick="alert('权限已保存')"><i class="fa-solid fa-floppy-disk"></i> 保存</button>
</div>
<div style="margin-bottom:12px;">
<label class="form-label">菜单权限</label>
<div class="perm-grid" id="permMenus">
<!-- rendered by JS -->
</div>
</div>
<div>
<label class="form-label">操作权限(按钮级)</label>
<div class="perm-grid" id="permActions">
<!-- rendered by JS -->
</div>
</div>
</div>
</div>
</div>
<!-- ===== PAGE: MATRIX ===== -->
<div id="page-matrix" class="page">
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:20px;flex-wrap:wrap;gap:12px;">
<div>
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">审批矩阵配置</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">可视化配置各业务类型的审批金额层级与路线</p>
</div>
<button class="btn btn-gold btn-sm" onclick="alert('矩阵配置已保存')"><i class="fa-solid fa-floppy-disk"></i> 保存全部</button>
</div>
<div class="card" style="padding:16px;margin-bottom:16px;">
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px;">
<select class="form-input" style="width:auto;min-width:140px;">
<option>全部业务类型</option>
<option>采购合同</option>
<option>销售订单</option>
<option>费用报销</option>
<option>项目立项</option>
<option>人事变动</option>
</select>
<select class="form-input" style="width:auto;min-width:140px;">
<option>全部审批层级</option>
<option>一级审批</option>
<option>二级审批</option>
<option>三级审批</option>
</select>
<span class="tag tag-gold"><i class="fa-regular fa-eye"></i> 只读模式</span>
</div>
<div class="matrix-grid">
<div class="matrix-cell" style="background:rgba(79,107,240,0.2);color:var(--accent);">≤5万</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.25);color:var(--accent);">5-20万</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.35);color:var(--accent);">20-50万</div>
<div class="matrix-cell" style="background:rgba(240,179,75,0.25);color:var(--gold);">50-200万</div>
<div class="matrix-cell" style="background:rgba(239,68,68,0.2);color:var(--danger);">≥200万</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.15);color:var(--text-secondary);">部门经理</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.15);color:var(--text-secondary);">部门经理</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.2);color:var(--accent);">总监</div>
<div class="matrix-cell" style="background:rgba(240,179,75,0.2);color:var(--gold);">VP</div>
<div class="matrix-cell" style="background:rgba(239,68,68,0.2);color:var(--danger);">CEO</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.1);color:var(--text-muted);">财务审核</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.15);color:var(--text-secondary);">财务审核</div>
<div class="matrix-cell" style="background:rgba(79,107,240,0.2);color:var(--accent);">+法务</div>
<div class="matrix-cell" style="background:rgba(240,179,75,0.2);color:var(--gold);">+董事会</div>
<div class="matrix-cell" style="background:rgba(239,68,68,0.25);color:var(--danger);">+董事会</div>
</div>
<div style="display:flex;gap:16px;margin-top:14px;flex-wrap:wrap;font-size:12px;color:var(--text-muted);">
<span><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:rgba(79,107,240,0.2);vertical-align:middle;margin-right:4px;"></span> 一级审批</span>
<span><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:rgba(240,179,75,0.25);vertical-align:middle;margin-right:4px;"></span> 二级审批</span>
<span><span style="display:inline-block;width:12px;height:12px;border-radius:3px;background:rgba(239,68,68,0.2);vertical-align:middle;margin-right:4px;"></span> 三级审批</span>
</div>
</div>
<div class="card" style="padding:16px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:10px;">
<i class="fa-solid fa-clock-rotate-left" style="color:var(--accent);margin-right:8px;"></i>最近矩阵变更记录
</h3>
<div style="display:flex;flex-direction:column;gap:6px;">
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid var(--border-color);font-size:13px;">
<span>「采购合同」审批金额上限调整 50万 → 200万</span>
<span style="color:var(--text-muted);font-size:12px;">2026-05-03 14:20</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid var(--border-color);font-size:13px;">
<span>「费用报销」新增三级审批路线</span>
<span style="color:var(--text-muted);font-size:12px;">2026-05-02 09:15</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;padding:6px 0;font-size:13px;">
<span>「销售订单」审批层级从二级调整为三级</span>
<span style="color:var(--text-muted);font-size:12px;">2026-04-30 16:42</span>
</div>
</div>
</div>
</div>
<!-- ===== PAGE: AUDIT ===== -->
<div id="page-audit" class="page">
<div style="margin-bottom:20px;">
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">审计日志</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">记录所有审批矩阵配置变更操作,确保可追溯性</p>
</div>
<div class="card" style="padding:16px;margin-bottom:16px;">
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<input type="text" class="form-input" style="flex:1;min-width:160px;" placeholder="搜索操作内容、操作人...">
<input type="date" class="form-input" style="width:auto;min-width:130px;" value="2026-04-26">
<input type="date" class="form-input" style="width:auto;min-width:130px;" value="2026-05-03">
<button class="btn btn-primary btn-sm"><i class="fa-solid fa-magnifying-glass"></i> 查询</button>
<button class="btn btn-ghost btn-sm"><i class="fa-solid fa-rotate"></i> 重置</button>
</div>
</div>
<div class="card" style="padding:0;overflow:hidden;">
<div class="table-wrap">
<table class="mat-table">
<thead>
<tr>
<th>时间</th>
<th>操作人</th>
<th>操作类型</th>
<th>操作内容</th>
<th>IP 地址</th>
<th style="text-align:right;">操作</th>
</tr>
</thead>
<tbody>
<tr><td>2026-05-03 14:20</td><td>张明远</td><td><span class="tag">配置修改</span></td><td>修改采购合同审批金额上限</td><td>192.168.1.100</td><td style="text-align:right;"><button class="btn btn-ghost btn-xs">详情</button></td></tr>
<tr><td>2026-05-02 09:15</td><td>李雪婷</td><td><span class="tag-gold tag">新增</span></td><td>费用报销新增三级审批路线</td><td>192.168.1.105</td><td style="text-align:right;"><button class="btn btn-ghost btn-xs">详情</button></td></tr>
<tr><td>2026-04-30 16:42</td><td>王磊</td><td><span class="tag">配置修改</span></td><td>销售订单审批层级调整</td><td>192.168.1.102</td><td style="text-align:right;"><button class="btn btn-ghost btn-xs">详情</button></td></tr>
<tr><td>2026-04-29 11:03</td><td>赵刚</td><td><span class="tag-danger tag">删除</span></td><td>删除过期审批规则 A-0341</td><td>192.168.1.108</td><td style="text-align:right;"><button class="btn btn-ghost btn-xs">详情</button></td></tr>
<tr><td>2026-04-28 08:30</td><td>张明远</td><td><span class="tag-success tag">发布</span></td><td>审批矩阵 v2.3 配置发布生效</td><td>192.168.1.100</td><td style="text-align:right;"><button class="btn btn-ghost btn-xs">详情</button></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- ===== PAGE: SETTINGS ===== -->
<div id="page-settings" class="page">
<div style="margin-bottom:20px;">
<h1 class="font-heading" style="font-size:22px;font-weight:700;color:var(--text-primary);">系统设置</h1>
<p style="color:var(--text-muted);font-size:14px;margin-top:4px;">配置系统全局参数与运行模式</p>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;">
<div class="card" style="padding:20px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-palette" style="color:var(--accent);margin-right:8px;"></i>主题设置
</h3>
<div style="display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">深色模式</span>
<div class="toggle-switch active" id="themeToggle" onclick="App.toggleTheme()"></div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">跟随系统</span>
<div class="toggle-switch" onclick="this.classList.toggle('active')"></div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">紧凑模式</span>
<div class="toggle-switch" onclick="this.classList.toggle('active')"></div>
</div>
</div>
</div>
<div class="card" style="padding:20px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-sliders" style="color:var(--gold);margin-right:8px;"></i>审批全局参数
</h3>
<div style="display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">自动归档天数</span>
<span style="font-size:14px;font-weight:600;color:var(--accent);">90 天</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">审批超时提醒</span>
<span style="font-size:14px;font-weight:600;color:var(--accent);">48 小时</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">最大审批层级</span>
<span style="font-size:14px;font-weight:600;color:var(--accent);">5 级</span>
</div>
</div>
</div>
<div class="card" style="padding:20px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-shield" style="color:var(--success);margin-right:8px;"></i>安全设置
</h3>
<div style="display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">登录超时 (分钟)</span>
<span style="font-size:14px;font-weight:600;color:var(--accent);">30</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">密码复杂度</span>
<span class="tag tag-success">强</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:14px;">操作审计</span>
<span class="tag tag-success">已开启</span>
</div>
</div>
</div>
<div class="card" style="padding:20px;">
<h3 class="font-heading" style="font-size:15px;font-weight:600;margin-bottom:14px;">
<i class="fa-solid fa-circle-info" style="color:var(--text-muted);margin-right:8px;"></i>系统信息
</h3>
<div style="display:flex;flex-direction:column;gap:8px;font-size:13px;color:var(--text-secondary);">
<div style="display:flex;justify-content:space-between;"><span>版本</span><span style="color:var(--text-primary);font-weight:500;">v2.3.1</span></div>
<div style="display:flex;justify-content:space-between;"><span>最后更新</span><span style="color:var(--text-primary);font-weight:500;">2026-04-28</span></div>
<div style="display:flex;justify-content:space-between;"><span>数据库</span><span style="color:var(--text-primary);font-weight:500;">PostgreSQL 15</span></div>
<div style="display:flex;justify-content:space-between;"><span>运行环境</span><span style="color:var(--text-primary);font-weight:500;">生产环境</span></div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- ===== MODAL: User Create/Edit ===== -->
<div id="userModal" class="modal-overlay">
<div class="modal-box">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
<h2 class="font-heading" style="font-size:18px;font-weight:700;" id="userModalTitle">新建用户</h2>
<button class="header-btn" onclick="App.closeUserModal()" style="width:32px;height:32px;">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<form onsubmit="App.saveUser(event)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
<div>
<label class="form-label">用户名</label>
<input type="text" class="form-input" id="f_username" placeholder="登录用户名" required>
</div>
<div>
<label class="form-label">姓名</label>
<input type="text" class="form-input" id="f_name" placeholder="真实姓名" required>
</div>
<div>
<label class="form-label">邮箱</label>
<input type="email" class="form-input" id="f_email" placeholder="email@company.com">
</div>
<div>
<label class="form-label">手机</label>
<input type="text" class="form-input" id="f_phone" placeholder="13800138000">
</div>
<div>
<label class="form-label">部门</label>
<select class="form-input" id="f_dept">
<option>技术部</option>
<option>财务部</option>
<option>人事部</option>
<option>市场部</option>
<option>运营部</option>
</select>
</div>
<div>
<label class="form-label">角色</label>
<select class="form-input" id="f_role">
<option>超级管理员</option>
<option>审批管理员</option>
<option>业务审批人</option>
<option>财务审批人</option>
<option>普通用户</option>
</select>
</div>
</div>
<div style="margin-top:20px;display:flex;gap:10px;justify-content:flex-end;">
<button type="button" class="btn btn-ghost" onclick="App.closeUserModal()">取消</button>
<button type="submit" class="btn btn-primary"><i class="fa-solid fa-check"></i> 保存</button>
</div>
</form>
</div>
</div>
<!-- ==================== JAVASCRIPT ==================== -->
<script>
'use strict';
/* ================================================================
APP - 主控制器 (IIFE)
================================================================ */
const App = (() => {
// --- State ---
const state = {
currentRoute: '/dashboard',
isDark: true,
isLoggedIn: false,
sidebarOpen: false,
users: [],
roles: [],
orgData: null,
charts: {},
userModalMode: 'create',
editingUserId: null,
};
// --- Mock Data ---
const MOCK_USERS = [
{ id: 1, username: 'zhangming', name: '张明远', dept: '技术部', role: '超级管理员', status: 'active',
lastLogin: '2026-05-03 14:20', email: 'zhang@corp.com', phone: '13800001001' },
{ id: 2, username: 'lixueting', name: '李雪婷', dept: '财务部', role: '财务审批人', status: 'active',
lastLogin: '2026-05-03 11:30', email: 'li@corp.com', phone: '13800001002' },
{ id: 3, username: 'wanglei', name: '王磊', dept: '技术部', role: '审批管理员', status: 'active',
lastLogin: '2026-05-03 09:45', email: 'wang@corp.com', phone: '13800001003' },
{ id: 4, username: 'zhaogang', name: '赵刚', dept: '人事部', role: '业务审批人', status: 'active',
lastLogin: '2026-05-02 17:20', email: 'zhao@corp.com', phone: '13800001004' },
{ id: 5, username: 'sunli', name: '孙丽', dept: '市场部', role: '业务审批人', status: 'disabled',
lastLogin: '2026-04-28 10:00', email: 'sun@corp.com', phone: '13800001005' },
{ id: 6, username: 'zhouwei', name: '周伟', dept: '运营部', role: '普通用户', status: 'active',
lastLogin: '2026-05-01 08:15', email: 'zhou@corp.com', phone: '13800001006' },
{ id: 7, username: 'chenhong', name: '陈红', dept: '财务部', role: '财务审批人', status: 'active',
lastLogin: '2026-04-30 14:50', email: 'chen@corp.com', phone: '13800001007' },
{ id: 8, username: 'liuwei', name: '刘伟', dept: '技术部', role: '审批管理员', status: 'active',
lastLogin: '2026-05-02 16:30', email: 'liu@corp.com', phone: '13800001008' },
];
const MOCK_ROLES = [
{ id: 1, name: '超级管理员', desc: '系统最高权限', userCount: 2, color: 'var(--accent)' },
{ id: 2, name: '审批管理员', desc: '配置审批矩阵规则', userCount: 5, color: 'var(--gold)' },
{ id: 3, name: '业务审批人', desc: '业务单据审批操作', userCount: 8, color: '#22c55e' },
{ id: 4, name: '财务审批人', desc: '财务类单据审批', userCount: 4, color: '#f97316' },
{ id: 5, name: '普通用户', desc: '基础访问权限', userCount: 5, color: '#94a3b8' },
];
const MOCK_MENUS = ['仪表盘', '组织管理', '用户管理', '角色权限', '审批矩阵', '审计日志', '系统设置'];
const MOCK_ACTIONS = ['创建', '编辑', '删除', '查看', '导出', '导入', '发布', '审批'];
// --- DOM refs ---
const $ = (s) => document.querySelector(s);
const $$ = (s) => document.querySelectorAll(s);
const el = {
sidebar: $('#sidebar'),
overlay: $('#sidebarOverlay'),
mainContent: $('#mainContent'),
pageTitle: $('#pageTitle'),
themeIcon: $('#themeIcon'),
themeToggle: $('#themeToggle'),
userTableBody: $('#userTableBody'),
userTotal: $('#userTotal'),
userModal: $('#userModal'),
userModalTitle: $('#userModalTitle'),
roleList: $('#roleList'),
permRoleTitle: $('#permRoleTitle'),
permMenus: $('#permMenus'),
permActions: $('#permActions'),
orgTree: $('#orgTree'),
orgDetail: $('#orgDetail'),
};
/* ============================================================
Routing
============================================================ */
const routes = {
'/dashboard': { title: '仪表盘', auth: true },
'/org': { title: '组织管理', auth: true },
'/user': { title: '用户管理', auth: true },
'/role': { title: '角色权限', auth: true },
'/matrix': { title: '审批矩阵', auth: true },
'/audit': { title: '审计日志', auth: true },
'/settings': { title: '系统设置', auth: true },
};
function getRoute() {
const hash = location.hash.replace('#', '') || '/dashboard';
return hash in routes ? hash : '/dashboard';
}
function navigate(route) {
if (!(route in routes)) route = '/dashboard';
if (routes[route].auth && !state.isLoggedIn) {
route = '/login';
location.hash = '#/login';
}
state.currentRoute = route;
location.hash = '#' + route;
renderPage(route);
updateNav(route);
el.pageTitle.textContent = routes[route]?.title || '未知页面';
// close sidebar on mobile
if (window.innerWidth < 768) {
toggleSidebar(false);
}
}
function renderPage(route) {
$$('.page').forEach(p => p.classList.remove('active'));
const page = document.getElementById('page-' + route.replace('/', ''));
if (page) {
page.classList.add('active');
// Trigger chart render if dashboard
if (route === '/dashboard') initDashboardCharts();
if (route === '/org') initOrgTree();
if (route === '/user') renderUserTable();
if (route === '/role') renderRoles();
}
}
function updateNav(route) {
$$('.nav-item').forEach(el => {
el.classList.toggle('active', el.dataset.route === route);
});
}
/* ============================================================
Theme
============================================================ */
function initTheme() {
const saved = localStorage.getItem('app-theme');
if (saved === 'light') {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
state.isDark = false;
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
state.isDark = true;
}
updateThemeUI();
}
function toggleTheme() {
state.isDark = !state.isDark;
if (state.isDark) {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
localStorage.setItem('app-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light');
localStorage.setItem('app-theme', 'light');
}
updateThemeUI();
// Re-render charts with new theme
if (state.currentRoute === '/dashboard') initDashboardCharts();
}
function updateThemeUI() {
const icon = el.themeIcon;
if (icon) {
icon.className = state.isDark ? 'fa-solid fa-moon' : 'fa-solid fa-sun';
}
const toggle = el.themeToggle;
if (toggle) {
toggle.classList.toggle('active', state.isDark);
}
}
/* ============================================================
Sidebar
============================================================ */
function toggleSidebar(force) {
state.sidebarOpen = force !== undefined ? force : !state.sidebarOpen;
el.sidebar.classList.toggle('open', state.sidebarOpen);
el.overlay.classList.toggle('open', state.sidebarOpen);
}
/* ============================================================
Auth
============================================================ */
function handleLogin(e) {
e.preventDefault();
const user = $('#loginUser').value.trim();
const pass = $('#loginPass').value.trim();
if (user && pass) {
// Simulate login
state.isLoggedIn = true;
localStorage.setItem('sstoken', 'mock-token-' + Date.now());
localStorage.setItem('user', JSON.stringify({ name: '张明远', role: '超级管理员' }));
navigate('/dashboard');
updateUserInfo();
} else {
alert('请输入用户名和密码');
}
}
function logout() {
state.isLoggedIn = false;
localStorage.removeItem('sstoken');
localStorage.removeItem('user');
navigate('/login');
}
function checkAuth() {
const token = localStorage.getItem('sstoken');
if (token) {
state.isLoggedIn = true;
updateUserInfo();
const route = getRoute();
if (route === '/login') navigate('/dashboard');
else navigate(route);
} else {
state.isLoggedIn = false;
navigate('/login');
}
}
function updateUserInfo() {
const userData = localStorage.getItem('user');
if (userData) {
try {
const u = JSON.parse(userData);
const footer = document.querySelector('.sidebar-footer');
if (footer) {
const avatar = footer.querySelector('.avatar');
const nameEl = footer.querySelector('div>div:first-child');
const roleEl = footer.querySelector('div>div:last-child');
if (avatar) avatar.textContent = u.name.charAt(0);
if (nameEl) nameEl.textContent = u.name;
if (roleEl) roleEl.textContent = u.role;
}
} catch (e) {}
}
}
/* ============================================================
Dashboard Charts
============================================================ */
function initDashboardCharts() {
if (typeof echarts === 'undefined') return;
const isDark = state.isDark;
const textColor = isDark ? '#94a3b8' : '#475569';
const axisColor = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.06)';
// Trend chart
const trendDom = document.getElementById('chartTrend');
if (trendDom && !trendDom._echarts) {
const chart = echarts.init(trendDom);
state.charts.trend = chart;
trendDom._echarts = true;
chart.setOption({
tooltip: { trigger: 'axis', backgroundColor: isDark ? '#1a1a2e' : '#fff',
borderColor: 'rgba(79,107,240,0.2)', textStyle: { color: textColor, fontSize: 12 } },
grid: { left: 40, right: 10, top: 20, bottom: 20 },
xAxis: { type: 'category', data: ['04-27', '04-28', '04-29', '04-30', '05-01', '05-02', '05-03'],
axisLine: { lineStyle: { color: axisColor } }, axisLabel: { color: textColor, fontSize: 11 } },
yAxis: { type: 'value', splitLine: { lineStyle: { color: axisColor, type: 'dashed' } },
axisLabel: { color: textColor, fontSize: 11 } },
series: [{
data: [28, 35, 42, 38, 45, 52, 38],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#4f6bf0', width: 2.5 },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(79,107,240,0.3)' },
{ offset: 1, color: 'rgba(79,107,240,0)' }
]
}
},
itemStyle: { color: '#4f6bf0' }
}]
});
} else if (trendDom && trendDom._echarts) {
// update if needed
}
// Pie chart
const pieDom = document.getElementById('chartPie');
if (pieDom && !pieDom._echarts) {
const chart = echarts.init(pieDom);
state.charts.pie = chart;
pieDom._echarts = true;
chart.setOption({
tooltip: { trigger: 'item', backgroundColor: isDark ? '#1a1a2e' : '#fff',
borderColor: 'rgba(79,107,240,0.2)', textStyle: { color: textColor, fontSize: 12 } },
series: [{
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: true,
label: { show: true, color: textColor, fontSize: 11, formatter: '{b}\n{d}%' },
emphasis: { label: { show: true, fontSize: 13, fontWeight: 'bold' } },
data: [
{ value: 486, name: '采购合同', itemStyle: { color: '#4f6bf0' } },
{ value: 352, name: '费用报销', itemStyle: { color: '#f0b34b' } },
{ value: 278, name: '销售订单', itemStyle: { color: '#22c55e' } },
{ value: 124, name: '项目立项', itemStyle: { color: '#f97316' } },
{ value: 44, name: '人事变动', itemStyle: { color: '#ef4444' } },
]
}]
});
}
// Matrix heatmap
const matrixDom = document.getElementById('chartMatrix');
if (matrixDom && !matrixDom._echarts) {
const chart = echarts.init(matrixDom);
state.charts.matrix = chart;
matrixDom._echarts = true;
const hours = ['采购', '销售', '费用', '项目', '人事'];
const days = ['部门经理', '总监', 'VP', 'CEO', '董事会'];
const data = [];
for (let i = 0; i < hours.length; i++) {
for (let j = 0; j < days.length; j++) {
data.push([j, i, Math.floor(Math.random() * 80) + 20]);
}
}
chart.setOption({
tooltip: {
position: 'top',
formatter: function(p) {
return days[p.data[0]] + ' - ' + hours[p.data[1]] + '<br/>审批量: ' + p.data[2];
},
backgroundColor: isDark ? '#1a1a2e' : '#fff',
borderColor: 'rgba(79,107,240,0.2)',
textStyle: { color: textColor, fontSize: 12 }
},
grid: { left: 70, right: 10, top: 10, bottom: 30 },
xAxis: { type: 'category', data: days, splitArea: { show: true },
axisLabel: { color: textColor, fontSize: 10 }, axisLine: { show: false } },
yAxis: { type: 'category', data: hours, splitArea: { show: true },
axisLabel: { color: textColor, fontSize: 10 }, axisLine: { show: false } },
visualMap: {
min: 0,
max: 100,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: 0,
inRange: { color: ['rgba(79,107,240,0.1)', '#4f6bf0', '#f0b34b', '#ef4444'] },
textStyle: { color: textColor }
},
series: [{
type: 'heatmap',
data: data,
label: { show: false },
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)' } }
}]
});
}
}
/* ============================================================
User Management
============================================================ */
function renderUserTable(filter) {
const data = filter ? state.users.filter(u => {
const q = filter.toLowerCase();
return u.username.toLowerCase().includes(q) || u.name.includes(q) || u.email.toLowerCase().includes(q);
}) : state.users;
const tbody = el.userTableBody;
if (!tbody) return;
if (data.length === 0) {
tbody.innerHTML =
`<tr><td colspan="8" style="text-align:center;padding:40px;color:var(--text-muted);">暂无数据</td></tr>`;
if (el.userTotal) el.userTotal.textContent = '0';
return;
}
tbody.innerHTML = data.map(u => `
<tr>
<td><input type="checkbox" style="accent-color:var(--accent);"></td>
<td style="font-weight:500;color:var(--text-primary);">${u.username}</td>
<td>${u.name}</td>
<td>${u.dept}</td>
<td><span class="tag">${u.role}</span></td>
<td><span class="status-badge ${u.status === 'active' ? 'tag-success' : 'tag-danger'}">
<i class="fa-solid ${u.status === 'active' ? 'fa-circle' : 'fa-circle'}"></i>
${u.status === 'active' ? '启用' : '禁用'}
</span></td>
<td style="font-size:12px;">${u.lastLogin}</td>
<td style="text-align:right;">
<button class="btn btn-ghost btn-xs" onclick="App.editUser(${u.id})"><i class="fa-solid fa-pen"></i></button>
<button class="btn btn-ghost btn-xs" onclick="App.deleteUser(${u.id})"><i class="fa-solid fa-trash-can" style="color:var(--danger);"></i></button>
</td>
</tr>
`).join('');
if (el.userTotal) el.userTotal.textContent = data.length;
}
function filterUsers() {
const q = document.getElementById('userSearch')?.value || '';
renderUserTable(q);
}
function openUserModal(data) {
const modal = el.userModal;
if (!modal) return;
modal.classList.add('open');
if (data) {
state.userModalMode = 'edit';
state.editingUserId = data.id;
el.userModalTitle.textContent = '编辑用户';
$('#f_username').value = data.username;
$('#f_name').value = data.name;
$('#f_email').value = data.email || '';
$('#f_phone').value = data.phone || '';
$('#f_dept').value = data.dept;
$('#f_role').value = data.role;
} else {
state.userModalMode = 'create';
state.editingUserId = null;
el.userModalTitle.textContent = '新建用户';
$('#f_username').value = '';
$('#f_name').value = '';
$('#f_email').value = '';
$('#f_phone').value = '';
$('#f_dept').value = '技术部';
$('#f_role').value = '普通用户';
}
}
function closeUserModal() {
const modal = el.userModal;
if (modal) modal.classList.remove('open');
}
function saveUser(e) {
e.preventDefault();
const data = {
username: $('#f_username').value.trim(),
name: $('#f_name').value.trim(),
email: $('#f_email').value.trim(),
phone: $('#f_phone').value.trim(),
dept: $('#f_dept').value,
role: $('#f_role').value,
};
if (!data.username || !data.name) { alert('请填写必要字段'); return; }
if (state.userModalMode === 'create') {
const newUser = {
id: Date.now(),
...data,
status: 'active',
lastLogin: '-',
};
state.users.unshift(newUser);
} else {
const idx = state.users.findIndex(u => u.id === state.editingUserId);
if (idx > -1) {
state.users[idx] = { ...state.users[idx], ...data };
}
}
closeUserModal();
renderUserTable();
}
function editUser(id) {
const u = state.users.find(u => u.id === id);
if (u) openUserModal(u);
}
function deleteUser(id) {
if (!confirm('确认删除该用户吗?')) return;
state.users = state.users.filter(u => u.id !== id);
renderUserTable();
}
/* ============================================================
Organization Tree
============================================================ */
const ORG_TREE = {
id: 1,
name: '企业集团',
children: [{
id: 2,
name: '技术中心',
children: [
{ id: 5, name: '研发部', children: [{ id: 9, name: '前端组' }, { id: 10, name: '后端组' },
{ id: 11, name: '算法组' }] },
{ id: 6, name: '运维部', children: [{ id: 12, name: '网络组' }, { id: 13, name: '安全组' }] },
{ id: 7, name: '测试部' },
]
}, {
id: 3,
name: '财务中心',
children: [
{ id: 8, name: '会计部' },
{ id: 14, name: '预算部' },
{ id: 15, name: '审计部' },
]
}, {
id: 4,
name: '人事中心',
children: [
{ id: 16, name: '招聘部' },
{ id: 17, name: '培训部' },
{ id: 18, name: '薪酬部' },
]
}, {
id: 19,
name: '市场中心',
children: [
{ id: 20, name: '品牌部' },
{ id: 21, name: '渠道部' },
]
}, ]
};
function initOrgTree() {
const container = el.orgTree;
if (!container) return;
container.innerHTML = renderOrgNode(ORG_TREE, 0, true);
container.querySelectorAll('.tree-node').forEach(node => {
node.addEventListener('click', function(e) {
e.stopPropagation();
const toggle = this.querySelector('.toggle');
const children = this.nextElementSibling;
if (toggle && children) {
toggle.classList.toggle('open');
children.classList.toggle('open');
}
showOrgDetail(this.dataset.name);
});
});
}
function renderOrgNode(node, depth, isRoot) {
const hasChildren = node.children && node.children.length > 0;
const icon = isRoot ? 'fa-solid fa-building' :
hasChildren ? 'fa-solid fa-folder' : 'fa-solid fa-folder-open';
const color = isRoot ? 'var(--gold)' : hasChildren ? 'var(--accent)' : 'var(--text-secondary)';
return `
<div>
<div class="tree-node" data-name="${node.name}" data-id="${node.id}" style="padding-left:${isRoot?12:12+depth*20}px;">
${hasChildren ? '<span class="toggle open"><i class="fa-solid fa-chevron-right"></i></span>' : '<span style="width:18px;"></span>'}
<i class="${icon}" style="color:${color};width:16px;font-size:13px;"></i>
<span style="font-weight:${isRoot?600:400};">${node.name}</span>
${isRoot ? '<span class="badge" style="margin-left:8px;">root</span>' : ''}
</div>
${hasChildren ? `<div class="tree-children open">${node.children.map(c => renderOrgNode(c, depth+1, false)).join('')}</div>` : ''}
</div>
`;
}
function showOrgDetail(name) {
const container = el.orgDetail;
if (!container) return;
const depts = {
'企业集团': { desc: '集团总部', head: '董事会', memberCount: 124, level: 0 },
'技术中心': { desc: '负责技术研发与运维', head: '张明远', memberCount: 48, level: 1 },
'研发部': { desc: '产品研发与技术创新', head: '王磊', memberCount: 24, level: 2 },
'前端组': { desc: '前端界面开发', head: '刘伟', memberCount: 8, level: 3 },
'后端组': { desc: '后端服务开发', head: '陈刚', memberCount: 10, level: 3 },
'算法组': { desc: 'AI算法与数据分析', head: '周婷', memberCount: 6, level: 3 },
'运维部': { desc: '系统运维与基础设施', head: '赵强', memberCount: 12, level: 2 },
'网络组': { desc: '网络架构与维护', head: '孙鹏', memberCount: 5, level: 3 },
'安全组': { desc: '信息安全与合规', head: '李安', memberCount: 7, level: 3 },
'测试部': { desc: '质量保障与自动化测试', head: '吴敏', memberCount: 12, level: 2 },
'财务中心': { desc: '财务管理与审计', head: '李雪婷', memberCount: 22, level: 1 },
'会计部': { desc: '日常账务处理', head: '陈红', memberCount: 8, level: 2 },
'预算部': { desc: '预算编制与管控', head: '钱红', memberCount: 6, level: 2 },
'审计部': { desc: '内部审计', head: '郑明', memberCount: 8, level: 2 },
'人事中心': { desc: '人力资源管理与培训', head: '赵刚', memberCount: 18, level: 1 },
'招聘部': { desc: '人才招聘与选拔', head: '杨柳', memberCount: 6, level: 2 },
'培训部': { desc: '员工培训与发展', head: '黄娟', memberCount: 4, level: 2 },
'薪酬部': { desc: '薪酬福利管理', head: '何敏', memberCount: 8, level: 2 },
'市场中心': { desc: '市场营销与品牌推广', head: '孙丽', memberCount: 16, level: 1 },
'品牌部': { desc: '品牌战略与传播', head: '高远', memberCount: 8, level: 2 },
'渠道部': { desc: '渠道拓展与管理', head: '马强', memberCount: 8, level: 2 },
};
const info = depts[name] || { desc: '暂无详细信息', head: '-', memberCount: 0, level: 0 };
container.innerHTML = `
<div style="display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;align-items:center;gap:10px;">
<div style="width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,var(--accent),var(--gold));display:flex;align-items:center;justify-content:center;color:#fff;font-size:18px;">
<i class="fa-solid fa-sitemap"></i>
</div>
<div>
<div style="font-size:16px;font-weight:600;color:var(--text-primary);">${name}</div>
<div style="font-size:12px;color:var(--text-muted);">${info.desc}</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:12px;background:var(--bg-hover);border-radius:8px;">
<div>
<div style="font-size:11px;color:var(--text-muted);">负责人</div>
<div style="font-size:14px;font-weight:600;color:var(--text-primary);">${info.head}</div>
</div>
<div>
<div style="font-size:11px;color:var(--text-muted);">人员数量</div>
<div style="font-size:14px;font-weight:600;color:var(--text-primary);">${info.memberCount} 人</div>
</div>
<div>
<div style="font-size:11px;color:var(--text-muted);">组织层级</div>
<div style="font-size:14px;font-weight:600;color:var(--text-primary);">Lv.${info.level}</div>
</div>
<div>
<div style="font-size:11px;color:var(--text-muted);">状态</div>
<span class="tag tag-success"><i class="fa-solid fa-circle"></i> 正常</span>
</div>
</div>
<div style="display:flex;gap:8px;margin-top:4px;">
<button class="btn btn-primary btn-xs" onclick="alert('编辑部门功能开发中')"><i class="fa-solid fa-pen"></i> 编辑</button>
<button class="btn btn-ghost btn-xs" onclick="alert('新增子部门功能开发中')"><i class="fa-solid fa-plus"></i> 新增子级</button>
</div>
</div>
`;
}
/* ============================================================
Roles & Permissions
============================================================ */
let selectedRoleId = 1;
function renderRoles() {
const list = el.roleList;
if (!list) return;
list.innerHTML = MOCK_ROLES.map(r => `
<div class="role-item" data-id="${r.id}" style="display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;cursor:pointer;transition:all 0.2s;${r.id === selectedRoleId ? 'background:var(--bg-hover);border-left:3px solid var(--accent);' : 'border-left:3px solid transparent;'}"
onclick="App.selectRole(${r.id})">
<div style="width:6px;height:6px;border-radius:50%;background:${r.color};"></div>
<div style="flex:1;min-width:0;">
<div style="font-size:14px;font-weight:600;color:var(--text-primary);">${r.name}</div>
<div style="font-size:11px;color:var(--text-muted);">${r.desc}</div>
</div>
<span style="font-size:11px;color:var(--text-muted);background:var(--bg-hover);padding:2px 8px;border-radius:6px;">${r.userCount}人</span>
</div>
`).join('');
renderPermissions(selectedRoleId);
}
function selectRole(id) {
selectedRoleId = id;
renderRoles();
}
function renderPermissions(roleId) {
const role = MOCK_ROLES.find(r => r.id === roleId);
if (!role) return;
el.permRoleTitle.textContent = role.name;
// Menus
el.permMenus.innerHTML = MOCK_MENUS.map(m => `
<label class="perm-item checked">
<input type="checkbox" checked> ${m}
</label>
`).join('');
// Actions
el.permActions.innerHTML = MOCK_ACTIONS.map(a => {
const checked = ['创建', '编辑', '查看', '审批', '导出'].includes(a);
return `
<label class="perm-item ${checked ? 'checked' : ''}">
<input type="checkbox" ${checked ? 'checked' : ''}> ${a}
</label>
`;
}).join('');
// Toggle checked class
el.permMenus.querySelectorAll('.perm-item').forEach(item => {
const cb = item.querySelector('input[type="checkbox"]');
cb.addEventListener('change', function() {
item.classList.toggle('checked', this.checked);
});
});
el.permActions.querySelectorAll('.perm-item').forEach(item => {
const cb = item.querySelector('input[type="checkbox"]');
cb.addEventListener('change', function() {
item.classList.toggle('checked', this.checked);
});
});
}
/* ============================================================
Init
============================================================ */
function init() {
// Copy mock data
state.users = JSON.parse(JSON.stringify(MOCK_USERS));
// Theme
initTheme();
// Hash change listener
window.addEventListener('hashchange', () => {
const route = getRoute();
if (routes[route]?.auth && !state.isLoggedIn) {
navigate('/login');
return;
}
navigate(route);
});
// Check auth and navigate
checkAuth();
// Handle window resize for sidebar
window.addEventListener('resize', () => {
if (window.innerWidth >= 768) {
toggleSidebar(false);
}
});
// Click outside modal
el.userModal?.addEventListener('click', function(e) {
if (e.target === this) closeUserModal();
});
console.log('✅ 企业审批矩阵配置平台已启动');
}
// Public API
return {
init,
navigate,
toggleTheme,
toggleSidebar,
handleLogin,
logout,
filterUsers,
openUserModal,
closeUserModal,
saveUser,
editUser,
deleteUser,
selectRole,
renderRoles,
initOrgTree,
};
})();
// Boot
document.addEventListener('DOMContentLoaded', () => App.init());
</script>
</body>
</html>
```