手寫可拖動穿梭框組件CustormTransfer vue實(shí)現(xiàn)示例
本文內(nèi)容
需求是實(shí)現(xiàn)類似 el-transfer
的組件,右側(cè)框內(nèi)容可以拖動排序;
手寫div
樣式 + vuedraggable
組件實(shí)現(xiàn)。
最終效果圖
組件html布局
新建一個組件文件 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>
此時頁面上看不到組件內(nèi)容。
穿梭框左側(cè)內(nèi)容
左側(cè)內(nèi)容是個列表,列表的每一項是多選框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ù)為空時顯示 --> <div v-if="leftData.length === 0" class="empty-text">{{ emptypText }}</div> </div>
解析:
- 列表標(biāo)題使用
h4
標(biāo)簽,titles是組件使用者傳入props的標(biāo)題數(shù)組的第一項; - 列表數(shù)據(jù)
leftData
是組件使用者傳入的數(shù)據(jù)處理之后的,因為我們默認(rèn)el-checkbox
不勾選,所以在生命周期mounted時,checked設(shè)為false; el-checkbox
觸發(fā)change事件時,執(zhí)行函數(shù)leftCheckChange(left)
,去改變leftData
數(shù)組對應(yīng)項的checked設(shè)為取反;- 當(dāng)
leftData
數(shù)據(jù)為空時,顯示數(shù)據(jù)為空的文本,此文本組件使用者可通過 屬性emptypText
傳入,默認(rèn)'數(shù)據(jù)為空'; - 列表的每一項的樣式在
.item-cls
定義,內(nèi)容過長時顯示省略號,在title
屬性中顯示全部內(nèi)容; - 列表整體內(nèi)容多時,顯示滾動條,滾動條樣式重寫;
以上內(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兩個屬性 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)題在列表滾動時吸附在頂部 */ 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; } /* 列表每項的樣式,文字很長時顯示省略號 */ .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'
,使用時配合 <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è)一樣;
- 只是多了一個
<draggable></draggable>
組件的使用
此時整體的代碼如下:
<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兩個屬性 return [] } }, checkedData: { type: Array, default: () => { // 對象數(shù)組需要有l(wèi)abel、key兩個屬性 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)題在列表滾動時吸附在頂部 */ 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; } /* 列表每項的樣式,文字很長時顯示省略號 */ .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()
,在拖動完成時的@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兩個屬性 return [] } }, checkedData: { type: Array, default: () => { // 對象數(shù)組需要有l(wèi)abel、key兩個屬性 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ù)為空時的樣式 */ .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>
小結(jié)
本文主要寫了一個可拖動排序的穿梭框組件,更多關(guān)于拖動穿梭框CustormTransfer vue的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue 使用class創(chuàng)建和清除水印的示例代碼
這篇文章主要介紹了vue 使用class創(chuàng)建和清除水印的示例代碼,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下2020-12-12Vue報錯ERR_OSSL_EVP_UNSUPPORTED解決方法
Vue項目啟動時報錯ERR_OSSL_EVP_UNSUPPORTED,本文主要介紹了Vue報錯ERR_OSSL_EVP_UNSUPPORTED解決方法,具有一定的參考價值,感興趣的可以了解一下2024-08-08vue history 模式打包部署在域名的二級目錄的配置指南
這篇文章主要介紹了vue history 模式打包部署在域名的二級目錄的配置指南 ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-07-07vue自定義一個v-model的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue自定義一個v-model的實(shí)現(xiàn)代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06Vue的移動端多圖上傳插件vue-easy-uploader的示例代碼
這篇文章主要介紹了Vue的移動端多圖上傳插件vue-easy-uploader的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11