主题
面经 H5 端:Vant 与项目搭建(三)
本页对应 面经 H5 端:Vant 与项目搭建,共 6 段视频,约 1 小时 15 分。
学习目标
- 能使用 Vant 表单的
rules配置required与pattern规则,完成用户名、密码校验,并验证失焦或提交时的提示信息。 - 能创建独立的 axios 实例,配置
baseURL、timeout并导出request,在组件中替代原生 axios 发起请求。 - 能按 API 模块抽离注册、登录、面经列表等请求方法,使用按需导出/导入并在组件中调用对应 API 方法。
- 能处理注册成功与失败场景:成功时提示、重置表单并跳转登录页;失败时读取
error.response.data.message提示,无响应时给出兜底提示。 - 能完成登录流程:调用登录 API、保存 token 到
localStorage、跳转面经列表页,并对登录失败进行提示。 - 能添加请求拦截器,在
Authorization请求头中携带Bearer token,并调用面经列表 API 传入分页与排序参数获取数据。
知识点
本节目标与调整思路
这一节的核心不是写业务功能,而是把 Vue CLI 初始化出来的“示例工程”整理成更符合企业项目规范的目录结构。强调要严格按照步骤调整,原因是后续封装请求、抽离 API、配置工具模块时,都会依赖这套目录约定。
为什么需要:默认脚手架会带 HelloWorld.vue、HomeView.vue、AboutView.vue、logo.png 等示例文件,它们和面经项目无关;如果继续保留,路由、组件引用容易混乱,也不利于后面按模块存放接口和工具方法。
怎么想:先清理默认示例,再清空默认路由,最后补上项目需要的 api、utils 目录。顺序上要先处理引用关系,避免删文件后编译报“找不到模块”。
怎么做:按下面三步执行:删除默认文件、修改 router/index.js 和 App.vue、新增目录。
易错点:不要只删文件不改路由;不要保留 HomeView、AboutView 的 import;main.js 本节不需要修改。
删除初始化默认文件
为什么需要:这些文件是脚手架示例,不是项目业务文件。保留它们会让路由和组件目录看起来“有内容”,但实际后续不会使用。
怎么想:先确认哪些文件只被示例路由或示例组件引用,再删除。
怎么做:删除以下文件:
src/assets/logo.pngsrc/components/HelloWorld.vuesrc/views/AboutView.vuesrc/views/HomeView.vue
易错点:删除后如果页面报错,优先检查 router/index.js 是否还引用 HomeView、AboutView,以及 App.vue 或其它组件是否还引用 logo.png、HelloWorld.vue。转写稿里反复出现“路径写错、语法写错会导致请求或编译异常”,这里同理,删文件后要同步清理引用。
修改路由与根组件
为什么需要:默认路由指向已删除的示例页面,必须清空;App.vue 只保留路由出口,页面结构才干净。
怎么想:路由表先置空,后续再按业务添加;根组件只保留 <router-view/>,让路由页面统一渲染。
怎么做:main.js 不需要修改。修改 router/index.js,删除默认路由配置,保留如下代码:
js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
]
const router = new VueRouter({
routes
})
export default router修改 App.vue:
html
<template>
<div id="app">
<router-view/>
</div>
</template>易错点:routes 是空数组但不要删掉;Vue.use(VueRouter) 要保留;export default router 不能漏。若路由仍写 component: HomeView,启动就会报模块找不到。
新增 api 与 utils 目录
为什么需要:后续要发请求。转写稿里明确说,不要在组件里每次都复制根路径、直接写 axios,而要“整体配置一下”。因此需要 utils 放工具模块,api 放接口模块。
怎么想:utils 存自己封装的方法,例如后面新建 request.js 配置 axios 实例、基准地址、超时时间,甚至请求拦截器;api 按业务分文件,例如 user.js 放登录、注册等用户相关请求,article.js 放面经列表相关请求。
怎么做:新增:
src/api目录:存储接口模块,发送 ajax 请求接口的模块。src/utils目录:存储一些工具模块,自己封装的方法。
转写稿中的落地方式是:在 utils/request.js 中导入 axios,创建实例,配置 baseURL、timeout,最后导出;在 api/user.js 中按需导出请求函数,组件里按需导入并调用。还强调“请求方法单独封装成函数,谁要用谁调”,目的是复用。
易错点:api 和 utils 职责不要混;按需导出、按需导入要写大括号;请求根路径不要在每个组件重复写;拦截器要加在封装后的 request 实例上,不要加错对象。转写稿中后续出现 401,展开响应发现未带 token,回到 request.js 的请求拦截器统一加 Authorization,正说明 utils 集中配置的价值。
调整后的目录效果与验证
调整后的目录效果如下:

为什么需要验证:目录调整后要保证项目还能启动、路由不报错。
怎么想:先启动项目看编译,再检查 src 下是否还有示例文件残留。
怎么做:确认 src/api、src/utils 已创建,router/index.js 路由为空,App.vue 只保留路由出口。
易错点:如果启动报错,按“路由引用 → 组件引用 → 图片资源引用”的顺序排查;后续写接口时,组件中应导入 api 里封装的方法,而不是继续直接写 axios 和根路径。这样这一节的目录调整才真正为后面的请求封装、注册登录和面经列表打好了基础。
组件库是什么,为什么需要
组件库是第三方把常用组件封装好、整合到一起的集合。本节笔记给出的定义很直接:日历组件、键盘组件、打分组件、登录组件等,都可以从组件库里直接取用。
为什么需要:项目里大量交互是重复的,比如弹窗、表单、日历、评分;如果每个页面都手写,既慢又容易不一致。怎么想:先判断项目端型,移动端优先选移动组件库,PC 后台优先选 PC 组件库;再查文档看组件是否覆盖需求。怎么做:按文档安装、引入、注册、使用。易错点:不要为了“用组件库”而堆组件;先看文档版本,Vant 2 的文档地址是 https://vant-contrib.gitee.io/vant/v2/#/zh-CN/ ,移动端项目不要误用 PC 库的交互习惯。

Vant 与移动端组件库
为什么需要:移动端页面强调轻量、触控友好,Vant 这类库提供了适合移动端的组件和样式。怎么想:本节笔记把移动端常用库列为 Vant UI、Mint UI(饿了么)、Cube UI(滴滴);PC 端列了 element-ui、element-plus、iview、ant-design。怎么做:根据项目技术栈和团队习惯选一个,不要同一项目混用多套 UI。易错点:组件库不是唯一的,看到“别人用这个”不等于自己项目必须用;选型要看维护状态、文档、组件覆盖和团队熟悉度。
从表单验证看组件库的规则
转写稿里反复强调 rules(同音“路子/ro子”)就是规则。为什么需要:登录、注册不能只靠后端报错,前端要先拦住空值和格式错误。怎么想:一条规则是一个小对象,required 表示必填,pattern 配正则,message 是校验不通过时的提示。怎么做:规则少可直接写在模板里,规则多就抽成变量:
js
const userRules = [
{ required: true, message: '请输入用户名' },
{ pattern: /^\w{2,10}$/, message: '请输入2到10位的用户名' }
]密码可写成:
js
{ pattern: /^\S{6,20}$/, message: '请输入6到20位的密码' }易错点:pattern 和 message 是对象里的键值,不要漏写;\w 表示字母、数字、下划线,\S 表示非空白字符;提示语要写清楚,否则用户不知道错在哪。转写稿还演示了“先写 A 提示格式不对,再写 ABC 通过”的调试过程,说明规则要逐条验证。
Vue 周边:请求封装与 API 抽离
为什么需要:转写稿里一开始直接在组件里写 axios.post,每次复制根路径,代码“太low”,也不利于复用。怎么想:把 axios 配置抽到 utils/request.js,创建实例,配 baseURL、timeout,再导出;把具体接口抽到 api/user.js、api/article.js,组件只调方法。怎么做:
js
import axios from 'axios'
const request = axios.create({
baseURL: '请求根路径',
timeout: 5000
})
request.interceptors.request.use((config) => {
config.headers.Authorization = 'Bearer ' + localStorage.getItem('mobile-token')
return config
})
export default requestAPI 方法示例:
js
import request from '@/utils/request'
export const registerAPI = (data) => request.post('接口地址', data)
export const loginAPI = (data) => request.post('接口地址', data)
export const articleAPI = (params) => request.get('接口地址', { params })易错点:转写稿里“跟录证/跟录镜”应纠正为根路径 baseURL;“南京器/蓝截器”是拦截器;token 不要写成“偷ken”;请求头 Authorization 的值要拼成 Bearer 空格 token;params 用于 query 参数,data 用于请求体,别混。
注册登录中的调试与后续处理
为什么需要:请求成功不等于流程结束。转写稿中注册成功要提示、重置表单、跳转登录;失败要提示原因。怎么想:用 try/catch 包住 await,成功走正常逻辑,失败判断 err.response 是否存在。怎么做:有响应结果就提示 err.response.data.message,没有响应就笼统提示“注册失败/登录失败”。登录成功还要存 token:
js
localStorage.setItem('mobile-token', res.data.token)易错点:转写稿里漏写 await 会拿到 undefined;401 通常说明请求没带 token;err.response 不存在时不能直接点 data.message,否则会再报错。
选型与学习建议
组件库解决“界面组件复用”,axios 封装和 API 抽离解决“请求逻辑复用”,二者都属于 Vue 项目周边能力。易错点是把组件库当万能:文档没写的规则不要猜;接口字段如 username、password 要看接口文档,转写稿中“用户名”不等于 username 就是典型坑。先跑通一个组件、一个请求、一次错误提示,再扩展页面,比一次性堆代码更稳。
课件图示








代码示例
代码一:注册/登录表单 + vant 校验规则(Vue SFC)
html
<template>
<div class="register-page">
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="username"
label="用户名"
placeholder="请输入用户名"
:rules="userRules"
/>
<van-field
v-model="password"
name="password"
type="password"
label="密码"
placeholder="请输入密码"
:rules="[
{ required: true, message: '请输入密码' },
{ pattern: /^\S{6,20}$/, message: '请输入6到20位的密码' }
]"
/>
<div class="btn-wrap">
<van-button type="primary" block native-type="submit">注册</van-button>
</div>
</van-form>
</div>
</template>
<script>
export default {
name: 'RegisterView',
data () {
return {
username: '',
password: '',
// 规则多时抽成变量放到 data 里,模板中只写 :rules="userRules"
userRules: [
{ required: true, message: '请输入用户名' },
{ pattern: /^\w{2,10}$/, message: '请输入2到10位的用户名' }
]
}
},
methods: {
onSubmit (values) {
// values 就是 { username: 'xxx', password: 'xxx' }
console.log(values)
}
}
}
</script>
<style scoped>
.btn-wrap { padding: 16px; }
</style>要点解释与易错点:
name决定values里的键名,必须和接口文档的字段一致(要username/password,不是name/password),这是最容易看走眼的地方;label只是前面的文字,placeholder只是输入框提示,改它们不影响提交数据。rules是数组,一个对象就是一条规则:required表示必填,pattern表示正则校验。pattern后面必须写正则字面量/^\w{2,10}$/,不能加引号写成字符串,否则校验不生效。\w= 字母数字下划线,\S= 非空白字符,{2,10}表示 2 到 10 位。- 只有校验通过才会触发
@submit,校验不通过点按钮不会有任何请求。
代码二:封装 axios 实例 + 抽离 API 方法
js
// src/utils/request.js
import axios from 'axios'
// 1. 创建 axios 的副本(新实例),不直接改原版 axios
const request = axios.create({
baseURL: 'http://toutiao.itheima.net', // 请求根路径,从接口文档复制
timeout: 5000
})
// 2. 请求拦截器:每次发请求前统一带上 token
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('mobile-token')
if (token) {
config.headers.Authorization = 'Bearer ' + token
}
return config
},
(error) => Promise.reject(error)
)
// 3. 导出配置好的实例
export default requestjs
// src/api/user.js
import request from '@/utils/request'
// 注册:参数由调用方传入,URL 以接口文档为准
export const registerAPI = (data) => request.post('/h5/interview/register', data)
// 登录
export const loginAPI = (data) => request.post('/h5/interview/login', data)要点解释与易错点:
- 用
axios.create()造“副本”,以后改副本不影响原版 axios;baseURL配好后,各组件发请求就不用再拼根路径。 - 拦截器要加在
request实例上,不是加在axios上,否则发请求时不会生效。 Authorization的值必须拼成'Bearer ' + token,Bearer和 token 之间的空格不能少。localStorage.getItem('mobile-token')的键名必须和登录时setItem的键名一字不差,否则永远取不到 token(表现就是 401)。- api 文件里用
export const按需导出,组件里必须用import { registerAPI } from '@/api/user'(带大括号)导入。
代码三:登录组件(调用 API + try/catch + 存 token + 跳转)
html
<template>
<div class="login-page">
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="username"
label="账号"
placeholder="请输入账号"
:rules="[{ required: true, message: '请输入账号' }]"
/>
<van-field
v-model="password"
name="password"
type="password"
label="密码"
placeholder="请输入密码"
:rules="[{ required: true, message: '请输入密码' }]"
/>
<div class="btn-wrap">
<van-button type="primary" block native-type="submit">登录</van-button>
</div>
</van-form>
</div>
</template>
<script>
import { loginAPI } from '@/api/user'
export default {
name: 'LoginView',
data () {
return { username: '', password: '' }
},
methods: {
async onSubmit (values) {
try {
const res = await loginAPI(values)
// 成功:提示 + 存 token + 清空 + 跳转
this.$toast('登录成功')
localStorage.setItem('mobile-token', res.data.data.token)
this.username = ''
this.password = ''
this.$router.push('/article')
} catch (err) {
// 失败:有响应结果就提示后端返回的 message
if (err.response) {
this.$toast(err.response.data.message)
} else {
this.$toast('登录失败')
}
}
}
}
}
</script>
<style scoped>
.btn-wrap { padding: 16px; }
</style>要点解释与易错点:
await必须写在async函数里,漏写async时res是undefined,后面取res.data会直接报错。- token 的位置是
res.data.data.token:axios 包了一层data,后端返回体里还有一层data。 - 所有“正常流程”的代码都放在
try里,任何一步出错都会进catch。 catch里必须先判断err.response是否存在:断网、URL 写错时err.response是undefined,直接写err.response.data.message会再抛一个错。- 登录成功后要做四件事:提示、存 token、清空表单、
this.$router.push跳转,少存 token 后面所有接口都会 401。 - 请求失败时控制台出现红色报错是正常的,把错误对象展开看
response.data.message就知道原因。
常见错误
1. 表单提交的 values 里是 name/password,一调接口就报参数缺失 现象:点登录能打印出 values,但请求发出后接口返回参数错误或"用户不存在"。 原因:v-model 绑的字段名按中文语义写成了 name,而接口文档要的是 username;label(左侧文字)、placeholder(提示语)、v-model(字段名)三者被混为一谈。 修法:只改 v-model 的字段名对齐接口文档,label/placeholder 想叫什么随意。
2. 写了 rules 却不校验,或正则规则永远不触发 现象:输入框失焦毫无提示,或必填提示有、正则提示没有。 原因:pattern 的值给成了字符串、或正则忘了首尾斜杠;把多条规则写成了对象而不是数组;rules 没加冒号绑定成静态字符串。 修法:一条规则=数组里一个小对象;pattern 给正则字面量(如 /^\w{2,10}$/);规则多时抽成变量再 :rules="xxx" 引用。
3. 每个组件都 import axios from 'axios' 并手拼 baseURL 现象:接口地址写错一个字母就全线报错,换环境要改十几个文件。 原因:没做统一封装,直接用原始 axios 发请求。 修法:utils/request.js 里 axios.create({ baseURL, timeout }) 创建实例并导出,组件只引这个实例,不再出现 baseURL。
4. 请求一直 401,接口提示"未携带 token" 现象:登录后进列表页,控制台 401,返回"未找到有效的 token"。 原因:要么本地没存 token,要么拦截器加在了原始 axios 上而不是 request 实例上,要么 authorization 值漏了 Bearer 和后面的空格。 修法:登录成功 localStorage.setItem 存 token;在 request 实例上 interceptors.request.use 加 config.headers.authorization = 'Bearer ' + token。
5. 拿到的返回值是 undefined 或一个 Promise 对象 现象:const res = getArticleList(...) 之后 res.data 报错。 原因:调用处函数没加 async、请求前没写 await。 修法:外层函数加 async,请求语句前加 await;api 层函数记得 return。
6. 错误提示里 err.response.data.message 直接崩 现象:断网或路径写错时,控制台报 Cannot read properties of undefined (reading 'data'),页面没有任何提示。 原因:不是所有错误都有 response(网络中断、地址错误时就没有)。 修法:catch 里先 if (err.response) 才取 message,否则走一句笼统的"注册失败/登录失败"。
7. 注册成功后 this.$router.push('/login') 没反应 现象:提示弹出来了,页面纹丝不动,也不报错。 原因:写成了 this.$route.push($route 是当前路由信息对象,没有 push 方法)。 修法:跳转一律用 this.$router.push(路径字符串)。
练习
练习 1|字段对齐排错 给出现有表单代码(v-model="name"、v-model="pwd"、label="用户名"),接口要求 username / password。要求:只做最小改动让 values 结构与接口一致,并在注释里说明 label、placeholder、v-model 各自的作用。
练习 2|手写校验规则 为登录表单补充规则:账号必填、且为 2–10 位字母数字下划线;密码必填、6–20 位非空白字符。要求其中一条规则写在模板内联、另一条抽成变量,并验证:空提交、只填 1 位、填超长、填合法值四种情况的表现。
练习 3|封装 request 并接入 新建 utils/request.js:创建实例、配置 baseURL 与 timeout、导出。把已有的某个组件从"直接用 axios + 手拼地址"改造成"引用 request 发请求",跑通一次成功请求。
练习 4|抽离 API 层 把注册和登录两个请求分别封装到 api/user.js 中的 registerAPI / loginAPI,用按需导出;组件内改为按需导入并调用,确认组件里不再出现 request 和接口地址字符串。
练习 5|补全成功/失败分支 为登录方法补全:成功时提示、清空表单、存 token、跳转面经列表;失败时区分"有 response"(提示后端 message)与"无 response"(笼统提示)。再手动制造一次 401(清掉 token)验证拦截器与提示是否都生效。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 表单验证 | 14:30 |
| 2 | 封装axios并完成注册 | 13:25 |
| 3 | 抽离注册的API方法 | 13:37 |
| 4 | 注册的后续处理 | 11:17 |
| 5 | 完成登录 | 10:26 |
| 6 | 获取面经列表数据 | 12:18 |
小结
- 组件库只是"别人写好的组件集合",PC 端与移动端各有主流选择,选型跟着项目形态走,不必死记 API。
- 表单三件套最容易踩坑的是字段名:接口要什么就绑什么,
label和placeholder只影响展示。 - 校验规则的记忆锚点是
rules(规则),一条规则=一个对象,required管必填、pattern管格式、message管提示。 - axios 的正确姿势是"先建副本再配置再导出",永远不要在业务组件里直接碰原始 axios,也不要改原对象。
- 请求方法抽到
api/目录的唯一目的是复用:一个接口多个页面用,改一次就够了;即使当前用不上,也按同一套格式写,保持工程一致。 - 需要全局携带的信息(如 token)属于"每次请求都要做的事",它的归宿是请求拦截器,而不是每个组件里手写一遍。
- 所有异步请求都要有
async/await+try/catch的完整闭环,并且 catch 里必须先判断err.response是否存在。 - 一次业务动作往往不止"发请求":提示、清空、存凭证、跳转这四步要作为整体来设计和验收。