Vue 消息通知中心:让你的应用会“说话”
各位观众老爷,大家好!我是今天的主讲人,江湖人称“代码老司机”。今天咱们来聊聊一个在 Vue 项目中非常实用的小玩意儿:消息通知中心。
想象一下,你的用户辛辛苦苦填完表单,提交后啥反应都没有,一脸懵逼,不知道是成功了还是失败了。这体验,你说糟不糟心?一个好的消息通知中心,就像应用的“嘴巴”,能及时地告诉用户发生了什么,让用户心里踏实。
今天我们就手把手地打造一个功能强大的消息通知中心,支持多种通知类型,自动关闭和手动关闭,让你的应用瞬间“活”起来。
需求分析:我们要做什么?
首先,我们要明确目标,一个优秀的消息通知中心应该具备以下特性:
- 多类型支持: 至少支持成功、失败、警告等常见类型,并能自定义样式。
- 自动关闭: 通知显示一段时间后自动消失,无需用户手动干预。
- 手动关闭: 用户可以点击按钮手动关闭通知。
- 队列管理: 多个通知出现时,按照顺序排队显示,避免信息拥堵。
- 全局可用: 在应用的任何地方都能方便地调用。
- 组件化: 方便复用和维护。
技术选型:用什么来“武装”?
- Vue.js: 这是毋庸置疑的,我们的主角。
- Vue Composition API: 让我们用更优雅的方式组织代码(当然,你也可以用 Options API)。
- CSS Transitions/Animations: 实现平滑的动画效果。
架构设计:如何搭建“舞台”?
我们将采用以下架构:
- Notification 组件: 负责显示单个通知消息。
- NotificationService (useNotification): 一个 Composition API hook,负责管理通知队列,添加、删除通知,并提供全局访问接口。
- App.vue (或其他根组件): 渲染 Notification 组件,并注入 NotificationService。
代码实现:撸起袖子就是干!
1. Notification 组件 (components/Notification.vue)
<template>
<div
class="notification"
:class="typeClass"
:style="{ top: topPosition + 'px' }"
@mouseenter="pauseTimer"
@mouseleave="resumeTimer"
>
<div class="notification-content">
{{ message }}
</div>
<button class="notification-close" @click="closeNotification">
X
</button>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
export default defineComponent({
props: {
id: {
type: Number,
required: true,
},
message: {
type: String,
required: true,
},
type: {
type: String,
default: 'info',
},
duration: {
type: Number,
default: 3000,
},
onClose: {
type: Function,
required: true,
},
topPosition: {
type: Number,
required: true,
}
},
setup(props) {
let timer = null;
const isPaused = ref(false);
const startTimer = () => {
timer = setTimeout(() => {
props.onClose(props.id);
}, props.duration);
};
const pauseTimer = () => {
isPaused.value = true;
clearTimeout(timer);
};
const resumeTimer = () => {
isPaused.value = false;
startTimer();
};
const closeNotification = () => {
clearTimeout(timer);
props.onClose(props.id);
};
onMounted(() => {
startTimer();
});
onUnmounted(() => {
clearTimeout(timer); //组件卸载时清除定时器,防止内存泄漏
});
return {
closeNotification,
typeClass: `notification--${props.type}`,
pauseTimer,
resumeTimer
};
},
});
</script>
<style scoped>
.notification {
position: fixed;
right: 20px;
background-color: #fff;
border: 1px solid #ccc;
padding: 10px 20px;
margin-bottom: 10px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
z-index: 9999; /* 确保在最上层 */
transition: all 0.3s ease;
}
.notification-content {
flex-grow: 1;
margin-right: 10px;
}
.notification-close {
background-color: transparent;
border: none;
cursor: pointer;
font-size: 16px;
}
.notification--success {
border-color: #28a745;
color: #28a745;
}
.notification--error {
border-color: #dc3545;
color: #dc3545;
}
.notification--warning {
border-color: #ffc107;
color: #ffc107;
}
.notification--info {
border-color: #17a2b8;
color: #17a2b8;
}
</style>
- Props: 接收
id
(唯一标识),message
(消息内容),type
(消息类型),duration
(自动关闭时间),onClose
(关闭回调函数),topPosition
(顶部位置)等参数。 typeClass
: 根据type
prop 生成不同的 CSS 类名,方便应用不同的样式。closeNotification
: 手动关闭通知的函数,调用父组件传递的onClose
回调函数。startTimer
,pauseTimer
,resumeTimer
: 实现自动关闭功能,并在鼠标悬停时暂停计时器。onMounted
,onUnmounted
: 组件挂载和卸载时启动和清理定时器,防止内存泄漏。
2. NotificationService (useNotification.js)
import { reactive, readonly, ref, computed } from 'vue';
let notificationId = 0; // 用于生成唯一ID
const state = reactive({
notifications: [],
});
const notificationHeight = 60; // 每个通知的高度,包括margin
const topOffset = 20; // 顶部偏移量
export function useNotification() {
const addNotification = (message, type = 'info', duration = 3000) => {
const id = notificationId++;
state.notifications.push({
id,
message,
type,
duration,
});
return id; //返回id方便后续操作
};
const removeNotification = (id) => {
state.notifications = state.notifications.filter(
(notification) => notification.id !== id
);
};
const success = (message, duration) => {
return addNotification(message, 'success', duration);
};
const error = (message, duration) => {
return addNotification(message, 'error', duration);
};
const warning = (message, duration) => {
return addNotification(message, 'warning', duration);
};
const info = (message, duration) => {
return addNotification(message, 'info', duration);
};
const notificationPositions = computed(() => {
const positions = {};
state.notifications.forEach((notification, index) => {
positions[notification.id] = topOffset + index * notificationHeight;
});
return positions;
});
return {
notifications: readonly(state.notifications),
addNotification,
removeNotification,
success,
error,
warning,
info,
notificationPositions
};
}
state.notifications
: 一个响应式数组,用于存储所有待显示的通知对象。addNotification
: 向队列中添加新的通知,生成唯一的id
。removeNotification
: 从队列中移除指定id
的通知。success
,error
,warning
,info
: 快捷方法,用于添加不同类型的通知。notificationPositions
: 计算属性,根据通知在队列中的位置,动态计算每个通知的top
值,避免重叠。使用computed
可以确保当notifications
数组发生变化时,positions
会自动更新。readonly(state.notifications)
: 将notifications
数组设置为只读,防止外部组件直接修改状态,确保数据的一致性。
3. App.vue (或其他根组件)
<template>
<div id="app">
<button @click="showSuccess">显示成功消息</button>
<button @click="showError">显示错误消息</button>
<button @click="showWarning">显示警告消息</button>
<button @click="showInfo">显示普通消息</button>
<Notification
v-for="notification in notifications"
:key="notification.id"
:id="notification.id"
:message="notification.message"
:type="notification.type"
:duration="notification.duration"
:onClose="removeNotification"
:topPosition="notificationPositions[notification.id]"
/>
</div>
</template>
<script>
import { defineComponent } from 'vue';
import Notification from './components/Notification.vue';
import { useNotification } from './useNotification';
export default defineComponent({
components: {
Notification,
},
setup() {
const {
notifications,
success,
error,
warning,
info,
removeNotification,
notificationPositions
} = useNotification();
const showSuccess = () => {
success('操作成功!');
};
const showError = () => {
error('发生错误!');
};
const showWarning = () => {
warning('请注意!');
};
const showInfo = () => {
info('这是一条普通消息。');
};
return {
notifications,
showSuccess,
showError,
showWarning,
showInfo,
removeNotification,
notificationPositions
};
},
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
button {
margin: 0 10px;
padding: 10px 20px;
border: 1px solid #ccc;
background-color: #fff;
cursor: pointer;
}
</style>
- 注入 NotificationService: 在
setup
函数中调用useNotification
,获取通知服务提供的各种方法和状态。 - 渲染 Notification 组件: 使用
v-for
遍历notifications
数组,动态渲染 Notification 组件,并将必要的数据传递给子组件。 showSuccess
等方法: 点击按钮时,调用相应的通知方法,触发通知的显示。
代码解释:知其然,更要知其所以然
- 为什么使用 Composition API? Composition API 能够更好地组织和复用逻辑代码,特别是对于像通知服务这种需要全局共享的状态和方法,使用 Composition API 可以更加清晰地划分职责,提高代码的可维护性。
- 为什么使用
reactive
和readonly
?reactive
用于创建响应式对象,当对象内部的属性发生变化时,Vue 会自动更新相关的视图。readonly
则用于创建只读对象,防止外部组件直接修改状态,确保数据的一致性。 - 为什么使用
computed
计算通知的位置? 使用computed
可以根据通知队列的长度动态计算每个通知的top
值,避免通知重叠。当notifications
数组发生变化时,computed
会自动更新,确保通知的位置始终正确。 - 为什么要在
Notification
组件的onUnmounted
钩子中清除定时器? 如果不清除定时器,当组件被销毁时,定时器仍然会继续运行,导致内存泄漏。
进阶:让通知中心更上一层楼
- 自定义样式: 通过 CSS Modules 或 scoped CSS,为不同类型的通知添加更丰富的样式,使其更符合应用的整体风格。
- 支持 HTML 内容: 允许通知消息中包含 HTML 代码,提供更灵活的内容展示方式。
- 全局配置: 提供全局配置选项,例如默认的自动关闭时间、通知位置等,方便统一管理。
- 过渡动画: 使用 Vue 的
Transition
组件,为通知的显示和隐藏添加平滑的过渡动画,提升用户体验。 - 持久化存储: 将通知消息存储到本地,即使刷新页面,也能恢复之前的通知状态。
总结:让你的应用“妙语连珠”
通过以上步骤,我们成功地构建了一个功能完善的 Vue 消息通知中心。它不仅能够显示各种类型的通知消息,还支持自动关闭和手动关闭,并且易于扩展和定制。
有了它,你的应用就能像一个健谈的朋友,随时向用户传递重要的信息,让用户体验更上一层楼。
最后,希望今天的讲座能对你有所帮助。记住,代码的世界是充满乐趣的,只要你肯动手实践,就能创造出无限可能。下次再见!