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

动态生成菜单的Click事件部分失效,求解决方案

动态导航菜单点击事件部分失效的解决方法

问题原因

核心问题在于使用innerHTML +=拼接DOM时,会重新渲染容器内的所有元素,导致之前绑定的点击事件丢失。例如添加第二个父菜单时,menuContainer.innerHTML += html会将已存在的菜单HTML重新解析生成新DOM节点,之前给第一个菜单绑定的事件会因原节点被替换而失效。同时,这种拼接方式也会导致部分动态生成的子菜单事件绑定不可靠。


方案一:事件委托(推荐)

利用事件冒泡机制,仅在父容器上绑定一次事件,即可处理所有动态生成的菜单元素点击,完全避免逐个绑定事件的问题,代码更简洁高效。

修改后的完整代码

let menuData = [
    {
        "id": 257,
        "nombreinterno": "TituloMenu02",
        "ruta": "",
        "titulo": "Segundo Titulo 257",
        "padre": "-1",
        "orden": "1"
    },
    {
        "id": 256,
        "nombreinterno": "TituloMenu01",
        "ruta": "",
        "titulo": "Primer Titulo 256",
        "padre": "-1",
        "orden": "2"
    },
    {
        "id": 258,
        "nombreinterno": "TituloMenu03",
        "ruta": "",
        "titulo": "Tercer Titulo 258",
        "padre": "-1",
        "orden": "3"
    },
    {
        "id": 260,
        "nombreinterno": "TituloMenu05",
        "ruta": "",
        "titulo": "Quinto Titulo 260",
        "padre": "-1",
        "orden": "4"
    },
    {
        "id": 259,
        "nombreinterno": "TituloMenu04",
        "ruta": "",
        "titulo": "Cuarto Titulo 259",
        "padre": "-1",
        "orden": "5"
    },
    {
        "id": 262,
        "nombreinterno": "MedidaList2",
        "ruta": "",
        "titulo": "Segundo Sub-Titulo 256",
        "padre": "256",
        "orden": "25610"
    },
    {
        "id": 261,
        "nombreinterno": "MedidaList",
        "ruta": "medidasListar.html",
        "titulo": "Primer Sub-Titulo 256",
        "padre": "256",
        "orden": "-1"
    },
    {
        "id": 263,
        "nombreinterno": "Submenu01",
        "ruta": "",
        "titulo": "Primer Sub-Titulo 257",
        "padre": "257",
        "orden": "-1"
    },
    {
        "id": 265,
        "nombreinterno": "Submenu01",
        "ruta": "",
        "titulo": "Primer Sub-Titulo 260",
        "padre": "260",
        "orden": "-1"
    },
    {
        "id": 264,
        "nombreinterno": "Submenu02",
        "ruta": "",
        "titulo": "Segundo Sub-Titulo 257",
        "padre": "257",
        "orden": "-1"
    }
];

// 模板函数新增data-ruta属性,存储页面路由
const menuItemTemplate = (id, title, hasChildren, ruta) => `
  <li class="nav-item">
    <a id="${id}" class="nav-link collapsed" href="#" data-ruta="${ruta}"${hasChildren ? ` data-toggle="collapse" data-target="#collapse${id}" aria-expanded="true" aria-controls="collapse${id}"` : ''}>
      <i class="fas fa-fw fa-folder"></i>
      <span>${title}</span>
    </a>
    ${hasChildren ? `<div id="collapse${id}" class="collapse" aria-labelledby="heading${id}" data-parent="#accordionSidebar">
      <div class="bg-white py-2 collapse-inner rounded">
        <!-- Contenido de los submenús -->
      </div>
    </div>` : ''}
  </li>`;

const subMenuItemTemplate = (id, title, ruta) => `<a id="${id}" class="collapse-item" href="#" data-ruta="${ruta}">${title}</a>`;

function generateMenuHTML(menuData) {
  const menuContainer = document.getElementById('accordionSidebar');
  let parentHTML = '';

  // 先排序菜单数据
  menuData.sort((a, b) => {
    if (a.orden === '-1' && b.orden === '-1') {
      return a.titulo.localeCompare(b.titulo);
    }
    else if (a.orden === '-1') {
      return 1;
    }
    else if (b.orden === '-1') {
      return -1;
    }
    else {
      return parseInt(a.orden) - parseInt(b.orden);
    }
  });

  // 先构建所有父菜单HTML,一次性插入
  const parentItems = menuData.filter(item => item.padre === '-1');
  parentItems.forEach(parentItem => {
    const { nombreinterno, titulo, ruta } = parentItem;
    const hasChildren = menuData.some(item => item.padre == parentItem.id);
    parentHTML += menuItemTemplate(nombreinterno, titulo, hasChildren, ruta);
  });
  menuContainer.innerHTML = parentHTML;

  // 再处理子菜单,逐个插入
  const childItems = menuData.filter(item => item.padre !== '-1');
  childItems.forEach(childItem => {
    const { nombreinterno, ruta, titulo, padre } = childItem;
    const parentItem = menuData.find(item => item.id == padre);
    const parentCollapseId = `collapse${parentItem.nombreinterno}`;
    const subMenuContainer = document.querySelector(`#${parentCollapseId} .collapse-inner`);
    subMenuContainer.innerHTML += subMenuItemTemplate(nombreinterno, titulo, ruta);
  });
}

$(function() {
  generateMenuHTML(menuData);
  
  // 事件委托:父容器监听所有菜单元素的点击
  $('#accordionSidebar').on('click', '.nav-link, .collapse-item', function(e) {
    e.preventDefault(); // 阻止默认跳转行为
    const itemId = this.id;
    const targetPage = $(this).data('ruta');
    
    console.log('点击菜单:', itemId);
    if (targetPage) {
      // $('#contenedor').load(targetPage);
      console.log('加载页面:', targetPage);
    }
  });
});

方案二:修复原有绑定逻辑

若不想使用事件委托,可通过appendChild替代innerHTML +=添加DOM元素,避免原节点被替换导致事件丢失。

核心修改点

  1. 将模板函数改为返回DOM元素,而非HTML字符串
  2. 使用appendChild将新元素添加到容器中
// 重写模板函数,返回DOM元素
function createMenuItem(id, title, hasChildren, ruta) {
  const li = document.createElement('li');
  li.className = 'nav-item';

  const a = document.createElement('a');
  a.id = id;
  a.className = 'nav-link collapsed';
  a.href = '#';
  a.dataset.ruta = ruta;
  
  if (hasChildren) {
    a.setAttribute('data-toggle', 'collapse');
    a.setAttribute('data-target', `#collapse${id}`);
  }

  const icon = document.createElement('i');
  icon.className = 'fas fa-fw fa-folder';
  a.appendChild(icon);

  const span = document.createElement('span');
  span.textContent = title;
  a.appendChild(span);
  li.appendChild(a);

  if (hasChildren) {
    const collapseDiv = document.createElement('div');
    collapseDiv.id = `collapse${id}`;
    collapseDiv.className = 'collapse';
    collapseDiv.setAttribute('data-parent', '#accordionSidebar');

    const innerDiv = document.createElement('div');
    innerDiv.className = 'bg-white py-2 collapse-inner rounded';
    collapseDiv.appendChild(innerDiv);
    li.appendChild(collapseDiv);
  }

  return li;
}

// 生成菜单时使用appendChild添加元素
function generateMenuHTML(menuData) {
  const menuContainer = document.getElementById('accordionSidebar');

  menuData.sort((a, b) => {
    // 排序逻辑不变
  });

  menuData.forEach(menuItem => {
    const { id, nombreinterno, ruta, titulo, padre } = menuItem;

    if (padre == '-1') {
      const hasChildren = menuData.some(item => item.padre == id);
      const menuElement = createMenuItem(nombreinterno, titulo, hasChildren, ruta);
      menuContainer.appendChild(menuElement);

      if (!hasChildren) {
        $(`#${nombreinterno}`).on('click', function(e) {
          e.preventDefault();
          console.log('点击菜单:', nombreinterno);
          if (ruta) $('#contenedor').load(ruta);
        });
      }
    } else {
      const parentItem = menuData.find(item => item.id == padre);
      const parentCollapse = document.getElementById(`collapse${parentItem.nombreinterno}`);
      const subMenuContainer = parentCollapse.querySelector('.collapse-inner');
      
      const subItem = document.createElement('a');
      subItem.id = nombreinterno;
      subItem.className = 'collapse-item';
      subItem.href = '#';
      subItem.dataset.ruta = ruta;
      subItem.textContent = titulo;
      subMenuContainer.appendChild(subItem);

      $(`#${nombreinterno}`).on('click', function(e) {
        e.preventDefault();
        console.log('点击子菜单:', nombreinterno);
        if (ruta) $('#contenedor').load(ruta);
      });
    }
  });
}

内容的提问来源于stack exchange,提问作者Jorge Cardenas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 05:28:08