主题
element-ui的基本使用与element-ui按需引入(Lesson20 2/2)
本页对应 Lesson20,共 6 段视频,约 1 小时 7 分。
学习目标
- 能够在前端搜索页中实现“加入购物车”:补齐商品详情所需字段,提交商品 ID 与购买数量,并完成成功页跳转与常见报错排查。
- 能够完成 Element UI 的基本使用:安装并全局引入组件库,使用 Button、Message 等组件及其属性、事件,并验证页面效果。
- 能够完成 Element UI 按需引入:配置 Babel 插件,在入口文件注册所需组件与 Message,并说明完整引入与按需引入的包体积差异。
- 能够完成注册页静态组件:挂载路由、引入模板与样式,并完成手机号、验证码、密码、确认密码、协议勾选等表单字段绑定。
- 能够实现发送验证码功能:封装验证码 API 与 Vuex action,校验手机号合法性,调用接口并将验证码回填或提示错误信息。
- 能够完成注册功能:校验注册表单,封装注册 API 与 Vuex action,处理响应码与错误提示,并在注册成功后跳转登录页。
知识点
先看官网和安装
Element UI 是面向 Vue 2 的桌面端组件库。转写稿里反复强调:组件库离不开官方文档,先看“组件 → 快速上手”,再照着文档安装、引入、使用。官网地址保留如下:
https://element.eleme.cn/#/zh-CN
为什么需要组件库?因为项目里按钮、表格、弹窗、消息提示这些界面元素重复率很高,自己写既慢又难统一风格。怎么想?先用官方文档确认版本和用法,不要凭记忆写。怎么做?在项目根目录安装:
shell
cnpm i element-ui易错点:安装命令要在当前项目目录执行;装完后要确认 package.json 里出现依赖。转写稿里还提到,后面做 Vue 3 项目会用 Element UI Plus,但当前 Vue 2 项目用 Element UI。
完整引入入口文件
为什么要在入口文件引入?因为 Element UI 的组件和样式需要注册到 Vue 全局,页面里才能直接写 el-button、el-row 等标签。怎么想?入口文件 src/index.js 是应用启动时最先执行的地方,适合做全局注册。怎么做?按笔记写:
js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);易错点:样式文件不能漏,否则组件结构在、样式不对;Vue.use(ElementUI) 不能漏,否则模板里的 el- 组件不识别。转写稿里还提醒,样式引入建议放在下面,保持入口文件清晰。完整引入的优点是省事,缺点是会把 Element UI 的组件都打进包里,项目变大后要考虑按需引入。
在注册界面使用按钮
为什么先拿按钮练手?按钮最常用,能快速验证“安装、引入、使用”这条链路是否通。怎么想?打开官方文档的 Button 组件,复制示例,放到自己的注册页面里看效果。怎么做?笔记中的 index.vue 如下,保留原代码:
vue
<template>
<div>
<h3>注册界面</h3>
<el-row>
<el-button @click="fn">默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "Register",
methods:{
fn(){
this.$message.error('这是一条消息提示');
}
}
}
</script>这里的 type 控制按钮风格,@click="fn" 是给默认按钮绑点击事件。易错点:el-row、el-button 是 Element UI 的组件名,不是普通 HTML 标签;如果页面报“未知组件”,先查入口文件有没有 Vue.use(ElementUI),再查样式有没有引入。转写稿里还演示了在文档里找“事件”:按钮文档没有直接写点击事件示例时,就按 Vue 的写法直接加 @click,然后在 methods 里写方法。
用 $message 做消息提示
为什么用 $message?因为注册、登录、加入购物车这类操作需要即时反馈,消息提示比 alert 更轻量。怎么想?官方文档里有 Message 消息提示,代码形式是 this.$message.error(...),说明它已经挂到 Vue 实例上。怎么做?在方法里写:
js
fn(){
this.$message.error('这是一条消息提示');
}点击默认按钮就能看到错误提示。转写稿里的调试过程是:先写 this.$message.error,看效果;再换成 success 看成功提示;文档里还有可关闭、文字居中、使用 HTML 等配置。易错点:$message 不是组件标签,不能写成 <el-message> 就完事;如果按需引入后报 this.$message 不存在,说明 Message 没有挂载,需要按官方文档把 Message 引入并挂到 Vue.prototype.$message 上。转写稿里就出现过“点击报错,发现 $message 没有”的情况,最后是按完整组件文档里的自定义方法补上的。
按需引入:项目变大后的优化
为什么需要按需引入?完整引入会把七八十个组件都带上,但项目可能只用十几个,打包体积会变大。怎么想?先保证功能跑通,再按官方“按需引入”文档优化。怎么做?转写稿里的步骤是:安装 babel-plugin-component,在 Babel 配置的 plugins 中按官方文档加入 component 插件配置,然后重启项目;入口文件不再 import ElementUI from 'element-ui',而是按需引入,例如:
js
import { Button, Row } from 'element-ui';
Vue.use(Button);
Vue.use(Row);如果还要用消息提示,就按文档引入 Message,并挂到 Vue.prototype.$message。易错点:按需引入后,页面用到的每个组件都要注册;转写稿里就出现过用了 el-row 却只注册了 Button,结果报错,补上 Row 才正常。改完 Babel 配置一定要重启;不要完整引入和按需引入混着写,否则优化效果会打折扣。
易错点小结
第一,官网文档是准绳,安装、引入、组件属性都以文档为准。第二,完整引入三件事:装依赖、引样式、Vue.use。第三,模板里用 el- 组件,事件仍按 Vue 的 @click 写。第四,$message 是方法不是标签,按需引入时要单独处理。第五,按需引入能减小打包体积,但组件和样式要逐个补全,改配置后要重启。转写稿里“加入购物车”那段也说明一个思路:先看已有页面怎么做,再补数据、调接口、加事件、看控制台报错,一步步调通;Element UI 的使用同样是“看文档 → 复制示例 → 改到自己的页面 → 看报错修正”。
为什么需要按需引入
为什么需要: Element UI 提供的组件很多,如果完整引入,项目会把大量当前页面用不到的组件和样式一起打进包里,增加最终打包体积,也会影响加载效率。转写稿里明确说,按需引入是“优化网站的一种方案”,目标是“需要按钮就引入按钮”。
怎么想: 先判断当前项目实际用了哪些组件。比如本节只用到 Button、Row,消息提示用到 Message,那就只引入这几个,而不是 import ElementUI from 'element-ui' 后整体 Vue.use(ElementUI)。
怎么做: 安装按需引入所需的 Babel 插件,并在 babel.config.js 中配置 libraryName 和 styleLibraryName,让插件按组件自动处理对应样式。
易错点: 按需引入不是只删掉完整引入就结束,必须同时配置 Babel 插件;配置修改后要重启开发服务器,否则新配置可能不生效。
安装 babel-plugin-component
为什么需要: Element UI 的按需引入依赖 babel-plugin-component 做组件和样式的转换。
怎么想: 这个插件只在开发、构建阶段使用,打包后运行不需要它,所以用 -D 安装为开发依赖。
怎么做:
shell
cnpm install babel-plugin-component -D易错点: 转写稿提到 -D 相当于开发依赖,打包后不需要;不要误装成生产依赖。安装完成后,下一步必须配置 babel.config.js。
配置 babel.config.js
为什么需要: 只安装插件不会自动生效,需要在 Babel 配置中告诉它:组件库是 element-ui,样式目录是 theme-chalk。
怎么想: 项目脚手架通常已经生成了 babel.config.js,不需要再新建 .babelrc。转写稿里也说明,当前项目把 Babel 内容放在 babel.config.js 中。
怎么做: 在项目根目录的 babel.config.js 中加入 plugins 配置:
js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}易错点: libraryName 必须是 element-ui,styleLibraryName 必须是 theme-chalk;配置写完后要重启项目。转写稿调试时也强调“需要重新启动”。
在入口文件注册所需组件
为什么需要: 按需引入后,组件不会自动全局可用,需要在入口文件 src/index.js 中注册当前页面用到的组件。Message 不是普通标签组件,还需要挂到 Vue.prototype 上,才能在组件里通过 this.$message 调用。
怎么想: 转写稿的调试过程是:先只引入 Button,页面里的 el-row 报未注册;补上 Row 后,点击按钮又报 $message 不存在;再补 Message 并挂到原型上。也就是说,模板里用哪个组件,入口文件就要注册哪个。
怎么做:
js
import {Button, Message, Row} from 'element-ui';
// Vue.component(Button.name, Button);
// Vue.component(Row.name, Row);
// 或者
Vue.use(Button);
Vue.use(Row);
Vue.prototype.$message = Message;Vue.use(Button) 和 Vue.component(Button.name, Button) 都能完成注册;Button.name 对应的是 ElButton,所以模板中写 el-button。Row 同理,模板中写 el-row。
易错点: 不要只引入 Button 就以为 el-row 可用;Message 不要写成 Vue.use(Message) 来当普通组件用,本节笔记的做法是 Vue.prototype.$message = Message。如果页面点击按钮报 $message 未定义,优先检查是否引入并挂载了 Message。
在组件中使用与验证
为什么需要: 注册完成后,要在实际页面中验证按钮、栅格和消息提示是否都正常。
怎么想: 模板中使用 el-row、el-button,点击按钮触发 fn,通过 this.$message.error 验证 Message 是否挂载成功。
怎么做:
vue
<template>
<div>
<h3>注册界面</h3>
<el-row>
<el-button @click="fn">默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "Register",
methods:{
fn(){
this.$message.error('这是一条消息提示');
}
}
}
</script>易错点: 如果按钮能显示但点击报错,检查 Message;如果 el-row 不生效或控制台提示组件未注册,检查 Row 是否引入并注册;如果样式缺失,检查 babel.config.js 中 styleLibraryName 是否配置正确。按需引入的核心就是:用到谁,就引入谁、注册谁。
从搜索页加入购物车说起
为什么需要:搜索页只拿到商品列表数据,但“加入购物车”需要商品详情里的 attrList、SPU、skuId 等信息。列表里没有这些字段,直接照搬详情页逻辑会缺数据。
怎么想:先看详情页“加入购物车”函数做了什么:组装 cartInfo、调用加入购物车、跳转成功页。搜索页缺商品详情,就根据商品 ID 再取一次详情,把数据补齐后再加入购物车。
怎么做:在搜索页复制详情页的加入购物车逻辑,用 dispatch 调商品详情接口,并注意 await;把详情数据补到 cartInfo,skuId 用当前商品 ID,数量先写 1。
js
async addCart(item) {
const result = await this.$store.dispatch('product/getProduct', item.id)
const cartInfo = {
skuId: item.id,
cartNum: 1,
attrList: result.attrList,
spuId: result.spuId
}
await this.$store.dispatch('cart/addCart', cartInfo)
this.$router.push('/cartSuccess')
}易错点:dispatch 前漏写 await,后面会拿不到详情;数组 find 的回调要写 return,否则会报错;跳转参数别写成 item.id.id。
Element UI 的基本使用与按需引入
为什么需要:注册页要按钮、消息提示、布局等基础组件,直接用组件库能减少重复样式和交互代码。
怎么想:先完整引入跑通,再改成按需引入优化打包体积。完整引入简单,但会把未使用组件也打进包;按需引入只引入当前用到的组件。
怎么做:安装后,在入口文件引入 Element UI 和样式,并 Vue.use(ElementUI)。按需引入需安装 babel-plugin-component,在 Babel 配置中按文档配置,再在入口文件按需注册。
js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)按需引入时,用到按钮就注册按钮,用到消息就引入 Message;漏了组件会报“未注册”或“找不到方法”。
js
import { Button, Message } from 'element-ui'
Vue.use(Button)
Vue.prototype.$message = Message易错点:样式建议放在入口文件靠下位置;按需引入后,ElRow、ElButton、$message 都要单独引入;改完 Babel 配置要重启项目。
完成注册的静态组件
为什么需要:先有稳定页面结构,才能接手机号、验证码、密码和协议校验。
怎么想:路由已经接好,直接把静态注册页内容复制到注册组件,删掉不需要的头部和旧提示,再引入对应样式。
怎么做:在 src 下找到注册组件,替换内容;把静态页的样式复制到注册样式文件;先保留错误提示区域,后续再处理。
易错点:只复制模板不引样式,页面会错位;旧 message 内容要清理,避免干扰。
发送验证码
为什么需要:注册要验证手机号归属。真实短信要花钱,这里用接口返回验证码模拟。
怎么想:先建用户相关 API 和 store 模块,再在组件里点击“获取验证码”时校验手机号,调用接口,把返回的验证码填入输入框。
怎么做:API 中新增获取验证码接口,请求方式为 GET,接收手机号;store 的 user 模块中调用该接口;组件里用 v-model 收集手机号,点击时先校验,再 dispatch。
js
async getCode() {
if (!/^1\d{10}$/.test(this.phone)) {
return this.$message.error('请输入正确的手机号')
}
const result = await this.$store.dispatch('user/getCode', this.phone)
this.code = result.data
}易错点:手机号在发送验证码后仍可修改,提交注册时要再次校验;接口返回结构要看清楚,别把 result.data.data 写错。
提交注册与跳转
为什么需要:完成注册要校验手机号、验证码、密码一致性和协议,再调用注册接口。
怎么想:提交前做完整校验,校验通过后组织接口需要的字段,成功则跳转登录页。
怎么做:给“完成注册”按钮绑定事件,校验 phone、code、password、repassword、isAgree;调用注册接口,根据返回 code 判断成功或提示错误。
js
async register() {
if (!/^1\d{10}$/.test(this.phone)) return this.$message.error('请输入正确的手机号')
if (!this.code) return this.$message.error('请输入验证码')
if (this.password !== this.repassword) return this.$message.error('两次密码不一致')
if (!this.isAgree) return this.$message.error('还未同意协议')
const result = await this.$store.dispatch('user/register', {
phone: this.phone,
password: this.password,
code: this.code
})
if (result.code === 200) {
this.$router.push('/login')
} else {
this.$message.error(result.message)
}
}易错点:校验不通过要 return,否则继续提交;接口字段名要对齐文档;成功跳转用 $router.push,不要写成 $route.push。
先做静态组件:为什么把“壳子”先搭出来
为什么需要:注册功能真正的逻辑是“输入手机号 → 获取验证码 → 填密码 → 提交注册”,背后要调两个接口。但在写任何一行请求代码之前,得先有一个看得见、点得着的页面。转写稿里讲师的原话是:“注册…首先我们是不是得搭建路由,不过这个路由是不是已经接好了?那建好了以后,我这里面是不是就完成注册的静态组件。”也就是说,路由这一层已经就绪,本节只解决“页面长什么样”。
怎么想:静态组件 = 结构 + 类名 + 样式,先不接数据、不写校验。把页面当成一张设计稿还原出来,后续所有交互(v-model、点击事件、错误提示显隐)都是往这个壳子里“填肉”。
怎么做:在 src/pages/Register/index.vue 新建文件,把准备好的注册页面结构搬进来。讲师的做法是打开已有的静态页面,“头部这块也不要,我们是不是只要这一部分就行,把这一块复制过来,然后替换了我这一块内容”,也就是只保留注册表单主体,去掉站点公共头尾。
易错点:不要一上来就写 v-model 和接口调用。转写稿里明确说“像这里面的错误提示,这个我们先不用管,整个后面…我再说”,静态阶段就把它们当纯文本放着。
组件骨架与命名:name: "Register" 不是可写可不写
为什么需要:Vue 组件需要一个名字,方便调试工具里识别,也方便后续 <keep-alive> 的 include/exclude 按名字匹配。
怎么想:文件名是 index.vue,如果只靠文件名,组件名会变成 index,容易和别的页面重名。所以显式声明 name 更稳妥。
怎么做:
vue
<script>
export default {
name: "Register"
}
</script>易错点:name 的值要用大驼峰(Register),不要写成 register 或 "register-index",否则后面做缓存排除时对不上。
模板结构拆解:五块内容区 + 一组协议 + 一个提交按钮
为什么需要:注册表单字段是固定的(手机号、验证码、密码、确认密码),把结构写清楚,后面加校验时才知道每个 span.error-msg 对应哪个输入框。
怎么想:整体用 .register 包一层,标题 h3 里带“去登录”的链接,下面每个字段统一用 .content 包裹 label + input + span.error-msg,协议单独用 .controls,按钮单独用 .btn。这种“一个字段一个 content”的写法,好处是样式统一、后续做错误提示也统一。
怎么做:
vue
<template>
<!-- 注册内容 -->
<div class="register">
<h3>注册新用户
<span class="go">我有账号,去 <a href="login.html" target="_blank">登录</a>
</span>
</h3>
<div class="content">
<label>手机号:</label>
<input type="text" placeholder="请输入你的手机号">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>验证码:</label>
<input type="text" placeholder="请输入验证码">
<button class="getcode">获取验证码</button>
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>登录密码:</label>
<input type="text" placeholder="请输入你的登录密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>确认密码:</label>
<input type="text" placeholder="请输入确认密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="controls">
<input name="m1" type="checkbox">
<span>同意协议并注册《尚品汇用户协议》</span>
<span class="error-msg">错误提示信息</span>
</div>
<div class="btn">
<button>完成注册</button>
</div>
</div>
</template>易错点:验证码那一行比别的字段多一个 .getcode 按钮,位置在 input 和 error-msg 之间,别漏了;协议那一行的 span.error-msg 是挂在 .controls 里的,不是 .content,写错会导致后面控制显隐时选择器对不上。
样式从 less 里搬:类名就是“接口”
为什么需要:页面能跑起来不代表长得对。转写稿里讲师是“打开我的 CSS,找到我的注册,把 less 直接拿过来”,说明这套类名(.register、.content、.controls、.btn、.error-msg)在样式文件里已经写好了。
怎么想:模板里的 class 不是随便起的,它是模板和样式之间的约定。样式文件里写的是 .register .content,模板就必须用 content,不能自己改成 item。
怎么做:把注册相关的 less 复制进组件(或全局样式),刷新页面确认布局、间距、按钮位置都对。
易错点:复制样式时容易漏掉外层容器类,导致样式“不生效”;另外 .error-msg 的初始样式通常是红色小字,如果不加,后面做错误提示时会看不出效果。
静态阶段的“完成标准”与下一节衔接
为什么需要:明确边界,避免在静态阶段就把接口、正则、校验全塞进来,导致后面改不动。
怎么想:本节完成的标准是——访问注册路由能看到完整表单,输入框能打字,按钮能点(但暂无反应),错误提示文案以占位形式存在。转写稿里也提到“我先把这一块内容复制一份,这是我们的一个静态页面,先把静态的页面准备好”。
怎么做:启动项目,走一遍路由,检查五个字段、一个勾选框、两个按钮是否都在正确位置。
易错点:不要提前把 error-msg 删掉“看着碍眼”。讲师明确说过错误提示后面再处理——如果现在删了,下一节做校验时还得重新补结构,反而多干一遍活。
本节要完成什么
注册页需要“获取验证码”:用户输入手机号,点击按钮后,前端先校验手机号格式,再请求后端验证码接口。后端这里不是真的发短信,而是模拟返回一个验证码,前端把验证码填入验证码输入框,方便继续完成注册。整体数据流是:Register/index.vue 触发方法 → store/user.js 的 action → api/user.js 的请求函数 → 后端接口。
为什么需要分层:接口地址集中在 api,状态与异步逻辑放在 store,组件只负责收集手机号和展示结果。怎么想:先封装接口,再写 action,最后在组件里 dispatch。怎么做:按下面三步落地。
第一步:在 api/user.js 封装验证码接口
为什么需要:组件里不要直接写请求地址,否则地址一变就要到处改。怎么想:验证码接口是 GET,路径中带手机号,因此封装成接收 phone 的函数。怎么做:在 src/api/user.js 中引入请求方法并导出 getSendCode。
js
import {sphRequest} from "@/request";
// 获取验证码 /api/user/passport/sendCode/{phone}
export const getSendCode = phone => sphRequest(`/user/passport/sendCode/${phone}`);易错点:路径拼接要使用模板字符串;手机号是路径参数,不要误写成请求体;sphRequest 的引入路径要与项目一致。转写稿里提到的“尚品会”应理解为“尚品汇”,这里沿用笔记中的 sphRequest。
第二步:在 store/user.js 增加 action
为什么需要:发送验证码是异步请求,放到 Vuex 的 action 中便于组件调用,也方便后续统一处理。怎么想:action 接收 phone,调用 getSendCode,把后端返回的验证码数据返回给组件。怎么做:在 src/store/user.js 中写 getSendCodeAsync。
js
import {getSendCode} from "@/api/user";
const actions = {
async getSendCodeAsync(content,phone){
const result = await getSendCode(phone);
return result.data;
}
}
export default {
namespaced:true,
actions
}易错点:action 的第一个参数是上下文 content,真正手机号是第二个参数 phone;组件 dispatch 时要传第二个参数;await 后不要忘记 return,否则组件拿不到验证码。转写稿中调试时强调“得到验证码后要返回”,否则页面无法把验证码填进输入框。
第三步:在注册页触发发送验证码
为什么需要:用户点击“获取验证码”时,要先判断手机号是否合法,再发请求。怎么想:用 phoneReg 校验手机号,合法则 dispatch,不合法则用 $message.error 提示。怎么做:在 src/pages/Register/index.vue 中给按钮绑定 sendCode,并把返回的验证码赋给 code。
vue
<template>
<!-- 注册内容 -->
<div class="register">
<h3>注册新用户
<span class="go">我有账号,去 <a href="login.html" target="_blank">登录</a>
</span>
</h3>
<div class="content">
<label>手机号:</label>
<input v-model="phone" type="text" placeholder="请输入你的手机号">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>验证码:</label>
<input v-model="code" type="text" placeholder="请输入验证码">
<button @click="sendCode" class="getcode">获取验证码</button>
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>登录密码:</label>
<input v-model="password" type="text" placeholder="请输入你的登录密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>确认密码:</label>
<input v-model="rePassword" type="text" placeholder="请输入确认密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="controls">
<input v-model="isAllow" name="m1" type="checkbox">
<span>同意协议并注册《尚品汇用户协议》</span>
<span class="error-msg">错误提示信息</span>
</div>
<div class="btn">
<button >完成注册</button>
</div>
</div>
</template>
<script>
import {phoneReg} from "@/utils/reg";
export default {
name: "Register",
data(){
return {
phone:"",
code:"",
password:"",
rePassword:"",
isAllow:false
}
},
methods:{
async sendCode(){
// 判断手机号是否合法
if(phoneReg.test(this.phone)){
this.code = await this.$store.dispatch("user/getSendCodeAsync",this.phone);
}else {
this.$message.error("请输入正确的手机号!");
}
}
}
}
</script>易错点:dispatch 的命名空间要写全 "user/getSendCodeAsync";this.code 要接收 action 的返回值;sendCode 必须是 async;手机号校验用 phoneReg.test(this.phone)。转写稿调试时还出现过“请输入正确的手机号”的提示,这是校验分支生效,不是接口失败。若接口返回结构不同,要以实际返回为准,不要凭空改字段。
先明确“完成注册”要做什么
注册页面的目标不是只把静态页摆出来,而是把用户输入收集起来,先获取验证码,再调用注册接口,成功之后跳到登录页。按转写稿的推导,整条链路可以拆成三层:api 层负责发请求,store 层负责把请求包成 action,组件层负责收集表单、校验、提交。为什么要这样拆?因为接口地址和请求方式集中管理,组件只关心“拿数据、做校验、调 action”,后面维护时不容易把地址写散。怎么想?先看注册接口需要什么参数,再看页面有哪些输入项,最后把校验顺序定下来。怎么做?本节笔记已经给出三段核心代码。
接口层:api/user.js
js
// 注册用户 /api/user/passport/register post
export const postRegister = body =>sphRequest.post(`/user/passport/register`,body);这里 postRegister 接收一个 body,整体作为 POST 请求体传给 /user/passport/register。为什么用 POST?注册会提交手机号、密码、验证码,属于写操作,不应拼在 URL 上。易错点是:不要把 body 拆成多个参数,也不要在组件里直接写请求地址;统一走 sphRequest,基础地址由请求实例处理。
Vuex 层:api/store/user.js
js
async postRegisterAsync(content,body){
const result = await postRegister(body);
return result;
}这里的 postRegisterAsync 是 action。第一个参数 content 是 Vuex action 的上下文占位,本节实际使用的是第二个参数 body。为什么要有这一层?组件里通过 this.$store.dispatch("user/postRegisterAsync", {...}) 调用,组件不直接依赖接口文件,职责更清楚。关键点是必须 return result,否则组件拿不到接口返回,后面判断 result.code === 200 就会失效。易错点:dispatch 时传参要传对象本身,不要多包一层没必要的字段;action 名要和模块命名空间对应。
组件层:表单数据、验证码与注册提交
src/pages/Register/index.vue 的核心代码如下,保留了本节笔记内容:
vue
<template>
<!-- 注册内容 -->
<div class="register">
<h3>注册新用户
<span class="go">我有账号,去 <a href="login.html" target="_blank">登录</a>
</span>
</h3>
<div class="content">
<label>手机号:</label>
<input v-model="phone" type="text" placeholder="请输入你的手机号">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>验证码:</label>
<input v-model="code" type="text" placeholder="请输入验证码">
<button @click="sendCode" class="getcode">获取验证码</button>
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>登录密码:</label>
<input v-model="password" type="text" placeholder="请输入你的登录密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>确认密码:</label>
<input v-model="rePassword" type="text" placeholder="请输入确认密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="controls">
<input v-model="isAllow" name="m1" type="checkbox">
<span>同意协议并注册《尚品汇用户协议》</span>
<span class="error-msg">错误提示信息</span>
</div>
<div class="btn">
<button @click="goRegister">完成注册</button>
</div>
</div>
</template>
<script>
import {phoneReg} from "@/utils/reg";
export default {
name: "Register",
data(){
return {
phone:"",
code:"",
password:"",
rePassword:"",
isAllow:false
}
},
methods:{
async sendCode(){
// 判断手机号是否合法
if(phoneReg.test(this.phone)){
this.code = await this.$store.dispatch("user/getSendCodeAsync",this.phone);
}else {
this.$message.error("请输入正确的手机号!");
}
},
async goRegister(){
if(!phoneReg.test(this.phone)){
this.$message.error("请输入正确的手机号");
return;
}
if(this.code.length <1){
this.$message.error("请输入验证码");
return;
}
if(this.password.length<1){
this.$message.error("请输入密码]");
return;
}
if(this.password !== this.rePassword){
this.$message.error("两次密码不一致!");
return;
}
if(!this.isAllow){
this.$message.error("您还未同意协议!");
return;
}
const {phone,password,code} = this;
const result = await this.$store.dispatch("user/postRegisterAsync",{
phone,password,code
})
if(result.code === 200){
console.log(result);
this.$router.push("/login");
}else{
this.$message.error(result.message)
}
}
}
}
</script>这里用 v-model 收集 phone、code、password、rePassword、isAllow。sendCode 先判断手机号是否合法,合法才 dispatch 获取验证码;转写稿里说明这是教学模拟,把返回的验证码直接赋给 this.code,不是真实发短信。goRegister 按顺序校验:手机号、验证码非空、密码非空、两次密码一致、是否同意协议,全部通过后才构造 {phone,password,code} 提交。成功后判断 result.code === 200,跳转 /login;失败用 result.message 提示。
调试过程与易错点
转写稿里实际调试时出现过“手机号已被注册”“验证码错误”,换手机号或重新获取验证码后成功,并跳转到登录页。这说明组件里的提示不是摆设,要按接口返回处理。常见坑:第一,发送验证码时手机号校验过,但提交前用户仍可能改手机号,所以 goRegister 里要再校验一次;第二,不要把 rePassword、isAllow 传给后端,接口只需要 phone、password、code;第三,this.$message 依赖 Element UI 的 Message,按需引入时如果只引了 Button 没引 Message,会报错,需要确认 Message 已注册;第四,action 和接口都要把结果 return 出来,否则组件无法判断成功失败。
课堂画面






代码示例
示例 1:element-ui 完整引入 + 按钮 / 消息提示
js
// src/index.js(项目入口文件,脚手架默认叫 main.js,笔记中改成了 index.js)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 1. 引入 element-ui 组件库本体
import ElementUI from 'element-ui'
// 2. 引入 element-ui 的样式文件(建议放在样式引入的最下面)
import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
// 3. 注册到 Vue 上,之后所有组件里都能直接用 el-xxx
Vue.use(ElementUI)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')vue
<!-- src/pages/Register/index.vue -->
<template>
<div>
<h3>注册界面</h3>
<el-row>
<el-button @click="fn">默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "Register",
methods:{
fn(){
this.$message.error('这是一条消息提示');
}
}
}
</script>要点解释
- 安装:
cnpm i element-ui;三步入场:引入组件库 → 引入主题样式 →Vue.use(ElementUI),缺一步就报“Unknown custom element:<el-button>”。 Vue.use(ElementUI)是完整引入,会把 element-ui 全部组件都打进包里,学习/演示阶段够用,但包体积大。el-row、el-button都是全局组件,模板里不用再import,也不用components注册。this.$message.error(...)能直接用的前提是Vue.use(ElementUI),它把$message挂到了原型上;按需引入时不会自动挂,这是最典型的报错点。- 样式
import 'element-ui/lib/theme-chalk/index.css'忘了写,按钮会变成没有样式的裸<button>。
示例 2:element-ui 按需引入
js
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}js
// src/index.js(按需引入版入口文件)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 需要谁就引入谁
import {Button, Message, Row} from 'element-ui';
// 方式一:Vue.component(Button.name, Button);
// Vue.component(Row.name, Row);
// 方式二(等价,推荐):
Vue.use(Button);
Vue.use(Row);
// Message 是“方法式”组件,不能 Vue.use,要挂到原型上
Vue.prototype.$message = Message;
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')vue
<!-- src/pages/Register/index.vue -->
<template>
<div>
<h3>注册界面</h3>
<el-row>
<el-button @click="fn">默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "Register",
methods:{
fn(){
this.$message.error('这是一条消息提示');
}
}
}
</script>要点解释
- 先装插件:
cnpm install babel-plugin-component -D;-D等价于--save-dev,只在开发/打包时用,打包后不需要。 babel.config.js里加plugins: [["component", {...}]],作用是按需把import {Button} from 'element-ui'转成只引这一个组件及其对应样式,这就是按需引入能减小打包体积的关键。- 改完
babel.config.js必须重启 dev server,否则配置不生效。 Vue.use(Button)和Vue.component(Button.name, Button)效果一样;Button.name就是组件名(ElButton),所以模板里写el-button。Message不是标签组件,不能Vue.use(Message),必须Vue.prototype.$message = Message,否则this.$message是undefined。- 用哪个组件就引哪个,漏引就报“Unknown custom element”,比如只引了
Button却写了<el-row>,就会报错——这是笔记里演示过的坑。 - 按需引入后不再需要
import 'element-ui/lib/theme-chalk/index.css',样式由插件按需带入。
示例 3:完成注册(发送验证码 + 提交注册)
js
// src/api/user.js
import {sphRequest} from "@/request";
// 获取验证码 /api/user/passport/sendCode/{phone} get
export const getSendCode = phone => sphRequest(`/user/passport/sendCode/${phone}`);
// 注册用户 /api/user/passport/register post
export const postRegister = body => sphRequest.post(`/user/passport/register`, body);js
// src/store/user.js
import {getSendCode, postRegister} from "@/api/user";
const actions = {
// 发送验证码
async getSendCodeAsync(content, phone){
const result = await getSendCode(phone);
return result.data;
},
// 完成注册
async postRegisterAsync(content, body){
const result = await postRegister(body);
return result;
}
}
export default {
namespaced: true,
actions
}vue
<!-- src/pages/Register/index.vue -->
<template>
<!-- 注册内容 -->
<div class="register">
<h3>注册新用户
<span class="go">我有账号,去 <a href="login.html" target="_blank">登录</a>
</span>
</h3>
<div class="content">
<label>手机号:</label>
<input v-model="phone" type="text" placeholder="请输入你的手机号">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>验证码:</label>
<input v-model="code" type="text" placeholder="请输入验证码">
<button @click="sendCode" class="getcode">获取验证码</button>
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>登录密码:</label>
<input v-model="password" type="text" placeholder="请输入你的登录密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="content">
<label>确认密码:</label>
<input v-model="rePassword" type="text" placeholder="请输入确认密码">
<span class="error-msg">错误提示信息</span>
</div>
<div class="controls">
<input v-model="isAllow" name="m1" type="checkbox">
<span>同意协议并注册《尚品汇用户协议》</span>
<span class="error-msg">错误提示信息</span>
</div>
<div class="btn">
<button @click="goRegister">完成注册</button>
</div>
</div>
</template>
<script>
import {phoneReg} from "@/utils/reg";
export default {
name: "Register",
data(){
return {
phone:"",
code:"",
password:"",
rePassword:"",
isAllow:false
}
},
methods:{
async sendCode(){
// 判断手机号是否合法
if(phoneReg.test(this.phone)){
this.code = await this.$store.dispatch("user/getSendCodeAsync", this.phone);
}else {
this.$message.error("请输入正确的手机号!");
}
},
async goRegister(){
if(!phoneReg.test(this.phone)){
this.$message.error("请输入正确的手机号");
return;
}
if(this.code.length < 1){
this.$message.error("请输入验证码");
return;
}
if(this.password.length < 1){
this.$message.error("请输入密码");
return;
}
if(this.password !== this.rePassword){
this.$message.error("两次密码不一致!");
return;
}
if(!this.isAllow){
this.$message.error("您还未同意协议!");
return;
}
const {phone, password, code} = this;
const result = await this.$store.dispatch("user/postRegisterAsync", {
phone, password, code
});
if(result.code === 200){
console.log(result);
this.$router.push("/login");
}else{
this.$message.error(result.message);
}
}
}
}
</script>要点解释
- 三层分工固定套路:
api/user.js只负责发请求 →store/user.js用action包一层 → 组件dispatch拿数据。namespaced: true之后必须写全"user/getSendCodeAsync",少写user/会找不到 action。 getSendCode是 GET,直接把 phone 拼在 URL 上;postRegister是 POST,用sphRequest.post(url, body),两种写法别混。sendCode里this.code = await ...:笔记中后端返回的result.data就是验证码,所以直接把验证码回填到输入框,省去用户手输(真实发短信要花钱,这里是模拟)。async/await里dispatch前面必须await,不写的话this.code拿到的是 Promise,页面显示[object Promise]。- 提交前的一连串校验每一条都要
return,漏掉return后面的代码还会继续执行,出现“提示了错误却依然提交”的怪现象。 - 校验顺序建议:手机号 → 验证码 → 密码非空 → 两次密码一致 → 是否勾选协议,和笔记保持一致,方便对照提示语。
- 提交成功判断用
result.code === 200(严格相等),成功后this.$router.push("/login")跳登录;失败时把后端返回的result.message(如“手机号已被注册”)用$message.error弹出来。 phoneReg来自@/utils/reg(笔记中只引用了它,未给出正则具体内容),需要自己在该文件里导出手机号正则,否则phoneReg为undefined,.test会直接报错。
常见错误
- 点击按钮调用
this.$message报undefined→ 按需引入时只注册了Button/Row,没有引入并挂载Message→import { Message } from 'element-ui'后执行Vue.prototype.$message = Message。 - 页面出现
<el-row>不渲染或控制台提示未知组件 → 按需引入时模板用了el-row,入口未Vue.use(Row)或未Vue.component(Row.name, Row)→ 补齐对应组件注册后重启项目。 - 按需引入配置后仍全量加载、按钮无样式或构建报错 →
babel-plugin-component未安装/配置位置错误/改完babel.config.js未重启 → 安装-D依赖,按官方示例放入plugins,重启开发服务并检查样式是否仍全量引入。 - 发送验证码后
this.code是Promise、对象或undefined→dispatch/接口调用漏写await,或返回层级result.data与result.data.data混用 → 补await,打印接口完整返回后按实际层级取值。 - 注册提交时后端提示参数缺失或字段错误 → 提交对象字段名与接口不一致,如误传
userName、rePassword等多余字段 → 只提交接口要求的phone/password/code,提交前打印body核对。 - 未勾选协议仍能注册,或勾选状态异常 →
isAllow初始值写成true,或提交前未判断!this.isAllow→ 初始值设为false,提交前校验并提示“还未同意协议”。 - 发送验证码时手机号合法,但注册时手机号被改坏仍提交 → 只在发送验证码处校验,注册提交处未二次校验 → 在
goRegister中再次用phoneReg.test(this.phone)拦截。 - 注册成功后未跳转或跳到错误地址 → 判断层级不对、
result.code取值错误,或路由写成login.html→ 打印result确认code,成功时this.$router.push('/login')。
练习
- 把当前全量引入改为按需引入
Button、Row、Message,写出babel.config.js、入口注册代码和模板使用代码,并说明如何验证没有全量引入。 - 在注册页补全“发送验证码”流程:手机号校验、
await调用、错误提示、验证码回填,并说明如何确认返回数据的取值层级。 - 完成“完成注册”提交:列出提交前校验清单、最终提交字段、成功与非成功分支处理,以及跳转登录页的代码位置。
- 排查题:页面中
el-row不显示、按钮无样式、点击按钮$message报错同时出现,请给出排查顺序和每步修复动作。 - 优化题:对比全量引入与按需引入的打包结果,说明按需引入后还需要检查哪些配置和样式引入,避免“配置了但包没变小”。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 搜索页加入购物车 | 09:39 |
| 2 | element-ui的基本使用 | 14:18 |
| 3 | element-ui按需引入 | 13:18 |
| 4 | 完成注册的静态组件 | 02:29 |
| 5 | 发送验证码 | 16:44 |
| 6 | 完成注册 | 11:06 |
小结
- Element-UI 使用以官方文档为准,组件属性、事件和服务型组件的用法不要凭记忆猜。
- 全量引入接入快,但会把未使用组件带入项目;按需引入适合正式项目,但配置链路更长。
- 按需引入不是只改入口文件,必须同时处理 Babel 插件、组件注册、服务型组件挂载和样式。
Message这类服务型组件与普通组件注册方式不同,按需引入时最容易漏挂原型。- 注册功能应按 API 层、Store 层、组件层拆分,组件只负责收集数据、校验和提示。
- 前端校验要做两次:发送验证码前校验手机号,提交注册前再次校验手机号、验证码、密码和协议。
- 异步流程必须核对
await与返回层级,否则验证码回填、注册结果判断和路由跳转都会出错。 - 注册是否成功以接口返回
code为准,非成功分支用$message提示,成功后再跳转登录页。