SpringBoot + Thymeleaf 前端下拉框动态加载后端数据库数据

最近在做后台管理系统时,表单里遇到下拉框要从数据库里查出来再展示的情况,通过学习和实践,将实现方法总结如下:

前端在页面加载时用 axios 请求后端接口,拿到数据后用原生 JS 动态拼接 <option>,并在修改页实现“加载 + 回显当前值”。文中的例子以我最近在练手的学生管理系统为场景,但方法与具体业务无关,换成任何“主表带外键下拉”的场景都适用。

一、场景拆解

下拉框动态加载其实只有两种典型场景:

场景 需要做的事 对应页面
新增页 只需加载下拉选项 添加表单
修改页 加载下拉选项 + 回显当前已选的值 编辑表单

新增页只需要关注下拉框加载数据即可,修改页在新增页的基础上多了一步“回显”。

  1. 后端:提供一个查询接口,返回统一格式的 JSON(包含状态码、数据、提示信息)。

  2. 前端加载:页面加载时(window.onload)用 axios 请求该接口,遍历返回的列表,用 document.createElement("option") 动态生成选项并 appendChild<select> 上。

  3. 修改页前端回显:把当前记录的已选值传到前端,等选项加载完成后,用 select.value = 已选值 选中它。


二、后端:返回统一格式的数据

1. 统一返回对象 Result

前后端分离开发中,强烈建议后端所有接口都返回统一格式,而不是直接把 List 扔回去。一个最简单的统一返回对象长这样:

public class Result {
     private Integer code;  // 状态码:1 表示成功,0 表示失败
     private Object data;   // 真正的业务数据
     private String msg;    // 提示信息
 ​
     public static Result success(Object data) {
         Result r = new Result();
         r.code = 1;
         r.data = data;
         r.msg = "操作成功";
         return r;
    }
 ​
     public static Result error(String msg) {
         Result r = new Result();
         r.code = 0;
         r.data = null;
         r.msg = msg;
         return r;
    }
 ​
     // 省略 getter / setter
 }

2. Controller 接口

提供一个查询接口,把数据库里的列表查出来塞进 Result.success(...) 即可:

@GetMapping("/class/selectClass")
 @ResponseBody
 public Result selectClass() {
     List<ClassInfo> list = classService.selectAll();
     return Result.success(list);
 }

返回的 JSON 大致如下:

{
   "code": 1,
   "data": [
    { "id": 1, "name": "一班" },
    { "id": 2, "name": "二班" }
  ],
   "msg": "操作成功"
 }

注意:返回的每个对象里至少要有两个属性,一个作为 optionvalue(通常是主键 id),一个作为 option 的显示文本(通常是名称),这是后面拼接选项的关键。


三、新增页:纯加载下拉选项

1. HTML 结构

先在页面里放一个空的 <select>,只留一个占位的提示项:

<div class="form-group">
     <label for="classId">所属班级</label>
     <select class="form-control" id="classId" name="classId">
         <option value="-1">--请选择班级--</option>
     </select>
 </div>

2. JS 逻辑

引入 axios,在 window.onload 里调用加载函数:

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
 <script>
     window.onload = function () {
         loadClassList();
    }
 ​
     function loadClassList() {
         axios.get("/class/selectClass").then(function (resp) {
             let result = resp.data;
             if (result.code === 1) {
                 let classList = result.data;
                 let select = document.getElementById("classId");
                 for (let i = 0; i < classList.length; i++) {
                     let option = document.createElement("option");
                     option.value = classList[i].id;
                     option.text = classList[i].name + "(ID:" + classList[i].id + ")";
                     select.appendChild(option);
                }
            } else {
                 alert(result.msg);
            }
        }).catch(function (error) {
             console.log(error);
             alert("请求失败" + error);
        });
    }
 </script>

核心就是这一段循环:

for (let i = 0; i < classList.length; i++) {
     let option = document.createElement("option");   // 1. 创建一个 option 节点
     option.value = classList[i].id;                  // 2. 设置提交给后端的值(主键)
     option.text  = classList[i].name + "(ID:" + classList[i].id + ")"; // 3. 设置用户看到的文本
     select.appendChild(option);                      // 4. 把节点挂到 select 上
 }
  • option.value:表单提交时实际发送的值,一般用主键 id,这样后端能根据 id 关联外键。

  • option.text:用户在下拉框里看到的文字。

  • appendChild:把生成好的节点追加到 <select> 末尾,占位的“请选择”项依然保留在最上面。


四、修改页:加载和回显当前值

修改页比新增页多一个需求:下拉框不仅要列出所有选项,还要默认选中这条记录原本所属的班级

1. 把当前已选值传到前端

修改页通常是由后端渲染(Thymeleaf)出来的,后端已经把这条学生记录查好传过来了。我们需要把“当前班级 id”先存到一个地方,供 JS 取用。

用一个隐藏域存id

<input type="hidden" id="cid" th:value="${student.classId}">

为什么不直接在 JS 里写 [[${student.classId}]]?也可以,但通过隐藏域取值更接近纯 JS 写法,逻辑和数据分离,迁移到纯前后端分离项目时几乎不用改 JS。

其它字段(姓名、年龄)用 Thymeleaf 回显:

<input type="text" id="name" th:value="${student.name}">
 <input type="text" id="age"  th:value="${student.age}">

性别这种固定选项的下拉框,用 th:selected 回显:

<select id="gender" name="gender">
     <option value="男" th:selected="${student.gender == '男'}">男</option>
     <option value="女" th:selected="${student.gender == '女'}">女</option>
 </select>

2. 加载函数接收当前 id

window.onload 时取出隐藏域里的班级 id,作为参数传给 loadClassList

window.onload = function () {
     let cid = document.getElementById("cid").value;
     loadClassList(cid);
 }

3. 在回调里设置 select.value

loadClassList 的加载逻辑和新增页一模一样,只多了一行,在所有 option 都 append 完之后,设置 select.value = id

function loadClassList(id) {
     axios.get("/class/selectClass").then(function (resp) {
         let result = resp.data;
         if (result.code === 1) {
             let classList = result.data;
             let select = document.getElementById("classId");
             for (let i = 0; i < classList.length; i++) {
                 let option = document.createElement("option");
                 option.value = classList[i].id;
                 option.text = classList[i].name + "(ID:" + classList[i].id + ")";
                 select.appendChild(option);
            }
             // 所有班级加载完后再选中当前学生的班级
             select.value = id;
        } else {
             alert(result.msg);
        }
    }).catch(function (error) {
         console.log(error);
         alert("请求失败" + error);
    });
 }

五、踩坑总结

这也是我在实际操作中碰到的问题,我当时写成了这样:

function loadClassList(id) {
     axios.get("/class/selectClass").then(function (resp) {
         // ... append options ...
    });
     select.value = id;
 }

下拉框正常显示,但一直只显示第一个选项。

原因:axios 请求是异步的。代码执行到 select.value = id 时,请求可能还没返回,<select> 里还只有占位项,根本没有 value 等于 id 的 option,应该把 select.value = id 放进 then 回调里,且必须放在循环 append 之后。 


六、完整代码

新增页:

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>添加信息</title>
 </head>
 <body>
     <form>
         <select id="classId" name="classId">
             <option value="-1">--请选择--</option>
         </select>
         <button type="button" onclick="add()">添加</button>
     </form>
 ​
     <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
     <script>
         window.onload = function () {
             loadClassList();
        }
 ​
         function loadClassList() {
             axios.get("/class/selectClass").then(function (resp) {
                 let result = resp.data;
                 if (result.code === 1) {
                     let classList = result.data;
                     let select = document.getElementById("classId");
                     for (let i = 0; i < classList.length; i++) {
                         let option = document.createElement("option");
                         option.value = classList[i].id;
                         option.text = classList[i].name + "(ID:" + classList[i].id + ")";
                         select.appendChild(option);
                    }
                } else {
                     alert(result.msg);
                }
            }).catch(function (error) {
                 console.log(error);
                 alert("请求失败" + error);
            });
        }
 ​
         function add() {
             let classId = document.getElementById("classId").value;
             // ...组装数据、POST 提交...
        }
     </script>
 </body>
 </html>

修改页:

<!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
     <meta charset="UTF-8">
     <title>修改信息</title>
 </head>
 <body>
     <form>
         <!-- 用隐藏域把当前已选值传给 JS -->
         <input type="hidden" id="cid" th:value="${student.classId}">
         <select id="classId" name="classId"></select>
         <button type="button" th:onclick="|update(${student.id})|">修改</button>
     </form>
 ​
     <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
     <script>
         window.onload = function () {
             let cid = document.getElementById("cid").value;
             loadClassList(cid);
        }
 ​
         function loadClassList(id) {
             axios.get("/class/selectClass").then(function (resp) {
                 let result = resp.data;
                 if (result.code === 1) {
                     let classList = result.data;
                     let select = document.getElementById("classId");
                     for (let i = 0; i < classList.length; i++) {
                         let option = document.createElement("option");
                         option.value = classList[i].id;
                         option.text = classList[i].name + "(ID:" + classList[i].id + ")";
                         select.appendChild(option);
                    }
                     // ★ 所有选项加载完后,再选中当前值(必须在回调内)
                     select.value = id;
                } else {
                     alert(result.msg);
                }
            }).catch(function (error) {
                 console.log(error);
                 alert("请求失败" + error);
            });
        }
 ​
         function update(id) {
             let classId = document.getElementById("classId").value;
             // ...组装数据、PUT 提交...
        }
     </script>
 </body>
 </html>
THE END
文章版权归Tinsur.cn所有,转载分享请标注原链接
点赞1 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容