<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
需求是實現類似 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的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45