Skip to content

面经 H5 端:Vant 与项目搭建(三) ​

本页对应 面经 H5 端:Vant 与项目搭建,共 6 段视频,约 1 小时 15 分。

学习目标 ​

  1. 能使用 Vant 表单的 rules 配置 required 与 pattern 规则,完成用户名、密码校验,并验证失焦或提交时的提示信息。
  2. 能创建独立的 axios 实例,配置 baseURL、timeout 并导出 request,在组件中替代原生 axios 发起请求。
  3. 能按 API 模块抽离注册、登录、面经列表等请求方法,使用按需导出/导入并在组件中调用对应 API 方法。
  4. 能处理注册成功与失败场景:成功时提示、重置表单并跳转登录页;失败时读取 error.response.data.message 提示,无响应时给出兜底提示。
  5. 能完成登录流程:调用登录 API、保存 token 到 localStorage、跳转面经列表页,并对登录失败进行提示。
  6. 能添加请求拦截器,在 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.png
  • src/components/HelloWorld.vue
  • src/views/AboutView.vue
  • src/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 这类库提供了适合移动端的组件和样式。怎么想:本节笔记把移动端常用库列为 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 request

API 方法示例:

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 就是典型坑。先跑通一个组件、一个请求、一次错误提示,再扩展页面,比一次性堆代码更稳。

课件图示 ​

课件第3页:项目收获 目标:明确做完本项目,能够收获哪些内容 脚手架自定义项目构建 组件库v

课件第5页:ESlint 代码规范 目标:认识代码规范 代码规范:一套写代码的约定规则。例如

课件第8页:调整初始化目录 目标:将目录调整成符合企业规范的目录 1. 删除 多余的文件 2

课件第9页:vant 组件库 目标:认识第三方 Vue组件库 vant-ui 组件库:第三方

课件第10页:其他 Vue 组件库 目标:了解其他 Vue 组件库 Vue的组件库并不是唯一的

课件第11页:vant 全部导入 和 按需导入 目标:明确 全部导入 和 按需导入 的区别 v

课件第12页:目标:阅读文档,掌握 全部导入 的基本使用 官网:vant-ui 全部导入: ①

课件第13页:目标:阅读文档,掌握 按需导入 的基本使用 按需导入: ① 安装 vant-ui

代码示例 ​

代码一:注册/登录表单 + 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 request
js
// 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

小结 ​

  1. 组件库只是"别人写好的组件集合",PC 端与移动端各有主流选择,选型跟着项目形态走,不必死记 API。
  2. 表单三件套最容易踩坑的是字段名:接口要什么就绑什么,label 和 placeholder 只影响展示。
  3. 校验规则的记忆锚点是 rules(规则),一条规则=一个对象,required 管必填、pattern 管格式、message 管提示。
  4. axios 的正确姿势是"先建副本再配置再导出",永远不要在业务组件里直接碰原始 axios,也不要改原对象。
  5. 请求方法抽到 api/ 目录的唯一目的是复用:一个接口多个页面用,改一次就够了;即使当前用不上,也按同一套格式写,保持工程一致。
  6. 需要全局携带的信息(如 token)属于"每次请求都要做的事",它的归宿是请求拦截器,而不是每个组件里手写一遍。
  7. 所有异步请求都要有 async/await + try/catch 的完整闭环,并且 catch 里必须先判断 err.response 是否存在。
  8. 一次业务动作往往不止"发请求":提示、清空、存凭证、跳转这四步要作为整体来设计和验收。