<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组织结构图 - 连接线优化</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);
            color: #333;
            padding: 20px;
            line-height: 1.6;
            min-height: 100vh;
        }
        
        .container {
            max-width: 100%;
            margin: 0 auto;
            padding: 20px;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 25px;
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
        }
        
        h1 {
            color: #2c3e50;
            margin-bottom: 10px;
            font-size: 2.2rem;
            position: relative;
            display: inline-block;
        }
        
        h1:after {
            content: '';
            position: absolute;
            width: 60px;
            height: 4px;
            background: linear-gradient(90deg, #3498db, #2ecc71);
            bottom: -10px;
            left: 50%;
            transform: translateX(-50%);
            border-radius: 2px;
        }
        
        .description {
            color: #7f8c8d;
            max-width: 800px;
            margin: 20px auto 0;
            font-size: 1.05rem;
            line-height: 1.7;
        }
        
        .controls {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
            gap: 12px;
            margin-bottom: 25px;
            padding: 20px;
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
        }
        
        .btn {
            background: linear-gradient(to right, #3498db, #2980b9);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
            box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11);
        }
        
        .btn:hover:not(:disabled) {
            transform: translateY(-3px);
            box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1);
        }
        
        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        .btn-expand-all {
            background: linear-gradient(to right, #2ecc71, #27ae60);
        }
        
        .btn-collapse-all {
            background: linear-gradient(to right, #e74c3c, #c0392b);
        }
        
        .btn-loading {
            background: linear-gradient(to right, #95a5a6, #7f8c8d);
        }
        
        .btn-loading::after {
            content: '';
            width: 16px;
            height: 16px;
            border: 2px solid #ffffff;
            border-radius: 50%;
            border-top-color: transparent;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        
        .org-chart-container {
            position: relative;
            padding: 30px 20px;
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
            min-height: 600px;
            overflow: hidden;
            margin-bottom: 30px;
        }
        
        .org-chart-wrapper {
            position: relative;
            min-width: 100%;
            min-height: 100%;
            overflow-x: auto;
            overflow-y: auto;
        }
        
        .org-chart {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            position: relative;
            min-width: fit-content;
            padding: 40px 20px;
        }
        
        .level {
            display: flex;
            justify-content: center;
            width: 100%;
            margin-bottom: 100px;
            position: relative;
            min-width: fit-content;
        }
        
        .level:last-child {
            margin-bottom: 0;
        }
        
        .level-label {
            position: absolute;
            left: 10px;
            top: 50%;
            transform: translateY(-50%);
            background-color: #f8f9fa;
            padding: 6px 12px;
            border-radius: 20px;
            font-size: 14px;
            font-weight: 600;
            color: #7f8c8d;
            border: 1px solid #e1e4e8;
            z-index: 1;
            white-space: nowrap;
        }
        
        .node-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
            z-index: 2;
        }
        
        .node-wrapper {
            margin: 0 20px;
            position: relative;
        }
        
        .node {
            background-color: white;
            border-radius: 10px;
            padding: 18px;
            width: 280px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
            transition: all 0.3s ease;
            cursor: pointer;
            position: relative;
            z-index: 2;
            border: 1px solid #e1e4e8;
        }
        
        .node:hover {
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.12);
            transform: translateY(-5px);
            border-color: #3498db;
        }
        
        .node-header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            margin-bottom: 15px;
            padding-bottom: 12px;
            border-bottom: 2px solid #f1f5f9;
        }
        
        .node-title {
            font-weight: 700;
            font-size: 16px;
            color: #2c3e50;
            display: flex;
            flex-direction: column;
            gap: 4px;
        }
        
        .node-name {
            font-size: 14px;
            color: #7f8c8d;
            font-weight: 500;
        }
        
        .toggle-btn {
            width: 28px;
            height: 28px;
            border-radius: 50%;
            background: linear-gradient(135deg, #3498db, #2ecc71);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 14px;
            color: white;
            transition: all 0.2s ease;
            box-shadow: 0 3px 6px rgba(52, 152, 219, 0.3);
            flex-shrink: 0;
            margin-top: 4px;
        }
        
        .toggle-btn:hover {
            transform: scale(1.1);
        }
        
        .toggle-btn.loading {
            background: #95a5a6;
        }
        
        .toggle-btn.loading::after {
            content: '';
            width: 12px;
            height: 12px;
            border: 2px solid #ffffff;
            border-radius: 50%;
            border-top-color: transparent;
            animation: spin 1s linear infinite;
        }
        
        .toggle-btn.loading span {
            display: none;
        }
        
        .node-details {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 8px;
            font-size: 13px;
        }
        
        .detail-item {
            display: flex;
            justify-content: space-between;
            padding: 4px 0;
        }
        
        .detail-label {
            color: #7f8c8d;
            font-weight: 500;
        }
        
        .detail-value {
            color: #2c3e50;
            font-weight: 600;
        }
        
        .level-1 .node { border-left: 5px solid #e74c3c; }
        .level-2 .node { border-left: 5px solid #e67e22; }
        .level-3 .node { border-left: 5px solid #f39c12; }
        .level-4 .node { border-left: 5px solid #3498db; }
        .level-5 .node { border-left: 5px solid #2ecc71; }
        .level-6 .node { border-left: 5px solid #1abc9c; }
        .level-7 .node { border-left: 5px solid #9b59b6; }
        .level-8 .node { border-left: 5px solid #34495e; }
        .level-9 .node { border-left: 5px solid #e67e22; }
        .level-10 .node { border-left: 5px solid #f39c12; }
        
        .level-1 .detail-value { color: #e74c3c; }
        .level-2 .detail-value { color: #e67e22; }
        .level-3 .detail-value { color: #f39c12; }
        .level-4 .detail-value { color: #3498db; }
        .level-5 .detail-value { color: #2ecc71; }
        .level-6 .detail-value { color: #1abc9c; }
        .level-7 .detail-value { color: #9b59b6; }
        .level-8 .detail-value { color: #34495e; }
        .level-9 .detail-value { color: #e67e22; }
        .level-10 .detail-value { color: #f39c12; }
        
        .level-badge {
            display: inline-block;
            padding: 4px 10px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 700;
            color: white;
            margin-top: 4px;
            align-self: flex-start;
        }
        
        .level-1 .level-badge { background-color: #e74c3c; }
        .level-2 .level-badge { background-color: #e67e22; }
        .level-3 .level-badge { background-color: #f39c12; }
        .level-4 .level-badge { background-color: #3498db; }
        .level-5 .level-badge { background-color: #2ecc71; }
        .level-6 .level-badge { background-color: #1abc9c; }
        .level-7 .level-badge { background-color: #9b59b6; }
        .level-8 .level-badge { background-color: #34495e; }
        .level-9 .level-badge { background-color: #e67e22; }
        .level-10 .level-badge { background-color: #f39c12; }
        
        .stats {
            margin-top: 30px;
            padding: 25px;
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
        }
        
        .stats h3 {
            margin-bottom: 20px;
            color: #2c3e50;
            font-size: 1.5rem;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .stats h3:before {
            content: '';
            width: 8px;
            height: 25px;
            background: linear-gradient(to bottom, #3498db, #2ecc71);
            border-radius: 4px;
        }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 20px;
        }
        
        .stat-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 18px 20px;
            background: linear-gradient(135deg, #f8f9fa, #e9ecef);
            border-radius: 10px;
            border-left: 5px solid #3498db;
            transition: all 0.3s ease;
        }
        
        .stat-item:hover {
            transform: translateY(-3px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
        }
        
        .stat-label {
            color: #7f8c8d;
            font-weight: 600;
            font-size: 15px;
        }
        
        .stat-value {
            color: #2c3e50;
            font-weight: 800;
            font-size: 22px;
        }
        
        .empty-level {
            font-style: italic;
            color: #bdc3c7;
            padding: 20px;
            text-align: center;
            background-color: #f8f9fa;
            border-radius: 8px;
            border: 2px dashed #e1e4e8;
            min-width: 200px;
        }
        
        .performance-bar {
            height: 6px;
            background-color: #ecf0f1;
            border-radius: 3px;
            margin-top: 8px;
            overflow: hidden;
        }
        
        .performance-fill {
            height: 100%;
            border-radius: 3px;
        }
        
        .level-1 .performance-fill { background-color: #e74c3c; }
        .level-2 .performance-fill { background-color: #e67e22; }
        .level-3 .performance-fill { background-color: #f39c12; }
        .level-4 .performance-fill { background-color: #3498db; }
        .level-5 .performance-fill { background-color: #2ecc71; }
        .level-6 .performance-fill { background-color: #1abc9c; }
        .level-7 .performance-fill { background-color: #9b59b6; }
        .level-8 .performance-fill { background-color: #34495e; }
        .level-9 .performance-fill { background-color: #e67e22; }
        .level-10 .performance-fill { background-color: #f39c12; }
        
        .svg-connectors {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 1;
        }
        
        .connector-line {
            stroke-width: 2;
            fill: none;
        }
        
        .node-content-wrapper {
            position: relative;
            z-index: 3;
        }
        
        .loading-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(255, 255, 255, 0.8);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1000;
            border-radius: 12px;
        }
        
        .loading-spinner {
            width: 50px;
            height: 50px;
            border: 5px solid #f3f3f3;
            border-top: 5px solid #3498db;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        
        .notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 8px;
            background-color: #2ecc71;
            color: white;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            transform: translateX(150%);
            transition: transform 0.3s ease;
            z-index: 10000;
        }
        
        .notification.show {
            transform: translateX(0);
        }
        
        .notification.error {
            background-color: #e74c3c;
        }
        
        .notification.warning {
            background-color: #f39c12;
        }
        
        /* 水平滚动提示 */
        .scroll-hint {
            position: absolute;
            right: 20px;
            top: 20px;
            display: flex;
            align-items: center;
            gap: 8px;
            color: #7f8c8d;
            font-size: 14px;
            background: rgba(255, 255, 255, 0.9);
            padding: 8px 12px;
            border-radius: 6px;
            border: 1px solid #e1e4e8;
        }
        
        .scroll-hint-icon {
            animation: bounce 2s infinite;
        }
        
        @keyframes bounce {
            0%, 20%, 50%, 80%, 100% {
                transform: translateX(0);
            }
            40% {
                transform: translateX(-5px);
            }
            60% {
                transform: translateX(-3px);
            }
        }
        
        @media (max-width: 1200px) {
            .node {
                width: 250px;
            }
            
            .level {
                margin-bottom: 80px;
            }
            
            .node-wrapper {
                margin: 0 15px;
            }
        }
        
        @media (max-width: 992px) {
            .node {
                width: 220px;
                padding: 15px;
            }
            
            .node-details {
                grid-template-columns: 1fr;
                gap: 6px;
            }
            
            .level {
                margin-bottom: 60px;
            }
        }
        
        @media (max-width: 768px) {
            .level {
                flex-direction: column;
                align-items: center;
                margin-bottom: 40px;
            }
            
            .node-wrapper {
                margin: 10px 0;
            }
            
            .level-label {
                position: static;
                transform: none;
                margin-bottom: 10px;
                align-self: flex-start;
            }
            
            .org-chart-wrapper {
                overflow-x: scroll;
                overflow-y: visible;
            }
            
            .org-chart {
                padding: 30px 15px;
            }
        }
        
        @media (max-width: 576px) {
            .container {
                padding: 10px;
            }
            
            header {
                padding: 20px 15px;
            }
            
            .controls {
                padding: 15px;
            }
            
            .btn {
                padding: 10px 20px;
                font-size: 14px;
            }
            
            .node {
                width: 200px;
                padding: 12px;
            }
            
            .node-title {
                font-size: 15px;
            }
            
            .node-name {
                font-size: 13px;
            }
            
            .stats-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>组织结构图 - 连接线优化</h1>
            <p class="description">修复连接线显示问题,确保所有连接线完整显示,优化节点布局和滚动体验。</p>
        </header>
        
        <div class="controls">
            <button class="btn btn-expand-all" id="expandAll">
                <span>展开全部</span>
            </button>
            <button class="btn btn-collapse-all" id="collapseAll">
                <span>折叠全部</span>
            </button>
            <button class="btn" id="refreshData">
                <span>刷新数据</span>
            </button>
        </div>
        
        <div class="org-chart-container">
            <div id="loadingOverlay" class="loading-overlay" style="display: none;">
                <div class="loading-spinner"></div>
            </div>
            
            <div class="scroll-hint" id="scrollHint" style="display: none;">
                <span class="scroll-hint-icon">←→</span>
                <span>可左右滚动查看更多节点</span>
            </div>
            
            <div class="org-chart-wrapper" id="orgChartWrapper">
                <svg class="svg-connectors" id="svgConnectors"></svg>
                <div class="org-chart" id="orgChart">
                    <!-- 组织结构图将在这里动态生成 -->
                </div>
            </div>
        </div>
        
        <div class="stats">
            <h3>组织统计</h3>
            <div class="stats-grid" id="statsGrid">
                <!-- 统计信息将在这里动态生成 -->
            </div>
        </div>
    </div>

    <!-- 通知弹窗 -->
    <div id="notification" class="notification"></div>

    <script>
        // 配置
        const API_URL = 'http://abc'; // 接口地址
        const TOKEN = '123'; // 认证token
        const DEFAULT_PID = 0; // 根节点pid

        // 存储组织结构数据
        let orgData = {
            id: null, // 虚拟根节点
            pid: DEFAULT_PID,
            nickname: "虚拟根节点",
            level_name: "根级",
            maxq: 0,
            minq: 0,
            team_perf: 0,
            num: 0,
            bind_contact: "",
            children: [],
            expanded: true,
            loaded: false,
            level: 0
        };

        // 节点ID映射,用于快速查找节点
        const nodeMap = new Map();

        // 记录每个节点的实际层级
        const nodeLevels = new Map();

        // 格式化数字,添加千位分隔符
        function formatNumber(num) {
            return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
        }

        // 显示通知
        function showNotification(message, type = 'success') {
            const notification = document.getElementById('notification');
            notification.textContent = message;
            notification.className = `notification ${type}`;
            notification.classList.add('show');
            
            setTimeout(() => {
                notification.classList.remove('show');
            }, 3000);
        }

        // 显示/隐藏加载遮罩
        function toggleLoading(show) {
            const loadingOverlay = document.getElementById('loadingOverlay');
            loadingOverlay.style.display = show ? 'flex' : 'none';
        }

        // 显示/隐藏滚动提示
        function toggleScrollHint(show) {
            const scrollHint = document.getElementById('scrollHint');
            scrollHint.style.display = show ? 'flex' : 'none';
        }

        // 从接口获取数据
        async function fetchOrgData(pid = DEFAULT_PID) {
            try {
                toggleLoading(true);
                
                // 模拟API调用 - 实际使用时取消注释
                /*
                const response = await fetch(API_URL, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    body: `pid=${pid}&token=${TOKEN}`
                });
                
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                
                const data = await response.json();
                
                if (data.code !== 1) {
                    throw new Error(data.msg || '获取数据失败');
                }
                
                return data.data || [];
                */
                
                // 模拟数据 - 实际使用时删除
                return simulateApiResponse(pid);
                
            } catch (error) {
                console.error('获取组织数据失败:', error);
                showNotification(`获取数据失败: ${error.message}`, 'error');
                return [];
            } finally {
                toggleLoading(false);
            }
        }

        // 模拟API响应
        function simulateApiResponse(pid) {
            return new Promise(resolve => {
                setTimeout(() => {
                    if (pid === 0) {
                        // 根节点下的第一级
                        resolve([
                            {
                                id: 1,
                                bind_contact: "13800138000",
                                level_name: "董事长",
                                maxq: 3850000,
                                minq: 1250000,
                                nickname: "董事长",
                                num: 5,
                                team_perf: 1250000
                            },
                            {
                                id: 2,
                                bind_contact: "13800138001",
                                level_name: "董事",
                                maxq: 2850000,
                                minq: 850000,
                                nickname: "董事A",
                                num: 3,
                                team_perf: 850000
                            },
                            {
                                id: 20,
                                bind_contact: "13800138020",
                                level_name: "董事",
                                maxq: 2200000,
                                minq: 750000,
                                nickname: "董事B",
                                num: 2,
                                team_perf: 750000
                            },
                            {
                                id: 21,
                                bind_contact: "13800138021",
                                level_name: "董事",
                                maxq: 1800000,
                                minq: 650000,
                                nickname: "董事C",
                                num: 1,
                                team_perf: 650000
                            },
                            {
                                id: 22,
                                bind_contact: "13800138022",
                                level_name: "董事",
                                maxq: 1500000,
                                minq: 550000,
                                nickname: "董事D",
                                num: 0,
                                team_perf: 550000
                            }
                        ]);
                    } else if (pid === 1) {
                        // 董事长的下级
                        resolve([
                            {
                                id: 3,
                                bind_contact: "13800138002",
                                level_name: "北区总监",
                                maxq: 2100000,
                                minq: 850000,
                                nickname: "北区总监",
                                num: 3,
                                team_perf: 850000
                            },
                            {
                                id: 4,
                                bind_contact: "13800138003",
                                level_name: "南区总监",
                                maxq: 1750000,
                                minq: 920000,
                                nickname: "南区总监",
                                num: 2,
                                team_perf: 920000
                            },
                            {
                                id: 23,
                                bind_contact: "13800138023",
                                level_name: "东区总监",
                                maxq: 1900000,
                                minq: 780000,
                                nickname: "东区总监",
                                num: 2,
                                team_perf: 780000
                            },
                            {
                                id: 24,
                                bind_contact: "13800138024",
                                level_name: "西区总监",
                                maxq: 1600000,
                                minq: 680000,
                                nickname: "西区总监",
                                num: 2,
                                team_perf: 680000
                            },
                            {
                                id: 25,
                                bind_contact: "13800138025",
                                level_name: "中区总监",
                                maxq: 1400000,
                                minq: 580000,
                                nickname: "中区总监",
                                num: 1,
                                team_perf: 580000
                            }
                        ]);
                    } else if (pid === 2) {
                        // 董事A的下级
                        resolve([
                            {
                                id: 5,
                                bind_contact: "13800138004",
                                level_name: "西区总监",
                                maxq: 1500000,
                                minq: 650000,
                                nickname: "西区总监",
                                num: 2,
                                team_perf: 650000
                            },
                            {
                                id: 26,
                                bind_contact: "13800138026",
                                level_name: "西北区总监",
                                maxq: 1200000,
                                minq: 520000,
                                nickname: "西北区总监",
                                num: 1,
                                team_perf: 520000
                            },
                            {
                                id: 27,
                                bind_contact: "13800138027",
                                level_name: "西南区总监",
                                maxq: 1100000,
                                minq: 480000,
                                nickname: "西南区总监",
                                num: 0,
                                team_perf: 480000
                            }
                        ]);
                    } else if (pid === 3) {
                        // 北区总监的下级
                        resolve([
                            {
                                id: 6,
                                bind_contact: "13800138005",
                                level_name: "北区经理A",
                                maxq: 850000,
                                minq: 320000,
                                nickname: "北区经理A",
                                num: 1,
                                team_perf: 320000
                            },
                            {
                                id: 7,
                                bind_contact: "13800138006",
                                level_name: "北区经理B",
                                maxq: 1250000,
                                minq: 530000,
                                nickname: "北区经理B",
                                num: 1,
                                team_perf: 530000
                            },
                            {
                                id: 28,
                                bind_contact: "13800138028",
                                level_name: "北区经理C",
                                maxq: 950000,
                                minq: 420000,
                                nickname: "北区经理C",
                                num: 1,
                                team_perf: 420000
                            },
                            {
                                id: 29,
                                bind_contact: "13800138029",
                                level_name: "北区经理D",
                                maxq: 780000,
                                minq: 380000,
                                nickname: "北区经理D",
                                num: 0,
                                team_perf: 380000
                            }
                        ]);
                    } else {
                        // 默认返回空数组
                        resolve([]);
                    }
                }, 500); // 模拟网络延迟
            });
        }

        // 将API数据转换为节点格式
        function apiDataToNode(apiData, pid, level = 1) {
            return {
                id: apiData.id,
                pid: pid,
                bind_contact: apiData.bind_contact,
                level_name: apiData.level_name,
                maxq: apiData.maxq || 0,
                minq: apiData.minq || 0,
                nickname: apiData.nickname,
                num: apiData.num || 0,
                team_perf: apiData.team_perf || 0,
                children: [],
                expanded: false,
                loaded: false,
                hasChildren: apiData.num > 0,
                level: level
            };
        }

        // 获取或创建节点
        async function getOrLoadNode(pid, forceRefresh = false) {
            // 如果是根节点
            if (pid === DEFAULT_PID) {
                if (!orgData.loaded || forceRefresh) {
                    const data = await fetchOrgData(DEFAULT_PID);
                    orgData.children = data.map(item => apiDataToNode(item, DEFAULT_PID, 1));
                    orgData.loaded = true;
                    orgData.expanded = true;
                }
                return orgData;
            }
            
            // 查找节点
            let targetNode = findNodeById(pid);
            
            if (!targetNode) {
                console.warn(`未找到ID为${pid}的节点`);
                return null;
            }
            
            // 如果强制刷新或未加载过,则重新加载
            if (forceRefresh || !targetNode.loaded) {
                const data = await fetchOrgData(pid);
                targetNode.children = data.map(item => apiDataToNode(item, pid, targetNode.level + 1));
                targetNode.loaded = true;
            }
            
            return targetNode;
        }

        // 查找节点
        function findNodeById(id) {
            return nodeMap.get(id);
        }

        // 计算节点的实际层级
        function calculateNodeLevels(node, level = 0) {
            nodeLevels.set(node.id, level);
            node.level = level;
            
            if (node.children && node.expanded) {
                node.children.forEach(child => {
                    calculateNodeLevels(child, level + 1);
                });
            }
        }

        // 获取树的最大深度
        function getTreeDepth(node) {
            calculateNodeLevels(node, 0);
            let maxDepth = 0;
            
            nodeLevels.forEach(level => {
                if (level > maxDepth) {
                    maxDepth = level;
                }
            });
            
            return maxDepth;
        }

        // 按层级组织节点
        function organizeNodesByLevel() {
            const levels = {};
            
            // 从虚拟根节点的子节点开始
            function addNodeToLevel(node) {
                const level = nodeLevels.get(node.id);
                
                if (level === undefined) return;
                
                if (!levels[level]) {
                    levels[level] = [];
                }
                
                // 检查是否已经添加过这个节点
                if (!levels[level].some(n => n.id === node.id)) {
                    levels[level].push(node);
                }
                
                // 如果节点展开且有子节点,添加子节点
                if (node.expanded && node.children && node.children.length > 0) {
                    node.children.forEach(child => {
                        addNodeToLevel(child);
                    });
                }
            }
            
            // 从根节点的子节点开始
            orgData.children.forEach(child => {
                addNodeToLevel(child);
            });
            
            return levels;
        }

        // 创建节点元素
        function createNodeElement(node) {
            const nodeElement = document.createElement('div');
            nodeElement.className = 'node';
            nodeElement.setAttribute('data-id', node.id);
            nodeElement.setAttribute('data-level', node.level);
            
            const hasChildren = node.hasChildren;
            const levelClass = `level-${Math.min(node.level, 10)}`;
            
            // 计算业绩百分比
            const maxPerformance = Math.max(node.maxq, 1000000);
            const realtimePercent = Math.min((node.team_perf / maxPerformance) * 100, 100);
            
            nodeElement.innerHTML = `
                <div class="node-content-wrapper">
                    <div class="node-header">
                        <div class="node-title">
                            <span>${node.nickname}</span>
                            <span class="node-name">${node.bind_contact || '无手机号'}</span>
                            <span class="level-badge">${node.level_name || '未知级别'}</span>
                        </div>
                        ${hasChildren ? 
                            `<div class="toggle-btn ${node.loading ? 'loading' : ''}">
                                <span>${node.expanded ? '▼' : '▶'}</span>
                            </div>` : 
                            `<div class="toggle-btn" style="opacity: 0.5; cursor: default;">•</div>`
                        }
                    </div>
                    <div class="node-details">
                        <div class="detail-item">
                            <span class="detail-label">直推人数:</span>
                            <span class="detail-value">${node.num || 0}人</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">新增业绩:</span>
                            <span class="detail-value">¥${formatNumber(node.team_perf || 0)}</span>
                        </div>
                        <div class="performance-bar">
                            <div class="performance-fill ${levelClass}" style="width: ${realtimePercent}%"></div>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">大区业绩:</span>
                            <span class="detail-value">¥${formatNumber(node.maxq || 0)}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">小区业绩:</span>
                            <span class="detail-value">¥${formatNumber(node.minq || 0)}</span>
                        </div>
                    </div>
                </div>
            `;
            
            nodeElement.classList.add(levelClass);
            
            return nodeElement;
        }

        // 优化连接线渲染
        function renderConnectors() {
            const svg = document.getElementById('svgConnectors');
            svg.innerHTML = '';
            
            // 获取容器和包装器的位置
            const container = document.querySelector('.org-chart-container');
            const wrapper = document.querySelector('.org-chart-wrapper');
            
            if (!container || !wrapper) return;
            
            const containerRect = container.getBoundingClientRect();
            const wrapperRect = wrapper.getBoundingClientRect();
            
            // 计算滚动偏移
            const scrollLeft = wrapper.scrollLeft;
            const scrollTop = wrapper.scrollTop;
            
            // 更新SVG的尺寸以匹配内容
            const orgChart = document.getElementById('orgChart');
            if (orgChart) {
                svg.style.width = orgChart.scrollWidth + 'px';
                svg.style.height = orgChart.scrollHeight + 'px';
            }
            
            // 收集所有需要绘制连接线的父节点
            const parentNodes = [];
            nodeMap.forEach((node, nodeId) => {
                if (node.children && node.children.length > 0 && node.expanded) {
                    parentNodes.push(node);
                }
            });
            
            // 为每个父节点绘制连接线
            parentNodes.forEach(parentNode => {
                const parentElement = document.querySelector(`.node[data-id="${parentNode.id}"]`);
                if (!parentElement) return;
                
                const parentRect = parentElement.getBoundingClientRect();
                
                // 计算父节点的中心点(相对于SVG)
                const parentX = parentRect.left - containerRect.left + scrollLeft + parentRect.width / 2;
                const parentY = parentRect.top - containerRect.top + scrollTop + parentRect.height;
                
                // 获取所有可见的子节点
                const visibleChildren = parentNode.children.filter(child => {
                    const childElement = document.querySelector(`.node[data-id="${child.id}"]`);
                    return childElement && childElement.offsetParent !== null;
                });
                
                if (visibleChildren.length === 0) return;
                
                // 为每个子节点绘制连接线
                visibleChildren.forEach(child => {
                    const childElement = document.querySelector(`.node[data-id="${child.id}"]`);
                    if (!childElement) return;
                    
                    const childRect = childElement.getBoundingClientRect();
                    
                    // 计算子节点的中心点(相对于SVG)
                    const childX = childRect.left - containerRect.left + scrollLeft + childRect.width / 2;
                    const childY = childRect.top - containerRect.top + scrollTop;
                    
                    // 计算中间点
                    const midY = parentY + (childY - parentY) / 2;
                    
                    // 创建连接线路径
                    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                    
                    // 使用贝塞尔曲线创建更平滑的连接线
                    const d = `M ${parentX} ${parentY} 
                              C ${parentX} ${midY}, ${childX} ${midY}, ${childX} ${childY}`;
                    
                    path.setAttribute('d', d);
                    path.setAttribute('class', 'connector-line');
                    
                    // 设置线条样式
                    const level = parentNode.level || 1;
                    const lineColor = getLevelColor(level);
                    path.setAttribute('stroke', lineColor);
                    path.setAttribute('stroke-width', '2');
                    path.setAttribute('fill', 'none');
                    
                    svg.appendChild(path);
                    
                    // 在连接线起点添加小圆点
                    const startDot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                    startDot.setAttribute('cx', parentX);
                    startDot.setAttribute('cy', parentY);
                    startDot.setAttribute('r', '3');
                    startDot.setAttribute('fill', lineColor);
                    svg.appendChild(startDot);
                    
                    // 在连接线终点添加小圆点
                    const endDot = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
                    endDot.setAttribute('cx', childX);
                    endDot.setAttribute('cy', childY);
                    endDot.setAttribute('r', '3');
                    endDot.setAttribute('fill', lineColor);
                    svg.appendChild(endDot);
                });
            });
        }

        // 获取层级颜色
        function getLevelColor(level) {
            const colors = {
                1: '#e74c3c',
                2: '#e67e22',
                3: '#f39c12',
                4: '#3498db',
                5: '#2ecc71',
                6: '#1abc9c',
                7: '#9b59b6',
                8: '#34495e',
                9: '#e67e22',
                10: '#f39c12'
            };
            return colors[Math.min(level, 10)] || '#d1d8e0';
        }

        // 检查是否需要显示水平滚动提示
        function checkScrollHint() {
            const orgChart = document.getElementById('orgChart');
            const orgChartWrapper = document.getElementById('orgChartWrapper');
            
            if (orgChart && orgChartWrapper) {
                const chartWidth = orgChart.scrollWidth;
                const wrapperWidth = orgChartWrapper.clientWidth;
                
                // 如果内容宽度大于容器宽度,显示滚动提示
                toggleScrollHint(chartWidth > wrapperWidth + 50);
            }
        }

        // 渲染组织图
        async function renderOrgChart() {
            const orgChart = document.getElementById('orgChart');
            orgChart.innerHTML = '';
            
            // 清除节点映射
            nodeMap.clear();
            nodeLevels.clear();
            
            // 计算节点层级
            calculateNodeLevels(orgData);
            
            // 获取按层级组织的节点
            const levels = organizeNodesByLevel();
            const maxDepth = getTreeDepth(orgData);
            
            // 为每个层级创建容器
            for (let level = 1; level <= maxDepth; level++) {
                const levelContainer = document.createElement('div');
                levelContainer.className = `level level-${Math.min(level, 10)}`;
                levelContainer.setAttribute('data-level', level);
                levelContainer.id = `level-${level}`;
                
                // 添加层级标签
                const levelLabel = document.createElement('div');
                levelLabel.className = 'level-label';
                const levelNames = ["", "一级", "二级", "三级", "四级", "五级", "六级", "七级", "八级", "九级", "十级"];
                levelLabel.textContent = levelNames[Math.min(level, 10)] || `层级 ${level}`;
                levelContainer.appendChild(levelLabel);
                
                // 获取该层级的所有节点
                const levelNodes = levels[level] || [];
                
                if (levelNodes.length === 0) {
                    const emptyMsg = document.createElement('div');
                    emptyMsg.className = 'empty-level';
                    emptyMsg.textContent = '该层级暂无成员';
                    levelContainer.appendChild(emptyMsg);
                } else {
                    // 创建节点容器
                    const nodesContainer = document.createElement('div');
                    nodesContainer.className = 'level-nodes';
                    nodesContainer.style.display = 'flex';
                    nodesContainer.style.justifyContent = 'flex-start';
                    nodesContainer.style.flexWrap = 'nowrap';
                    nodesContainer.style.minWidth = 'fit-content';
                    nodesContainer.id = `level-nodes-${level}`;
                    
                    // 添加该层级的所有节点
                    levelNodes.forEach(node => {
                        // 更新节点映射
                        nodeMap.set(node.id, node);
                        
                        const nodeWrapper = document.createElement('div');
                        nodeWrapper.className = 'node-wrapper';
                        nodeWrapper.id = `node-wrapper-${node.id}`;
                        
                        const nodeContainer = document.createElement('div');
                        nodeContainer.className = 'node-container';
                        nodeContainer.id = `node-container-${node.id}`;
                        
                        // 生成节点内容
                        const nodeElement = createNodeElement(node);
                        nodeContainer.appendChild(nodeElement);
                        
                        nodeWrapper.appendChild(nodeContainer);
                        nodesContainer.appendChild(nodeWrapper);
                    });
                    
                    levelContainer.appendChild(nodesContainer);
                }
                
                orgChart.appendChild(levelContainer);
            }
            
            // 等待DOM更新完成后渲染连接线
            setTimeout(() => {
                renderConnectors();
                // 检查是否需要显示滚动提示
                setTimeout(checkScrollHint, 100);
            }, 100);
            
            // 添加点击事件
            attachNodeEvents();
        }

        // 为节点添加点击事件
        function attachNodeEvents() {
            document.querySelectorAll('.toggle-btn').forEach(btn => {
                btn.addEventListener('click', async function(e) {
                    e.stopPropagation();
                    
                    if (this.classList.contains('loading')) return;
                    
                    const nodeElement = this.closest('.node');
                    const nodeId = parseInt(nodeElement.getAttribute('data-id'));
                    const node = nodeMap.get(nodeId);
                    
                    if (node && node.hasChildren) {
                        await toggleNode(node, nodeElement, this);
                    }
                });
            });
            
            // 节点点击事件
            document.querySelectorAll('.node').forEach(node => {
                node.addEventListener('click', async function(e) {
                    if (e.target.closest('.toggle-btn')) return;
                    
                    const nodeId = parseInt(this.getAttribute('data-id'));
                    const node = nodeMap.get(nodeId);
                    
                    if (node && node.hasChildren) {
                        const toggleBtn = this.querySelector('.toggle-btn');
                        if (toggleBtn && !toggleBtn.classList.contains('loading')) {
                            await toggleNode(node, this, toggleBtn);
                        }
                    }
                });
            });
        }

        // 切换节点展开/折叠
        async function toggleNode(node, nodeElement, toggleBtn) {
            // 如果节点未加载,先加载子节点
            if (!node.loaded) {
                toggleBtn.classList.add('loading');
                await getOrLoadNode(node.id);
                toggleBtn.classList.remove('loading');
            }
            
            // 切换展开状态
            node.expanded = !node.expanded;
            
            // 更新按钮图标
            const toggleIcon = toggleBtn.querySelector('span');
            if (toggleIcon) {
                toggleIcon.textContent = node.expanded ? '▼' : '▶';
            }
            
            // 重新渲染组织图
            await renderOrgChart();
            
            // 显示通知
            const action = node.expanded ? '展开' : '折叠';
            showNotification(`${node.nickname} 已${action}`, 'success');
        }

        // 计算组织统计数据
        function calculateStats() {
            let totalMembers = 0;
            let totalTeamPerf = 0;
            let totalMaxq = 0;
            let totalMinq = 0;
            const levelCounts = {};
            
            function traverse(node) {
                if (node.id !== null) {
                    totalMembers++;
                    totalTeamPerf += node.team_perf || 0;
                    totalMaxq += node.maxq || 0;
                    totalMinq += node.minq || 0;
                    
                    const levelName = node.level_name || '未知';
                    levelCounts[levelName] = (levelCounts[levelName] || 0) + 1;
                }
                
                if (node.children) {
                    node.children.forEach(child => {
                        traverse(child);
                    });
                }
            }
            
            traverse(orgData);
            
            return { totalMembers, totalTeamPerf, totalMaxq, totalMinq, levelCounts };
        }

        // 渲染统计数据
        function renderStats() {
            const stats = calculateStats();
            const statsGrid = document.getElementById('statsGrid');
            
            statsGrid.innerHTML = `
                <div class="stat-item">
                    <span class="stat-label">总成员数</span>
                    <span class="stat-value">${stats.totalMembers}</span>
                </div>
                <div class="stat-item">
                    <span class="stat-label">总新增业绩</span>
                    <span class="stat-value">¥${formatNumber(stats.totalTeamPerf)}</span>
                </div>
                <div class="stat-item">
                    <span class="stat-label">总大区业绩</span>
                    <span class="stat-value">¥${formatNumber(stats.totalMaxq)}</span>
                </div>
                <div class="stat-item">
                    <span class="stat-label">总小区业绩</span>
                    <span class="stat-value">¥${formatNumber(stats.totalMinq)}</span>
                </div>
            `;
            
            Object.entries(stats.levelCounts).forEach(([level, count]) => {
                if (level && level !== '未知') {
                    const statItem = document.createElement('div');
                    statItem.className = 'stat-item';
                    statItem.innerHTML = `
                        <span class="stat-label">${level}人数</span>
                        <span class="stat-value">${count}</span>
                    `;
                    statsGrid.appendChild(statItem);
                }
            });
        }

        // 展开所有节点
        async function expandAll() {
            document.getElementById('expandAll').disabled = true;
            
            async function expandNode(node) {
                if (node.hasChildren && !node.loaded) {
                    await getOrLoadNode(node.id);
                }
                
                node.expanded = true;
                
                if (node.children) {
                    for (let child of node.children) {
                        await expandNode(child);
                    }
                }
            }
            
            for (let child of orgData.children) {
                await expandNode(child);
            }
            
            await renderOrgChart();
            showNotification('已展开所有节点', 'success');
            
            document.getElementById('expandAll').disabled = false;
        }

        // 折叠所有节点
        async function collapseAll() {
            document.getElementById('collapseAll').disabled = true;
            
            function collapseNode(node) {
                node.expanded = false;
                
                if (node.children) {
                    for (let child of node.children) {
                        collapseNode(child);
                    }
                }
            }
            
            for (let child of orgData.children) {
                collapseNode(child);
            }
            
            await renderOrgChart();
            showNotification('已折叠所有节点', 'success');
            
            document.getElementById('collapseAll').disabled = false;
        }

        // 刷新数据
        async function refreshData() {
            document.getElementById('refreshData').disabled = true;
            
            await getOrLoadNode(DEFAULT_PID, true);
            await renderOrgChart();
            renderStats();
            showNotification('数据已刷新', 'success');
            
            document.getElementById('refreshData').disabled = false;
        }

        // 初始化
        async function init() {
            document.getElementById('expandAll').addEventListener('click', expandAll);
            document.getElementById('collapseAll').addEventListener('click', collapseAll);
            document.getElementById('refreshData').addEventListener('click', refreshData);
            
            // 窗口大小变化时重新渲染连接线和检查滚动提示
            window.addEventListener('resize', () => {
                setTimeout(() => {
                    renderConnectors();
                    checkScrollHint();
                }, 100);
            });
            
            // 监听滚动事件,重新渲染连接线
            const wrapper = document.getElementById('orgChartWrapper');
            if (wrapper) {
                wrapper.addEventListener('scroll', () => {
                    setTimeout(renderConnectors, 50);
                });
            }
            
            // 初始化加载数据
            try {
                await getOrLoadNode(DEFAULT_PID);
                await renderOrgChart();
                renderStats();
                showNotification('组织结构图加载完成', 'success');
            } catch (error) {
                console.error('初始化失败:', error);
                showNotification('初始化失败,请检查网络连接', 'error');
            }
        }

        document.addEventListener('DOMContentLoaded', init);
    </script>
</body>
</html>

元宝ai实现的效果图