前言
本文主要讲述 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 对象太过冗余。