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

Oracle APEX 5.1层级联动动态选择列表实现技术问询

在Oracle APEX 5.1中实现动态层级联动选择列表

嘿,我在APEX 5.1里折腾过好多次这种多层级联动的需求,给你分享两种最实用的实现思路——分固定层级数和动态层级数两种场景,你可以根据自己的实际情况来选:


场景1:固定层级数(比如3级联动)

假设你要做类似「行业大类→细分品类→具体产品」这种固定3级的联动,数据库里有个关联表(比如CATEGORIES,字段是ID, PARENT_ID, CATEGORY_NAME),咱们一步步来:

第一步:创建基础选择列表

  1. 先做第一个顶级列表(比如行业大类):

    • 类型选动态选择列表
    • 写SQL的时候只捞顶级分类:
      SELECT category_name AS display_value, id AS return_value
      FROM categories
      WHERE parent_id IS NULL -- 筛选顶级分类
      ORDER BY category_name
      
    • 给它设个好记的静态ID,比如P1_TOP_CATEGORY,后面动态动作要用到它
  2. 接着做第二个列表(细分品类),初始状态设为隐藏:

    • 类型同样是动态选择列表
    • SQL要绑定第一个列表的值:
      SELECT category_name AS display_value, id AS return_value
      FROM categories
      WHERE parent_id = :P1_TOP_CATEGORY -- 绑定上级选择的ID
      ORDER BY category_name
      
    • 静态ID设为P1_SUB_CATEGORY
    • 加个服务器端条件:P1_TOP_CATEGORY IS NOT NULL,确保只有选了上级才显示这个列表
  3. 第三个列表(具体产品)同理,初始隐藏,SQL绑定P1_SUB_CATEGORY,静态IDP1_PRODUCT

第二步:配置动态动作实现联动

  1. 给P1_TOP_CATEGORY加个on change动态动作:

    • 触发条件选「当值改变时」
    • 先加一个刷新动作,目标选P1_SUB_CATEGORY(这样它的SQL会重新执行,加载对应子分类)
    • 再加一个显示动作,目标也是P1_SUB_CATEGORY(把之前隐藏的列表显示出来)
  2. 给P1_SUB_CATEGORY也加个on change动态动作:

    • 触发条件同样是「当值改变时」
    • 动作选刷新+显示,目标换成P1_PRODUCT就行

场景2:动态层级数(层级数不固定)

如果你的层级是动态的——比如有的分类只有2级,有的能到5级,那就要用动态动作动态生成选择列表了,思路是用AJAX递归加载下一级:

第一步:准备基础容器和顶级列表

  1. 先创建第一个顶级选择列表,静态IDP1_ROOT_CAT,SQL和场景1的顶级列表一样
  2. 拖一个「静态内容」区域当容器,给它设静态IDP1_CAT_CONTAINER,用来放后续动态生成的列表

第二步:给顶级列表加动态动作

给P1_ROOT_CAT加on change动态动作,选「执行PL/SQL代码」,代码逻辑是清空容器、生成第一级子列表,还要绑定on change事件:

DECLARE
  l_child_count NUMBER;
  l_html VARCHAR2(32767);
  l_parent_id NUMBER := :P1_ROOT_CAT;
  l_level NUMBER := 1;
BEGIN
  -- 先清空容器里的旧列表
  apex_javascript.add_onload_code('$("#P1_CAT_CONTAINER").empty();');
  
  -- 先查有没有子分类,没有就直接退出
  SELECT COUNT(*) INTO l_child_count FROM categories WHERE parent_id = l_parent_id;
  EXIT WHEN l_child_count = 0;
  
  l_level := l_level + 1;
  -- 生成子列表的HTML
  l_html := '<label for="P1_CAT_' || l_level || '">第' || l_level || '级分类</label>' ||
            '<select id="P1_CAT_' || l_level || '" name="P1_CAT_' || l_level || '" class="apex-item-select">' ||
            '<option value="">请选择</option>';
  
  -- 填充子分类选项,记得转义HTML避免XSS
  FOR rec IN (SELECT category_name, id FROM categories WHERE parent_id = l_parent_id ORDER BY category_name) LOOP
    l_html := l_html || '<option value="' || rec.id || '">' || apex_escape.html(rec.category_name) || '</option>';
  END LOOP;
  
  l_html := l_html || '</select>';
  
  -- 把HTML输出到页面
  apex_web_service.print(l_html);
  
  -- 给新生成的列表加on change事件,用APEX的AJAX请求加载下一级
  apex_javascript.add_onload_code(
    '$("#P1_CAT_' || l_level || '").change(function() {' ||
    '  var parentId = $(this).val();' ||
    '  if (parentId) {' ||
    '    apex.server.process("LOAD_NEXT_CAT_LEVEL", {x01: parentId, x02: ' || l_level || '}, {' ||
    '      success: function(data) {' ||
    '        // 先删掉之前的下一级列表,避免重复' ||
    '        $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_level + 1) || '").remove();' ||
    '        $("#P1_CAT_CONTAINER").append(data);' ||
    '      }' ||
    '    });' ||
    '  } else {' ||
    '    // 清空下一级列表' ||
    '    $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_level + 1) || '").remove();' ||
    '  }' ||
    '});'
  );
END;

第三步:创建AJAX进程处理后续层级

创建一个名为LOAD_NEXT_CAT_LEVEL的AJAX进程,用来处理用户选择后加载下一级的请求:

DECLARE
  l_parent_id NUMBER := apex_application.g_x01; -- 上级ID
  l_current_level NUMBER := apex_application.g_x02; -- 当前层级
  l_next_level NUMBER := l_current_level + 1;
  l_html VARCHAR2(32767);
  l_child_count NUMBER;
BEGIN
  -- 先查有没有子分类
  SELECT COUNT(*) INTO l_child_count FROM categories WHERE parent_id = l_parent_id;
  IF l_child_count = 0 THEN
    apex_web_service.print('');
    RETURN;
  END IF;
  
  -- 生成下一级列表的HTML
  l_html := '<label for="P1_CAT_' || l_next_level || '">第' || l_next_level || '级分类</label>' ||
            '<select id="P1_CAT_' || l_next_level || '" name="P1_CAT_' || l_next_level || '" class="apex-item-select">' ||
            '<option value="">请选择</option>';
  
  FOR rec IN (SELECT category_name, id FROM categories WHERE parent_id = l_parent_id ORDER BY category_name) LOOP
    l_html := l_html || '<option value="' || rec.id || '">' || apex_escape.html(rec.category_name) || '</option>';
  END LOOP;
  
  l_html := l_html || '</select>';
  
  -- 给新列表加on change事件,递归加载下一级
  apex_web_service.print(l_html ||
    '<script>' ||
    '$("#P1_CAT_' || l_next_level || '").change(function() {' ||
    '  var parentId = $(this).val();' ||
    '  if (parentId) {' ||
    '    apex.server.process("LOAD_NEXT_CAT_LEVEL", {x01: parentId, x02: ' || l_next_level || '}, {' ||
    '      success: function(data) {' ||
    '        $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_next_level + 1) || '").remove();' ||
    '        $("#P1_CAT_CONTAINER").append(data);' ||
    '      }' ||
    '    });' ||
    '  } else {' ||
    '    $("#P1_CAT_CONTAINER").find("#P1_CAT_' || (l_next_level + 1) || '").remove();' ||
    '  }' ||
    '});' ||
    '</script>'
  );
END;

一些关键注意事项

  • 绑定变量要正确用:所有动态列表的SQL一定要用绑定变量(比如:P1_TOP_CATEGORY),不仅能防SQL注入,还能提升查询性能
  • 静态ID不能重复:不管是静态创建还是动态生成的列表,静态ID必须唯一,不然动态动作会找不到目标
  • APEX 5.1兼容性:APEX 5.1里的AJAX请求要用apex.server.process,别用旧的htmldb_Get,不然会出问题
  • 用户体验优化:下级列表初始隐藏,选了上级再显示,避免页面乱糟糟的;如果用户清空上级选择,记得把下级列表也隐藏或者清空

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:14:59