Spring中如何从视图传递未知长度的Experience列表至控制器
解决动态Experience列表的Spring MVC绑定问题
要实现前端动态添加任意数量的Experience并正确传递到控制器的DTO列表中,需要修正Thymeleaf绑定规则和前端动态字段的命名逻辑,具体步骤如下:
1. 修正控制器与模型初始化
首先确保GET请求初始化DTO并传入视图,同时POST请求正确接收DTO:
// GET请求:初始化表单模型 @GetMapping("/buildcv") public String showCvBuilder(Model model) { ExperienceDTO dto = new ExperienceDTO(); // 初始化一个空Experience,方便渲染第一个表单条目 List<Experience> expList = new ArrayList<>(); expList.add(new Experience()); dto.setExperienceList(expList); model.addAttribute("experienceDTO", dto); return "buildcv"; } // POST请求:接收DTO @PostMapping("/buildcv") public ResponseEntity<byte[]> buildCv(@ModelAttribute("experienceDTO") ExperienceDTO experienceDTO) { // 处理experienceDTO中的experienceList ... }
2. 修正Thymeleaf表单绑定
在视图中使用th:object绑定DTO,初始表单条目通过索引绑定到列表元素:
<form th:object="${experienceDTO}" th:action="@{/buildcv}" method="post"> <!-- 存放所有Experience条目的容器 --> <div id="experience-container"> <!-- 初始第一个Experience条目 --> <div class="work-expirience-fields"> <a class="remove-image close-button">×</a> <div class="col-one-row-one"> <label>Job title</label> <input th:field="*{experienceList[0].position}" id="jobTitle_0"> </div> <div class="col-two-row-one"> <label>Employer</label> <input th:field="*{experienceList[0].company}" id="employer_0"> </div> <div class="col-one-row-two"> <label>Responsibilities</label> <textarea th:field="*{experienceList[0].responsibilities}" id="responsibilities_0"></textarea> </div> <div class="col-two-row-two"> <label>Start Date</label> <input type="date" th:field="*{experienceList[0].startDate}" id="startDate_0"> </div> <div class="col-three-row-two"> <label>End Date</label> <input type="date" th:field="*{experienceList[0].endDate}" id="endDate_0"> </div> </div> </div> <!-- 添加新条目的按钮 --> <button type="button" id="add-experience-btn">Add New Experience</button> <button type="submit">Generate CV</button> </form>
3. 动态添加/删除条目的JavaScript逻辑
核心是确保新增条目的字段name属性符合experienceList[N].字段名格式,Spring MVC会自动识别该格式并填充到DTO列表中:
// 绑定添加按钮事件 document.getElementById('add-experience-btn').addEventListener('click', function() { const container = document.getElementById('experience-container'); const currentCount = container.children.length; // 创建新条目DOM const newExpDiv = document.createElement('div'); newExpDiv.className = 'work-expirience-fields'; newExpDiv.innerHTML = ` <a class="remove-image close-button">×</a> <div class="col-one-row-one"> <label>Job title</label> <input name="experienceList[${currentCount}].position" id="jobTitle_${currentCount}"> </div> <div class="col-two-row-one"> <label>Employer</label> <input name="experienceList[${currentCount}].company" id="employer_${currentCount}"> </div> <div class="col-one-row-two"> <label>Responsibilities</label> <textarea name="experienceList[${currentCount}].responsibilities" id="responsibilities_${currentCount}"></textarea> </div> <div class="col-two-row-two"> <label>Start Date</label> <input type="date" name="experienceList[${currentCount}].startDate" id="startDate_${currentCount}"> </div> <div class="col-three-row-two"> <label>End Date</label> <input type="date" name="experienceList[${currentCount}].endDate" id="endDate_${currentCount}"> </div> `; container.appendChild(newExpDiv); // 绑定新条目的删除事件 newExpDiv.querySelector('.close-button').addEventListener('click', function() { container.removeChild(newExpDiv); // 删除后重新索引字段(可选,Spring MVC允许非连续索引,但连续更规范) reindexExpFields(); }); }); // 初始化已有删除按钮事件 document.querySelectorAll('.close-button').forEach(btn => { btn.addEventListener('click', function() { const container = document.getElementById('experience-container'); container.removeChild(this.parentElement); reindexExpFields(); }); }); // 重新索引所有字段的函数 function reindexExpFields() { const container = document.getElementById('experience-container'); const expDivs = container.querySelectorAll('.work-expirience-fields'); expDivs.forEach((div, index) => { // 更新每个字段的name属性 div.querySelector('input[name*="position"]').setAttribute('name', `experienceList[${index}].position`); div.querySelector('input[name*="company"]').setAttribute('name', `experienceList[${index}].company`); div.querySelector('textarea[name*="responsibilities"]').setAttribute('name', `experienceList[${index}].responsibilities`); div.querySelector('input[name*="startDate"]').setAttribute('name', `experienceList[${index}].startDate`); div.querySelector('input[name*="endDate"]').setAttribute('name', `experienceList[${index}].endDate`); // 更新id(可选) div.querySelector('input[name*="position"]').setAttribute('id', `jobTitle_${index}`); div.querySelector('input[name*="company"]').setAttribute('id', `employer_${index}`); }); }
4. 日期字段的格式兼容
在Experience实体类的日期字段添加注解,确保Spring MVC能正确解析前端传递的日期字符串:
@Entity public class Experience { @Id @GeneratedValue private int id; private String company; private String position; private String responsibilities; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date startDate; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date endDate; // getters and setters }
内容的提问来源于stack exchange,提问作者SimeonAngelov
相关产品推荐
相关产品推荐

