需求
由于信息安全问题,现要做信息脱敏传输,在页面上不再展示敏感信息,需要用户手动的点击查看明文,后台会记录访问信息和次数。
例如 手机号13812345678 在页面上需展示为 138****5678
138****5678 旁边有个查看的眼睛,点击一下,会带着这个字段的加密串(非138****5678这种脱敏数据)、定义好的信息类型编码及这个页面的Id去调用接口,接口会返回解密后的密文(这里仍然返回的是密文,在封装好的请求中解密,不再赘述)。
后面的点击就是密文和明文的切换了,且每次查看都需要调用接口去记录访问信息和次数
这个功能基本上每个页面都有很多,那就封装成组件
组件的设计
1、模板的确定
需求基本上都是 一个脱敏数据+eye图标
138****5678 eye图标
有的地方点一下,可以查看多个敏感信息,如
烨*,138****5678 eye图标
实际上就是一个内容区加图标,考虑内容的拓展性,比如加上其他的文字
手机号:138****5678 eye图标
内容区就使用插槽,在父组件去组合显示内容,这样更灵活一点
2、数据的通信
脱敏的数据 138****5678 和解密后的明文如何展示?在父组件还是子组件中显示呢?
我希望功能在组件内完成,尽可能的依赖外部,就在组件内部调用接口,所以会在子组件中返回明文。要是子传父,用明文替换脱敏值,那得每个组件都写一个update的方法去更新传入的脱敏的字段,一个页面中会有多个这样的组件,而且多个页面都有,想着就太冗余了。
既然用了插槽,那就把脱敏的数据 138****5678 也传进去,显示的数据直接就在子组件中组装呗,父组件通过插槽去引用子组件的值 (想的挺好,坑就在这)
3、确定props
在内部调用接口需要这些参数
字段的加密串:encode、定义好的信息类型:typeCode、 页面ID:pagePath
加上刚才说的,有的地方点一下,可以查看两个密文
烨*,138****5678 eye图标
所以props这么传
pagePath: {
type: String,
required: true
},
ciphertextList: {
type: Array,
default: () => [
{
type: null,
typeCode: null, // 密文类型 - 接口所需参数
textEncr: null, // 显示带星号的密文
encode: null // 密文编码 - 接口所需参数
}
]
}
4、组件设计
<template>
<div>
<slot :values="values"></slot>
<!-- view-btn、viewed 眼睛查看的开闭图标 -->
<div v-if="checkAuth" class="view-btn" :class="{ viewed: isViewed }" @click="toggleView"></div>
</div>
</template>
<script>
import { decryptInfoApi } from '@/utils/tools' // 调用接口和一些数据的内部处理,不再赘述
export default {
props: {
checkAuth: {
type: Boolean,
default: false
}, // 后台返回空字段就不要显示眼睛的按钮
pagePath: {
type: String,
required: true
}, // 页面Id
ciphertextList: {
type: Array,
default: () => [
{
type: null,
typeCode: null, // 密文类型 - 接口所需参数
textEncr: null, // 显示带星号的密文
encode: null // 密文编码 - 接口所需参数
}
]
}
},
data() {
return {
isViewed: false,
values: {}
}
},
created() {
this.displayCiphertext() // 初始化全显示带星号的脱敏数据
},
methods: {
/* 显示脱敏数据 */
displayCiphertext() {
this.values = this.ciphertextList.reduce((obj, item) => {
obj[item.type] = item.textEncr
return obj
}, {})
},
/* 切换密文和明文 */
toggleView() {
if (this.isViewed) {
this.displayCiphertext()
this.isViewed = false
} else {
const tempValues = {}
const promises = this.ciphertextList.map(({ typeCode, encode, type }) => {
// 解密
return decryptInfoApi({ typeCode, encode, pagePath: this.pagePath }).then((res) => {
tempValues[type] = res
})
})
Promise.all(promises).then(() => {
this.values = { ...this.tempValues } // 一次性更新values
this.isViewed = true
})
}
}
}
}
</script>
组件的使用
使用组件,直接传递数据就行了,只是ciphertextList是对象数组,看起来臃肿点。此时看来,不用在引用的地方写其他额外的东西,还是比较方便的
<decrypt-box
:check-auth="!!(form.orderLinkMan && form.orderLinkPhone)"
page-path="xx页面"
:ciphertext-list="[
{
type: 'orderLinkMan',
typeCode: 'CUSTOMER_MAN',
textEncr: form.orderLinkManEncr,
encode: form.orderLinkManEncode
},
{
type: 'orderLinkPhone',
typeCode: 'CUSTOMER_PHONE',
textEncr: form.orderLinkPhoneEncr,
encode: form.orderLinkPhoneEncode
}
]"
>
<template #default="{ values }">{{ values.orderLinkMan }},{{ values.orderLinkPhone }}</template>
</decrypt-box>
坑来了
点击查看,解密之后,子组件中的数据发生变化,但页面没有更新!!!
插槽经常用,这种场景倒还是第一次遇到。父组件提供的内容依赖于子组件的数据,并且这些数据是响应式的,如果子组件的数据发生变化,理论上父组件提供的插槽内容也应该响应这些变化并更新,但在Vue 2.x中,插槽内容并不总是能够正确地响应子组件数据的更新。
这个问题的起因通常是Vue无法追踪到子组件数据的变化,导致父组件的插槽内容没有重新渲染。这种情况尤其可能出现在这种将插槽内容和子组件的响应式数据结合使用时。
解决
既然都这样设计了,不改了,都用过v-for吧,这里只用这个key
就在decrypt-box组件上绑定一个:key,当这个key改变时,Vue会知道要重新创建这个元素或组件,而不是重用它。其实这是一种反模式的策略,只是在这时强制重新渲染组件,改变数据
但注意,过度使用这种方法来强制更新组件可能会导致性能问题,因为它涉及到销毁和重新实例化组件,可能还包括重新创建DOM元素
// 在页面组件中添加以下代码,每个decrypt-box绑一个decryptBoxList[decryptKey]
export default {
data() {
return {
decryptBoxList: {
decryptKey1: 0,
decryptKey2: 0,
// 更多的 decryptBox组件 key
},
};
},
methods: {
refreshDecryptBox(keyName) {
this.decryptBoxList[keyName]++;
},
},
};
// decrypt-box组件中wacth一下isViewed的变化发送给refreshDecryptBox
最后
最后考虑到性能问题,还是重写了。将对象和所需对象的key值props传入,加解密后直接修改这个对象的属性,但这是一种反模式的操作,违反了数据的单向传输的规则,但为了使用灵活性和使用成本还是这些写了。
此文主要记录这次使用vue2插槽的遇到的问题
Vue2中将插槽内容和子组件的响应式数据结合使用时,父组件的插槽内容没有重新渲染