前端实战:Vue3 + Element Plus 全局 Message、Notification 封装教程,从概念区分、场景选择到统一错误处理、代码落地,一站式学会前端提示框封装,告别混乱代码与重复开发。
📑 文章目录
- 一、我们为什么要封装?
- [二、概念扫盲:Message / Notification / Toast 有啥区别?](#二、概念扫盲:Message / Notification / Toast 有啥区别? "#part2")
- 三、典型使用场景
- 四、封装思路:三层结构
- 五、统一风格:主题、样式、交互
- [5.1 风格统一要管什么?](#5.1 风格统一要管什么? "#part5_1")
- [5.2 示例:统一配置](#5.2 示例:统一配置 "#part5_2")
- 六、统一错误处理:拦截、提示、降级
- [6.1 核心思路](#6.1 核心思路 "#part6_1")
- [6.2 错误码与文案映射示例](#6.2 错误码与文案映射示例 "#part6_2")
- [6.3 在 axios 里用](#6.3 在 axios 里用 "#part6_3")
- [七、完整封装示例(Vue 3 + Element Plus)](#七、完整封装示例(Vue 3 + Element Plus) "#part7")
- [7.1 封装文件结构](#7.1 封装文件结构 "#part7_1")
- [7.2 封装实现](#7.2 封装实现 "#part7_2")
- [7.3 业务里怎么用](#7.3 业务里怎么用 "#part7_3")
- [7.4 全局挂载(可选)](#7.4 全局挂载(可选) "#part7_4")
- 八、常见坑点与排查思路
- [8.1 同一个提示狂弹](#8.1 同一个提示狂弹 "#part8_1")
- [8.2 样式跟项目不一致](#8.2 样式跟项目不一致 "#part8_2")
- [8.3 错误提示内容太"技术"](#8.3 错误提示内容太“技术” "#part8_3")
- [8.4 封装后换 UI 库很痛苦](#8.4 封装后换 UI 库很痛苦 "#part8_4")
- [8.5 在 setup 里没有 this](#8.5 在 setup 里没有 this "#part8_5")
- 九、实战规范总结
- 十、小结
同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~
(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)
你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?
你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?
就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。
一天只有24小时,时间永远不够用,常常感到力不从心。
技术行业,本就是逆水行舟,不进则退。
如果你也有同样的困扰,别慌。
从现在开始,跟着我一起心态归零 ,利用碎片时间,来一次彻彻底底的基础扫盲。
这一次,我们一起慢慢来,扎扎实实变强。
不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,
咱们一起稳步积累,真正摆脱"面向搜索引擎写代码"的尴尬。
一、我们为什么要封装?
很多同学会直接这样写:
javascript
// 散落在业务里的各种提示
this.$message.success('保存成功')
ElMessage.error('网络错误')
alert('操作失败') // 甚至还有人用 alert
看起来能用,但会带来这些问题:
- 提示风格不统一:有的用
Message,有的用Notification,有的用alert - 错误处理分散:每个接口各自
try-catch各自message - 难以维护:改文案、改样式、加埋点,要改很多地方
- 用户体验差:错误提示不统一,成功/失败没规范
所以需要:把通知和消息系统统一封装,集中管理风格和错误处理。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
二、概念扫盲:Message / Notification / Toast 有啥区别?
| 类型 | 特点 | 典型场景 |
|---|---|---|
| Message | 轻量、短暂、通常居中或顶部,自动消失 | 操作结果反馈:保存成功、删除成功 |
| Notification | 带标题、正文,可带操作按钮,位置可配置 | 系统通知、任务完成、重要提示 |
| Toast | 和 Message 概念接近,有些库叫 Toast | 同上,多用于移动端 |
可以简单记:Message 偏轻量,Notification 偏正式、信息更多。封装时建议:
- 简单反馈 → Message
- 需要标题、描述、操作 → Notification
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
三、典型使用场景
- 接口成功/失败:统一用 Message,成功/警告/错误三种类型
- 表单校验失败:一般用 Message,文案来自校验规则
- 全局错误:如 401、403、500 → 统一错误处理 + Message/Notification
- 长时间任务完成:如导出、报表生成 → 用 Notification 更合适
- 业务重要事件:如订单状态变更 → Notification + 操作入口
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
四、封装思路:三层结构
scss
┌─────────────────────────────────────┐
│ 业务层:直接调用 msg.success() 等
├─────────────────────────────────────┤
│ 封装层:msg / notify 统一入口
│ - 统一风格
│ - 统一文案模板
│ - 统一埋点/日志
├─────────────────────────────────────┤
│ 底层:Element Plus / Ant Design 等
└─────────────────────────────────────┘
业务层只调用封装好的 API,不直接接触 UI 库。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
五、统一风格:主题、样式、交互
5.1 风格统一要管什么?
- 类型:success / warning / error / info
- 位置:如 Message 顶部居中,Notification 右上角
- 持续时间:成功 2s,错误 4s 等
- 样式:颜色、圆角、阴影等
- 防重复:相同文案不重复弹
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
5.2 示例:统一配置
javascript
// src/utils/message.config.js
/**
* Message 统一配置
* 所有地方用 Message 时都走这套配置,保证风格一致
*/
export const MESSAGE_CONFIG = {
duration: 2000, // 默认 2 秒消失
showClose: false, // 不显示关闭按钮,靠自动消失
center: true, // 水平居中
offset: 80, // 距离顶部的距离
grouping: true, // 相同内容合并显示,避免刷屏
}
/**
* 不同类型建议的 duration
* 成功可以短一点,错误要留足阅读时间
*/
export const DURATION_BY_TYPE = {
success: 2000,
warning: 3000,
error: 4000,
info: 2500,
}
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
六、统一错误处理:拦截、提示、降级
6.1 核心思路
- HTTP 拦截器:统一捕获 401、403、500 等
- 业务错误码映射:后端错误码 → 前端文案
- 兜底:网络异常、超时等给出通用提示
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
6.2 错误码与文案映射示例
javascript
// src/utils/errorCodeMap.js
/**
* 后端错误码 → 前端展示文案
* 避免把后端原始错误直接抛给用户
*/
export const ERROR_CODE_MAP = {
401: '登录已过期,请重新登录',
403: '没有权限执行此操作',
404: '请求的资源不存在',
500: '服务器异常,请稍后重试',
10001: '参数错误',
10002: '数据已存在',
// ... 按你们项目补充
}
/**
* 根据错误码获取友好提示
*/
export function getErrorMessage(code, defaultMsg = '操作失败,请稍后重试') {
return ERROR_CODE_MAP[code] || defaultMsg
}
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
6.3 在 axios 里用
javascript
// src/api/request.js 示意
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { getErrorMessage } from '@/utils/errorCodeMap'
const request = axios.create({
baseURL: '/api',
timeout: 10000,
})
// 响应拦截器:统一错误处理
request.interceptors.response.use(
(response) => {
const { code, data, message } = response.data
// 假设业务成功是 code === 0
if (code !== 0) {
ElMessage.error(getErrorMessage(code, message))
return Promise.reject(new Error(message))
}
return data
},
(error) => {
if (error.response) {
const { status } = error.response
const msg = getErrorMessage(status)
ElMessage.error(msg)
// 401 可以在这里跳转登录
if (status === 401) {
// router.push('/login')
}
} else {
ElMessage.error('网络异常,请检查网络后重试')
}
return Promise.reject(error)
}
)
export default request
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
七、完整封装示例(Vue 3 + Element Plus)
7.1 封装文件结构
bash
src/
├── utils/
│ ├── message.config.js # 配置
│ ├── errorCodeMap.js # 错误码映射
│ └── message.js # 封装入口
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
7.2 封装实现
javascript
// src/utils/message.js
import { ElMessage, ElNotification } from 'element-plus'
import { MESSAGE_CONFIG, DURATION_BY_TYPE } from './message.config'
import { getErrorMessage } from './errorCodeMap'
/**
* 全局 Message 封装
* 统一风格、统一入口,方便以后替换 UI 库或加埋点
*/
function createMessage(type) {
return (content, duration) => {
ElMessage({
...MESSAGE_CONFIG,
type,
message: typeof content === 'string' ? content : content?.message || '操作成功',
duration: duration ?? DURATION_BY_TYPE[type] ?? MESSAGE_CONFIG.duration,
})
}
}
// 对外暴露的 API
export const msg = {
success: createMessage('success'),
warning: createMessage('warning'),
error: createMessage('error'),
info: createMessage('info'),
}
/**
* 全局 Notification 封装
* 适合需要标题、描述、操作按钮的场景
*/
export const notify = {
success(title, message, options = {}) {
ElNotification({
type: 'success',
title: title || '成功',
message: message || '',
duration: 4000,
position: 'top-right',
...options,
})
},
error(title, message, options = {}) {
ElNotification({
type: 'error',
title: title || '错误',
message: message || '',
duration: 5000,
position: 'top-right',
...options,
})
},
// warning、info 同理...
}
/**
* 统一错误提示入口
* 支持:错误码、Error 对象、字符串
*/
export function showError(error) {
let message = '操作失败,请稍后重试'
if (typeof error === 'number') {
message = getErrorMessage(error)
} else if (error?.message) {
message = error.message
} else if (typeof error === 'string') {
message = error
}
msg.error(message)
}
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
7.3 业务里怎么用
javascript
// 业务组件里
import { msg, notify, showError } from '@/utils/message'
// 简单成功反馈
msg.success('保存成功')
// 接口失败时(如果拦截器没处理,可以手动调)
try {
await saveData()
msg.success('保存成功')
} catch (e) {
showError(e)
}
// 重要通知
notify.success('导出完成', '您的报表已生成,请到下载中心查看')
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
7.4 全局挂载(可选)
javascript
// main.js
import { msg, notify, showError } from '@/utils/message'
app.config.globalProperties.$msg = msg
app.config.globalProperties.$notify = notify
app.config.globalProperties.$showError = showError
// 组件内:this.$msg.success('保存成功')
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
八、常见坑点与排查思路
8.1 同一个提示狂弹
- 原因:接口失败在循环/频繁请求里被多次触发。
- 做法 :开启
grouping,或在封装层做「相同文案节流」。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
8.2 样式跟项目不一致
- 原因:直接用了 UI 库默认主题,或部分地方用内联样式覆盖。
- 做法:所有 Message/Notification 都走封装层,在封装里统一传入配置,必要时用 CSS 变量或主题覆盖。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
8.3 错误提示内容太"技术"
- 原因 :直接把后端
message或Error文本展示给用户。 - 做法:用错误码映射表,把技术信息转成用户可读文案。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
8.4 封装后换 UI 库很痛苦
- 原因 :业务里到处直接调用
ElMessage、ElNotification。 - 做法 :业务只依赖
msg、notify,底层实现集中在message.js,换库只改这一层。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
8.5 在 setup 里没有 this
- 做法 :用
import { msg } from '@/utils/message'直接引入,不依赖this.$msg。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
九、实战规范总结
| 规范 | 说明 |
|---|---|
| 统一入口 | 只用 msg / notify,不直接调用 UI 库 |
| 统一风格 | 通过 message.config.js 统一 duration、位置、样式 |
| 统一错误处理 | 用错误码映射 + axios 拦截器,业务少写 try-catch |
| 类型区分 | 简单反馈用 Message,复杂通知用 Notification |
| 文案友好 | 错误码转成用户能看懂的话,不暴露技术细节 |
| 可扩展 | 封装层预留埋点、日志、国际化等扩展点 |
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
十、小结
封装全局 Message / Notification 的核心是:
- 统一入口 :所有提示都从
msg/notify走。 - 统一风格:配置集中管理,避免到处写死。
- 统一错误处理:拦截器 + 错误码映射,减少重复代码。
- 把用户当小白:错误文案要易懂,不吓人。
[⬆ 返回目录](#⬆ 返回目录 "#catalogue")
学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。
后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。
关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。
如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。
我是 Eugene,你的电子学友,我们下一篇干货见~