Skip to content

面经 PC 端(下)(一) ​

本页对应 面经 PC 端(下),共 7 段视频,约 1 小时 24 分。

学习目标 ​

  • 能清理 Vue 项目默认文件,重置 router/index.js 与 App.vue,并说明 pnpm、yarn、npm 项目锁文件的识别方式。
  • 能按模块化目录创建登录页、首页架子、数据看板、面经列表等组件文件,并为单词文件名组件设置多词 name 以通过 ESLint 校验。
  • 能配置一级路由、二级嵌套路由、/ 重定向、路由懒加载,并在 App.vue 与 layout 组件中正确放置 router-view 出口。
  • 能安装并封装 axios,创建 request 实例,配置 baseURL、请求拦截器携带 token、响应拦截器处理 401 并返回 response.data。
  • 能安装 Element UI,在 main.js 中完整引入组件库及样式,并通过在页面中使用按钮组件验证配置生效。

知识点 ​

注册页在项目中的位置 ​

这一节完成面经移动端前台的“注册”静态页面。转写稿里提到,移动端是给用户看的前台系统,用户需要注册、登录后才能刷面试题;同时移动端组件库用的是 Vant(转写稿里口误说成“万”),PC 后台才用 Element UI,所以这里不要混用组件库。

为什么需要:注册页是用户进入系统的入口之一,先把静态结构和表单校验搭好,后面才方便接注册接口。
怎么想:先不急着写请求,先按“导航栏 + 表单 + 登录链接”三块搭骨架;用户名、密码先用 Vant 表单做前端校验。
怎么做:新建 Register.vue,根节点用 login-page,里面放 van-nav-bar、van-form、两个 van-field、提交按钮和 router-link。
易错点:这是移动端页面,组件名是 van-xxx;不要因为 PC 端用 Element UI 就写成 el-xxx。另外本节只是静态布局和打印提交值,不要编造注册接口逻辑。

用 Vant 搭表单骨架 ​

为什么需要:移动端表单涉及输入、校验、提交状态,直接手写 input 也能做,但用组件库更统一,提示和交互也更省事。
怎么想:van-form 负责收集表单和触发 submit;每个 van-field 通过 v-model 绑定数据,通过 name 决定提交对象里的字段名。
怎么做:用户名绑定 username,密码绑定 password 并设置 type="password";按钮设置 native-type="submit",这样点击按钮才会触发 van-form 的 @submit。
易错点:name 不是显示文字,label 才是左侧标签;placeholder 是占位提示;密码框忘记 type="password" 会明文显示。按钮若不加 native-type="submit",表单提交事件可能不会按预期触发。

jsx
<template>
  <div class="login-page">
    <van-nav-bar title="面经注册" />

    <van-form @submit="onSubmit">
      <van-field
        v-model="username"
        name="username"
        label="用户名"
        placeholder="用户名"
         :rules="[
          { required: true, message: '请填写用户名' },
          { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符' }
        ]"
      />
      <van-field
        v-model="password"
        type="password"
        name="password"
        label="密码"
        placeholder="密码"
        :rules="[
          { required: true, message: '请填写密码' },
          { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' }
        ]"
      />
      <div style="margin: 16px">
        <van-button block type="primary" native-type="submit"
          >注册</van-button
        >
      </div>
    </van-form>
    <router-link class="link" to="/login">有账号,去登录</router-link>
  </div>
</template>

<script>
export default {
  name: 'Register-Page',
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    onSubmit (values) {
      console.log('submit', values)
    }
  }
}
</script>

<style lang="less" scoped>
.link {
  color: #069;
  font-size: 12px;
  padding-right: 20px;
  float: right;
}
</style>

用户名和密码校验规则 ​

为什么需要:前端先做必填和格式校验,能减少无效提交,也能让用户立刻看到提示。
怎么想:rules 是数组,可以放多条规则;required: true 管空值,pattern 管格式。用户名要求至少 5 个字符,密码至少 6 个字符。
怎么做:用户名用 /^\w{5,}$/,密码用 /^\w{6,}$/;每条规则都配 message。
易错点:\w 匹配的是字母、数字、下划线,不包含中文;如果产品要求中文用户名,这个正则需要调整。{5,} 表示至少 5 个,不是“最多 5 个”。rules 要绑定数组,不要写成普通字符串。

提交与跳转登录 ​

为什么需要:表单要有提交入口;没有账号的用户也要能去登录页。
怎么想:van-form 的 @submit 回调会拿到表单值,本节先 console.log 验证字段名和值;跳转登录用 router-link 指向 /login。
怎么做:onSubmit(values) 里打印 values,在控制台确认提交对象是否为 { username, password };链接写 to="/login"。
易错点:不要把这个 @submit 和原生表单事件混淆;调试时先看控制台有没有输出,再看路由是否配置了 /login。转写稿里反复强调“先想清楚做什么,再套语法”,这里就是先验证静态提交,不提前接接口。

样式和常见调试 ​

为什么需要:登录/注册页底部的“去登录”通常是小号文字链接,需要单独样式。
怎么想:给 router-link 加 class="link",用 float: right 靠右,padding-right 留边距。
怎么做:样式写在 scoped 里,避免影响其他页面。
易错点:scoped 只作用于当前组件;如果页面整体布局类 login-page 没有样式,不要误以为注册页样式丢失。调试顺序建议:先看 Vant 是否已全局注册,再看 van-form 是否包住字段,最后看控制台 submit 输出。转写稿里提到的“组件库安装配置”是前置条件,注册页能正常显示组件,说明 Vant 已经可用。

目标与思路 ​

本节要把 axios 请求方法封装到 request 模块中。接口文档地址:https://apifox.com/apidoc/project-934563/api-20384515
基地址:http://interview-api.t.itheima.net/h5/

为什么需要:项目里会反复请求后端接口。如果每个页面都手写完整域名、超时时间、token、错误处理,代码会重复,改地址时也要到处改。
怎么想:把 axios 做一次“二次封装”,单独放到 utils/request.js,页面只关心“请求哪个接口、传什么参数”。
怎么做:安装 axios,用 axios.create 创建实例,配置 baseURL、timeout,再按需加请求/响应拦截器。
易错点:不要把封装理解成“重新写一个 axios”,它本质是创建一个配置好的实例并导出。

安装 axios ​

为什么需要:项目要先有 axios 依赖,后面才能 import axios from 'axios'。
怎么做:在项目根目录执行:

bash
npm i axios

如果项目使用 pnpm,转写稿中也提到可用 pnpm i axios 或 pnpm add axios。
易错点:要确认命令在项目根目录执行;安装后检查 package.json 是否出现 axios 依赖。不要和移动端项目混用包管理器。

新建 utils/request.js ​

为什么需要:封装代码要集中管理,页面不要直接依赖 axios 原始用法。
怎么想:在 src 下建 utils 文件夹,再建 request.js。转写稿中的思路是:先导入 axios,创建实例,配置拦截器,最后导出实例。
怎么做:利用 axios.create 创建一个自定义 axios 实例。笔记代码如下:

js
/* 封装axios用于发送请求 */
import axios from 'axios'

// 创建一个新的axios实例
const request = axios.create({
  baseURL: 'http://interview-api.t.itheima.net/h5/',
  timeout: 5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
  // 在发送请求之前做些什么
  return config
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 对响应数据做点什么
  return response.data
}, function (error) {
  // 对响应错误做点什么
  return Promise.reject(error)
})

export default request

易错点:baseURL 末尾斜杠和请求路径开头斜杠要注意拼接;timeout 单位是毫秒;请求拦截器必须 return config,响应拦截器要明确返回什么。笔记中响应拦截器返回 response.data,所以页面拿到的已经是业务数据,不再是完整响应对象。

请求拦截器与响应拦截器的推导 ​

为什么需要:很多接口都要带 token;登录失效时也要统一处理。转写稿中强调,响应结果会先经过响应拦截器,再交给组件里的 await 结果。
怎么想:请求拦截器适合统一加请求头;响应拦截器适合统一处理 401、统一返回 response.data。
怎么做:转写稿中的做法是从本地存储取 token,拼成 Bearer 空格 token 放到 config.headers.Authorization;响应拦截器里判断 error.response.status === 401,移除 token 并跳转登录。因为要跳转,需要先导入 router。
易错点:Bearer 后面要有空格;复制接口地址时不要带前后空格;token 的存储键名要和登录时保存的一致;401 处理要放在响应拦截器的错误回调里。

注册测试 ​

为什么需要:封装完要验证页面能否正常请求,并确认返回值是否已被响应拦截器处理。
怎么做:笔记中的测试代码:

js
// 监听表单的提交,形参中:可以获取到输入框的值
async onSubmit (values) {
  console.log('submit', values)
  const res = await request.post('/user/register', values)
  console.log(res)
}

怎么想:页面只导入 request,请求路径写相对路径,完整地址由 baseURL 拼接。
易错点:因为响应拦截器返回了 response.data,这里的 res 通常就是后端返回的业务数据,不需要再写 res.data。如果打印不对,先检查拦截器有没有 return response.data,再检查请求地址和参数。

易错点汇总 ​

  1. axios.create 的配置对象里,baseURL 拼写不要错。
  2. 请求拦截器忘记 return config,请求可能发不出去或配置丢失。
  3. 响应拦截器返回 response.data 后,页面不要再重复解构 data。
  4. 401 跳登录要导入 router,不能在普通 js 文件里直接写 this.$router。
  5. 复制接口地址、token 拼接时注意空格;Bearer 后必须有空格。
  6. 封装文件最后要 export default request,页面才能正确导入使用。

目标:把请求代码从页面抽离 ​

这一节的目标很明确:把“发请求”这件事封装成方法,统一放到 api 模块里,让页面和请求代码分离。页面只关心“用户点了注册,我要调用注册接口,然后提示并跳转”,而不是在页面里直接写 axios.post。

为什么需要封装 API ​

先看以前的模式:

旧模式:页面中充斥请求代码

以前请求代码直接写在页面里,会带来三个问题:页面中充斥着请求代码,可阅读性不高;相同的请求没有复用;请求没有统一管理。比如注册接口在多个地方调用时,每处都写一遍 URL 和参数,改接口时容易漏改。

怎么想:请求是“数据层”的事,页面是“展示和交互”的事,两者混在一起,页面会越来越臃肿。所以要把请求抽到 api 模块,页面只调用函数。

怎么做:新建 api/user.js,在里面声明注册函数。

易错点:不要一边说封装,一边又在页面里直接写 request.post;封装后页面只认 register 这个业务函数。

怎么想:页面只关心业务动作 ​

期望的模式是:

期望模式:请求与页面逻辑分离

期望达到:请求与页面逻辑分离;相同的请求可以直接复用;请求进行统一管理。

怎么想:页面里的 onSubmit 应该表达业务语义——“提交注册”。它不需要知道请求方法是 post,也不需要知道接口地址是 /user/register,这些细节由 api/user.js 负责。

怎么做:在 api/user.js 中暴露 register,页面导入后调用。

易错点:不要把“提示成功”和“跳转登录”也塞进 api 函数里。api 只负责发请求,alert 和 $router.push 属于页面交互,应该留在页面。

怎么做:新建 api/user.js 提供注册 Api 函数 ​

笔记中的实现如下,保留原代码:

jsx
import request from '@/utils/request'

// 注册接口
export const register = (data) => {
  return request.post('/user/register', data)
}

这里的 request 不是裸的 axios,而是前面统一配置好的 axios 实例。转写稿里配置 axios 的思路是:先安装 axios,在 src/utils 下新建 request.js,导入 axios,创建实例,配置 baseURL 根路径和请求/响应拦截器,最后导出实例。请求拦截器可以统一带 token,响应拦截器可以统一处理 401:移除 token 并跳转登录。响应拦截器第一个函数返回 response.data 后,页面拿到的就是后端返回数据,不需要再解构 data。

所以 api/user.js 只做一件事:在成品 request 实例上声明“注册接口”。register 接收 data,并把它作为 post 的第二个参数传过去。

易错点:import request from '@/utils/request' 中的 @ 是项目配置的别名;request 是默认导出,不要写成 { request }。接口路径写 /user/register 即可,不要再拼完整域名,否则会和 baseURL 重复。

页面调用注册接口 ​

register.vue 页面中调用测试,保留原代码:

jsx
methods: {
  async onSubmit (values) {
    // 往后台发送注册请求了
    await register(values)
    alert('注册成功')
    this.$router.push('/login')
  }
}

怎么想:onSubmit 是表单提交处理函数,values 是表单收集到的注册数据。调用 register(values) 就是发注册请求;因为 register 返回 Promise,所以用 async/await 等待请求完成。请求成功后提示“注册成功”,再跳转到登录页。

怎么做:页面中要先从 @/api/user 导入 register,再在 methods 里使用。调试时先确认 request 实例能正常导出,再确认接口路径和参数;如果遇到 401,统一响应拦截器会移除 token 并跳转登录,这是统一配置在起作用。

易错点:await register(values) 后面才写成功提示和跳转,不能写在 await 前面;values 要确认是接口需要的参数对象;跳转路径 /login 要和路由配置一致。

调试与易错点小结 ​

封装完成后,页面里不应该再出现重复的请求地址和请求方法。相同请求直接复用 register,接口地址变化只改 api/user.js。调试顺序建议:先看 api/user.js 的导入路径是否正确,再看 request 的 baseURL 和接口路径是否拼接正确,最后看页面是否 await 并处理了成功后的提示与跳转。这样注册功能就完成了“请求封装、页面调用、统一管理”的闭环。

课件图示 ​

课件第21页:目标:将 axios 请求方法,封装到 request 模块 登录 & 注册功能

课件第27页:目标:local 模块,本地存储封装 登录 & 注册功能 // 将token存入

课件第22页:目标:将请求封装成方法,统一存放到 api 模块,与页面分离 登录 & 注册功能

课件第23页:目标:将请求封装成方法,统一存放到 api 模块,与页面分离 登录 & 注册功能

课件第25页:目标:响应拦截器中统一处理错误提示 登录 & 注册功能 try { await

课件第26页:目标:封装 api 完成登录功能 登录 & 注册功能 封装请求API 登录操作

课件第19页:目标:阅读文档,实现 登录页 & 注册页 静态布局 登录 & 注册功能

课件第20页:目标:阅读文档,实现 登录页 & 注册页 静态布局 登录 & 注册功能 阅读文档

代码示例 ​

1. 注册静态布局(src/views/Register.vue) ​

vue
<template>
  <div class="login-page">
    <van-nav-bar title="面经注册" />

    <van-form @submit="onSubmit">
      <van-field
        v-model="username"
        name="username"
        label="用户名"
        placeholder="用户名"
        :rules="[
          { required: true, message: '请填写用户名' },
          { pattern: /^\w{5,}$/, message: '用户名至少包含5个字符' }
        ]"
      />
      <van-field
        v-model="password"
        type="password"
        name="password"
        label="密码"
        placeholder="密码"
        :rules="[
          { required: true, message: '请填写密码' },
          { pattern: /^\w{6,}$/, message: '密码至少包含6个字符' }
        ]"
      />
      <div style="margin: 16px">
        <van-button block type="primary" native-type="submit">注册</van-button>
      </div>
    </van-form>

    <router-link class="link" to="/login">有账号,去登录</router-link>
  </div>
</template>

<script>
export default {
  name: 'Register-Page',
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    onSubmit (values) {
      console.log('submit', values)
    }
  }
}
</script>

<style lang="less" scoped>
.link {
  color: #069;
  font-size: 12px;
  padding-right: 20px;
  float: right;
}
</style>

关键行 / 易错点

  • van-form @submit="onSubmit":表单提交事件由组件触发,只有 van-button 上的 native-type="submit" 才能把表单提交出去,写成普通 button 是不会触发的。
  • onSubmit(values) 的形参 values 就是表单里所有带 name 的字段值,所以两个 van-field 必须写 name="username" / name="password"。
  • :rules 用数组,且 required 与 pattern 是两条独立规则,会依次提示;pattern 是正则对象(不带引号),写成字符串 /^\w{5,}$/ 就不生效了。
  • v-model 双向绑定的 username / password 必须在 data() 中先声明,否则视图不响应。
  • <style lang="less" scoped> 里 .link 用了 float: right,父级 .login-page 未清浮动时可能影响下方布局。

2. axios 二次封装(src/utils/request.js) ​

js
/* 封装axios用于发送请求 */
import axios from 'axios'

// 创建一个新的axios实例
const request = axios.create({
  baseURL: 'http://interview-api-t.itheima.net/h5/',
  timeout: 5000
})

// 添加请求拦截器
request.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    return config
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error)
  }
)

// 添加响应拦截器
request.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么
    return response.data
  },
  function (error) {
    // 对响应错误做点什么
    return Promise.reject(error)
  }
)

export default request

关键行 / 易错点

  • axios.create({ baseURL, timeout }):不是直接用 axios,而是创建一个新实例,这样才能统一改基地址、统一挂拦截器;baseURL 末尾的 / 要和接口路径 /user/register 的写法配合,别拼出双斜杠或丢斜杠。
  • 转写稿特别强调:从接口文档复制 baseURL 时不要带上多余空格,前后有空格会导致请求地址错误。
  • 请求拦截器的成功回调必须 return config,漏写 return 请求就发不出去。
  • 响应拦截器成功回调里 return response.data 是这次封装的核心变化:返回的是真正的响应体,页面里 await request.post(...) 拿到的直接就是数据,不需要再解构 .data(转写稿讲的“组件中无需解构 data”)。
  • 失败回调必须 return Promise.reject(error),否则错误被吞掉,页面里的 try/catch、await 都拿不到异常。
  • 转写稿中 PC 端的做法:请求拦截器里取 token 拼 Bearer ${token} 塞进 config.headers.Authorization;响应拦截器里判断 error.response.status === 401 时移除 token 并 router.push('/login')——注意在 .js 文件里要用 import router from '@/router' 导入后再用,不能用组件里的 this.$router。

3. 接口统一管理 + 页面调用(src/api/user.js) ​

js
// src/api/user.js
import request from '@/utils/request'

// 注册接口
export const register = (data) => {
  return request.post('/user/register', data)
}
vue
<!-- src/views/Register.vue 中 methods 部分 -->
<script>
import { register } from '@/api/user'

export default {
  name: 'Register-Page',
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    // 监听表单的提交,形参中:可以获取到输入框的值
    async onSubmit (values) {
      // 往后台发送注册请求了
      await register(values)
      alert('注册成功')
      this.$router.push('/login')
    }
  }
}
</script>

关键行 / 易错点

  • 目录约定:utils/request.js 只放 axios 配置,api/user.js 只放接口函数,页面只调函数——这就是“请求与页面逻辑分离、相同请求可复用、统一管理”三点好处。
  • 接口函数写成箭头函数 + export const,data 是形参,调用时传 values 即可;注意 post 的第二个参数才是请求体,别把 data 传到第一个参数(URL)位置。
  • 页面里 await register(values) 后面直接 alert 再 this.$router.push('/login'),因为封装时响应拦截器已经返回了 response.data,所以不需要写 res.data。
  • onSubmit 必须是 async,否则 await 会报语法错误;同时 van-form 的提交不会自动阻止跳转,跳转要自己写。
  • 别名 @ 指向 src,import { register } from '@/api/user' 若写成 @/api/user.js 一般也能跑,但推荐省掉后缀保持统一。
  • 如果注册失败(用户名重复等),await 会抛错,实际项目里建议包一层 try/catch 或用 this.$toast 提示,避免控制台未捕获异常。

常见错误 ​

  • 组件名报错 Component name "index" should always be multi-word 现象:新建 views/login/index.vue 后控制台飘红,页面本身能跑。 原因:ESLint 的 vue/multi-word-component-names 规则要求组件 name 至少两个单词,而 name: 'index' 只有一个。 修法:改成多词大驼峰(LoginIndex、ArticleIndex、LayoutIndex),或干脆不写 name;不建议为了它去关规则。

  • 封装 request 之后,页面里还写 res.data,结果拿到 undefined 现象:注册成功但 console.log(res) 打印的是 undefined,或者业务代码里 res.data.xxx 报错。 原因:响应拦截器已经 return response.data,业务层又手动剥了一层。 修法:全项目统一约定——拦截器返回 response.data,调用方直接拿返回值;写 api 层时就把这个约定定死,别一半剥一半不剥。

  • baseURL 复制进来带了空格 / 双斜杠 现象:请求路径变成 http://interview-api.itheima.net/h5//user/register,或直接 Invalid URL、404。 原因:从文档复制时首尾带了空格;baseURL 末尾有 /,api 里 /user/register 又以前导 / 开头。 修法:粘贴后肉眼扫一遍首尾空格;只保留一处斜杠(baseURL 结尾带 /,则 api 写 user/register)。

  • 路由懒加载写成了直接调用 现象:首屏加载变慢,Network 里所有页面组件一次性全被请求;或报 component is not defined。 原因:把 component: () => import('@/views/...') 简化时丢了箭头函数,写成 component: import('@/views/...'),函数变成了立即执行的 Promise。 修法:确认 component 的值是一个函数,即 () => import('@/views/xxx/index.vue')。

  • 二级路由怎么点都不出来,内容区一片空白 现象:地址栏已经是 /article、/dashboard,但页面只显示侧边栏和头部。 原因:Layout/index.vue 里忘了写 <router-view>;或者 children 的 path 写成了 /article(前导斜杠会当成绝对路径,不再拼接父路径)。 修法:父级组件模板里补上 <router-view>;children 的 path 一律不带前导斜杠。

  • 拦截器里写 this.$router.push('/login') 报 Cannot read properties of undefined 现象:401 时控制台报错,跳转不生效。 原因:request.js 是普通 JS 模块,没有组件实例的 this,this.$router 是 undefined。 修法:import router from '@/router',用 router.push('/login');同时确认 store、request 的导入不会形成循环依赖。

  • token 拼进请求头后后端仍返回 401 现象:本地明明存了 token,接口还是未授权。 原因:模板字符串少了 Bearer 后面的空格(Bearer${token});或 token 压根没取到就拼了个 Bearer undefined。 修法:写成 `Bearer ${token}`;拼之前先判空,没有 token 就不设这个头。

练习 ​

  1. 打开注册页,把用户名的 pattern 改成“必须包含字母和数字,长度 6–12”,然后故意留空提交、只输 3 位提交、输中文提交,分别记录三种情况下表单的提示文案与 onSubmit 是否被触发。
  2. 在 utils/request.js 里临时把响应拦截器改成 return response,回到注册页不改任何业务代码,观察 register() 的返回值变化;然后改回去,说明为什么“拦截器返回值”必须和“api 层调用方”约定一致。
  3. 用 npm run serve 启动面经 PC 端,依次访问 /login、/、/article、/dashboard,在 Vue Devtools 里观察组件树的层级差异,画出“谁套谁”的三层结构,并标注每一层的 <router-view> 位置。
  4. 把 / 的 redirect 删掉,重新访问根路径,描述你看到的画面;再加回来,解释这一行配置解决的到底是什么问题。
  5. 把 element-ui 的完整引入改成按需引入(参考官网 babel-plugin-component 方案),对比改造前后打包体积或首屏 Network 请求数量,写一句结论:这个项目值不值得改。

原视频对照 ​

#视频时长
1反馈和回顾16:50
2面经PC端介绍06:01
3初始化目录结构07:28
4创建每个组件文件17:35
5路由配置10:43
6axios安装配置19:32
7安装配置element-ui06:35

小结 ​

  • 注册页的校验规则写在 van-field 的 rules 里,提交按钮必须 native-type="submit",两者缺一表单就不会触发 @submit。
  • axios 二次封装的价值不在“能发请求”,而在“全项目只有一处配置 baseURL、超时、拦截器”,改一次全局生效。
  • 拦截器返回什么,业务层就拿到什么——这个约定必须写死在 api 层,否则每个页面都要猜要不要 .data。
  • api 层(api/user.js)是页面与接口之间的隔离带:页面只管调函数,接口路径变了只改一个文件。
  • 路由设计先想“谁占满全屏、谁只占内容区”,一级路由定骨架,二级路由定内容区,画完图再写代码比边写边试快得多。
  • 路由懒加载的本质是“返回 import 的函数”,写成直接调用就退化成一次性全量加载,性能优化瞬间失效。
  • 401 处理放在响应拦截器里是唯一正确的位置:清除本地凭证 + 跳登录,一处写完全站生效。
  • 组件库(Vant / Element UI)的接入套路完全一致:装包 → main.js 引入组件与样式 → Vue.use → 任意组件里试一个按钮验证。