点了「发表」却毫无反应:一次云声建议页的前端校验排查
在云声(bbs.huaweicloud.com/suggestion)提建议,标题、正文、类型、产品标签都填好了,协议也勾了,点【发表】——页面一动不动。没有 toast,没有报错,没有转圈。再点一次,还是没有。
第一反应通常是「被风控拦了」或者「接口挂了」。但真正的排查不该从这里开始。
一、先确认有没有发出请求
这种「点击无任何反馈」的场景,最省事的做法不是猜坐标、猜风控,而是先证明请求到底发没发。在页面里 hook 掉 fetch 和 XMLHttpRequest,把调用记录到全局数组,然后再点一次按钮:
window.__cap = [];
const of = window.fetch;
window.fetch = function (u, o) {
window.__cap.push({k: 'fetch', u: String(u), m: (o && o.method) || 'GET',
b: o && o.body ? String(o.body).slice(0, 300) : null});
return of.apply(this, arguments);
};
const oo = XMLHttpRequest.prototype.open, os = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (m, u) { this.__m = m; this.__u = u; return oo.apply(this, arguments); };
XMLHttpRequest.prototype.send = function (b) {
window.__cap.push({k: 'xhr', u: String(this.__u), m: this.__m, b: b ? String(b).slice(0, 300) : null});
return os.apply(this, arguments);
};
点完看 window.__cap:一条业务请求都没有。失败发生在前端校验,问题域一下从「网络/服务端」收缩到「表单校验」,这一步能省掉大半时间。
顺带一个有用的细节:页面会往 furiondata.myhuaweicloud.com 发埋点,里面带了 click 事件、坐标和命中元素。看到埋点在发、业务请求不发,基本能断定「鼠标事件确实打到了按钮,是按钮自己的逻辑拒绝提交」,而不是没点中。
二、找到校验函数
按钮 id 是 #submit-btn。把页面加载的 JS 抓下来搜这个 id,校验逻辑直接就能读到(这页还是 jQuery 时代的代码,比压缩后的 Vue 运行时好读得多)。核心判断是:
if ($("#labelTxt").text() === "") {
$("#suggestionProduct").addClass("warning");
$("#submit-btn").attr("disabled", "disabled");
$("#agreementBox").removeClass("checked");
return false;
}
也就是说,校验读的不是标签输入框,而是一个叫 #labelTxt 的文本节点。而界面上,产品标签区域明明已经显示了选中的标签芯片。
在控制台把两个值并排打出来:
$("[name=productTag]").val(); // 有值,输入框正常
$("#labelTxt").text(); // 空字符串
根因确认。同时还能看到校验失败时它顺手把协议勾选去掉了、给容器加了 .warning、给按钮加了 disabled——但没有任何一处文字提示。这就是「点了没反应」的全部真相。
三、为什么视觉上是对的
标签组件用的是 tagator 这类插件。翻它的源码能找到同步函数:
var setLabelText = function () {
document.getElementById('labelTxt').innerText = $source_element.val();
};
关键在于它挂在哪里:它绑在源输入框的 blur 事件和「点击已选中的产品项」上,没有绑在下拉候选项(autocomplete option)的点击上。于是出现这条路径:
- 在产品框里输入关键字;
- 从弹出的候选里点选一项;
- tagator 把标签芯片渲染出来,源输入框的 value 也更新了 —— 看上去完全成功;
- 但这次选择不经过 blur,
setLabelText()没执行,#labelTxt仍是空; - 校验读到空,静默失败。
用 document.elementFromPoint 做命中测试还发现另一件事:候选下拉在页面滚动后会被 .warning-box 盖住,命中测试返回的是那层遮罩,点上去只得到「已包含此标签,请勿重复提交」。所以这是个复合问题:校验字段不同步 + 浮层遮挡 + 无提示。
四、绕过与修复
对使用者,最省事的绕过是选完标签后点一下输入框外面,让它 blur 一次,#labelTxt 立刻就有值:
$(".tagator_input").focus().blur();
对实现方,三条建议成本都很低:
- 校验应该读真正的数据源(输入框 value 或组件状态),而不是一个用于展示的镜像文本节点。镜像节点本质是「把视图状态当模型状态用」,迟早出问题。
- 任何
return false的分支都要有可见反馈。表单校验失败却不提示,用户只能靠猜,这是可观测性问题,不是审美问题。 - 用 JS 派发事件在候选项上点了没反应时,别急着换成真实鼠标事件硬撞坐标:先用命中测试确认坐标是否被遮挡,再确认组件监听的是原生 click 还是 jQuery 事件。这两种原因的修法完全不同。
五、可复用的排查顺序
这类「点击无反馈」的问题,固定顺序是:
- hook 网络,确认请求发没发(30 秒,能砍掉一半可能性);
- 看埋点里的 click 记录,确认事件确实打到了目标元素;
- 抓页面 JS 搜按钮 id,直接读校验函数;
- 在控制台对比「视觉状态」和「校验读取的状态」两个值;
- 最后才怀疑坐标、遮挡、风控。
第 4 步是这次真正的收获:一个页面上同时存在两份状态(输入框 value 和展示用的 #labelTxt),而校验只看后者。这种「双份状态」的写法在老前端代码里非常常见,值得单独作为一类问题记住。
- 点赞
- 收藏
- 关注作者
评论(0)