
先别着急写代码。很多新手学前端第一个练手项目就是注册页面但做完之后总有一种“这页面能动但我说不清它为什么能动”的感觉。问题往往不在于HTML和CSS而在于请求到底发到哪去了、后端到底收到了什么、返回的数据前端又是怎么处理的。这次这篇实战我把整套链路拆开揉碎前端用原生的HTML/CSS/JavaScript写一个超简洁的注册页面请求部分用axios发POST后端接口直接用apifox来模拟。也就是说我们不依赖真实的后端服务先在apifox里把一个“假接口”造出来再让前端页面真实地请求它、拿到返回结果。整个过程完全可以在一台电脑上复现适合刚学完前端基础、想搞明白前后端联调是怎么回事的读者。1. 动手前的思路拆解注册页面为什么值得认真做1.1 注册功能的前后端协作逻辑注册页面的本质是用户在浏览器里填写信息前端收集并校验这些信息然后通过网络请求提交给服务器服务器处理完后返回一个结果前端再根据这个结果决定下一步动作。这一来一回就是一次完整的前后端交互。拆开看它包含三个核心角色前端页面负责信息采集、格式校验、请求发送、响应渲染。网络协议用HTTP协议里的POST方法把数据放在请求体里传给服务器。后端接口接收参数、校验数据、落库最后返回成功或失败的JSON数据。这里面容易忽略的是角色边界。很多新手写注册页习惯在前端把密码强度、手机号格式全校验完了就以为万事大吉。但真实的后端接口同样会做一套完整的校验。前端校验是为了用户体验后端校验才是为了安全和数据可靠。apifox模拟接口的时候我们可以通过Mock规则简单模拟这一层校验比如约定用户名长度、密码位数不符合规则就返回错误码。理解了这个角色划分后面所有的代码写起来思路都会清晰很多。1.2 为什么选apifox做模拟接口说实话能模拟接口的工具不少Postman、Apifox、YApi、Rap2都干得动这件事。我选择apifox主要是因为它在接口管理、Mock数据、文档生成、本地调试这几个环节上做得足够一体化对个人开发者和前端初学者尤其友好。apifox的核心逻辑是“先定义接口再生成数据”。你在界面里把接口的路径、请求方法、请求参数、返回数据结构定义好它可以自动生成一份可访问的Mock接口地址。前端拿着这个地址就能发请求。这比传统方式省事在什么地方呢传统方式去搭建一个真实的Node.js或Java后端光配置环境、写接口逻辑就得占掉大半时间对只想练前端的人来说负担太重。apifox把这个过程压缩到了几分钟。另外apifox可以在定义接口时就确定好字段名和字段类型这相当于提前和“后端”约定了数据格式。很多前端开发中的联调冲突根源就是字段名没商量好——前端传username后端要userName前端传phone后端要mobile类型也对不上排查起来极其耗费时间。用apifox先定义等于先把契约固定下来。1.3 为什么用axios而不是fetch浏览器原生提供了fetch方法一样可以发请求那为什么还要额外引入axios如果你只是发一次简单的GET请求两者区别不大。但是注册功能涉及请求拦截、响应拦截、超时设置、错误处理、统一加headers这些需求axios把这些问题全部封装成了简洁的API写起来更直观心智负担小得多。举个例子页面里可能会给每个请求统一加上一个token字段或者统一处理HTTP状态码401的情况。用fetch你得每个请求都写一遍处理逻辑或者自己封装一层工具函数。而axios有拦截器机制可以在请求发出前统一处理在响应回来后统一处理错误代码复用性明显更好。axios基于XMLHttpRequest这在兼容性上也有天然优势老版本浏览器也能用。fetch虽然是标准API但对一些老环境的支持要打折扣新手调起来容易踩坑。既然要做一个“拿来就能用”的注册页面axios显然更合适。2. 用apifox先把“假后端”搭起来2.1 创建项目与接口定义第一次打开apifox会看到几个内置示例项目。我建议你别直接用示例而是新建一个空项目命名成“注册模块demo”从零开始定义接口这样对接口结构的印象更深刻。新建项目之后在左侧“接口管理”里添加一个接口。关键配置如下请求方法POST路径/api/register接口名称用户注册这里要多说一句路径的命名规范。很多新手随手写一个/register就完事了但在真实项目里接口路径通常会带上前缀比如/api代表这是后端接口有的项目还会带版本号/v1。这不是形式主义而是为了后续维护方便。前端代码里写死路径之后如果后端调整了前缀你只需要在axios的baseURL里统一改不用去页面里一个个找这个设计习惯建议从一开始就养成。HTTP方法的选择也值得讲清楚。GET和POST虽然都能把数据传给服务器但语义完全不同。GET适合获取数据参数拼在URL后面会被浏览器记录进历史、被服务器记进日志数据裸奔且长度受限。POST适合提交数据参数放在请求体里相对隐蔽长度限制也宽松很多。注册这种场景用户名、密码、手机号都属于敏感信息用POST是必然选择。2.2 配置请求参数与Mock返回规则切换到“Body”标签页选择JSON格式定义请求体参数。我这次设计注册功能需要三个核心字段{ username: zhangsan, password: abc123456, email: zhangsanexample.com }这个JSON结构就是我们和“后端”约定的请求格式。注意字段风格这里用的是小驼峰命名。曾经有团队前端用user_name、后端用userName联调时查了半天才发现是字段名对不上。所以字段命名虽然小事但最好在接口定义阶段就统一好。返回数据同样用JSON定义。我计划让接口在成功时返回这样的结构{ code: 200, message: 注册成功, data: { userId: 1001 } }失败时返回{ code: 400, message: 用户名已存在, data: null }很多同学看到这里会问直接用HTTP状态码不就行了为什么还要在JSON里放一个code字段这个问题问得很好。真实项目里确实存在这种双轨并行的设计HTTP状态码是传输层的状态标识而业务code是业务逻辑的状态标识。比如接口通了但用户名重复HTTP状态码可能是200请求本身成功了但业务层通过code400告诉你“注册没成功”。前端拿到响应后不能只看HTTP状态码更要看业务code。这个习惯越早建立后面看真实接口越不吃力。apifox的Mock规则能帮你做一件事定义好返回数据的格式后它会在你发请求时根据字段类型自动生成模拟数据。不一定需要手动设置太复杂的动态规则直接用静态示例值也行够前端联调用了。2.3 本地Mock服务的启动与调试apifox有一个杀手级功能一键启动本地Mock服务。它会在你的电脑上开一个本地服务地址类似http://127.0.0.1:4523/m1/123456。这个地址就是我们可以直接请求的接口地址。为什么需要本地Mock服务而不是直接用apifox网页提供的临时Mock地址因为本地服务启动后前端axios请求本地地址不用走公网速度更快而且不会遇到公网Mock地址在某些网络环境下的稳定性问题。调试体验和真实联调非常接近。启动之后打开浏览器访问一下接口地址或者直接在apifox里点“发送”就能看到模拟的返回结果。这个步骤很重要相当于先确认“假后端”没死再去写前端代码。我见过太多同学前端代码写了一大堆回头发现接口地址配错了排查了半天浪费大量时间。另外apifox里还可以配置环境变量。比如你可以创建“本地环境”和“测试环境”两组变量把不同的Mock地址分别存好。axios的baseURL读取环境变量以后切换环境只需要在apifox里切换前端代码完全不用动。虽然是模拟阶段但这套思路和真实项目里的环境管理是一脉相承的。3. 注册页面代码实现写一个真正能用的表单3.1 HTML结构设计页面要简洁但简洁不等于简陋。设计目标是信息清晰、操作路径短、视觉干净。表单包含四个元素用户名输入框、密码输入框、邮箱输入框、注册按钮。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title用户注册/title link relstylesheet href./style.css /head body div classregister-container h2创建账号/h2 p classsubtitle只需填写以下信息即可完成注册/p form idregisterForm div classform-item label forusername用户名/label input typetext idusername nameusername placeholder请输入用户名 autocompleteusername /div div classform-item label forpassword密码/label input typepassword idpassword namepassword placeholder请输入密码 autocompletenew-password /div div classform-item label foremail邮箱/label input typeemail idemail nameemail placeholder请输入邮箱 autocompleteemail /div button typesubmit idregisterBtn注 册/button /form p idmessage classmessage/p /div script srchttps://cdn.jsdelivr.net/npm/axios1.6.0/dist/axios.min.js/script script src./register.js/script /body /html几个细节说说为什么这么写。输入框的autocomplete属性容易被忽略。这个属性控制浏览器要不要自动填充历史输入值。注册页面的密码框如果填了new-password浏览器就不会把之前登录过的密码自动带出来这对用户来说是正常体验。很多注册页密码自动被浏览器的老密码覆盖用户自己都不知道输了一堆以为是对的结果提交一直失败这个坑就是没设置autocomplete导致的。typeemail也是同理它不光是前端样式上有区别移动端会弹出对应键盘浏览器还会做基础格式校验。这个校验虽然在后端面前不值一提但对用户体验来说是一个低成本的正向反馈。form标签里没有加action属性也没有method属性。因为这次我们不打算走传统表单的同步提交方式而是由JavaScript拦截submit事件用axios异步发送请求。如果这里写了actionhttp://127.0.0.1:4523/m1/xxx用户点击注册按钮后浏览器会直接跳转到接口地址页面刷新体验非常糟糕。所以明确一点使用axios异步请求时表单的同步提交行为要禁掉。3.2 CSS样式与交互细节样式不追求花哨但要有最基本的视觉反馈。我尽量控制在60行以内的核心样式* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, sans-serif; background: #f5f6fa; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .register-container { background: #ffffff; padding: 40px 48px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08); width: 400px; } .register-container h2 { font-size: 24px; color: #333333; text-align: center; margin-bottom: 6px; } .subtitle { text-align: center; color: #888888; font-size: 14px; margin-bottom: 28px; } .form-item { margin-bottom: 20px; } .form-item label { display: block; font-size: 14px; color: #555555; margin-bottom: 8px; font-weight: 500; } .form-item input { width: 100%; padding: 10px 14px; border: 1px solid #dddddd; border-radius: 8px; font-size: 14px; outline: none; transition: border-color 0.2s ease; } .form-item input:focus { border-color: #4a7aff; box-shadow: 0 0 0 3px rgba(74, 122, 255, 0.1); } #registerBtn { width: 100%; padding: 11px 0; border: none; border-radius: 8px; background: #4a7aff; color: #ffffff; font-size: 16px; font-weight: 500; cursor: pointer; transition: background-color 0.2s ease; margin-top: 8px; } #registerBtn:hover { background: #3867d6; } #registerBtn:disabled { background: #a0b4f0; cursor: not-allowed; } .message { margin-top: 16px; text-align: center; font-size: 14px; min-height: 20px; } .message.success { color: #07a35a; } .message.error { color: #e04b4b; }这里面有两个交互细节值得展开。输入框的:focus状态加了边框变色和一圈淡蓝色光晕这能直观告诉用户“当前光标在这个输入框里”。没有焦点反馈的表单用户在填多个字段时很容易迷失尤其是一次性就要填好几项信息的时候。按钮的:disabled样式对应的是“防止重复提交”逻辑。用户点了一次注册后在请求返回之前按钮会被置为禁用状态等返回结果后再恢复。如果没有这层控制手快的用户连续点击五六次页面会发出好几个相同的注册请求后端可能创建多个账号也可能因为并发问题报错。这个防抖操作是真实项目里前端必做的基本功。3.3 前端校验规则前端校验的作用是提前拦截明显不合理的输入让用户立刻改正而不是等到请求发出去再等后端打回来。这次设计三个字段的校验规则用户名2到16个字符不能包含特殊字符。密码6到20个字符必须同时包含字母和数字。邮箱符合基本的邮箱格式。function validateForm(formData) { const username formData.get(username).trim(); const password formData.get(password).trim(); const email formData.get(email).trim(); if (username.length 2 || username.length 16) { return 用户名长度应在2到16个字符之间; } if (!/^[a-zA-Z0-9_\u4e00-\u9fa5]$/.test(username)) { return 用户名只能包含字母、数字、下划线或中文; } if (password.length 6 || password.length 20) { return 密码长度应在6到20个字符之间; } if (!/[a-zA-Z]/.test(password) || !/[0-9]/.test(password)) { return 密码必须同时包含字母和数字; } if (!/^[^\s][^\s]\.[^\s]$/.test(email)) { return 邮箱格式不正确; } return null; }用正则做用户名和密码校验是效率最高的方式。密码复杂度要求听起来简单但用正则实现时一定要拆开看先校验长度再分别判断是否包含字母和数字而不是用一个大正则一步到位。大正则看着唬人但报错信息没法细致区分“长度不够”和“缺数字”而在实际使用中用户最需要的是明确的提示哪个规则没满足你直接告诉他就行。顺便说一句正则这事。很多人一看到正则就头疼觉得难记。我的经验是把常用的几个正则存成自己的代码片段库手机号、邮箱、身份证、纯数字、纯字母以后哪里用到直接复制再微调。靠脑子硬背正则性价比太低。3.4 axios封装与请求配置axios的使用分两种层次一种是直接在页面里axios.post(url, data)一把梭另一种是封装一个实例统一配置baseURL、超时时间、拦截器。这个项目虽然只有一个接口我更建议直接按第二种方式来写因为以后接真实项目的时候无非就是把Mock地址换成线上地址其余代码几乎不用改。先看封装部分的代码const request axios.create({ baseURL: http://127.0.0.1:4523/m1/3956000, timeout: 10000, headers: { Content-Type: application/json } }); request.interceptors.request.use(config { console.log(请求发出, config); return config; }, error { return Promise.reject(error); }); request.interceptors.response.use(response { const res response.data; if (res.code ! 200) { return Promise.reject(new Error(res.message || 请求失败)); } return res; }, error { return Promise.reject(error); });这里要重点说两个概念。baseURL的作用是给所有请求路径加前缀。后面发起request.post(/api/register)的时候实际请求的完整地址就是http://127.0.0.1:4523/m1/3956000/api/register。这样做的好处是当接口地址整体迁移时只改这一个变量就行。开发环境、测试环境、生产环境的baseURL不同通过环境变量动态切换即可不用动业务代码。headers里的Content-Type决定了请求体以什么格式传输。这里用的是application/json对应的是axios把JS对象转换成JSON字符串放在请求体里。还有一种常见格式是application/x-www-form-urlencoded那是把数据编码成usernamexxxpasswordxxx的键值对形式。这两种格式各有用武之地但在RESTful API设计里JSON是默认选择。因为JSON结构清晰、支持嵌套后端解析也方便。如果你用axios默认的POST请求且传入的是对象axios会自动帮你转成JSON格式但你最好还是显式声明一下避免有些环境下因为没有正确设置Content-Type导致后端拿到的是空数据。再看看注册页面自己的逻辑层const form document.getElementById(registerForm); const messageEl document.getElementById(message); const registerBtn document.getElementById(registerBtn); form.addEventListener(submit, async function (event) { event.preventDefault(); const formData new FormData(form); const formDataObj { username: formData.get(username).trim(), password: formData.get(password).trim(), email: formData.get(email).trim() }; const validateMsg validateForm(formData); if (validateMsg) { showMessage(validateMsg, error); return; } registerBtn.disabled true; registerBtn.textContent 注册中...; try { const res await request.post(/api/register, formDataObj); if (res.code 200) { showMessage(注册成功, success); } } catch (err) { showMessage(err.message || 网络异常请稍后重试, error); } finally { registerBtn.disabled false; registerBtn.textContent 注 册; } }); function showMessage(text, type) { messageEl.textContent text; messageEl.className message type; }这里有几个关键点值得反复强调。event.preventDefault()是表单处理里最容易漏的一行代码。表单默认行为是提交后刷新页面如果你没拦住这个默认行为axios的异步请求刚发出去页面立刻刷新了结果什么都看不到。很多新手排了很久的错最后发现就是少了这一行。按钮的状态切换放在请求前和请求后。这不是可有可无的锦上添花而是绝对必要的交互控制。请求期间用户连续点击会产生重复注册的请求轻则后端多创建账号重则触发并发冲突。用一个简单的disabled状态就能在纯前端层面把这个风险降到最低。try...catch...finally的结构是异步编程的黄金组合。try里放正常请求流程catch捕获请求失败或业务失败finally保证无论结果如何都要恢复按钮状态。还有一点拦截器里已经把code ! 200的情况通过Promise.reject抛出来了所以在catch里拿到的err.message就是后端返回的业务错误信息用户能看到“用户名已存在”这样有意义的提示而不是冷冰冰的“Network Error”。4. 联调实战从页面到接口的完整请求链路4.1 axios实例配置与POST调用实战到这里前端的静态页面和apifox里的假后端都已经就绪了。打开HTML页面填入信息点击注册。这瞬间发生了什么我按时间线拆解浏览器拦截表单默认提交行为。执行前端校验函数通过后继续。axios实例创建POST请求URL拼接为http://127.0.0.1:4523/m1/3956000/api/register。设置请求头Content-Type: application/json。请求体序列化为{username:zhangsan,password:abc123456,email:zhangsanexample.com}。浏览器发送请求到本地Mock服务。apifox服务端匹配到接口定义执行数据校验。返回JSON响应到前端。axios响应拦截器拿到数据判断业务code。页面根据结果展示成功或失败信息。这条链路里每一步的职责都很单一任何一个环节出问题都有明确的排查方向。比如前端校验没通过请求根本不会发出如果请求发出去了但没返回问题在接口或网络层如果返回了但页面没反应问题在响应处理逻辑。4.2 前端收到的响应长什么样用浏览器的开发者工具切到Network标签页点一下注册按钮能看到一条名为register的请求记录。点开它可以看到请求详情Headers里就是Content-Type、User-Agent这些Payload里就是请求体JSONResponse里就是接口返回的JSON。这一步建议每个读者都亲自做一次亲眼看一遍比看十篇教程都管用。响应体长这样{ code: 200, message: 注册成功, data: { userId: 1001 } }前端拿到这个JSON后响应拦截器判断code 200于是整个请求流程走成功分支页面显示绿色“注册成功”文案。如果不满足注册条件比如用户名已经存在apifox按Mock规则返回code: 400响应拦截器发现业务code不对直接reject一个Error错误信息被catch捕获页面显示红色错误提示。这个过程没有刷新页面但有完整的成功和失败反馈这就是前后端分离结构下典型的交互方式。4.3 调试过程中网络面板的使用Network面板是联调时最重要的工具没有之一。我来说说怎么看它。打开面板后先清空之前的日志再操作页面这样面板里只保留你这次操作产生的请求线索不会被历史请求干扰。然后看三条信息Status CodeHTTP状态码200代表请求到达了接口并正常返回404是路径没匹配上500是服务端内部出错了。Request Payload确认请求体里的数据格式和值很多联调问题都出在这一步比如字段名拼错了、传了undefined。Response确认返回数据和预期是否一致。有一类很诡异的问题页面明明显示请求失败了但面板里Response看起来是正常的。这种情况十有八九是响应拦截器里做了特殊判断把业务code非200的情况当成了错误抛出。所以看问题要把前后端连起来看不能只看一头。5. 踩坑记录与排查思路合集5.1 请求能发出但Status Code是404404是路径问题。在Network面板里看请求URL和apifox接口定义里的路径比对是不是漏了/api前缀或者是mock服务地址的路径段不对。apifox本地服务的完整路径包含项目标识如果复制的时候漏掉了一段就会出现404。这里我建议养成一个习惯字不要手打全部从apifox里复制。人眼识别地址里的数字段很容易出错复制粘贴能规避大部分这类低级问题。5.2 请求发出后Response是CORS error跨域问题是本地联调最常见的一道坎。页面文件在file://协议下打开或者前端用的是某个本地端口而接口服务在另一个端口浏览器的同源策略就会拦截响应。解决办法有两个方向。一是在前端启动一个本地开发服务器比如用VSCode的Live Server插件把页面跑起来这样前端来自http://127.0.0.1:5500请求http://127.0.0.1:4523就属于跨域。二是去apifox的Mock服务设置里找到CORS相关配置把允许跨域打开。需要注意的是如果你直接双击HTML文件用file://方式打开NetWork面板里request可能显示为(cors)或(failed)这是浏览器的硬限制。我个人的习惯是所有前端联调一律用Live Server这种本地服务方式跑起来和线上环境更接近也少踩很多莫名其妙的错。5.3 apifox发送成功但前端一直请求失败可以先确认一下apifox里发送测试是否正常。如果apifox本身能正常返回说明接口定义没问题问题出在前端到接口之间的链路上。常见情况是你用的是apifox云端Mock地址但这个接口默认需要鉴权没有带token所以被拒了。我刚才写的代码里就没有处理鉴权所以这里有个重要提醒如果apifox创建的项目开启了“接口鉴权”功能前端请求会收到401或403。解决方式是在apifox的项目设置里关闭鉴权或者在前端请求中加上对应的鉴权头。初学者做模拟联调建议直接关掉鉴权避免在非核心问题上耗费时间。5.4 表单重置问题注册成功后表单数据没有清空这虽然不影响功能但体验上差了点。真实项目里注册成功后一般会跳转到登录页或首页所以表单清不清空影响不大。但如果你做了一个单页demo希望在注册成功后清空表单只需要在成功分支里调用form.reset()就行。放在成功分支而不是finally里是怕请求失败时把用户填好的信息清掉那就得不偿失了。5.5 字段名或数据类型不一致前端传字符串后端要数字或者前端传userName后端要username。这种问题在联调里极其常见有时候能折磨人一下午。规避的办法就是在apifox定义接口时把字段名和类型固定好前端代码严格对照接口文档来写。记住apifox里定义的请求参数就是事实标准前端照着抄不要发挥创造力。我见过有同学把email写成mail接口一下子返回参数缺失这就是典型的低级错误但也只有靠细心才能完全避免。6. 进阶扩展注册页面还能怎么变得更强6.1 添加Loading状态与按钮防重复提交按钮防重复提交我在上面的代码里已经写了但如果你追求更好一点的体验还可以把按钮上的文字改成带动画的加载状态。最简单的做法是给按钮加一个CSS类里面放一段旋转的边框动画视觉上告诉用户“请求正在路上”。还有一种更细腻的状态处理根据请求结果给按钮设置不同的文案。请求中显示“提交中...”成功后可以短暂显示“注册成功”再跳转失败则恢复原样。这些细节单独看不值钱但组合起来用户的感受完全是两个档次。6.2 密码加密与安全策略我在demo里直接明文传输密码这对模拟项目没有影响但到了真实项目里是绝对不行的。前端在发送密码之前至少要用HTTPS保证传输安全更进一步可以在前端对密码做哈希处理后再发送。当然哈希算法有很多细节什么加盐、什么算法选择属于安全领域的专门知识这里提出来是希望大家有这个意识不要以为JSON提交就万事大吉了。实际项目中前端做哈希处理是一种常见做法但要注意后端必须明确知道前端传过来的是哈希值还是原文要有对应的方案来配合处理避免前后端各自为政最后两边拧巴。6.3 增加图形验证码这是现实中注册功能几乎必备的环节目的是防止机器人批量注册。图形验证码的实现牵扯到后端生成图片、前端显示图片、校验逻辑等好几个环节代码量会成倍增加。用apifox模拟图形验证码接口可以定义两个接口一个获取验证码图片一个校验验证码。前端把用户填的验证码和注册信息一起提交由“后端”验证。这个扩展非常推荐在掌握基本注册流程后再做因为它的流程更完整也更接近真实业务场景。6.4 表单状态管理如果使用了Vue或React注册表单的状态管理可以更进一步。以Vue为例用reactive定义表单数据watch监听字段变化实时校验computed根据表单整体合法性控制按钮可用状态。这套组合拳能让交互反馈更即时用户不用等到点击按钮才知道哪里填错了。不过我的建议是用原生JavaScript完整实现一遍注册页之前不要急着上框架。原生的实现过程会把请求链路、DOM操作、事件处理的每个细节都暴露在你面前这些经验在框架里会被隐藏得很好但对理解系统本质极为关键。写在最后注册页面看起来是前端入门的小项目但它的价值在于把“用户交互、前端校验、网络请求、接口模拟、异常处理”这条完整的链路串了起来。我个人折腾这个demo时最大的体会是其实难的不是某个具体技术点而是搞清楚“谁负责什么、数据从哪来、到哪里去、每一步失败在哪里”。当你用apifool把后端接口提前定义好再回头看前端代码你会突然发现请求逻辑异常透明——这大概就是接口先行的魅力。最后再分享一个小习惯每次调试请求时先在apifox里确认接口返回正常再去页面上操作。如果页面报错打开Network面板看请求URL、状态码和载荷。按这个顺序排查百分之八十的问题五分钟内都能定位到。