在Django模板中用JS实现联动下拉框的动态数据填充
实现无刷新联动Select下拉框
问题背景
现有三级联动Select下拉框用于产品分类:
- 第一级选择分类方式(
usage或model) - 第二级根据第一级选择,显示对应分类的选项(
Usage模型数据或MainModel模型数据) - 第三级需要根据第二级选中值动态填充子选项,而非全量加载所有子数据(
SubUsage或PumpType),要求无刷新实现。
当前已完成显示/隐藏逻辑,但第三级仍全量加载数据,需优化为动态填充。
解决方案
1. 后端修改(views.py)
生成以父级ID为键、子级数据列表为值的映射字典,转成JSON格式传给前端(避免全量加载子数据):
def get_common_queryset(): usage_queryset = Usage.objects.all() main_model_queryset = MainModel.objects.all() # 构建Usage -> SubUsage的映射(键为usage.id字符串) sub_usage_map = {} for usage in usage_queryset: # 只取需要的字段,减少数据量 sub_usages = SubUsage.objects.filter(usage=usage).values('id', 'sub_usage_name_fa') sub_usage_map[str(usage.id)] = list(sub_usages) # 构建MainModel -> PumpType的映射(键为model.id字符串) pump_type_map = {} for model in main_model_queryset: pump_types = PumpType.objects.filter(main_model=model).values('id', 'type_name') pump_type_map[str(model.id)] = list(pump_types) queryset_dictionary = { "usage_queryset": usage_queryset, "main_model_queryset": main_model_queryset, "sub_usage_map": sub_usage_map, "pump_type_map": pump_type_map, } return queryset_dictionary def solution_main(request): context = get_common_queryset() return render(request, "solutions/solution_main.html", context)
2. 模板修改(solution_main.html)
- 移除第三级Select的全量循环,仅保留默认选项
- 添加脚本标签,将后端映射数据注入为JS变量
修改后的第三级Select代码:
{# select sub_usage or pump_type div #} <div class="col-md-3 mx-md-5"> {# sub_usage select div #} <div class="sub_usage visually-hidden" id="sub_usage_div"> <h2 class="h5 nm-text-color fw-bold mb-4">Select sub-usage:</h2> <select required aria-label="Select sub_usage" class="form-select" name="sub_usage_select" id="sub_usage_select"> <option selected>All sub-usages from this usage</option> </select> </div> {# pump type select div #} <div class="pump-type visually-hidden" id="pump_type_div"> <h2 class="h5 nm-text-color fw-bold mb-4">Select pump type:</h2> <select aria-label="Select pump_type" class="form-select" name="pump_type_select" id="pump_type_select"> <option selected>All pump types of this model</option> </select> </div> </div>
在模板底部添加JS脚本:
<script> // 从后端获取预加载的映射数据(使用safe过滤器避免转义) const subUsageMap = {{ sub_usage_map|safe }}; const pumpTypeMap = {{ pump_type_map|safe }}; // 辅助函数:重置Select到初始状态(保留默认选项) function resetSelect(selectElement) { while (selectElement.options.length > 1) { selectElement.remove(1); } selectElement.selectedIndex = 0; } // 第一级Select切换逻辑:显示/隐藏第二级容器,重置后续Select function set_usage_or_model_dic() { const selectedValue = document.getElementById('usage_model_select').value; const usageDiv = document.getElementById('usage_div'); const modelDiv = document.getElementById('model_div'); const subUsageDiv = document.getElementById('sub_usage_div'); const pumpTypeDiv = document.getElementById('pump_type_div'); // 重置所有子Select和容器 resetSelect(document.getElementById('sub_usage_select')); resetSelect(document.getElementById('pump_type_select')); subUsageDiv.classList.add('visually-hidden'); pumpTypeDiv.classList.add('visually-hidden'); // 切换第二级容器显示状态 if (selectedValue === 'usage') { usageDiv.classList.remove('visually-hidden'); modelDiv.classList.add('visually-hidden'); } else if (selectedValue === 'model') { modelDiv.classList.remove('visually-hidden'); usageDiv.classList.add('visually-hidden'); } else { usageDiv.classList.add('visually-hidden'); modelDiv.classList.add('visually-hidden'); } } // 第二级Usage选择后,填充SubUsage选项 function set_sub_usage_list() { const usageId = document.getElementById('usage_select_id').value; const subUsageSelect = document.getElementById('sub_usage_select'); const subUsageDiv = document.getElementById('sub_usage_div'); resetSelect(subUsageSelect); subUsageDiv.classList.add('visually-hidden'); if (usageId && subUsageMap[usageId]) { subUsageDiv.classList.remove('visually-hidden'); // 动态创建选项 subUsageMap[usageId].forEach(item => { const option = document.createElement('option'); option.value = item.id; option.textContent = item.sub_usage_name_fa; subUsageSelect.appendChild(option); }); } } // 第二级Model选择后,填充PumpType选项 function set_pump_type_list() { const modelId = document.getElementById('model_select_id').value; const pumpTypeSelect = document.getElementById('pump_type_select'); const pumpTypeDiv = document.getElementById('pump_type_div'); resetSelect(pumpTypeSelect); pumpTypeDiv.classList.add('visually-hidden'); if (modelId && pumpTypeMap[modelId]) { pumpTypeDiv.classList.remove('visually-hidden'); // 动态创建选项 pumpTypeMap[modelId].forEach(item => { const option = document.createElement('option'); option.value = item.id; option.textContent = item.type_name; pumpTypeSelect.appendChild(option); }); } } </script>
3. 关键说明
- 后端用
values()方法只获取需要的字段,减少传输数据量 - 映射字典用父级ID作为键,前端选中后可直接通过ID取值,无需遍历
- JS中使用
document.createElement动态创建选项,避免拼接HTML字符串的安全问题 - 切换时重置所有后续Select,避免残留旧数据
内容的提问来源于stack exchange,提问作者Vahid
相关产品推荐
相关产品推荐

