在我们项目中可能会用到element的穿梭框组件
正常数据量几十上百条使用没什么问题,但是数据量一旦大起来就会非常之慢,比如我在做的一个项目,有一万多条数据,结果就是全选或全选转移的时候要卡顿十几秒。这非常影响用户体验。element官方好像也没给解决方法,于是我觉得手搓一个穿梭框组件
首先先要编写出和element穿梭框一模一样的UI,我选择了用el-checkbox来实现
先写出左框
<div class="left common_main">
<div class="tranfer_title">
<div class="allCheced">
<el-checkbox v-model="humanCheckAll" label="Human-use APN" size="large"
@change="humanAllSelect" />
</div>
<div class="total">{{ humanCheck.length }}/{{ humanApnList.data.length }}</div>
</div>
<div class="serchBox">
<el-input v-model="humanSearch" :prefix-icon="Search"
placeholder="Search for humanUse"></el-input>
</div>
<div class="content">
<el-checkbox-group v-if="humanShowData.data.length > 0" v-model="humanCheck" class="checkGroup"
v-infinite-scroll="humanPageDown" :infinite-scroll-immediate="false"
infinite-scroll-distance="5">
<el-checkbox v-for="(item, index) in humanShowData.data" :label="item.apnName" :key="index"
@change="humanSelect(item.isCheck, item.index)" />
</el-checkbox-group>
<div v-else>
<div
style="display: flex;justify-content: center;margin-top: 15px;font-size: 14px;color: #909399">
<span v-if="humanSearch != ''">No Matching Data</span>
<span v-else>No Data</span>
</div>
</div>
</div>
</div>
中间的转移按钮
<div class="swapBtn">
<el-button class="serviceDelivery_btn left_btn" :disabled="machineCheck.length == 0" type="primary"
@click="swapclick('machine')">
<el-icon class="el-icon--right">
<ArrowLeft />
</el-icon></el-button>
<el-button class="serviceDelivery_btn right_btn" :disabled="humanCheck.length == 0" type="primary"
@click="swapclick('human')">
<el-icon class="el-icon--right">
<ArrowRight />
</el-icon></el-button>
</div>
右框
<div class="right common_main">
<div class="tranfer_title">
<div class="allCheced">
<el-checkbox v-model="machineCheckAll" label="Machine-use APN" size="large"
@change="machineAllSelect" />
</div>
<div class="total">{{ machineCheck.length }}/{{ machineApnList.data.length }}</div>
</div>
<div class="serchBox">
<el-input v-model="machineSearch" :prefix-icon="Search"
placeholder="Search for machineUse"></el-input>
</div>
<div class="content">
<el-checkbox-group v-if="machineShowData.data.length > 0" v-model="machineCheck"
class="checkGroup" v-infinite-scroll="machinePageDown" :infinite-scroll-immediate="false"
infinite-scroll-distance="5">
<el-checkbox v-for="(item, index) in machineShowData.data" :label="item.apnName"
:key="index" @change="machineSelect(item.isCheck, item.index)" />
</el-checkbox-group>
<div v-else>
<div
style="display: flex;justify-content: center;margin-top: 15px;font-size: 14px;color: #909399">
<span v-if="machineSearch != ''">No Matching Data</span>
<span v-else>No Data</span>
</div>
</div>
</div>
</div>
总html代码
<div class="main_dialog">
<div class="left common_main">
<div class="tranfer_title">
<div class="allCheced">
<el-checkbox v-model="humanCheckAll" label="Human-use APN" size="large"
@change="humanAllSelect" />
</div>
<div class="total">{{ humanCheck.length }}/{{ humanApnList.data.length }}</div>
</div>
<div class="serchBox">
<el-input v-model="humanSearch" :prefix-icon="Search"
placeholder="Search for humanUse"></el-input>
</div>
<div class="content">
<el-checkbox-group v-if="humanShowData.data.length > 0" v-model="humanCheck" class="checkGroup"
v-infinite-scroll="humanPageDown" :infinite-scroll-immediate="false"
infinite-scroll-distance="5">
<el-checkbox v-for="(item, index) in humanShowData.data" :label="item.apnName" :key="index"
@change="humanSelect(item.isCheck, item.index)" />
</el-checkbox-group>
<div v-else>
<div
style="display: flex;justify-content: center;margin-top: 15px;font-size: 14px;color: #909399">
<span v-if="humanSearch != ''">No Matching Data</span>
<span v-else>No Data</span>
</div>
</div>
</div>
</div>
<div class="swapBtn">
<el-button class="serviceDelivery_btn left_btn" :disabled="machineCheck.length == 0" type="primary"
@click="swapclick('machine')">
<el-icon class="el-icon--right">
<ArrowLeft />
</el-icon></el-button>
<el-button class="serviceDelivery_btn right_btn" :disabled="humanCheck.length == 0" type="primary"
@click="swapclick('human')">
<el-icon class="el-icon--right">
<ArrowRight />
</el-icon></el-button>
</div>
<div class="right common_main">
<div class="tranfer_title">
<div class="allCheced">
<el-checkbox v-model="machineCheckAll" label="Machine-use APN" size="large"
@change="machineAllSelect" />
</div>
<div class="total">{{ machineCheck.length }}/{{ machineApnList.data.length }}</div>
</div>
<div class="serchBox">
<el-input v-model="machineSearch" :prefix-icon="Search"
placeholder="Search for machineUse"></el-input>
</div>
<div class="content">
<el-checkbox-group v-if="machineShowData.data.length > 0" v-model="machineCheck"
class="checkGroup" v-infinite-scroll="machinePageDown" :infinite-scroll-immediate="false"
infinite-scroll-distance="5">
<el-checkbox v-for="(item, index) in machineShowData.data" :label="item.apnName"
:key="index" @change="machineSelect(item.isCheck, item.index)" />
</el-checkbox-group>
<div v-else>
<div
style="display: flex;justify-content: center;margin-top: 15px;font-size: 14px;color: #909399">
<span v-if="machineSearch != ''">No Matching Data</span>
<span v-else>No Data</span>
</div>
</div>
</div>
</div>
</div>
样式代码
.main_dialog {
height: 100%;
width: 100%;
display: flex;
justify-content: space-between;
border: none;
.left {
width: 260px;
height: 312px;
}
.swapBtn {
width: 180px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
button {
line-height: 32px;
}
.left_btn {
padding-left: 10px;
}
.right_btn {
padding-right: 10px;
}
}
.right {
width: 260px;
height: 312px;
}
.common_main {
display: flex;
flex-direction: column;
border: 1px solid #ebeef5;
border-radius: 4px;
.tranfer_title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f5f7fa;
height: 40px;
padding: 0 15px;
font-weight: 400;
}
.serchBox {
padding: 15px;
}
.content {
height: 210px;
.checkGroup {
display: flex;
flex-direction: column;
overflow: auto;
overflow-x: hidden;
height: 100%;
.el-checkbox {
padding: 8px 8px 8px 15px;
margin-right: 15px;
height: auto;
.el-checkbox__label {
width: 100%;
height: auto;
line-height: 130%;
word-break: keep-all;
word-wrap: break-word;
white-space: wrap;
}
.el-checkbox__label:hover {
color: #0085D0 !important;
}
}
}
}
}
}
效果如图
对于上万条数据的选择框加载也需要做一些处理,不然页面加载也是非常之慢的。
我选择了用element的无限滚动指令来进行懒加载
v-infinite-scroll
<el-checkbox-group v-if="humanShowData.data.length > 0" v-model="humanCheck" class="checkGroup"
v-infinite-scroll="humanPageDown" :infinite-scroll-immediate="false"
infinite-scroll-distance="5">
<el-checkbox v-for="(item, index) in humanShowData.data" :label="item.apnName" :key="index"
@change="humanSelect(item.isCheck, item.index)" />
</el-checkbox-group>
所有的逻辑代码如下
//全选判断
let humanCheckAll = ref(false)
let machineCheckAll = ref(false)
//已选选择框数据
let humanCheck = ref([])
let machineCheck = ref([])
//搜索框数据
let humanSearch = ref('')
let machineSearch = ref('')
//全选逻辑
const humanAllSelect = (val) => {
// humanCheck.value = val ? humanApnList.data : []
if (val) {
if (humanSearch.value != '') {
humanApnList.data.filter(e => e.apnName.toLowerCase().indexOf(humanSearch.value.toLowerCase()) != -1).forEach(item => {
item.isCheck = true
humanCheck.value.push(item.apnName)
})
} else {
humanApnList.data.forEach(e => {
e.isCheck = true
humanCheck.value.push(e.apnName)
})
}
} else {
humanCheck.value = []
humanApnList.data.forEach(e => {
e.isCheck = false
})
}
}
const machineAllSelect = (val) => {
// machineCheck.value = val ? machineApnList.data : []
if (val) {
if (machineSearch.value != '') {
machineApnList.data.filter(e => e.apnName.toLowerCase().indexOf(machineSearch.value.toLowerCase()) != -1).forEach(item => {
machineCheck.value.push(item.apnName)
item.isCheck = true
})
} else {
machineApnList.data.forEach(e => {
e.isCheck = true
machineCheck.value.push(e.apnName)
})
}
} else {
machineCheck.value = []
machineApnList.data.forEach(e => {
e.isCheck = false
})
}
}
//单选逻辑
const humanSelect = (checked, index) => {
// humanCheckAll.value = humanCheck.value.length == humanApnList.data.length
// humanCheck.value = section
let i = humanApnList.data.findIndex(e => e.index == index)
humanCheckAll.value = humanCheck.value.length == humanApnList.data.length
humanApnList.data[i].isCheck = !checked
}
const machineSelect = (checked, index) => {
// machineCheckAll.value = machineCheck.value.length == machineApnList.data.length
// machineCheck.value = section
let i = machineApnList.data.findIndex(e => e.index == index)
machineCheck.valueAll = machineCheck.value.length == machineApnList.data.length
machineApnList.data[i].isCheck = !checked
}
//储存穿梭框全量数据
const machineApnList = reactive({
data: []
})
const humanApnList = reactive({
data: []
})
//穿梭框数据中间数组
let humanShowData = reactive({
data: []
})
let machineShowData = reactive({
data: []
})
//穿梭框显示
let apnDialog = ref(false)
//穿梭框一次加载多少条数据
let humanActiveNum = ref(20)
let machineActiveNum = ref(20)
//穿梭框下拉加载数据逻辑
function humanPageDown() {
const l = humanShowData.data.length;
const totalLength = humanApnList.data.length
humanActiveNum.value += 20
if (humanSearch.value != '') {
humanShowData.data = humanApnList.data.filter(item => item.apnName.toLowerCase().indexOf(humanSearch.value.toLowerCase()) != -1).slice(0, humanActiveNum.value)
} else {
if (l < totalLength) {
humanShowData.data = humanApnList.data.slice(0, humanActiveNum.value > totalLength ? totalLength : humanActiveNum.value)
} else {
humanShowData.data = humanApnList.data.slice(0, totalLength)
}
}
}
function machinePageDown() {
const l = machineShowData.data.length;
const totalLength = machineApnList.data.length
machineActiveNum.value += 20
if (this.machineSearchApnName != '') {
machineShowData.data = machineApnList.data.filter(item => item.apnName.toLowerCase().indexOf(this.machineSearchApnName.toLowerCase()) != -1).slice(0, machineActiveNum.value)
} else {
if (l < totalLength) {
machineShowData.data = machineApnList.data.slice(0, machineActiveNum.value > totalLength ? totalLength : machineActiveNum.value)
} else {
machineActiveNum.value = 20
machineShowData.data = machineApnList.data.slice(0, machineActiveNum.value)
}
}
}
//穿梭框搜索逻辑
watch(humanSearch, (newVal) => {
if (newVal != '') {
humanShowData.data = humanApnList.data.filter(item => item.apnName.toLowerCase().indexOf(newVal.toLowerCase()) != -1).slice(0, humanActiveNum.value)
} else {
humanShowData.data = humanApnList.data.slice(0, humanActiveNum.value)
}
})
watch(machineSearch, (newVal) => {
if (newVal != '') {
machineShowData.data = machineApnList.data.filter(item => item.apnName.toLowerCase().indexOf(newVal.toLowerCase()) != -1).slice(0, machineActiveNum.value)
} else {
machineShowData.data = machineApnList.data.slice(0, machineActiveNum.value)
}
})
//穿梭框交换逻辑
const swapclick = (type) => {
if (type == 'human') {
if (humanCheckAll.value) {
if (humanSearch.value != '') {
let data = []
let indexArr = []
humanApnList.data.filter((e, index) => {
if (e.apnName.toLowerCase().indexOf(humanSearch.value.toLowerCase()) != -1) {
data.push(e)
indexArr.push(index)
}
})
let t = 0
indexArr.forEach(item => {
humanApnList.data.splice(item - t, 1)
t++
})
machineApnList.data.unshift(...data)
} else {
humanApnList.data.forEach(e => {
e.isCheck = false
})
machineApnList.data.unshift(...humanApnList.data)
humanApnList.data = []
}
} else {
if (humanSearch.value != '') {
let t = 0
let indexArr = []
humanApnList.data.filter((e, index) => {
if (e.apnName.toLowerCase().indexOf(humanSearch.value.toLowerCase()) != -1) {
if (e.isCheck) {
e.isCheck = false
indexArr.push(index)
machineApnList.data.unshift(e)
}
}
})
indexArr.forEach(item => {
humanApnList.data.splice(item - t, 1)
t++
})
} else {
let t = 0
let indexArr = []
humanApnList.data.forEach((e, index) => {
if (e.isCheck) {
indexArr.push(index)
e.isCheck = false
machineApnList.data.unshift(e)
}
})
indexArr.forEach(item => {
humanApnList.data.splice(item - t, 1)
t++
})
}
}
upApnList()
humanCheck.value = []
humanCheckAll.value = false
} else if (type == 'machine') {
if (machineCheckAll.value) {
if (machineSearch.value != '') {
let data = []
let indexArr = []
machineApnList.data.filter((e, index) => {
if (e.apnName.toLowerCase().indexOf(machineSearch.value.toLowerCase()) != -1) {
data.push(e)
indexArr.push(index)
}
})
let t = 0
indexArr.forEach(item => {
machineApnList.data.splice(item - t, 1)
t++
})
humanApnList.data.unshift(...data)
} else {
machineApnList.data.forEach(e => {
e.isCheck = false
})
humanApnList.data.unshift(...machineApnList.data)
machineApnList.data = []
}
} else {
if (machineSearch.value != '') {
let t = 0
let indexArr = []
machineApnList.data.forEach((e, index) => {
if (e.apnName.toLowerCase().indexOf(machineSearch.value.toLowerCase()) != -1) {
if (e.isCheck) {
e.isCheck = false
indexArr.push(index)
humanApnList.data.unshift(e)
}
}
})
indexArr.forEach(item => {
machineApnList.data.splice(item - t, 1)
t++
})
} else {
let t = 0
let indexArr = []
machineApnList.data.forEach((e, index) => {
if (e.isCheck) {
indexArr.push(index)
e.isCheck = false
humanApnList.data.unshift(e)
}
})
indexArr.forEach(item => {
machineApnList.data.splice(item - t, 1)
t++
})
}
}
upApnList()
machineCheckAll.value = false
machineCheck.value = []
}
}
const upApnList = () => {
if (humanSearch.value != '') {
humanShowData.data = humanApnList.data.filter(item => item.apnName.toLowerCase().indexOf(humanSearch.value.toLowerCase()) != -1).slice(0, humanActiveNum.value)
} else {
humanShowData.data = humanApnList.data.slice(0, humanActiveNum.value)
}
if (machineSearch.value != '') {
machineShowData.data = machineApnList.data.filter(item => item.apnName.toLowerCase().indexOf(machineSearch.value.toLowerCase()) != -1).slice(0, machineActiveNum.value)
} else {
machineShowData.data = machineApnList.data.slice(0, machineActiveNum.value)
}
}
做出来的效果如下所示
优化成功,速度大大提升,从十几秒到毫秒级别。