You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现两个多选下拉框协同过滤DataTable表格?

解决DataTable多下拉框协同过滤及动态选项更新问题

核心问题分析

原代码存在以下关键缺陷导致功能失效:

  • 两个下拉框共用同一DOM容器与状态变量,引发逻辑冲突
  • 过滤逻辑仅单维度生效,未结合两个下拉框的筛选条件
  • 用户下拉选项未随主管筛选结果动态更新
  • 原生DOM样式操作与DataTable API混用,导致状态不一致

解决方案代码

重构代码,为每个下拉框维护独立状态,实现协同过滤与动态选项更新:

// 存储两个下拉框的独立状态:选项集合、选中值、搜索关键词
const dropdownStates = {
  0: { options: new Set(), selected: new Set(), search: '' }, // 用户名下拉(列索引0)
  1: { options: new Set(), selected: new Set(), search: '' }  // 主管名下拉(列索引1)
};

// 初始化用户名下拉筛选
function searchUserName(input, tableName) {
  initDropdown(input, tableName, 0, 'searchInput');
}

// 初始化主管名下拉筛选
function searchSupervisorName(input, tableName) {
  initDropdown(input, tableName, 1, 'searchSupervisorInput');
}

function initDropdown(input, tableName, colPos, inputId) {
  const table = document.getElementById(tableName);
  const dataTable = $('#maintable').DataTable();

  // 创建独立的下拉容器
  let dropdown = document.getElementById(`dropdown-${colPos}`);
  if (!dropdown) {
    dropdown = document.createElement('div');
    dropdown.id = `dropdown-${colPos}`;
    dropdown.className = 'dropdown';
    input.parentNode.insertBefore(dropdown, input.nextSibling);

    // 添加全选/取消全选选项
    const toggleAll = createOptionContainer('Toggle All', colPos);
    dropdown.appendChild(toggleAll);
    dropdown.appendChild(document.createElement('hr'));

    // 绑定下拉框变更事件
    dropdown.addEventListener('change', () => {
      updateSelected(colPos);
      applyDualFilter();
    });

    // 绑定搜索框输入事件
    input.addEventListener('keyup', () => {
      dropdownStates[colPos].search = input.value.trim().toUpperCase();
      filterDropdownOptions(colPos);
      applyDualFilter();
    });

    // 绑定搜索框点击事件(触发选项刷新)
    input.addEventListener('click', () => {
      refreshDropdownOptions(colPos);
      dropdown.style.display = 'block';
    });

    // 点击外部关闭下拉框
    document.addEventListener('click', (e) => {
      if (dropdown && !dropdown.contains(e.target) && e.target !== input) {
        dropdown.style.display = 'none';
      }
    });
  }

  // 创建单个选项的DOM容器
  function createOptionContainer(name, colPos) {
    const container = document.createElement('label');
    container.className = 'option-container';
    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.value = name;
    checkbox.dataset.col = colPos;
    container.appendChild(checkbox);
    container.appendChild(document.createTextNode(name));
    if (name === 'Toggle All') {
      container.classList.add('toggle-all-option');
    }
    return container;
  }

  // 更新当前下拉框的选中值集合
  function updateSelected(colPos) {
    const dropdown = document.getElementById(`dropdown-${colPos}`);
    const checkboxes = dropdown.querySelectorAll('input[type="checkbox"]:not([value="Toggle All"])');
    const toggleAll = dropdown.querySelector('input[value="Toggle All"]');
    const selected = new Set();

    if (toggleAll.checked) {
      checkboxes.forEach(cb => {
        cb.checked = true;
        selected.add(cb.value.trim().toUpperCase());
      });
    } else {
      checkboxes.forEach(cb => {
        if (cb.checked) selected.add(cb.value.trim().toUpperCase());
      });
    }

    dropdownStates[colPos].selected = selected;
    // 更新搜索栏选中状态样式
    const searchBar = document.getElementById(inputId);
    searchBar.classList.toggle('selected-search-bar', selected.size > 0);
  }

  // 根据当前可见行刷新下拉选项
  function refreshDropdownOptions(colPos) {
    const dropdown = document.getElementById(`dropdown-${colPos}`);
    // 清空现有选项(保留全选按钮与分隔线)
    const existingOptions = dropdown.querySelectorAll('.option-container:not(.toggle-all-option)');
    existingOptions.forEach(opt => opt.remove());

    // 从DataTable可见行中提取对应列的值
    const visibleRows = dataTable.rows({ search: 'applied' }).nodes().to$();
    const values = new Set();

    visibleRows.each(function() {
      const td = this.querySelector(`td:nth-child(${colPos + 1})`);
      if (td) values.add(td.textContent.trim());
    });

    dropdownStates[colPos].options = values;

    // 添加排序后的新选项,并保留之前的选中状态
    Array.from(values).sort((a, b) => a.localeCompare(b)).forEach(val => {
      const container = createOptionContainer(val, colPos);
      container.querySelector('input').checked = dropdownStates[colPos].selected.has(val.toUpperCase());
      dropdown.appendChild(container);
    });

    filterDropdownOptions(colPos);
  }

  // 根据搜索关键词过滤下拉选项
  function filterDropdownOptions(colPos) {
    const dropdown = document.getElementById(`dropdown-${colPos}`);
    const options = dropdown.querySelectorAll('.option-container:not(.toggle-all-option)');
    const search = dropdownStates[colPos].search;

    options.forEach(opt => {
      const text = opt.textContent.trim().toUpperCase();
      opt.style.display = text.includes(search) ? 'block' : 'none';
    });
  }

  // 应用双维度联合过滤
  function applyDualFilter() {
    const userSelected = dropdownStates[0].selected;
    const supervisorSelected = dropdownStates[1].selected;
    const userSearch = dropdownStates[0].search;
    const supervisorSearch = dropdownStates[1].search;

    // 遍历所有行,同时验证两个筛选条件
    dataTable.rows().every(function() {
      const row = this.node();
      const userName = row.querySelector('td:nth-child(1)').textContent.trim().toUpperCase();
      const supervisorName = row.querySelector('td:nth-child(2)').textContent.trim().toUpperCase();

      // 用户名匹配规则:无选中则全部通过,否则匹配选中值+搜索关键词
      const userMatch = (userSelected.size === 0 || userSelected.has(userName)) && userName.includes(userSearch);
      // 主管名匹配规则同上
      const supervisorMatch = (supervisorSelected.size === 0 || supervisorSelected.has(supervisorName)) && supervisorName.includes(supervisorSearch);

      // 仅当两个条件都满足时显示行
      this.nodes().to$().toggle(userMatch && supervisorMatch);
    });

    // 更新无数据提示
    const visibleCount = dataTable.rows({ visible: true }).count();
    const tbody = $('#maintable tbody');
    tbody.find('.no-records').remove();
    if (visibleCount === 0) {
      tbody.append('<tr class="no-records"><td colspan="100%">No matching records found</td></tr>');
    }

    // 重新应用奇偶行样式
    applyEvenOddStyling();
  }

  // 为可见行应用奇偶样式
  function applyEvenOddStyling() {
    const visibleRows = dataTable.rows({ visible: true }).nodes().to$();
    visibleRows.each(function(index) {
      $(this).toggleClass('odd-row', index % 2 === 0)
             .toggleClass('even-row', index % 2 !== 0);
    });
  }
}

关键改动说明

  1. 独立状态管理:为每个下拉框创建独立状态对象,避免多筛选器之间的逻辑冲突
  2. 协同过滤逻辑:applyDualFilter函数同时校验两个下拉框的筛选条件,只有同时满足的行才会显示
  3. 动态选项更新:点击用户下拉框时,自动从当前可见行提取用户名并更新选项,实现主管筛选后的联动效果
  4. 统一DataTable API:放弃原生DOM样式操作,改用DataTable官方API控制行显示,保证状态一致性
  5. 简化全选逻辑:将原"Deselect All"改为"Toggle All",实现一键全选/取消全选,逻辑更直观

使用注意事项

  • 确保DataTable初始化完成后,再调用searchUserName和searchSupervisorName绑定对应输入框
  • 表格列索引需与代码中nth-child的序号对应(示例中用户名为第1列,主管名为第2列)
  • 保留原有的CSS样式(.dropdown、.option-container、.selected-search-bar等)以保证界面正常显示

内容的提问来源于stack exchange,提问作者Bakes Torafder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:55:53