(() => { // 注入样式 const style = document.createElement('style'); style.textContent = ` .tree-select-menu { max-height: 500px; overflow-y: auto; min-width: 250px; font-family: Arial, sans-serif; font-size: 12px; user-select: none; border: 1px solid #dee2e6; padding-left: 4px; } .tree-node { cursor: default; padding: 4px 8px; display: block; white-space: nowrap; position: relative; text-align: left; } .tree-node::before { content: ''; position: absolute; top: 0; left: 0px; bottom: 0; width: 1px; border-left: 1px dashed #ccc; } .tree-node:not(.leaf) [data-level="0"] >.tree-label { font-weight: bold; } .tree-node[data-level="0"]::before, .tree-node[data-level="0"]::after { display: none !important; } .tree-node::after { content: ''; position: absolute; top: 14px; left: 0px; width: 10px; height: 1px; background-color: #ccc; } .tree-node:last-child::before { height: 14px; } .tree-node.leaf { cursor: pointer; color: #007bff; background-color: #fff; } .tree-node.leaf:hover { background-color: #e9ecef; } .tree-label { display: inline-block; vertical-align: middle; } .tree-toggle { display: inline-block; width: 14px; text-align: center; margin-right: 6px; user-select: none; font-size: 12px; vertical-align: middle; } .tree-children { display: none; margin-left: 18px; padding-left: 8px; } .tree-expanded > .tree-children { display: block; } .tree-select button { color: #495057; border: 1px solid #ced4da; padding: .25rem .5rem; } .tree-select button:active { color: #6c757d !important; border: 1px solid #ced4da; background-color: #fff !important; } #tree-container .show>.btn-outline-secondary.dropdown-toggle { color: #6c757d !important; border: 1px solid #80bdff; background-color: #fff !important; box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25); } #tree-container .show>.btn-outline-secondary.dropdown-toggle:focus { border-color: #80bdff; box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25); } .tree-select .btn-outline-secondary:hover { color: #6c757d; border: 1px solid #ced4da; background-color: #fff; } .tree-select .btn-outline-secondary:focus { border-color: #80bdff; box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25); } `; document.head.appendChild(style); class TreeSelect { constructor({ element, data, selected, onSelect }) { this.container = typeof element === 'string' ? document.querySelector(element) : element; this.onSelect = onSelect; this.selectedId = selected; this.data = this.buildTree(data); this.selectedNode = this.findNodeById(this.data, this.selectedId); this.render(); } // ✅ flat → tree 转换函数 buildTree(data, parentId = '-1') { // 找出所有属于 parentId 的节点 const children = data .filter(item => item.tree_pid === parentId) .sort((a, b) => a.tree_order - b.tree_order); // ✅ 按 tree_order 升序排序 return children.map(item => { const nodeChildren = this.buildTree(data, item.id); return { id: item.id, label: item.name, selected: item.id === this.selectedId, children: nodeChildren.length ? nodeChildren : undefined }; }); } findNodeById(nodes, id) { for (const node of nodes) { if (node.id === id) return node; if (node.children) { const found = this.findNodeById(node.children, id); if (found) return found; } } return null; } render() { this.container.innerHTML = ` `; this.button = this.container.querySelector('button'); this.menu = this.container.querySelector('.dropdown-menu'); this.button.dataset.selectedId = this.selectedNode?.id || ''; this.renderTree(this.data, this.menu, 0); } renderTree(nodes, parentEl, level = 0) { nodes.forEach(node => { const wrapper = document.createElement('div'); wrapper.className = 'tree-node tree-expanded'; // 默认展开 wrapper.dataset.level = level; const isLeaf = !node.children || node.children.length === 0; if (isLeaf) wrapper.classList.add('leaf'); const toggle = document.createElement('span'); toggle.className = 'tree-toggle'; toggle.innerHTML = isLeaf ? '' : ''; if (!isLeaf) { wrapper.appendChild(toggle); const toggle2 = document.createElement('span'); toggle2.className = 'tree-folder'; toggle2.innerHTML = ' '; wrapper.appendChild(toggle2); } const label = document.createElement('span'); label.className = 'tree-label'; label.innerHTML = isLeaf ? '  ' + node.label : node.label; wrapper.appendChild(label); if (!isLeaf) { const childrenContainer = document.createElement('div'); childrenContainer.className = 'tree-children'; wrapper.appendChild(childrenContainer); this.renderTree(node.children, childrenContainer, level + 1); if (this.isNodeOrChildSelected(node)) { wrapper.classList.add('tree-expanded'); toggle.innerHTML = ''; } // 👇 点击整行展开/收起 wrapper.addEventListener('click', e => { e.stopPropagation(); const expanded = wrapper.classList.toggle('tree-expanded'); toggle.innerHTML = expanded ? '' : ''; }); } else { // 👇 叶子节点可选中 wrapper.addEventListener('click', e => { e.stopPropagation(); this.selectNode(node); }); } parentEl.appendChild(wrapper); }); } isNodeOrChildSelected(node) { if (node.selected) return true; if (!node.children) return false; return node.children.some(child => this.isNodeOrChildSelected(child)); } selectNode(node) { this.selectedNode = node; this.button.textContent = node.label; this.button.dataset.selectedId = node.id; $('.tree-select button').dropdown('toggle'); if (typeof this.onSelect === 'function') { this.onSelect(node.id, node.label); } } getValue() { return this.button.dataset.selectedId || null; } } window.TreeSelect = TreeSelect; })();