如何在.NET Core中用jQuery实现动态多题型章节评估的校验与跳转
四类题型适配的章节评估校验与跳转实现方案
现有代码的核心问题
- 仅覆盖radio类型的选中状态判断,完全未纳入多选checkbox、下拉排序题的作答收集与校验逻辑
- 未内置作答与标准答案的比对流程,仅将选中值存入数组无实际校验作用
- 依赖
parent().parent()的固定层级遍历定位表单容器,动态生成DOM时只要结构微调就会定位错误,直接导致跳转失效 - 未区分多选(无序匹配)、排序题(有序匹配)的校验规则,容易出现判分逻辑错误
- 绑定方式为直接给元素加click事件,动态插入的DOM元素无法触发事件,是之前fieldset方案在动态场景失效的核心原因
分步实现方案
1. 统一DOM锚点规则(解决动态定位失效问题)
摒弃对固定嵌套层级的依赖,给动态生成的元素加上统一的标识属性,不管内部结构怎么调整都能准确定位:
- 每个章节外层容器统一添加
class="quiz-chapter"属性,同时在标签上直接存当前章节的标准答案:data-correct-answer='{"q1":"A","q2":["A","C"],"q3":"true","q4":["item2","item1","item3"]}' - 不同题型的输入控件添加
data-question-type标识,区分single(单选)、multi(多选)、judge(判断)、sort(排序) - 跳转按钮统一保留原有类名
next/back,不做额外类名调整
2. 通用作答收集函数
针对四类题型的输入特性,统一收集当前章节内的所有用户作答,不需要为每类题型单独写收集逻辑:
function collectChapterAnswers($chapterEl) { const userAnswers = {}; // 收集单选、判断题(radio类) $chapterEl.find('input[type="radio"]:checked').each(function() { const qId = $(this).data('question-id'); userAnswers[qId] = $(this).val(); }); // 收集多选题(checkbox类) $chapterEl.find('input[type="checkbox"]').each(function() { const qId = $(this).data('question-id'); if (!userAnswers[qId]) userAnswers[qId] = []; if ($(this).is(':checked')) userAnswers[qId].push($(this).val()); }); // 收集排序题(下拉选择类),按页面上下拉的先后顺序拼接排序结果 $chapterEl.find('select[data-question-type="sort"]').each(function() { const qId = $(this).data('question-id'); const sortResult = []; $chapterEl.find(`select[data-question-id="${qId}"]`).each(function() { sortResult.push($(this).val()); }); userAnswers[qId] = sortResult; }); return userAnswers; }
3. 分层校验逻辑
先校验作答完整性(漏答拦截),再校验作答正确性(答错拦截),最后才允许跳转:
function validateCurrentChapter($chapterEl, userAnswers) { const correctAnswers = JSON.parse($chapterEl.attr('data-correct-answer')); // 第一层:漏答检查 for (const qId of Object.keys(correctAnswers)) { if (userAnswers[qId] === undefined || (Array.isArray(userAnswers[qId]) && userAnswers[qId].length === 0)) { return { passed: false, tip: '请完成当前章节的所有题目后再跳转' } } } // 第二层:正确性比对 for (const qId of Object.keys(correctAnswers)) { const standardVal = correctAnswers[qId]; const userVal = userAnswers[qId]; const qType = $chapterEl.find(`[data-question-id="${qId}"]`).first().data('question-type'); // 单选、判断题:值严格匹配即可 if (!Array.isArray(standardVal)) { if (String(userVal) !== String(standardVal)) { return { passed: false, tip: '当前章节有题目作答错误,请核对后再尝试跳转' } } } // 多选题:转换为无序列表匹配,不要求选项顺序一致 if (qType === 'multi') { const standardSet = [...standardVal].sort().join(','); const userSet = [...userVal].sort().join(','); if (standardSet !== userSet) { return { passed: false, tip: '当前多选题作答错误,请核对选项后再尝试跳转' } } } // 排序题:严格按顺序匹配,不打乱数组比对 if (qType === 'sort') { if (standardVal.join(',') !== userVal.join(',')) { return { passed: false, tip: '当前排序题顺序错误,请调整排序结果后再尝试跳转' } } } } return { passed: true, tip: '作答正确,已解锁下一章节' }; }
4. 绑定跳转事件(适配动态生成场景)
用全局事件委托绑定点击事件,不管章节是静态写的还是后续动态生成的,都能正常触发交互,不需要在每次生成DOM后重新绑定:
$(document).on('click', '.next', function() { // 沿DOM树向上找最近的章节容器,替代原来两层parent的定位方式,避免找错节点 const $currentChapter = $(this).closest('.quiz-chapter'); const userAnswers = collectChapterAnswers($currentChapter); const checkResult = validateCurrentChapter($currentChapter, userAnswers); if (!checkResult.passed) { alert(checkResult.tip); return; } // 校验通过才执行章节切换 $currentChapter.hide(); const $nextChapter = $currentChapter.next('.quiz-chapter'); if ($nextChapter.length) { $nextChapter.show(); // 切换后自动滚动到章节顶部,优化操作体验 $('html, body').animate({scrollTop: $nextChapter.offset().top}, 300); } else { alert('已完成所有章节的评估测试'); } }); // 上一步跳转不需要做校验,直接切换即可 $(document).on('click', '.back', function() { const $currentChapter = $(this).closest('.quiz-chapter'); $currentChapter.hide(); const $prevChapter = $currentChapter.prev('.quiz-chapter'); if ($prevChapter.length) { $prevChapter.show(); $('html, body').animate({scrollTop: $prevChapter.offset().top}, 300); } });
补充说明:前端校验仅用于优化交互体验,最终的作答对错判定必须在后端接口做兜底,避免用户通过修改前端DOM绕过校验直接跳转到后续章节。
内容的提问来源于stack exchange,提问作者DENNIS D'CRUZ
相关产品推荐
相关产品推荐

