问题开始
本以为超级简单,就把之前几个小页面的功能整合到一个页面,多简单啊,没啥问题!!!
好的,整个一个页面不用一天就搞定了,优化优化样式啥的,完美!
重新测试下之前的接口,发现页面非常卡,震惊几十秒!
开始排查
首先查看浏览器加载接口的时间,发现接口请求就十几秒,如此离谱?好的 用postman测试下接口,确定了,没错了后台接口加载慢;
既然初始加载慢,那就loading等等看,但是为啥数据都加上上来了,操作如此卡顿!!!也没干啥啊,就仅仅把几个页面弄个选项卡切换下,点个选择框都点不开呢,得好几秒之后才行,通过performance查看js处理很快,反而是渲染耗时长,一顿操作调试之后发现有一个下拉框一下渲染了一万多条记录,难怪卡死......
看了并测试了vue-virtual-scroller-list等库的功能,感觉都不太适用,想简单快速是实现下目前需要的功能,因为数据要和el-select分组一样的功能,滚动的高度还得适应原本的弹窗高度
简单实现
子组件:
html:
<div ref="scroller"
class="recycle-scroller"
:style="{'minHeight': `${viewHeight - preHeight}px`}"
@scroll="onScroll">
<div class="recycle-scroller-content"
:style="{'minHeight': `${totalHeight}px`}">
<div class="recycle-scroller-wrapper"
:style="{ transform: `translateY(${translate}px)` }">
<div class="recycle-scroller-item"
:style="{'height': `${itemHeight}px`}"
v-for="(item, index) in showList" :key="item[itemKey] || index">
<slot :item="item"/>
</div>
</div>
</div>
</div>
css:
.recycle-scroller{
position: relative;
overflow: auto;
}
.recycle-scroller-content, .recycle-scroller-wrapper{
position: absolute;
top: 0;
width: 100%;
}
js:
Vue.component('recycle-scroller', {
template: html,
props: {
// 是否显示虚拟列表
flag: {
type: Boolean,
default: false
},
// 父组件传来的需要渲染的数据
listData: {
type: Array,
default: () => {
return []
},
require: true
},
// 数组的唯一标识字段,提升性能
itemKey: {
type: String,
default: 'id',
require: true
},
// 每条数据的高度
itemHeight: {
type: Number,
default: 40
},
// 可视区域高度
viewHeight: {
type: Number,
default: 600
},
// 可视区域减掉的默认一块的高度
preHeight: {
type: Number,
default: 0
}
},
data() {
return {
showList: [], // 当前渲染的数据
translate: 0
}
},
watch: {
flag(newVal) {
if(newVal) {
this.onScroll()
}
},
listData(newVal) {
this.onScroll();
}
},
computed: {
totalHeight () {
return this.itemHeight * this.listData.length
}
},
methods: {
// 滚动 当前显示的数组数据
onScroll () {
let me = this
let scrollTop = me.$refs.recycle.scrollTop
let viewNum = Math.ceil(me.viewHeight / me.itemHeight)
// 可视区域 显示第一个条数据序号
let firstIdx = Math.floor(scrollTop / me.itemHeight)
// 利用VUE中Diff算法的复用机制,我们可以截取可视区域的上一屏第一条至下一屏最后一条
let start = firstIdx - viewNum > 0 ? firstIdx - viewNum : 0
let len = me.listData.length
let end = firstIdx + (2 * viewNum) < len ? firstIdx + (2 * viewNum) : len
me.showList = me.listData.slice(start, end)
me.translate = start * me.itemHeight
}
}
});
父组件:
<el-form id="msgForm" ref="msgForm" :model="msgData" label-width="130px">
<el-form-item label="接收人" prop="roleGroup" :rules="[{ required: true, trigger: 'blur', message: '接收人不能为空' }]">
<el-select ref="roleSelect" v-model="msgData.roleGroup"
placeholder="查找用户、角色、组织架构"
multiple
filterable
style="width: 100%"
:size="size"
:filter-method="filterMethod"
@change="roleGroupChange"
@visible-change="handleVisibleChange">
<template v-if="virtualData && virtualData.length > 0">
<recycle-scroller
ref="recycle"
:flag="flag"
:list="virtualData"
itemKey="uid"
:itemHeight = "30"
:viewHeight = "300">
<template slot-scope="props">
<!-- 分组的组名 使用el-option-group添加判断会出现显示为空白的问题 -->
<ul v-if="props.item.level == 0"
class="el-select-group__wrap">
<li class="el-select-group__title">
{{props.item.label}}
</li>
</ul>
<el-option v-else
:label="props.item.label"
:value="props.item.value"
value-key>
{{props.item.label}}
</el-option>
</template>
</recycle-scroller>
</template>
</el-select>
</el-form-item>
</el-form>
<script>
export default {
data() {
return {
size: 'small',
msgData: {
roleGroup: []
},
originFlattenData: [],
// 虚拟列表中渲染的数据
virtualData: [],
flag: false
}
},
methods: {
// 自定义搜索
filterMethod: function(query) {
let me = this;
if(!!query) {
let filterData = me.filterTreeData(me.roleGroups,'label',query);
me.virtualData = me.flatTree(filterData);
me.$forceUpdate();
}
},
// 接收人 选项列表关闭的时候设置初始所有数据
handleVisibleChange(visible) {
if (visible) {
me.flag = true;
} else {
me.flag = false;
me.virtualData = me.originFlattenData;
}
return visible
},
roleGroupChange(val) {
this.$refs.msgForm.validateField('roleGroup');
let arr = me.originFlattenData.filter(el => {
return me.msgData.roleGroup.includes(el.value)
});
arr.map(item => {
// 搜索查询回显为value值怎么办?看了el-select源码后发现渲染的显示的都是option中的对象 然后有一个cachedOptions,查询选中后的项添加到cachedOptions对象中,回显的时候就能读的这个对象的值了
me.$refs.roleSelect.cachedOptions.push({
currentLabel: item.label,
currentValue: item.value,
label: item.label,
value: item.value
})
});
},
/**
* 扁平化树形数组
* idx 唯一值
* level 层级
* */
flatTree(data,idx=0, level=0) {
let me = this;
let result = [];
let temp = JSON.parse(JSON.stringify(data));
temp.forEach((item,index) => {
result.push({
...item,
level,
uid: '' + idx + index
});
if(item.options && item.options.length > 0) {
result = result.concat(me.flatTree(item.options,'' + idx + index, level + 1))
}
});
return result;
},
/**
* 根据指定字段过滤树形数组数据
* field 指定字段
* value 条件过滤值
* */
filterTreeData(data, field, value) {
let me = this;
let tree = JSON.parse(JSON.stringify(data));
return tree.filter(node => {
// 根据指定字段进行条件判断
if (node[field].includes(value)) {
return true;
}
if (node.options) {
// 递归处理子节点
node.options = me.filterTreeData(node.options, field, value);
return (node.options.length > 0); // 返回是否保留该节点
}
return false;
});
}
}
}
</script>
优化后:
后期还需要封装优化下,现在先简单试下