悟生慧

 

使用agGrid的社区版实现层级列表显示

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>工单管理系统 - 层级展示</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Bootstrap Icons -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
    <!-- Ag-Grid -->
    <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.js"></script>
    <style>
        body {
            background-color: #f8f9fa;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        .container-fluid {
            padding: 20px;
        }
        .card {
            box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
            border: 1px solid rgba(0, 0, 0, 0.125);
        }
        .card-header {
            background-color: #f8f9fa;
            border-bottom: 1px solid rgba(0, 0, 0, 0.125);
        }
        #myGrid {
            height: 600px;
            width: 100%;
        }
        .level-0 { padding-left: 10px; }
        .level-1 { padding-left: 40px; }
        .level-2 { padding-left: 70px; }
        .level-3 { padding-left: 100px; }
        .expand-button {
            background: none;
            border: none;
            cursor: pointer;
            width: 20px;
            height: 20px;
            margin-right: 5px;
            font-size: 12px;
            color: #6c757d;
            padding: 0;
        }
        .expand-button:hover {
            color: #495057;
        }
        .ag-theme-alpine {
            --ag-header-background-color: #f8f9fa;
            --ag-odd-row-background-color: #fcfcfc;
            --ag-header-foreground-color: #495057;
            --ag-border-color: #dee2e6;
        }
        .status-badge {
            font-size: 0.75rem;
        }
    </style>
</head>
<body>
    <div class="container-fluid">
        <!-- 页面标题和操作区 -->
        <div class="row mb-4">
            <div class="col-12">
                <div class="card">
                    <div class="card-header bg-light">
                        <div class="d-flex justify-content-between align-items-center">
                            <div>
                                <h1 class="h4 mb-0 text-primary">
                                    <i class="bi bi-clipboard-data me-2"></i>工单管理系统
                                </h1>
                            </div>
                            <div class="btn-group">
                                <button id="expandAll" class="btn btn-success btn-sm" disabled>
                                    <i class="bi bi-arrows-expand me-1"></i>展开所有
                                </button>
                                <button id="collapseAll" class="btn btn-secondary btn-sm" disabled>
                                    <i class="bi bi-arrows-collapse me-1"></i>折叠所有
                                </button>
                                <button id="refresh" class="btn btn-outline-primary btn-sm" disabled>
                                    <i class="bi bi-arrow-clockwise me-1"></i>刷新
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 信息提示 -->
        <div class="row mb-3">
            <div class="col-12">
                <div class="alert alert-info d-flex align-items-center">
                    <i class="bi bi-info-circle-fill me-2 fs-5"></i>
                    <div>
                        <strong>工单层级展示</strong> - 显示父子工单关系,支持展开/折叠查看详细层级
                        <span class="badge bg-primary status-badge ms-2">总工单数: <span id="totalCount">0</span></span>
                        <span class="badge bg-success status-badge ms-1">主工单: <span id="parentCount">0</span></span>
                        <span class="badge bg-warning status-badge ms-1">子工单: <span id="childCount">0</span></span>
                    </div>
                </div>
            </div>
        </div>

        <!-- 数据表格 -->
        <div class="row">
            <div class="col-12">
                <div class="card">
                    <div class="card-body p-0">
                        <div id="myGrid" class="ag-theme-alpine"></div>
                    </div>
                </div>
            </div>
        </div>

        <!-- 使用说明 -->
        <div class="row mt-3">
            <div class="col-12">
                <div class="card">
                    <div class="card-body py-2">
                        <small class="text-muted">
                            <i class="bi bi-lightbulb me-1"></i>
                            <strong>使用说明:</strong> 点击行前的箭头图标可展开/折叠子工单,父工单以粗体显示,支持排序和筛选功能
                        </small>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

    <script>
        // 工单数据 - 基于您提供的示例
        const workOrderData = [
            // 主工单和子工单
            { id: 'DD202510270025', parentId: null, workOrderName: 'DJ04/500', materialName: 'IMRC阀', materialCode: 'DJ04', customerDrawing: '', level2: 0, isParent: true },
            { id: 'DD202510270028', parentId: 'DD202510270025', workOrderName: 'DJ04/500-子工单', materialName: '外壳', materialCode: 'DJ04-008', customerDrawing: '', level2: 1, isParent: false },
            
            { id: 'DD202510300014', parentId: null, workOrderName: 'CP_456/20', materialName: '电脑', materialCode: 'CP_456', customerDrawing: '', level2: 0, isParent: true },
            { id: 'DD202510300015', parentId: 'DD202510300014', workOrderName: 'CP_456/20-子工单', materialName: '键盘', materialCode: 'BCP-455', customerDrawing: '', level2: 1, isParent: false },
            
            { id: 'DD202510270016', parentId: null, workOrderName: 'DJ27C/500', materialName: '进气控制电机', materialCode: 'DJ27C', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202511070007', parentId: null, workOrderName: '水杯1', materialName: '水杯——管', materialCode: 'CP-2025...', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202510270001', parentId: null, workOrderName: 'DJ03A/332', materialName: '低压打气泵', materialCode: 'DJ03A', customerDrawing: '', level2: 0, isParent: true },
            
            // 其他工单
            { id: 'DD202510300011', parentId: null, workOrderName: 'CP_456/500', materialName: '电脑', materialCode: 'CP_456', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202511070002', parentId: null, workOrderName: 'CP-20251103/10...', materialName: '吸管——管', materialCode: 'BCP-202...', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202511110009', parentId: null, workOrderName: '联想小新CS11-11', materialName: '联想小新14', materialCode: '1-002', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202510270002', parentId: null, workOrderName: 'DJ100B/539', materialName: '电动EGR阀', materialCode: 'DJ100B-E...', customerDrawing: '', level2: 0, isParent: true },
            
            { id: 'DD202510140006', parentId: null, workOrderName: 'DJ04/700-子工单', materialName: '外壳', materialCode: 'DJ04-008', customerDrawing: '', level2: 0, isParent: true }
        ];

        // 状态管理
        let expandedState = new Map();
        let displayData = [];
        let gridApi = null;

        // 更新统计信息
        function updateStatistics() {
            const totalCount = workOrderData.length;
            const parentCount = workOrderData.filter(item => item.parentId === null).length;
            const childCount = workOrderData.filter(item => item.parentId !== null).length;
            
            document.getElementById('totalCount').textContent = totalCount;
            document.getElementById('parentCount').textContent = parentCount;
            document.getElementById('childCount').textContent = childCount;
        }

        // 递归查找完整路径
        function findFullPath(nodeId, allData, cache = new Map()) {
            if (cache.has(nodeId)) {
                return cache.get(nodeId);
            }
            
            const currentNode = allData.find(item => item.id === nodeId);
            if (!currentNode) {
                return null;
            }
            
            if (currentNode.parentId === null) {
                const path = [currentNode.id];
                cache.set(nodeId, path);
                return path;
            }
            
            const parentPath = findFullPath(currentNode.parentId, allData, cache);
            if (parentPath) {
                const fullPath = [...parentPath, currentNode.id];
                cache.set(nodeId, fullPath);
                return fullPath;
            }
            
            const path = [currentNode.id];
            cache.set(nodeId, path);
            return path;
        }

        // 构建层级数据
        function buildHierarchyData(data) {
            const hierarchyData = [];
            const pathCache = new Map();
            
            // 首先添加所有根节点
            const rootNodes = data.filter(item => item.parentId === null);
            
            function addNode(node, depth = 0) {
                const fullPath = findFullPath(node.id, data, pathCache);
                const pathKey = fullPath ? fullPath.join('/') : node.id;
                const isExpanded = expandedState.get(pathKey) !== false;
                
                // 添加当前节点
                hierarchyData.push({
                    ...node,
                    _depth: depth,
                    _isFolder: node.isParent || data.some(item => item.parentId === node.id),
                    _path: pathKey,
                    _isExpanded: isExpanded,
                    _hasChildren: data.some(item => item.parentId === node.id)
                });
                
                // 如果节点有子节点且展开状态,添加子节点
                if (isExpanded && data.some(item => item.parentId === node.id)) {
                    const children = data.filter(item => item.parentId === node.id)
                                        .sort((a, b) => a.workOrderName.localeCompare(b.workOrderName));
                    children.forEach(child => addNode(child, depth + 1));
                }
            }
            
            rootNodes.forEach(root => addNode(root));
            return hierarchyData;
        }

        // 切换展开/折叠状态
        function toggleExpand(path) {
            const currentState = expandedState.get(path);
            expandedState.set(path, !currentState);
            refreshGrid();
        }

        // 刷新网格数据
        function refreshGrid() {
            if (!gridApi) {
                console.error('Grid API 未初始化');
                return;
            }
            
            try {
                displayData = buildHierarchyData(workOrderData);
                gridApi.setGridOption('rowData', displayData);
                console.log('数据刷新成功,显示记录数:', displayData.length);
            } catch (error) {
                console.error('刷新数据时出错:', error);
            }
        }

        // 启用按钮
        function enableButtons() {
            document.getElementById('expandAll').disabled = false;
            document.getElementById('collapseAll').disabled = false;
            document.getElementById('refresh').disabled = false;
        }

        // 自定义工单编号单元格渲染器
        function workOrderIdRenderer(params) {
            if (!params.data) return '';
            
            const data = params.data;
            const levelClass = `level-${Math.min(data._depth, 3)}`;
            
            let expandButton = '';
            if (data._hasChildren) {
                const expandIcon = data._isExpanded ? 
                    '<i class="bi bi-caret-down-fill"></i>' : 
                    '<i class="bi bi-caret-right-fill"></i>';
                expandButton = `<button class="expand-button" onclick="toggleExpand('${data._path}')">${expandIcon}</button>`;
            } else {
                expandButton = '<span style="display: inline-block; width: 20px;"></span>';
            }
            
            const textStyle = data._depth === 0 ? 
                'font-weight: 600; color: #0d6efd;' : 
                'font-weight: normal; color: #495057;';
            
            const badge = data._depth === 0 ? 
                '<span class="badge bg-primary badge-sm ms-1">主</span>' : 
                '<span class="badge bg-secondary badge-sm ms-1">子</span>';
            
            return `
                <div class="${levelClass}" style="display: flex; align-items: center;">
                    ${expandButton}
                    <span style="${textStyle}">${data.id}</span>
                    ${badge}
                </div>
            `;
        }

        // 自定义工单名称单元格渲染器
        function workOrderNameRenderer(params) {
            if (!params.data) return params.value;
            
            const data = params.data;
            const textStyle = data._depth === 0 ? 
                'font-weight: 600; color: #0d6efd;' : 
                'font-weight: normal; color: #495057;';
            
            const icon = data._depth === 0 ? 
                '<i class="bi bi-folder-fill text-warning me-1"></i>' : 
                '<i class="bi bi-file-earmark text-success me-1"></i>';
            
            return `
                <div style="${textStyle}">
                    ${icon}${params.value}
                </div>
            `;
        }

        // 列定义
        const columnDefs = [
            { 
                field: 'id', 
                headerName: '工单编号',
                cellRenderer: workOrderIdRenderer,
                minWidth: 220,
                flex: 1,
                sortable: true,
                filter: true
            },
            { 
                field: 'workOrderName', 
                headerName: '工单名称',
                //cellRenderer: workOrderNameRenderer,
                minWidth: 200,
                flex: 1,
                sortable: true,
                filter: true
            },
            { 
                field: 'materialName', 
                headerName: '物料名称',
                minWidth: 150,
                flex: 1,
                sortable: true,
                filter: true
            },
            { 
                field: 'materialCode', 
                headerName: '物料编码',
                minWidth: 150,
                flex: 1,
                sortable: true,
                filter: true
            },
            { 
                field: 'customerDrawing', 
                headerName: '客户图号',
                minWidth: 120,
                flex: 1,
                sortable: true,
                filter: true,
                cellRenderer: (params) => {
                    return params.value || '<span class="text-muted">-</span>';
                }
            }
        ];

        // Grid选项配置
        const gridOptions = {
            columnDefs: columnDefs,
            rowData: [],
            animateRows: true,
            
            // 默认列配置
            defaultColDef: {
                resizable: true,
                sortable: true,
                filter: true,
                minWidth: 100
            },

            // 行样式
            getRowStyle: function(params) {
                if (params.data && params.data._depth === 0) {
                    return { 
                        'background-color': '#f8f9fa',
                        'font-weight': '600'
                    };
                }
                return { 'background-color': '#ffffff' };
            },

            // Grid就绪回调
            onGridReady: function(params) {
                console.log('✅ Ag-Grid 初始化完成!');
                gridApi = params.api;
                
                // 启用按钮
                enableButtons();
                
                // 更新统计信息
                updateStatistics();
                
                // 初始加载数据
                refreshGrid();
                
                console.log('✅ 初始数据加载完成');
            }
        };

        // 初始化Grid - 修复 agGrid 引用问题
        document.addEventListener('DOMContentLoaded', function() {
            console.log('🚀 开始初始化Ag-Grid...');
            
            const gridDiv = document.querySelector('#myGrid');
            if (!gridDiv) {
                console.error('❌ 找不到网格容器元素');
                return;
            }
            
            try {
                // 检查 agGrid 是否已加载
                if (typeof agGrid === 'undefined') {
                    console.error('❌ agGrid 未正确加载,请检查 CDN 链接');
                    return;
                }
                
                // 使用正确的初始化方式
                const gridInstance = new agGrid.createGrid(gridDiv, gridOptions);
                console.log('✅ Ag-Grid 实例创建成功');
            } catch (error) {
                console.error('❌ 创建Ag-Grid实例时出错:', error);
                
                // 显示错误信息给用户
                gridDiv.innerHTML = `
                    <div class="alert alert-danger m-3">
                        <h4 class="alert-heading">初始化失败</h4>
                        <p>无法加载表格组件,请检查网络连接或刷新页面重试。</p>
                        <hr>
                        <p class="mb-0">错误详情: ${error.message}</p>
                    </div>
                `;
            }
            
            // 展开所有按钮
            document.getElementById('expandAll').addEventListener('click', function() {
                if (!gridApi) {
                    console.warn('Grid API 尚未就绪');
                    return;
                }
                
                workOrderData.forEach(item => {
                    const fullPath = findFullPath(item.id, workOrderData);
                    const pathKey = fullPath ? fullPath.join('/') : item.id;
                    expandedState.set(pathKey, true);
                });
                refreshGrid();
                console.log('✅ 已展开所有节点');
            });
            
            // 折叠所有按钮
            document.getElementById('collapseAll').addEventListener('click', function() {
                if (!gridApi) {
                    console.warn('Grid API 尚未就绪');
                    return;
                }
                
                workOrderData.forEach(item => {
                    const fullPath = findFullPath(item.id, workOrderData);
                    const pathKey = fullPath ? fullPath.join('/') : item.id;
                    expandedState.set(pathKey, false);
                });
                refreshGrid();
                console.log('✅ 已折叠所有节点');
            });
            
            // 刷新数据按钮
            document.getElementById('refresh').addEventListener('click', function() {
                refreshGrid();
                console.log('✅ 数据已刷新');
            });
        });

        // 页面加载完成后显示调试信息
        window.addEventListener('load', function() {
            console.log('=== 系统状态 ===');
            console.log('Grid API:', gridApi ? '已初始化' : '未初始化');
            console.log('数据总数:', workOrderData.length);
            console.log('agGrid 对象:', typeof agGrid !== 'undefined' ? '已加载' : '未加载');
        });
    </script>
</body>
</html>

  

posted on 2025-11-17 16:17  悟生慧  阅读(0)  评论(0)    收藏  举报

导航