背景
最近项目中有一个需求是列表里面有一些敏感字段,一开始不想直接展示出来,需要用户手动去请求解密接口才展示,而且并非整列解密,而是单个数据解密,这就需要行内操作了。
UI对应该需求出了如下一张图:
过程
从图中可以看到,需要在字段内容后面增加一个眼睛图标,可以切换显示内容。
起初我觉得是很容易的一个需求,直接就对当前 column
做了自定义的渲染,把内容和图标放在了单独的元素中,内容使用了单行省略的形式来做。
解密状态下点击眼睛请求解密接口得到真实的值并缓存在标签上(下次点击就不用再请求解密接口),再次点击闭眼图标,还原为加密的值。这里都是使用的 dom
操作,方便快捷。
效果也做出来了,但是后来产品说列宽可以自由拖拽调整,el-table
默认就可以拖拽调整列宽,我测试了一下,发现图标和内容在列宽比较窄的情况下,样式会出问题。而且有时候表格的 tooltip
并不会出现,导致内容截断时无法查看完整内容,如图:
分析
简单分析了一下,主要有这样几个问题要解决:
- 在保持内容和图标居中的情况下,如何适配列宽变化?
- 保证表格的
tooltip
在内容展示不全时生效
解决
经过一些调试,发现可以用 flex
布局配合一点小技巧来解决:
表格 column
自定义模板部分如下:
<span class="tb-title-decrypt" v-if="data.item.encryption && ![null, ''].includes(data.row[data.item.prop])">
<span class="tb-value">{{ data.row[data.item.prop] }}</span>
<span class="icon-holder">
<svg-icon icon-class="eye" @click="data.item.toggleField(data.item, data.row, $event, 'show')" />
<svg-icon icon-class="eye-close" @click="data.item.toggleField(data.item, data.row, $event, 'hide')" />
</span>
</span>
解密方法如下:
async toggleField(column, row, e, type) {
const parentNode = e.currentTarget.parentNode.parentNode;
const valueNode = parentNode.querySelector(".tb-value");
if (type === "show") {
const tagVal = parentNode.getAttribute("data-val");
if (tagVal) {
// 标签上有内容直接取就可以,不用再请求接口
parentNode.classList.add("decrypted");
valueNode.innerHTML = tagVal;
return;
}
await this.$api.user
.encryptionField({
id: row.id,
code: column.prop,
})
.then((res) => {
const val = res.data || "-";
parentNode.setAttribute("data-val", val); // 缓存真实内容到标签上
parentNode.classList.add("decrypted");
valueNode.innerHTML = val;
});
return;
}
parentNode.classList.remove("decrypted");
valueNode.innerHTML = row[column.prop];
}
对应的样式如下:
.tb-title-decrypt {
position: relative;
text-align: center;
display: flex;
width: 110%; // 这个宽度是为了让表格 tooltip 提前一点生效,保证可以看到完整内容
justify-content: center;
align-items: center;
.tb-value {
position: relative;
display: inline-block;
padding-right: 15px;
vertical-align: middle;
overflow: hidden;
white-space: nowrap;
// 这里很关键,在宽度不够时我使用了一个渐变来遮住内容,没用单行省略号来做(有坑)
&:after {
position: absolute;
right: 0;
content: "";
width: 20px;
height: 100%;
background: linear-gradient(to right, transparent, #fff 50%);
}
}
.icon-holder {
position: relative;
z-index: 2;
display: inline-block;
margin-left: -10px; // 图标部分往前移一点,这样看起来不会离内容太远
}
svg {
display: none;
cursor: pointer;
font-size: 16px;
fill: #666;
&:hover {
fill: #1863fb;
}
}
.icon-eye {
display: inline-block;
}
&.decrypted {
.icon-eye {
display: none;
}
.icon-eye-close {
display: inline-block;
}
}
}
最终得到如下效果:
评论区