各位观众老爷们,大家好!今天咱们不开车,聊点正经的——Vue 通知中心的设计与实现。别怕,不是让你搞个 CRM 系统,咱就做一个简单好用的通知中心,让你的应用也能像微信一样,收到各种各样的消息,而且还能离线查看,是不是想想就有点小激动?
第一章:需求分析与技术选型
在撸起袖子写代码之前,咱们先来捋一捋需求,明确目标,才能避免写出屎山代码。
- 多种通知类型: 比如系统消息、用户消息、活动通知等等,不能千篇一律。
- 持久化存储: 刷新页面消息还在,不能一刷新就清空,用户会骂娘的。
- 离线通知: 即使没网,也能看到之前的消息,体现用户关怀。
- 自定义模板: 不同的通知类型,样式肯定不一样,得支持自定义。
有了需求,咱们再来看看用什么武器来打这场仗。
- Vue.js: 废话,都说了是 Vue 应用了。
- Vuex: 管理全局状态,存储通知消息,方便组件访问。
- LocalStorage/IndexedDB: 用于持久化存储,前者简单粗暴,后者性能更好,可以根据实际情况选择。
- Service Worker: 实现离线通知,让你的应用即使没网也能耍流氓。
- Vue Template Compiler: 用于编译自定义模板,让你的通知消息更加个性化。
第二章:数据结构设计
好的数据结构是成功的一半。咱们来定义一下通知消息的数据结构。
const notification = {
id: 'unique_id', // 唯一 ID,用于区分不同的消息
type: 'system | user | activity', // 通知类型
title: '通知标题',
content: '通知内容',
timestamp: 1678886400000, // 时间戳,方便排序
read: false, // 是否已读
data: { // 附加数据,用于自定义模板
username: '张三',
productName: 'iPhone 14 Pro Max'
}
}
用表格来更清晰地展示:
字段 | 类型 | 描述 |
---|---|---|
id | String | 唯一 ID,可以使用 UUID 生成 |
type | String | 通知类型,例如 ‘system’, ‘user’, ‘activity’ |
title | String | 通知标题 |
content | String | 通知内容 |
timestamp | Number | 时间戳,毫秒数 |
read | Boolean | 是否已读 |
data | Object | 附加数据,用于自定义模板 |
第三章:Vuex 状态管理
Vuex 是咱们的弹药库,用来存储和管理通知消息。
// store/modules/notification.js
import Vue from 'vue'
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) {
Vue.set(notification, 'read', true) // 使用 Vue.set 确保响应式更新
}
},
REMOVE_NOTIFICATION (state, id) {
state.notifications = state.notifications.filter(n => n.id !== id)
},
SET_NOTIFICATIONS (state, notifications) {
state.notifications = notifications
}
}
const actions = {
addNotification ({ commit }, notification) {
commit('ADD_NOTIFICATION', notification)
},
markAsRead ({ commit }, id) {
commit('MARK_AS_READ', id)
},
removeNotification ({ commit }, id) {
commit('REMOVE_NOTIFICATION', id)
},
loadNotifications ({ commit }) {
// 从 LocalStorage/IndexedDB 加载通知消息
const notifications = JSON.parse(localStorage.getItem('notifications') || '[]') // 这里只是示例,实际情况需要处理加载失败的情况
commit('SET_NOTIFICATIONS', notifications)
},
saveNotifications ({ state }) {
// 将通知消息保存到 LocalStorage/IndexedDB
localStorage.setItem('notifications', JSON.stringify(state.notifications)) // 这里只是示例,实际情况需要处理保存失败的情况
}
}
const getters = {
unreadNotificationsCount: state => state.notifications.filter(n => !n.read).length,
allNotifications: state => state.notifications
}
export default {
namespaced: true, // 开启命名空间,避免命名冲突
state,
mutations,
actions,
getters
}
别忘了在你的 store/index.js
中引入这个 module:
import Vue from 'vue'
import Vuex from 'vuex'
import notification from './modules/notification'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
notification
}
})
第四章:持久化存储
持久化存储是让消息“永垂不朽”的关键。这里咱们先用 LocalStorage 简单实现一下。
- 在
actions
中添加loadNotifications
和saveNotifications
方法:
// store/modules/notification.js (接上面的代码)
const actions = {
addNotification ({ commit, dispatch }, notification) {
commit('ADD_NOTIFICATION', notification)
dispatch('saveNotifications') // 添加消息后保存
},
markAsRead ({ commit, dispatch }, id) {
commit('MARK_AS_READ', id)
dispatch('saveNotifications') // 标记已读后保存
},
removeNotification ({ commit, dispatch }, id) {
commit('REMOVE_NOTIFICATION', id)
dispatch('saveNotifications') // 移除消息后保存
},
loadNotifications ({ commit }) {
// 从 LocalStorage 加载通知消息
try {
const notifications = JSON.parse(localStorage.getItem('notifications') || '[]')
commit('SET_NOTIFICATIONS', notifications)
} catch (error) {
console.error('Failed to load notifications from localStorage:', error)
// 处理加载失败的情况,例如清空 localStorage 或显示错误信息
localStorage.removeItem('notifications')
commit('SET_NOTIFICATIONS', [])
}
},
saveNotifications ({ state }) {
// 将通知消息保存到 LocalStorage
try {
localStorage.setItem('notifications', JSON.stringify(state.notifications))
} catch (error) {
console.error('Failed to save notifications to localStorage:', error)
// 处理保存失败的情况,例如显示错误信息
}
}
}
- 在应用初始化时加载通知消息:
在你的 App.vue
或其他入口文件中,在 mounted
钩子函数中调用 loadNotifications
action:
// App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
mounted () {
this.$store.dispatch('notification/loadNotifications') // 加载通知消息
}
}
</script>
第五章:离线通知(Service Worker)
Service Worker 是实现离线功能的利器。但是配置起来稍微有点麻烦,咱们一步一步来。
- 创建
service-worker.js
文件:
在你的项目根目录下创建一个 service-worker.js
文件。
// service-worker.js
const CACHE_NAME = 'notification-cache-v1'
const urlsToCache = [
'/',
'/index.html',
'/static/js/app.js', // 替换成你实际的 app.js 文件路径
'/static/css/app.css' // 替换成你实际的 app.css 文件路径
]
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache')
return cache.addAll(urlsToCache)
})
)
})
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(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(
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 because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
const responseToCache = response.clone()
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache)
})
return response
}
)
})
)
})
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME]
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName)
}
})
)
})
)
})
self.addEventListener('message', event => {
if (event.data.type === 'NEW_NOTIFICATION') {
const notification = event.data.payload
self.registration.showNotification(notification.title, {
body: notification.content,
icon: '/icon.png' // 替换成你的通知图标
})
}
})
- 注册 Service Worker:
在你的 main.js
或其他入口文件中注册 Service Worker。
// main.js
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)
})
}
- 发送离线通知:
当应用在离线状态下收到新的通知时,可以通过 Service Worker 发送系统通知。
// 在你的 Vue 组件中
methods: {
sendOfflineNotification (notification) {
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'NEW_NOTIFICATION',
payload: notification
})
} else {
console.warn('Service Worker is not available.')
}
}
}
注意:
- 你需要使用 HTTPS 协议才能使用 Service Worker。
- 需要在浏览器中启用 Service Worker。
- 缓存的资源需要根据实际情况进行调整。
- Service Worker 的更新可能需要一些时间,可以手动清除缓存或强制更新。
第六章:自定义模板
自定义模板是让你的通知消息更加个性化的关键。
- 使用 Vue Template Compiler:
Vue Template Compiler 可以将字符串模板编译成渲染函数。
import { compile } from 'vue-template-compiler'
function renderTemplate (template, data) {
const { render } = compile(template)
const vm = new Vue({
data: data,
render: render
})
return vm.$mount().$el.outerHTML
}
// 使用示例
const template = '<div>Hello, {{ username }}! You have a new {{ productName }}!</div>'
const data = {
username: '李四',
productName: 'iPad Pro'
}
const renderedHtml = renderTemplate(template, data)
console.log(renderedHtml) // 输出:<div>Hello, 李四! You have a new iPad Pro!</div>
- 在 Vue 组件中使用:
<template>
<div v-html="renderedContent"></div>
</template>
<script>
import { compile } from 'vue-template-compiler'
export default {
props: {
notification: {
type: Object,
required: true
}
},
computed: {
renderedContent () {
const template = this.getTemplateByType(this.notification.type)
return this.renderTemplate(template, this.notification.data)
}
},
methods: {
getTemplateByType (type) {
// 根据通知类型返回不同的模板
switch (type) {
case 'system':
return '<div>System Message: {{ message }}</div>'
case 'user':
return '<div>Hello, {{ username }}!</div>'
case 'activity':
return '<div>New Activity: {{ activityName }}</div>'
default:
return '<div>{{ content }}</div>'
}
},
renderTemplate (template, data) {
const { render } = compile(template)
const vm = new Vue({
data: data,
render: render
})
return vm.$mount().$el.outerHTML
}
}
}
</script>
第七章:总结与展望
到这里,一个简单的 Vue 通知中心就完成了。当然,这只是一个基础版本,还有很多可以改进的地方。
- 更完善的持久化存储: 可以使用 IndexedDB 替代 LocalStorage,提供更好的性能和更大的存储空间。
- 更强大的 Service Worker: 可以实现更复杂的离线功能,例如预缓存资源、后台数据同步等。
- 更灵活的自定义模板: 可以使用更强大的模板引擎,例如 Handlebars 或 Mustache,支持更复杂的模板语法。
- 更友好的用户界面: 可以设计更美观、更易用的用户界面,提升用户体验。
希望今天的分享对你有所帮助。记住,代码只是工具,重要的是思考和解决问题的能力。下次再见!