前言
本文主要讲述 Vue 插件的主要应用场景以及如何开发一个 Vue2 版本的插件,并且举例一些在项目中的实际应用。
一、应用场景
插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
二、开发插件
- 定义一个插件
一个插件可以是一个拥有 install() 方法的对象
,也可以直接是一个安装函数本身
。插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
- 注册一个插件
通过全局方法 Vue.use() 注册插件,需要在调用 new Vue() 启动应用之前注册,它会自动阻止多次注册相同插件,调用时也可传入一个可选的选项对象
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
// 或者传入选项对象
Vue.use(MyPlugin, { someOption: true })
new Vue({
// ...组件选项
})
三、在项目中应用
文中涉及的工具函数请参考:https://blog.csdn.net/qq_42966167/article/details/136479222
1.添加全局指令/过滤器
plugins/directive/install.js
import { toThousands } from '@/util';
let directives = {}
directives.color = (el, binding) => {
el.style.color = binding.value
}
directives.width = (el, binding) => {
el.style.width = Number.isFinite(binding.value) ? binding.value + 'px' : binding.value
}
directives.height = (el, binding) => {
el.style.height = Number.isFinite(binding.value) ? binding.value + 'px' : binding.value
}
directives.toThousands = (el, binding) => {
el.innerHTML = toThousands(binding.value, binding.arg);
}
export default {
install: Vue => {
for (let k of Object.keys(directives)) {
Vue.directive(k, directives[k])
}
}
}
plugins/filter/install.js
import { toThousands, thousandToNum, formatDate } from '@/util'
let filters = {}
// 千分位显示
filters.toThousands = (value, precision = 2) => {
return toThousands(value, precision);
}
// 千分位转数字
filters.thousandToNum = value => {
return thousandToNum(value);
}
// Date 转指定格式的时间字符串
filters.formatDate = (date, format) => {
return formatDate(date, format);
}
export default {
install: Vue => {
for (let k of Object.keys(filters)) {
Vue.filter(k, filters[k])
}
// 挂载到原型上
Vue.prototype.$filter = filters
}
}
2. 组件封装成插件
项目中通常都会使用到 Loading 加载组件和 Message 消息提示框组件等,可将此类组件封装成插件,挂在到 Vue.prototype 上作为实例方法进行调用。此项目中使用 Element-UI 组件进行二次封装。
plugins/loading/install.js
import { Loading } from 'element-ui'
let loadingInstance = null
const loading = {
show: (params = {}) => {
let opts = Object.assign({
fullscreen: true,
background: 'rgba(255,255,255,0.5)'
}, params)
loadingInstance = Loading.service(opts)
},
close: () => {
if (loadingInstance) {
loadingInstance.close()
}
}
}
export default {
install: Vue => {
Vue.prototype.$loading = loading
}
}
plugins/message/install.js
import { Message } from 'element-ui'
const closeMessage = () => {
try {
Message.closeAll()
} catch (err) {
console.warn('Too Frequent!')
}
}
/**
* @param msg 消息提示的内容
* @param args 消息的可配置项
*/
const message = {
info: (msg = '', args = {}) => {
let config = { message: msg, duration: 0, showClose: true, ...args }
closeMessage()
Message.info.apply(Message.info, [config])
return true
},
// 成功的提示信息设置默认2s后关闭
success: (msg = '', args = {}) => {
let config = { message: msg, duration: 2000, ...args }
closeMessage()
Message.success.apply(Message.success, [config])
return true
},
warning: (msg = '', args = {}) => {
let config = { message: msg, duration: 0, showClose: true, ...args }
closeMessage()
Message.warning.apply(Message.warning, [config])
return false
},
error: (msg = '', args = {}) => {
let config = { message: msg, duration: 0, showClose: true, ...args }
closeMessage()
Message.error.apply(Message.error, [config])
return false
}
}
export default {
install: Vue => {
Vue.prototype.$message = message
}
}
3. 封装插件简化繁琐重复事项
3.1 敏感信息脱敏显示
在项目中需要对一些用户的敏感信息进行加密传输,并且解密之后需要根据规则对这些重要信息进行脱敏显示,要求如下:
1)身份证号码:保留前3位和后4位
如441*********4613
2)手机号码:保留前3位和后2位
如136******73
固定电话:区号不隐藏,7-8位电话号码保留最后3位
如0756-****888
3)银行卡账号:保留前3位和后4位
如621*******5213
4)E-MAIL:保留前2位和@后缀
如li****@ygsoft.com
5)住址:中间数字脱敏
如珠海市**号楼101
6)出生日期:隐藏中间4位
如19****15
7)姓名:3个字以内隐藏第1个字,4-6个字隐藏前2个字,大于6个字隐藏第3-6个字
plugins/desensitize/install.js
/**
* 脱敏处理函数
* @param {string} input - 需要脱敏处理的数据
* @param {string} type - 数据类型,包括 'email', 'id', 'bankCard', 'address', 'birthdate', 'name', 'phone', 'custom'(不脱敏)
* @return {string} 脱敏后的数据
*/
function maskData(input, type = 'phone') {
if (typeof input !== 'string') return input
const { securityEncryption } = session.get('THEME_CONFIG') || {}
// 与服务端商定加解密方式
input = decrypt(input)
// 只解密不脱敏
if (type === 'custom') return input
// 地址脱敏 中间的数字进行脱敏 明月小区*单元**号楼101
if (type === 'address') return input.replace(/(\d+)(?=\D)/g, match => '*'.repeat(match.length))
let start, end
switch (type) {
case 'email': {
// 邮箱,保留前两位和 "@" 后的所有字符
const [localPart, domain] = input.split('@')
if (localPart.length > 2) {
start = 2
end = domain.length + 1 }
break
}
case 'id':
case 'bankCard': {
// 身份证号或银行卡号,保留前三位和后四位
start = 3
end = 4
break
}
case 'birthdate': {
// 出生日期,隐藏中间四位
start = 2
end = 2
break
}
case 'name': {
// 姓名,3个字以内隐藏第1个字,4-6个字隐藏前2个字,大于6个字隐藏第3-6个字
const length = input.length
if (length <= 3) {
start = 0
end = length - 1
} else if (length <= 6) {
start = 0
end = length - 2
} else {
start = 2
end = length - 6
}
break
}
default: // phone 包含手机号和固定电话
// 手机号,保留前三位和后两位,电话号码保留区号和后三位
// telRegExp-固定电话正则 phoneRegExp-手机号正则
if (telRegExp.test(input) && !phoneRegExp.test(input)) {
let matchRes = input.match(telRegExp)
start = matchRes[1].length
end = matchRes[2].length
} else {
start = 3
end = 2
}
}
// 计算被脱敏处理后的字符的长度
let maskLength = input.length - start - end
// 使用正则表达式进行脱敏处理
const reg = new RegExp(`^(.{${start}}).*(.{${end}})$`)
return input.replace(reg, `$1${'*'.repeat(maskLength)}$2`)
}
export default {
install: Vue => {
Vue.prototype.$maskData = maskData
}
}
3.2 记录审计日志
用户在前端界面点击某个操作,需要记录当前操作的审计日志,例如用户点击了列表页的查询按钮,需要记录以下内容:在XXX页面,执行查询/新增/删除/更新等操作,操作内容:查询XXX,查询条件包含:XXX,操作结果:成功/失败。审计日志的内容由前端拼接,然后调用后端接口保存到数据库。
对于一些拼接审计日志等内容,涉及到比较多的重复性工作,定义一个 ActLog 类,类中包含了对应操作类型的实例方法,使用 Vue 插件的方式把将 ActLog 的实例挂载到 Vue 的原型上。
plugins/actLog/install.js
import http from '@/service/http'
class ActLog {
// 查询
query(params = {}, status = true) {
params = this._setParams(params, status)
let content = `在${params.path},执行查询操作,`
if (params.content) {
content += `操作内容:${params.content},`
}
content += `操作结果:${params.result}`
this._save(content, params.auditId)
}
// 新增
add(params = {}, status = true) {
// ...
}
// 更新
update(params = {}, status = true) {
// ...
}
// 删除
delete(params = {}, status = true) {
// ...
}
other(params = {}, status = true) {
// ...
}
_setParams(params = {}, status = true) {
params = Object.assign({
path: '', // 操作路径
content: '', // 日志内容
auditId: '', // 审计ID
gid: '', // 操作唯一标识
result: '成功'
}, params)
params.path += '页面'
if (!status) params.result = '失败'
return params
}
// 调接口保存
_save(content = '', auditId = '') {
http.post('createLog', { auditId, content })
}
}
export default {
install: Vue => {
Vue.prototype.$actLog = new ActLog()
}
}
注册插件
将所有的插件都存放到 plugins 目录下,使得文件结构更清晰,易于维护,然后可以借助 require.context 遍历所有的 plugins 目录下的所有 install.js,然后使用 Vue.use 进行注册。
总结
- 使用 Vue 插件方便给 Vue 添加全局功能,把一些常用的工具函数、全局组件、指令、过滤器等以插件的形式注册到 Vue 的实例或者原型上,简化了调用方式,对于工具函数等无需每次引用之后再调用。
- 将这些插件都放到 plugins 目录下,使得目录结构更加清晰,易于维护。
- 不建议将过多的工具函数,比如一些不常用的挂载到 Vue 上,使得 Vue 对象太过冗余。