本文详解如何为 disc 测试表单添加 javascript 实时校验逻辑,防止用户对同一题目的同一选项(如“daring”)同时选择 most 和 least,通过语义化分组、动态配对检测与视觉反馈提升表单健壮性与用户体验。
本文详解如何为 disc 测试表单添加 javascript 实时校验逻辑,防止用户对同一题目的同一选项(如“daring”)同时选择 most 和 least,通过语义化分组、动态配对检测与视觉反馈提升表单健壮性与用户体验。
在 DISC 人格测评类表单中,每道题通常包含若干行为描述(如 Enthusiastic、Daring),并要求用户从同一组选项中分别选出 MOST(最符合)和 LEAST(最不符合)——二者必须对应不同特质(D/I/S/C),否则逻辑矛盾。原始代码仅校验“是否全部作答”,却未阻止用户对同一题目中相同特质(如 q1most 和 q1least 都选 d-*)进行双重选择,导致评分失真。
核心解决方案在于:按题号抽象出唯一上下文标识,并追踪每个题中“MOST”与“LEAST”的特质字母是否重复。以下为推荐的现代、可维护实现方式:
✅ 关键设计思路
- 使用 name 属性提取题号(如 "q1most" → "q1"),作为逻辑分组键;
- 利用 Map 或普通对象缓存每题已选的特质字母(d, i, s, c);
- 每次勾选时解析 value(如 "d-most" → "d"),若该题已存相同字母,则触发校验失败;
- 提供视觉反馈(高亮错误题块 + alert 提示),而非静默忽略。
✅ 推荐实现(ES6 Class 版本)
class DISCValidator {
constructor() {
this.pairs = {}; // { 'q1': ['d', 'i'], 'q2': ['s'] }
this.errors = [];
this.warning = '不能对同一选项同时选择 MOST 和 LEAST!';
}
// 清除所有错误样式
clearErrors() {
document.querySelectorAll('.q.error').forEach(el => el.classList.remove('error'));
}
// 标记错误题块
markError(questionId) {
const questionEl = document.querySelector(`.q:has(input[name="${questionId}most"]), .q:has(input[name="${questionId}least"])`);
if (questionEl) questionEl.classList.add('error');
}
// 解析 name 获取题号(q1most → q1)
getQuestionId(name) {
return name.replace(/(most|least)$/i, '');
}
// 解析 value 获取特质字母(d-most → d)
getTrait(value) {
return value.split('-')[0].toLowerCase();
}
// 记录并校验配对
logAndValidate(name, value) {
const qId = this.getQuestionId(name);
const trait = this.getTrait(value);
if (!this.pairs[qId]) this.pairs[qId] = [];
this.pairs[qId].push(trait);
// 同一题出现两次相同特质 → 冲突
if (this.pairs[qId].length === 2 && this.pairs[qId][0] === this.pairs[qId][1]) {
this.errors.push(qId);
return true;
}
return false;
}
// 主校验入口:检查所有已选 radio
validateAll() {
this.clearErrors();
this.errors = [];
this.pairs = {};
const checkedRadios = document.querySelectorAll('input[type="radio"]:checked');
let hasConflict = false;
checkedRadios.forEach(radio => {
if (this.logAndValidate(radio.name, radio.value)) {
hasConflict = true;
this.markError(this.getQuestionId(radio.name));
}
});
// 额外检查:是否每题都完成选择(MOST + LEAST 各一)
const totalQuestions = new Set([...checkedRadios].map(r => this.getQuestionId(r.name))).size;
const expected = [...document.querySelectorAll('input[type="radio"]')].length / 4; // 每题4×2=8个选项,共2个需选
const isComplete = checkedRadios.length === expected * 2;
if (hasConflict) {
alert(this.warning);
return false;
}
if (!isComplete) {
alert('请为每道题完整选择 MOST 和 LEAST 各一项。');
return false;
}
return true;
}
}
// 绑定提交事件
document.getElementById('submit').addEventListener('click', function(e) {
e.preventDefault();
const validator = new DISCValidator();
if (validator.validateAll()) {
// 此处调用原有 scores 计算、图表渲染等逻辑
DiscModule.processForm(); // 或重构为 Promise 链式调用
}
});
✅ HTML 结构建议(增强可维护性)
为便于 JS 精准定位,推荐为每道题包裹唯一 data-question-id:
<div class="q" data-question-id="q1">
<div class="row">
<div class="col-xs-6">...</div>
<div class="col-xs-3">
MOST<br><br><input type="radio" name="q1most" value="i-most"><input type="radio" name="q1most" value="d-most"><!-- 其他 -->
</div>
<div class="col-xs-3">
LEAST<br><br><input type="radio" name="q1least" value="i-least"><input type="radio" name="q1least" value="d-least"><!-- 其他 -->
</div>
</div>
</div>
⚠️ 注意事项
- 避免全局变量污染:原方案使用闭包内 pairs={} 易受多次调用干扰;Class 实例化确保状态隔离。
- 不要依赖 return false 中断循环:getScores() 中提前 return 会跳过后续校验,应统一在末尾判断。
- CSS 增强体验:添加 .q.error { border: 2px solid #e74c3c; background-color: #fdf2f2; } 实现即时视觉提示。
- 无障碍友好:可进一步添加 aria-invalid="true" 与错误描述 此题选择冲突。
该方案兼顾健壮性、可读性与扩展性,既解决原始问题,也为后续增加「实时校验」「错误定位」或「自动修正提示」打下坚实基础。











