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

如何将Neo4j返回的嵌套JSON从Node.js传递到EJS渲染多级下拉表单

EJS 多级下拉选择框渲染解决方案

问题根因

你之前用ul/li可以正常嵌套渲染是因为列表标签本身支持多层嵌套,但<select>属于表单控件,HTML语法明确规定其内部仅允许嵌套<option>、<optgroup>两类子元素,直接在<select>里嵌套另一个<select>属于语法错误,浏览器解析时会自动把嵌套的<select>移到外层,最终导致页面结构混乱。

实现方案

首先假设你服务端传递到EJS的四级嵌套数据结构如下(可根据你的实际字段调整):

const neo4jData = {
  name: 'UofO',
  campBusList: [
    {
      id: 'cb_001',
      name: '主校区',
      schColUnitList: [
        {
          id: 'scu_001',
          name: '计算机学院',
          deptList: [
            { id: 'dept_001', name: '软件工程系' },
            { id: 'dept_002', name: '网络安全系' }
          ]
        }
      ]
    }
  ]
}

提供两种常用实现方案可选:

方案1:静态全量渲染(适合数据量小的场景)

不嵌套<select>标签,每一级下拉独立渲染,用optgroup给同父级的选项做分组:

<!-- 一级选择:CampBus -->
<select name="campBus" id="campBus">
  <% neo4jData.campBusList.forEach(cb => { %>
    <option value="<%= cb.id %>"><%= cb.name %></option>
  <% }) %>
</select>

<!-- 二级选择:SchColUnit -->
<select name="schColUnit" id="schColUnit">
  <% neo4jData.campBusList.forEach(cb => { %>
    <optgroup label="<%= cb.name %>" data-parent-cbid="<%= cb.id %>">
      <% cb.schColUnitList.forEach(scu => { %>
        <option value="<%= scu.id %>"><%= scu.name %></option>
      <% }) %>
    </optgroup>
  <% }) %>
</select>

<!-- 三级选择:Dept -->
<select name="dept" id="dept">
  <% neo4jData.campBusList.forEach(cb => { %>
    <% cb.schColUnitList.forEach(scu => { %>
      <optgroup label="<%= scu.name %>" data-parent-scuid="<%= scu.id %>">
        <% scu.deptList.forEach(dept => { %>
          <option value="<%= dept.id %>"><%= dept.name %></option>
        <% }) %>
      </optgroup>
    <% }) %>
  <% }) %>
</select>

如果需要联动效果,只需加少量前端JS监听上一级下拉的change事件,隐藏不符合父级ID的optgroup即可。

方案2:动态联动渲染(适合数据量大的场景)

EJS只输出基础下拉结构和全量数据,后续层级的选项根据上一级的选择动态生成,性能更高:

<!-- 把全量数据挂载到全局供JS调用 -->
<script>
  window.fullTreeData = <%- JSON.stringify(neo4jData) %>
</script>

<!-- 各级下拉空结构 -->
<select name="campBus" id="campBus"></select>
<select name="schColUnit" id="schColUnit" disabled></select>
<select name="dept" id="dept" disabled></select>

<script>
  const cbSelect = document.getElementById('campBus')
  const scuSelect = document.getElementById('schColUnit')
  const deptSelect = document.getElementById('dept')

  // 初始化一级选项
  fullTreeData.campBusList.forEach(cb => {
    const opt = document.createElement('option')
    opt.value = cb.id
    opt.textContent = cb.name
    cbSelect.appendChild(opt)
  })

  // 一级变更触发二级渲染
  cbSelect.addEventListener('change', () => {
    const selectedCb = fullTreeData.campBusList.find(cb => cb.id === cbSelect.value)
    scuSelect.innerHTML = ''
    deptSelect.innerHTML = ''
    deptSelect.disabled = true
    // 渲染二级选项
    selectedCb.schColUnitList.forEach(scu => {
      const opt = document.createElement('option')
      opt.value = scu.id
      opt.textContent = scu.name
      scuSelect.appendChild(opt)
    })
    scuSelect.disabled = false
    scuSelect.dispatchEvent(new Event('change'))
  })

  // 二级变更触发三级渲染
  scuSelect.addEventListener('change', () => {
    const selectedCb = fullTreeData.campBusList.find(cb => cb.id === cbSelect.value)
    const selectedScu = selectedCb.schColUnitList.find(scu => scu.id === scuSelect.value)
    deptSelect.innerHTML = ''
    selectedScu.deptList.forEach(dept => {
      const opt = document.createElement('option')
      opt.value = dept.id
      opt.textContent = dept.name
      deptSelect.appendChild(opt)
    })
    deptSelect.disabled = false
  })

  // 初始化渲染
  cbSelect.dispatchEvent(new Event('change'))
</script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 14:24:03