<!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>