如何用Select2实现基于字符串的三级动态链式选择
实现三级联动的Select2职业选择器(DnD计算器)
问题背景
开发DnD风格计算器时,需要三个Select2选择框分别展示一级、二级、三级职业选项,选项需根据上一级的选择动态生成。现有职业数组结构,但尝试用find()仅能返回第一个匹配项,filter()则返回undefined,且不想通过拆分数组加parent键的方式实现(维护成本高)。
关键问题修正
首先注意原职业数组的结构错误:每个一级职业的children被写成了单个对象,其中重复的id会导致后项覆盖前项(比如fighter的children中soldier会覆盖barbarian)。正确的结构应该是children为数组,每个子职业是独立对象。
解决方案
修正后的职业数组
const classes1 = [ { id: '', text: 'Select ICQ...' }, { id: 'fighter', text: 'Fighter', children: [ { id: 'barbarian', text: 'Barbarian', children: [{ id: 'beserker', text: 'Beserker' }] }, { id: 'soldier', text: 'Soldier', children: [{ id: 'knight', text: 'Knight' }] } ] }, { id: 'archer', text: 'Archer', children: [ { id: 'ranger', text: 'Ranger', children: [{ id: 'elite-ranger', text: 'Elite Ranger' }] }, { id: 'paladin', text: 'Paladin', children: [{ id: 'bard', text: 'Bard' }] } ] }, { id: 'mage', text: 'Mage', children: [ { id: 'wizard', text: 'Wizard', children: [{ id: 'warlock', text: 'Warlock' }] }, { id: 'druid', text: 'Druid', children: [{ id: 'artificer', text: 'Artificer' }] } ] } ];
完整实现代码
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <div id="classes" class="form-int"> <h5>Classes</h5> <select id="classtree1" class="frm-select" name="class-1"></select> <select id="classtree2" class="frm-select" name="class-2"></select> <select id="classtree3" class="frm-select" name="class-3"></select> <div class="row"> <div class="box"> <h6>Class1</h6> <span id="test1"></span> </div> <div class="box"> <h6>Class2</h6> <span id="test2"></span> </div> <div class="box"> <h6>Class3</h6> <span id="test3"></span> </div> </div> </div>
CSS
#classes select { width:30%; margin-right: 10px; }
JavaScript
// 初始化所有Select2组件 function initSelect2(selector, data, placeholder) { $(selector).empty().select2({ multiple: false, placeholder: placeholder || "Select...", data: data, allowClear: true }); } // 初始化一级选择框 initSelect2('#classtree1', classes1, 'Select class...'); // 一级选择框联动逻辑 $("#classtree1").on("change", function () { const selectedId = $(this).val(); $('#test1').text(selectedId); // 清空二级、三级选择框 initSelect2('#classtree2', [{id: '', text: 'Select subclass...'}]); initSelect2('#classtree3', [{id: '', text: 'Select specialization...'}]); $('#test2').text(''); $('#test3').text(''); if (!selectedId) return; // 找到选中的一级职业,获取其子项作为二级数据源 const selectedClass = classes1.find(item => item.id === selectedId); if (selectedClass?.children) { // 添加默认项 const secondLevelData = [{id: '', text: 'Select subclass...'}, ...selectedClass.children]; initSelect2('#classtree2', secondLevelData); } }); // 二级选择框联动逻辑 $("#classtree2").on("change", function () { const selectedId = $(this).val(); $('#test2').text(selectedId); // 清空三级选择框 initSelect2('#classtree3', [{id: '', text: 'Select specialization...'}]); $('#test3').text(''); if (!selectedId) return; // 遍历所有一级职业的子项,找到选中的二级职业 let selectedSubclass = null; for (const cls of classes1) { if (cls.children) { selectedSubclass = cls.children.find(item => item.id === selectedId); if (selectedSubclass) break; } } if (selectedSubclass?.children) { // 添加默认项 const thirdLevelData = [{id: '', text: 'Select specialization...'}, ...selectedSubclass.children]; initSelect2('#classtree3', thirdLevelData); } }); // 三级选择框变化监听 $("#classtree3").on("change", function () { const selectedId = $(this).val(); $('#test3').text(selectedId); });
逻辑说明
- 初始化封装:用
initSelect2函数统一处理Select2的初始化,避免重复代码,同时支持清空旧选项、设置新数据源。 - 一级联动:选中一级职业后,通过
find()找到对应项,提取其children作为二级选择框的数据源,同时重置二级、三级选择框。 - 二级联动:遍历所有一级职业的子项,找到选中的二级职业,提取其
children作为三级选择框的数据源。 - 默认项处理:每个选择框都添加空默认项,提升用户体验。
内容的提问来源于stack exchange,提问作者ConnectedToast
相关产品推荐
相关产品推荐

