手寫可拖動穿梭框組件CustormTransfer vue實(shí)現(xiàn)示例
本文內(nèi)容
需求是實(shí)現(xiàn)類似 el-transfer的組件,右側(cè)框內(nèi)容可以拖動排序;
手寫div樣式 + vuedraggable組件實(shí)現(xiàn)。
最終效果圖

組件html布局
新建一個(gè)組件文件 CustormTransfer.vue,穿梭框 html 分為左中右三部分,使用flex布局使其橫向布局,此時(shí)代碼如下
<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>
此時(shí)頁面上看不到組件內(nèi)容。
穿梭框左側(cè)內(nèi)容
左側(cè)內(nèi)容是個(gè)列表,列表的每一項(xiàng)是多選框checkbox加文字標(biāo)題,列表最上面是標(biāo)題;所以.left-side的代碼如下:
<div class="left-side">
<!-- 標(biāo)題 -->
<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>
<!-- 數(shù)據(jù)為空時(shí)顯示 -->
<div v-if="leftData.length === 0" class="empty-text">{{ emptypText }}</div>
</div>
解析:
- 列表標(biāo)題使用
h4標(biāo)簽,titles是組件使用者傳入props的標(biāo)題數(shù)組的第一項(xiàng); - 列表數(shù)據(jù)
leftData是組件使用者傳入的數(shù)據(jù)處理之后的,因?yàn)槲覀兡J(rèn)el-checkbox不勾選,所以在生命周期mounted時(shí),checked設(shè)為false; el-checkbox觸發(fā)change事件時(shí),執(zhí)行函數(shù)leftCheckChange(left),去改變leftData數(shù)組對應(yīng)項(xiàng)的checked設(shè)為取反;- 當(dāng)
leftData數(shù)據(jù)為空時(shí),顯示數(shù)據(jù)為空的文本,此文本組件使用者可通過 屬性emptypText傳入,默認(rèn)'數(shù)據(jù)為空'; - 列表的每一項(xiàng)的樣式在
.item-cls定義,內(nèi)容過長時(shí)顯示省略號,在title屬性中顯示全部內(nèi)容; - 列表整體內(nèi)容多時(shí),顯示滾動條,滾動條樣式重寫;
以上內(nèi)容加上樣式、函數(shù)后如下:
<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: () => {
// 對象數(shù)組需要有l(wèi)abel、key兩個(gè)屬性
return []
}
},
emptypText: {
type: String,
default: '數(shù)據(jù)為空'
},
titles: {
type: Array,
default: () => {
return ['列表 1', '列表 2']
}
}
},
data () {
return {
leftData: []
}
},
computed: { },
created () {},
mounted () {
// 初始化列表1的數(shù)據(jù)
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 {
/* 列表標(biāo)題在列表滾動時(shí)吸附在頂部 */
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
/* 數(shù)據(jù)為空的樣式 */
.empty-text {
text-align: center;
color: #ccc;
}
/* 列表每項(xiàng)的樣式,文字很長時(shí)顯示省略號 */
.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>
穿梭框右側(cè)內(nèi)容
右側(cè)的列表需要具有可拖動排序的功能,我使用的使 vuedraggable組件,所以首先需要先安裝npm install vuedraggable -S, 再引入 import draggable from 'vuedraggable',使用時(shí)配合 <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>
解析:
- 右側(cè)的列表樣式和左側(cè)一樣;
- 只是多了一個(gè)
<draggable></draggable>組件的使用
此時(shí)整體的代碼如下:
<template>
<div class="custom-transfer-cls">
<!-- 左側(cè)列表 -->
<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>
<!-- 右側(cè)列表 -->
<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: () => {
// 對象數(shù)組需要有l(wèi)abel、key兩個(gè)屬性
return []
}
},
checkedData: {
type: Array,
default: () => {
// 對象數(shù)組需要有l(wèi)abel、key兩個(gè)屬性
return []
}
},
emptypText: {
type: String,
default: '數(shù)據(jù)為空'
},
titles: {
type: Array,
default: () => {
return ['標(biāo)題1', '標(biāo)題2']
}
}
},
data () {
return {
leftData: [],
rightData: []
}
},
computed: {},
created () {},
mounted () {
// 初始化左側(cè)列表1的數(shù)據(jù)
this.leftData = this.allData.map(a => {
a.checked = false
return a
})
// 初始化右側(cè)列表2的數(shù)據(jù)
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 {
/* 列表標(biāo)題在列表滾動時(shí)吸附在頂部 */
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
/* 數(shù)據(jù)為空的樣式 */
.empty-text {
text-align: center;
color: #ccc;
}
/* 列表每項(xiàng)的樣式,文字很長時(shí)顯示省略號 */
.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>實(shí)現(xiàn),代碼如下:
<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; - 按鈕的點(diǎn)擊事件
toRight、toLeft,是對左右兩側(cè)列表數(shù)組的運(yùn)算;
此部分的代碼如下:
<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>
<!-- 向左、向右按鈕結(jié)束 -->
<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: {
// 數(shù)據(jù)向右穿梭
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
})
},
// 數(shù)據(jù)向左穿梭
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>
把排序好的穿梭數(shù)據(jù)傳給父組件
即把rightData: []數(shù)據(jù)通過$emit()傳遞出去,父組件監(jiān)聽dragedData事件之后獲??; 定義函數(shù) transferData(),在拖動完成時(shí)的@end事件調(diào)用,在向左向右更新了右側(cè)列表數(shù)據(jù)之后調(diào)用;
代碼如下:
methods: {
// 傳遞數(shù)據(jù)
transferData () {
this.$emit('dragedData', this.rightData)
}
}
整體代碼
<template>
<div class="custom-transfer-cls">
<!-- 左側(cè)列表 -->
<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>
<!-- 右側(cè)列表 -->
<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: () => {
// 對象數(shù)組需要有l(wèi)abel、key兩個(gè)屬性
return []
}
},
checkedData: {
type: Array,
default: () => {
// 對象數(shù)組需要有l(wèi)abel、key兩個(gè)屬性
return []
}
},
emptypText: {
type: String,
default: '數(shù)據(jù)為空'
},
titles: {
type: Array,
default: () => {
return ['標(biāo)題1', '標(biāo)題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 () {
// 初始化左側(cè)列表1的數(shù)據(jù)
this.leftData = this.allData.map(a => {
a.checked = false
return a
})
// 初始化右側(cè)列表2的數(shù)據(jù)
this.rightData = this.checkedData.map(a => {
a.checked = false
return a
})
},
methods: {
// 傳遞數(shù)據(jù)
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
})
},
// 數(shù)據(jù)向右穿梭
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
})
// 傳遞數(shù)據(jù)
this.transferData()
},
// 數(shù)據(jù)向左穿梭
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
})
// 傳遞數(shù)據(jù)
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;
/* 標(biāo)題樣式 */
h4 {
position: sticky;
top: 0px;
z-index: 9;
background: white;
text-align: center;
font-weight: 400;
margin-bottom: 16px;
}
/* 數(shù)據(jù)為空時(shí)的樣式 */
.empty-text {
text-align: center;
color: #ccc;
}
/* 列表每一項(xiàng)樣式 */
.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>
小結(jié)
本文主要寫了一個(gè)可拖動排序的穿梭框組件,更多關(guān)于拖動穿梭框CustormTransfer vue的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 使用class創(chuàng)建和清除水印的示例代碼
這篇文章主要介紹了vue 使用class創(chuàng)建和清除水印的示例代碼,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下2020-12-12
Vue快速實(shí)現(xiàn)通用表單驗(yàn)證的示例代碼
這篇文章主要介紹了Vue快速實(shí)現(xiàn)通用表單驗(yàn)證的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Vue報(bào)錯(cuò)ERR_OSSL_EVP_UNSUPPORTED解決方法
Vue項(xiàng)目啟動時(shí)報(bào)錯(cuò)ERR_OSSL_EVP_UNSUPPORTED,本文主要介紹了Vue報(bào)錯(cuò)ERR_OSSL_EVP_UNSUPPORTED解決方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
vue history 模式打包部署在域名的二級目錄的配置指南
這篇文章主要介紹了vue history 模式打包部署在域名的二級目錄的配置指南 ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07
vue自定義一個(gè)v-model的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue自定義一個(gè)v-model的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
Vue的移動端多圖上傳插件vue-easy-uploader的示例代碼
這篇文章主要介紹了Vue的移動端多圖上傳插件vue-easy-uploader的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11

