业精于勤 荒于嬉
需求描述
element 的 穿梭框el-transfer 使用起来样式很受限制,一般我们需要根据自己的需求实现数据的灵活选择,左右穿梭,就需要用到自定义的穿梭框了
思路分析
封装自定义的穿梭框,要考虑的是自己的需求样式,以及数据展示格式
A:什么样式?
Q:穿梭框样式,一般分为左右两侧,此处不多说,根据自己的 UI 画页面就可以了
A:展示的数据格式?
Q : 主要是根据自己的页面展示,分析自己需要的是什么样的数据,
A : 其他逻辑功能分析?
Q: 你需求的穿梭框需要什么样的功能,是否有搜索,是单选还是双选等?一步一步,实现功能,细化逻辑。
A:穿梭框的主要功能是数据可以左右穿梭怎么实现?
B:数据展示分为左右两侧,与此同时有一个包含左右两侧的总数据,左侧数据=总数据.filter(右侧数据)
table 穿梭框实现案例
实现后的效果(忽略粗糙的样式)
基本功能
搜索功能自动过滤
其实上面效果的实现还是依赖了element ui ,因为项目中本来用了element-ui 并设定了统一的格式,所以就是用了element
搜索功能好实现,就是根据搜索内容过滤数据 改变 table 的 data 就可以了,在element 的 el-transfer 中,过滤值的基础是label ,而我们自己写可以根据自己的条件进行过滤搜索
思路是:1、总的data 过滤到右侧数据 2、搜索内容+自己需要的过滤逻辑实现搜索过滤
const filterData = []
const leftData = this.tableData.filter(item => !this.currentSelection.some(citem => citem.no == item.no))
leftData.map(item => {
if (item.no == this.inputContent || item.name.includes(this.inputContent)) {
// 根据自己的搜索条件来判断
filterData.push(item)
}
})
table多选,并控制最多选择数量
table 使用的是elemtn ui ,所以同样有很多现成的东西可以用
1、设置可以多选 并可以通过点击行进行选中
el-table 满足上述功能,只需要添加对应的属性和事件就可以 row-click 操作行点击选中的操作
<el-table ref="tableRef" :data="currentTableData" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange" @row-click="handleRowClick">
<el-table-column type="selection" width="40" :selectable="setSelectable"> </el-table-column>
<el-table-column v-for="(item, index) in labelKey" :label="item.label" :prop="item.key" :width="item.width" :key="index" align="center"> </el-table-column>
</el-table>
2、根据选择数量控制checkbox 状态
checkbox 的可选状态需要用 :selectable=‘setSelectable’ 设置 两个table设置代码如下。
setSelectable(row, index) {
if (this.leftSelected.length + this.currentSelection.length >= this.maxSelect) {
return this.leftSelected.findIndex(item => item.no == row.no) != -1
}
return true
},
handleRowClick(row) {
const allSelectLength = this.leftSelected.length + this.currentSelection.length
if (allSelectLength == this.maxSelect && this.leftSelected.findIndex(item => item.no === row.no) == -1) {
return false
}
this.$refs.tableRef.toggleRowSelection(row)
},
中间选择按钮动态切换可选择状态
思路:中间按钮是否可点击的状态左侧是否选中数据来决定 ,在触发table选中触发selection 时 进行实时判断
handleSelectionChange(selection) {
this.leftSelected = selection
this.disabled = !(this.leftSelected.length > 0)
},
在table 重新渲染时,已经选择的数据状态保持
在table 的数据更新时,table控件会重新渲染,重新渲染后会导致 selection 清空
什么时候数据会变化呢?1、搜索内容发生变化 2、右侧的数据删除左侧会增加
主要修改 handleSelectionChange 方法中 this.leftSelected = selection 需要根据条件设置,比如如果是重新渲染触发的事件,不能直接赋值使用,要恢复选中的状态
选中状态切换关键:this.$refs.tableRef.toggleRowSelection(item, true)
handleSelectionChange(selection) {
if (this.needSaveCheck) {
this.saveSelect()
} else {
this.leftSelected = selection
}
this.disabled = !(this.leftSelected.length > 0)
},
saveSelect() {
this.$nextTick(() => {
this.currentTableData.map(item => {
if (this.leftSelected.findIndex(lef => item.no == lef.no) !== -1) {
this.$refs.tableRef.toggleRowSelection(item, true)
}
})
this.needSaveCheck = false // 写在nextTick 方法里面很重要
})
}
问题总结
在保存选中状态时候,一定要在$nextTick 方法中,且this.needSaveCheck = false 一定在nextTick 中 ,不然会被handleSelectionChange 触发的selection 覆盖 ,另外在$nextTick之前设置选中的属性,页面渲染完成以后,会没有蓝色状态,只是没有selectable 不一样 。
整体代码
<template>
<div class="transfer-container">
<div class="table-transfer">
<el-row :gutter="24">
<el-col :span="10" class="left">
<div class="header">{{titles[0]}}</div>
<div class="panel">
<el-input placeholder="请输入内容" v-model="inputContent" clearable @input="handleInputChange" @clear="inputClear"> </el-input>
<el-table ref="tableRef" :data="currentTableData" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange" @row-click="handleRowClick">
<el-table-column type="selection" width="40" :selectable="setSelectable"> </el-table-column>
<el-table-column v-for="(item, index) in labelKey" :label="item.label" :prop="item.key" :width="item.width" :key="index" align="center"> </el-table-column>
</el-table>
</div>
</el-col>
<el-col :span="4" class="buttons">
<el-button @click="selectChange" ref="selectbtn" :disabled="disabled">选择</el-button>
</el-col>
<el-col :span="10" class="right">
<div class="header">{{titles[1]}}</div>
<div class="panel">
<ul>
<li class="item" v-for="(item, index) in currentSelection" :key="index">
<span>{{ item.name }}</span>
<span>{{ item.no }}</span>
<i class="el-icon-close" @click="deleteHandle(item)"></i>
</li>
</ul>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
// import specialList from './mock/specialist.json'
export default {
props: {
titles: {
type: Array,
default: function() {
return ['', '']
}
},
tableData: {
type: Array
},
selectedData: {
type: Array
},
labelKey: {
type: Array
},
maxSelect: {
type: Number
}
},
model: {
prop: 'selectedData',
event: 'selectChange'
},
data() {
return {
inputContent: '',
currentSelection: [],
currentTableData: [],
leftSelected: [],
disabled: true, // 选择按钮默认值
needSaveCheck: false // 是否需要保存左侧已选择状态
}
},
methods: {
createMockData() {
// 随机成成数据
let data = []
let zm = ['aa', 'bb', '测试', 'cc', 'n', 'nib', 'speci01', 'p2', 'ccccc', '2021a', '2021b', '2021c', '2021jj', '2021333', 'ccc2021', '231546sss', '3654kkk', 'sssuy']
for (let i = 0; i < 18; i++) {
let special = {}
special.name = zm[i]
special.no = i + 'i'
special.finished = Math.round(Math.random() * 100)
special.unfinished = Math.round(Math.random() * 100)
data.push(special)
}
// console.log(JSON.stringify(data))
},
selectChange() {
// 点击选择按钮
this.currentSelection = this.currentSelection.concat(this.leftSelected)
this.currentTableData = this.tableData.filter(item => !this.currentSelection.some(citem => citem.no == item.no))
this.leftSelected = []
this.$emit('selectChange', this.currentSelection)
},
handleSelectionChange(selection) {
if (this.needSaveCheck) {
this.saveSelect()
} else {
this.leftSelected = selection
}
this.disabled = !(this.leftSelected.length > 0)
},
handleInputChange() {
if (this.inputContent == '') {
this.inputClear()
return
}
const filterData = []
const leftData = this.tableData.filter(item => !this.currentSelection.some(citem => citem.no == item.no))
leftData.map(item => {
if (item.no == this.inputContent || item.name.includes(this.inputContent)) {
// 根据自己的搜索条件来判断
filterData.push(item)
}
})
this.currentTableData = filterData
if (this.leftSelected.length > 0) {
this.needSaveCheck = true
}
},
inputClear() {
this.currentTableData = this.tableData.filter(item => !this.currentSelection.some(citem => citem.no == item.no))
if (this.leftSelected.length > 0) {
this.needSaveCheck = true
this.handleSelectionChange()
}
},
setSelectable(row, index) {
if (this.leftSelected.length + this.currentSelection.length >= this.maxSelect) {
return this.leftSelected.findIndex(item => item.no == row.no) != -1
}
return true
},
deleteHandle(special) {
this.currentSelection.splice(
this.currentSelection.findIndex(item => item.no === special.no),
1
)
this.currentTableData = this.tableData.filter(item => !this.currentSelection.some(citem => citem.no == item.no))
this.handleInputChange()
},
handleRowClick(row) {
const allSelectLength = this.leftSelected.length + this.currentSelection.length
if (allSelectLength == this.maxSelect && this.leftSelected.findIndex(item => item.no === row.no) == -1) {
return false
}
this.$refs.tableRef.toggleRowSelection(row)
},
saveSelect() {
this.$nextTick(() => {
this.currentTableData.map(item => {
if (this.leftSelected.findIndex(lef => item.no == lef.no) !== -1) {
this.$refs.tableRef.toggleRowSelection(item, true)
}
})
this.needSaveCheck = false // 写在nextTick 方法里面很重要
})
}
},
mounted() {
this.$nextTick(() => {
this.currentTableData = this.tableData
})
}
}
</script>
<style lang="less" scope>
div {
box-sizing: border-box;
}
.transfer-container {
width: 100%;
padding: 30px;
text-align: center;
.table-transfer {
width: 900px;
min-width: 800px;
margin: 0 auto;
.header {
height: 28px;
line-height: 28px;
background-color: cornflowerblue;
padding-left: 30px;
color: darkblue;
text-align: left;
}
.panel {
width: 100%;
height: 400px;
border: 1px solid cornflowerblue;
padding: 10px;
}
.buttons {
line-height: 300px;
}
.left {
.header {
border-radius: 0px 20px 0 0px;
}
.el-input {
width: 80%;
margin: 20px 0;
}
.el-input__inner {
height: 30px;
}
.el-table__body-wrapper {
min-height: 200px;
max-height: 270px;
overflow: auto;
}
.cell {
padding: 0px;
}
td {
border: none;
}
th.is-leaf {
border: none;
}
th {
.el-checkbox {
display: none;
}
}
.el-table td {
padding: 5px 0px;
}
}
.right {
.header {
border-radius: 20px 0px 0px 0px;
}
ul {
width: 100%;
padding-top: 30px;
li {
display: flex;
justify-content: space-between;
height: 30px;
line-height: 30px;
}
span {
display: inline-block;
}
}
}
}
}
</style>
积跬步 至千里