目录本文内容最终效果图组件html布局穿梭框左侧内容穿梭框右侧内容穿梭框中间向左、向右按钮把排序好的穿梭数据传给父组件整体代码小结本文内容 需求是实现类似 el-transfer的组
需求是实现类似 el-transfer
的组件,右侧框内容可以拖动排序;
手写div
样式 + Vuedraggable
组件实现。
新建一个组件文件 CustORMTransfer.vue
,穿梭框 html
分为左中右三部分,使用flex布局使其横向布局,此时代码如下
<template>
<div class="custom-transfer-cls">
<div class="left-side"></div>
<div class="btn-cls"></div>
<div class="right-side"></div>
</div>
</template>
<script>
export default {
name: 'CustomTransferName',
components: {},
props: {},
data () {
return { }
},
computed: { },
created () {},
mounted () { },
methods: {}
}
</script>
<style lang="less" scoped>
.custom-transfer-cls {
display: flex;
justify-content: space-between;
min-height: 120px;
.left-side,
.right-side {}
.btn-cls { }
}
</style>
此时页面上看不到组件内容。
左侧内容是个列表,列表的每一项是多选框checkbox
加文字标题,列表最上面是标题;所以.left-side
的代码如下:
<div class="left-side">
<!-- 标题 -->
<h4>{{ titles[0] }}</h4>
<!-- 列表 -->
<div v-for="left in leftData" :key="left.key" class="item-cls">
<el-checkbox :checked="left.checked" @change="leftCheckChange(left)" />
<span :title="left.label">{{ left.label }}</span>
</div>
<!-- 数据为空时显示 -->
<div v-if="leftData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
解析:
h4
标签,titles是组件使用者传入props的标题数组的第一项;leftData
是组件使用者传入的数据处理之后的,因为我们默认el-checkbox
不勾选,所以在生命周期mounted时,checked设为false;el-checkbox
触发change事件时,执行函数leftCheckChange(left)
,去改变leftData
数组对应项的checked设为取反;leftData
数据为空时,显示数据为空的文本,此文本组件使用者可通过 属性 emptypText
传入,默认'数据为空';.item-cls
定义,内容过长时显示省略号,在 title
属性中显示全部内容;以上内容加上样式、函数后如下:
<template>
<div class="custom-transfer-cls">
<div class="left-side"></div>
<div class="btn-cls"></div>
<div class="right-side"></div>
</div>
</template>
<script>
export default {
name: 'CustomTransferName',
components: {},
props: {
allData: {
type: Array,
default: () => {
// 对象数组需要有label、key两个属性
return []
}
},
emptypText: {
type: String,
default: '数据为空'
},
titles: {
type: Array,
default: () => {
return ['列表 1', '列表 2']
}
}
},
data () {
return {
leftData: []
}
},
computed: { },
created () {},
mounted () {
// 初始化列表1的数据
this.leftData = this.allData.map(a => {
a.checked = false
return a
})
},
methods: {
// 左边checkbox的change事件
leftCheckChange (check) {
this.leftData = this.leftData.map(l => {
if (l.key === check.key) {
l.checked = !l.checked
}
return l
})
}
}
}
</script>
<style lang="less" scoped>
.custom-transfer-cls {
display: flex;
justify-content: space-between;
min-height: 120px;
.left-side {
height: 240px;
overflow-y: scroll;
background-color: white;
width: 140px;
border: 1px solid #eee;
border-radius: 4px;
h4 {
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
.empty-text {
text-align: center;
color: #ccc;
}
.item-cls {
margin-left: 12px;
margin-right: 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&::-WEBkit-scrollbar {
width: 1px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
}
&::-webkit-scrollbar-track {
background: #ededed;
}
}
.btn-cls { }
}
</style>
右侧的列表需要具有可拖动排序的功能,我使用的使 vuedraggable
组件,所以首先需要先安装npm install vuedraggable -S
, 再引入 import draggable from 'vuedraggable'
,使用时配合 <transition-group>
增加过渡效果;代码如下:
<div class="right-side">
<h4>{{ titles[1] }}</h4>
<draggable v-model="rightData">
<transition-group>
<div v-for="(right, index) in rightData" :key="right.key" class="item-cls">
<el-checkbox :checked="right.checked" @change="rightCheckChange(right)" />
<span>{{ index + 1 + '.' }}</span>
<span :title="right.label">{{ right.label }}</span>
</div>
</transition-group>
</draggable>
<div v-if="rightData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
解析:
<draggable></draggable>
组件的使用此时整体的代码如下:
<template>
<div class="custom-transfer-cls">
<!-- 左侧列表 -->
<div class="left-side">
<h4>{{ titles[0] }}</h4>
<div v-for="left in leftData" :key="left.key" class="item-cls">
<el-checkbox :checked="left.checked" @change="leftCheckChange(left)" />
<span :title="left.label">{{ left.label }}</span>
</div>
<div v-if="leftData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
<!-- 向左、向右操作按钮 -->
<div class="btn-cls"></div>
<!-- 右侧列表 -->
<div class="right-side">
<h4>{{ titles[1] }}</h4>
<draggable v-model="rightData">
<transition-group>
<div v-for="(right, index) in rightData" :key="right.key" class="item-cls">
<el-checkbox :checked="right.checked" @change="rightCheckChange(right)" />
<span>{{ index + 1 + '.' }}</span>
<span :title="right.label">{{ right.label }}</span>
</div>
</transition-group>
</draggable>
<div v-if="rightData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'CustomTransferName',
components: {
draggable
},
props: {
allData: {
type: Array,
default: () => {
// 对象数组需要有label、key两个属性
return []
}
},
checkedData: {
type: Array,
default: () => {
// 对象数组需要有label、key两个属性
return []
}
},
emptypText: {
type: String,
default: '数据为空'
},
titles: {
type: Array,
default: () => {
return ['标题1', '标题2']
}
}
},
data () {
return {
leftData: [],
rightData: []
}
},
computed: {},
created () {},
mounted () {
// 初始化左侧列表1的数据
this.leftData = this.allData.map(a => {
a.checked = false
return a
})
// 初始化右侧列表2的数据
this.rightData = this.checkedData.map(a => {
a.checked = false
return a
})
},
methods: {
// 左边选中
leftCheckChange (check) {
this.leftData = this.leftData.map(l => {
if (l.key === check.key) {
l.checked = !l.checked
}
return l
})
},
// 右边选中
rightCheckChange (check) {
this.rightData = this.rightData.map(l => {
if (l.key === check.key) {
l.checked = !l.checked
}
return l
})
}
}
}
</script>
<style lang="less" scoped>
.custom-transfer-cls {
display: flex;
justify-content: space-between;
min-height: 120px;
.left-side,
.right-side {
height: 240px;
overflow-y: scroll;
background-color: white;
width: 140px;
border: 1px solid #eee;
border-radius: 4px;
h4 {
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
.empty-text {
text-align: center;
color: #ccc;
}
.item-cls {
margin-left: 12px;
margin-right: 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&::-webkit-scrollbar {
width: 1px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
}
&::-webkit-scrollbar-track {
background: #ededed;
}
}
}
</style>
穿梭框的向左、向右按钮,使用<el-button icon="el-icon-arrow-right"></el-button>
实现,代码如下:
<div class="btn-cls">
<el-button
:disabled="toRightDisable"
plain
type="default"
size="small"
icon="el-icon-arrow-right"
@click="toRight"
/>
<el-button
:disabled="toLeftDisable"
class="right-btn"
plain
type="default"
size="small"
icon="el-icon-arrow-left"
@click="toLeft"
/>
</div>
解析:
disabled
逻辑,在computed
中定义toRightDisable、toLeftDisable
;toRight、toLeft
,是对左右两侧列表数组的运算;此部分的代码如下:
<template>
<div class="custom-transfer-cls">
<div class="left-side"></div>
<!-- 向左、向右按钮开始 -->
<div class="btn-cls">
<el-button
:disabled="toRightDisable"
plain
type="default"
size="small"
icon="el-icon-arrow-right"
@click="toRight"
/>
<el-button
:disabled="toLeftDisable"
class="right-btn"
plain
type="default"
size="small"
icon="el-icon-arrow-left"
@click="toLeft"
/>
</div>
<!-- 向左、向右按钮结束 -->
<div class="right-side"></div>
</div>
</template>
<script>
export default {
name: 'CustomTransferName',
components: { },
props: {},
data () {
return {
leftData: [],
rightData: []
}
},
computed: {
// 向左穿梭按钮的disabled逻辑
toLeftDisable () {
return !this.rightData.some(r => r.checked)
},
// 向右穿梭按钮的disabled逻辑
toRightDisable () {
return !this.leftData.some(r => r.checked)
}
},
created () {},
mounted () { },
methods: {
// 数据向右穿梭
toRight () {
// 左减去,右加上
const leftUnchecked = this.leftData.filter(l => !l.checked)
const leftChecked = this.leftData.filter(l => l.checked)
this.leftData = leftUnchecked
this.rightData = [].concat(this.rightData, leftChecked).map(r => {
r.checked = false
return r
})
},
// 数据向左穿梭
toLeft () {
// 右减去,左加上
const rightUnchecked = this.rightData.filter(l => !l.checked)
const rightChecked = this.rightData.filter(l => l.checked)
this.rightData = rightUnchecked
this.leftData = [].concat(this.leftData, rightChecked).map(r => {
r.checked = false
return r
})
}
}
}
</script>
<style lang="less" scoped>
.custom-transfer-cls {
display: flex;
justify-content: space-between;
min-height: 120px;
.btn-cls {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.right-btn {
margin-left: 0;
margin-top: 8px;
}
}
}
</style>
即把rightData: []
数据通过$emit()
传递出去,父组件监听dragedData
事件之后获取; 定义函数 transferData()
,在拖动完成时的@end
事件调用,在向左向右更新了右侧列表数据之后调用;
代码如下:
methods: {
// 传递数据
transferData () {
this.$emit('dragedData', this.rightData)
}
}
<template>
<div class="custom-transfer-cls">
<!-- 左侧列表 -->
<div class="left-side">
<h4>{{ titles[0] }}</h4>
<div v-for="left in leftData" :key="left.key" class="item-cls">
<el-checkbox :checked="left.checked" @change="leftCheckChange(left)" />
<span :title="left.label">{{ left.label }}</span>
</div>
<div v-if="leftData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
<!-- 向左、向右按钮开始 -->
<div class="btn-cls">
<el-button
:disabled="toRightDisable"
plain
type="default"
size="small"
icon="h-icon-angle_right"
@click="toRight"
/>
<el-button
:disabled="toLeftDisable"
class="right-btn"
plain
type="default"
size="small"
icon="h-icon-angle_left"
@click="toLeft"
/>
</div>
<!-- 右侧列表 -->
<div class="right-side">
<h4>{{ titles[1] }}</h4>
<draggable v-model="rightData" @end="transferData">
<transition-group>
<div v-for="(right, index) in rightData" :key="right.key" class="item-cls">
<el-checkbox :checked="right.checked" @change="rightCheckChange(right)" />
<span>{{ index + 1 + '.' }}</span>
<span :title="right.label">{{ right.label }}</span>
</div>
</transition-group>
</draggable>
<div v-if="rightData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
</div>
</template>
<script>
// 可拖动组件
import draggable from 'vuedraggable'
export default {
name: 'CustomTransferName',
components: {
draggable
},
props: {
allData: {
type: Array,
default: () => {
// 对象数组需要有label、key两个属性
return []
}
},
checkedData: {
type: Array,
default: () => {
// 对象数组需要有label、key两个属性
return []
}
},
emptypText: {
type: String,
default: '数据为空'
},
titles: {
type: Array,
default: () => {
return ['标题1', '标题2']
}
}
},
data () {
return {
leftData: [],
rightData: []
}
},
computed: {
// 向左穿梭按钮的disabled逻辑
toLeftDisable () {
return !this.rightData.some(r => r.checked)
},
// 向右穿梭按钮的disabled逻辑
toRightDisable () {
return !this.leftData.some(r => r.checked)
}
},
created () {},
mounted () {
// 初始化左侧列表1的数据
this.leftData = this.allData.map(a => {
a.checked = false
return a
})
// 初始化右侧列表2的数据
this.rightData = this.checkedData.map(a => {
a.checked = false
return a
})
},
methods: {
// 传递数据
transferData () {
this.$emit('dragedData', this.rightData)
},
// 左边选中
leftCheckChange (check) {
this.leftData = this.leftData.map(l => {
if (l.key === check.key) {
l.checked = !l.checked
}
return l
})
},
// 右边选中
rightCheckChange (check) {
this.rightData = this.rightData.map(l => {
if (l.key === check.key) {
l.checked = !l.checked
}
return l
})
},
// 数据向右穿梭
toRight () {
// 左减去,右加上
const leftUnchecked = this.leftData.filter(l => !l.checked)
const leftChecked = this.leftData.filter(l => l.checked)
this.leftData = leftUnchecked
this.rightData = [].concat(this.rightData, leftChecked).map(r => {
r.checked = false
return r
})
// 传递数据
this.transferData()
},
// 数据向左穿梭
toLeft () {
// 右减去,左加上
const rightUnchecked = this.rightData.filter(l => !l.checked)
const rightChecked = this.rightData.filter(l => l.checked)
this.rightData = rightUnchecked
this.leftData = [].concat(this.leftData, rightChecked).map(r => {
r.checked = false
return r
})
// 传递数据
this.transferData()
}
}
}
</script>
<style lang="less" scoped>
.custom-transfer-cls {
display: flex;
justify-content: space-between;
min-height: 120px;
.left-side,
.right-side {
height: 240px;
overflow-y: scroll;
background-color: white;
width: 140px;
border: 1px solid #eee;
border-radius: 4px;
h4 {
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
.empty-text {
text-align: center;
color: #ccc;
}
.item-cls {
margin-left: 12px;
margin-right: 12px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&::-webkit-scrollbar {
width: 1px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
}
&::-webkit-scrollbar-track {
background: #ededed;
}
}
.btn-cls {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.right-btn {
margin-left: 0;
margin-top: 8px;
}
}
}
</style>
本文主要写了一个可拖动排序的穿梭框组件,更多关于拖动穿梭框CustormTransfer vue的资料请关注编程网其它相关文章!
--结束END--
本文标题: 手写可拖动穿梭框组件CustormTransfervue实现示例
本文链接: https://lsjlt.com/news/171221.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-12
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0