各位观众老爷,晚上好!今天咱们就来聊聊怎么用 Vue.js 撸一个功能强大的通知中心,保证让你的应用立马高大上起来!
一、通知中心的需求分析
在撸代码之前,咱们先得搞清楚需求。一个优秀的通知中心,至少得满足以下几个要求:
- 多种通知类型: 比如系统消息、用户互动、订单更新等等。
- 持久化存储: 刷新页面后,通知还在,用户不会错过重要信息。
- 离线通知: 用户离线时也能收到通知,下次上线时可以查看。
- 自定义模板: 可以根据不同的通知类型,定制不同的显示样式。
- 操作性: 可以标记已读、删除通知等。
二、技术选型
- Vue.js: 前端框架,负责 UI 展示和交互。
- Vuex: 状态管理工具,管理通知数据。
- localStorage/IndexedDB: 浏览器本地存储,用于持久化存储通知数据。
- Service Worker (可选): 实现离线通知。
三、项目结构搭建
咱们先搭个简单的项目框架:
notify-center/
├── src/
│ ├── components/
│ │ └── NotificationItem.vue // 单个通知组件
│ ├── store/
│ │ ├── index.js // Vuex 根模块
│ │ ├── modules/
│ │ │ └── notifications.js // 通知相关的状态管理
│ ├── App.vue
│ └── main.js
├── public/
│ └── index.html
├── package.json
└── vue.config.js
四、Vuex 状态管理
咱们把通知数据放到 Vuex 里管理,这样方便组件之间共享和修改。
src/store/modules/notifications.js
const state = {
notifications: []
};
const mutations = {
ADD_NOTIFICATION(state, notification) {
state.notifications.unshift(notification); // 新消息放最前面
},
MARK_AS_READ(state, id) {
const notification = state.notifications.find(n => n.id === id);
if (notification) {
notification.read = true;
}
},
DELETE_NOTIFICATION(state, id) {
state.notifications = state.notifications.filter(n => n.id !== id);
},
SET_NOTIFICATIONS(state, notifications) {
state.notifications = notifications;
}
};
const actions = {
addNotification({ commit }, notification) {
notification.id = Date.now(); // 简单生成唯一 ID
notification.read = false;
commit('ADD_NOTIFICATION', notification);
localStorage.setItem('notifications', JSON.stringify(state.notifications)); // 持久化存储
},
markAsRead({ commit }, id) {
commit('MARK_AS_READ', id);
localStorage.setItem('notifications', JSON.stringify(state.notifications));
},
deleteNotification({ commit }, id) {
commit('DELETE_NOTIFICATION', id);
localStorage.setItem('notifications', JSON.stringify(state.notifications));
},
loadNotifications({ commit }) {
const notifications = JSON.parse(localStorage.getItem('notifications') || '[]');
commit('SET_NOTIFICATIONS', notifications);
}
};
const getters = {
unreadCount: state => state.notifications.filter(n => !n.read).length,
allNotifications: state => state.notifications
};
export default {
namespaced: true,
state,
mutations,
actions,
getters
};
src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import notifications from './modules/notifications';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
notifications
}
});
五、通知组件 (NotificationItem.vue
)
这个组件负责展示单个通知。
<template>
<div class="notification-item" :class="{ 'read': notification.read }">
<div class="notification-content">
{{ notification.message }}
<div v-if="notification.type === 'order'">
订单号: {{ notification.orderId }}
</div>
</div>
<div class="notification-actions">
<button @click="markAsRead(notification.id)" v-if="!notification.read">
标记为已读
</button>
<button @click="deleteNotification(notification.id)">删除</button>
</div>
</div>
</template>
<script>
export default {
props: {
notification: {
type: Object,
required: true
}
},
methods: {
markAsRead(id) {
this.$store.dispatch('notifications/markAsRead', id);
},
deleteNotification(id) {
this.$store.dispatch('notifications/deleteNotification', id);
}
}
};
</script>
<style scoped>
.notification-item {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-item.read {
background-color: #f0f0f0;
}
</style>
六、主组件 (App.vue
)
主组件负责展示通知列表,并提供添加通知的功能。
<template>
<div id="app">
<h1>通知中心</h1>
<p>未读消息数: {{ unreadCount }}</p>
<input type="text" v-model="newMessage" placeholder="输入消息内容">
<select v-model="notificationType">
<option value="system">系统消息</option>
<option value="user">用户互动</option>
<option value="order">订单更新</option>
</select>
<input type="text" v-if="notificationType === 'order'" v-model="orderId" placeholder="订单号">
<button @click="addNotification">添加通知</button>
<div v-if="allNotifications.length === 0">
暂无通知
</div>
<div v-else>
<NotificationItem
v-for="notification in allNotifications"
:key="notification.id"
:notification="notification"
/>
</div>
</div>
</template>
<script>
import NotificationItem from './components/NotificationItem.vue';
export default {
components: {
NotificationItem
},
data() {
return {
newMessage: '',
notificationType: 'system',
orderId: ''
};
},
computed: {
unreadCount() {
return this.$store.getters['notifications/unreadCount'];
},
allNotifications() {
return this.$store.getters['notifications/allNotifications'];
}
},
methods: {
addNotification() {
const notification = {
message: this.newMessage,
type: this.notificationType,
orderId: this.orderId
};
this.$store.dispatch('notifications/addNotification', notification);
this.newMessage = '';
this.orderId = '';
}
},
mounted() {
this.$store.dispatch('notifications/loadNotifications');
}
};
</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;
}
</style>
七、main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
八、代码讲解
- Vuex: 使用 Vuex 管理通知数据,方便组件之间共享和修改。
notifications.js
模块定义了state
(通知数据),mutations
(修改 state 的方法),actions
(提交 mutations 的方法) 和getters
(获取 state 的计算属性)。 - localStorage:
actions
中的addNotification
、markAsRead
和deleteNotification
方法,在修改了 Vuex 的 state 之后,都会将state.notifications
序列化成 JSON 字符串,然后存储到 localStorage 中。actions
中的loadNotifications
方法,会在应用加载时,从 localStorage 中读取通知数据,然后更新 Vuex 的 state。 - 通知组件 (
NotificationItem.vue
): 接收一个notification
对象作为 prop,然后根据notification
的属性,展示不同的内容。 - 主组件 (
App.vue
): 展示通知列表,并提供添加通知的功能。使用v-if
指令,根据notificationType
的值,展示不同的输入框。使用this.$store.dispatch
方法,调用 Vuex 的 actions,添加、标记已读、删除通知。使用this.$store.getters
方法,获取 Vuex 的 getters,展示未读消息数和所有通知。
九、进阶功能
-
IndexedDB 替代 localStorage: localStorage 的存储空间有限,而且是同步 API,可能会阻塞 UI 线程。IndexedDB 存储空间更大,而且是异步 API,更适合存储大量的通知数据。
// 简单示例,需要引入 indexedDB 封装库,比如 localForage import localForage from 'localforage'; const actions = { async addNotification({ commit }, notification) { notification.id = Date.now(); notification.read = false; commit('ADD_NOTIFICATION', notification); await localForage.setItem('notifications', state.notifications); }, async loadNotifications({ commit }) { const notifications = await localForage.getItem('notifications') || []; commit('SET_NOTIFICATIONS', notifications); } };
-
Service Worker 实现离线通知: Service Worker 是一个在浏览器后台运行的脚本,可以拦截网络请求,并提供离线缓存功能。可以用它来接收推送通知,并在用户离线时,将通知存储到 IndexedDB 中。当用户上线时,再将通知展示出来。
-
注册 Service Worker: 在
main.js
中注册 Service Worker。if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') .then(registration => { console.log('Service Worker registered with scope:', registration.scope); }) .catch(error => { console.error('Service Worker registration failed:', error); }); }
-
创建
public/service-worker.js
: 这个文件包含 Service Worker 的逻辑。self.addEventListener('push', function(event) { const notificationData = event.data.json(); const title = notificationData.title || '新消息'; const options = { body: notificationData.body || '您有一条新消息', icon: notificationData.icon || '/icon.png' // optional }; event.waitUntil(self.registration.showNotification(title, options)); }); self.addEventListener('notificationclick', function(event) { event.notification.close(); // Open the app or navigate to a specific URL event.waitUntil( clients.openWindow('/') // Replace with your app's URL ); }); // 离线缓存策略 (简单示例) const CACHE_NAME = 'my-site-cache-v1'; const urlsToCache = [ '/', '/css/app.css', '/js/app.js', '/icon.png' // Optional ]; self.addEventListener('install', function(event) { // Perform install steps event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); }) ); }); self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response if (response) { return response; } // IMPORTANT: Clone the request. A request is a stream and // can only be consumed once. Since we are consuming this // once by cache and once by the browser for fetch, we need // to clone the response. const fetchRequest = event.request.clone(); return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; } // IMPORTANT: Clone the response. A response is a stream // and can only be consumed once. Since we are consuming this // once by cache and once by the browser for fetch, we need // to clone the response. const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); }); return response; } ); }) ); });
-
后端推送通知: 需要一个后端服务,使用 Web Push 协议,向 Service Worker 推送通知。
-
-
自定义模板: 可以使用 Vue 的
component
标签,根据notification.type
动态渲染不同的组件。// App.vue <template> <div> <component :is="getNotificationComponent(notification.type)" :notification="notification" /> </div> </template> <script> import SystemNotification from './components/SystemNotification.vue'; import OrderNotification from './components/OrderNotification.vue'; export default { components: { SystemNotification, OrderNotification }, methods: { getNotificationComponent(type) { switch (type) { case 'system': return 'SystemNotification'; case 'order': return 'OrderNotification'; default: return 'NotificationItem'; // 默认组件 } } } }; </script>
-
更完善的 ID 生成策略:
Date.now()
生成的 ID 在高并发情况下可能会重复,可以使用 UUID 或者更复杂的算法生成唯一 ID。
十、总结
咱们今天撸了一个功能比较完善的 Vue 通知中心,包括了多种通知类型、持久化存储、以及自定义模板。还简单介绍了如何使用 Service Worker 实现离线通知,以及使用 IndexedDB 存储大量通知数据。当然,这只是一个基础版本,还有很多可以优化和扩展的地方,比如:
- 更好的 UI 交互: 添加动画效果,优化通知的显示和隐藏。
- 更完善的错误处理: 处理 localStorage/IndexedDB 读写错误。
- 后端集成: 与后端服务集成,实现实时通知。
希望今天的讲座能对你有所帮助! 各位,下课!