前言
這篇文章主要是分享一個時空穿梭框功能,也就是我們平時用的選擇功能。勾選了的項就會進入到另一個框中。
時空穿梭框之旅
示例演示:
這個時空穿梭框實現了:
- 1、可以全選、反選
- 2、沒有選中時,不可以點穿梭按鈕
- 3、自動計數(共有多少個,選中了多少個)
- 4、沒有數據時,全選不可點擊
這里主要是想通過這個示例來拋磚引玉,更多的功能,你可以根據自己的實踐需要來實現。下面我們就來看看這示例的相關文件及代碼。
├── index.html
├── main.js
├── router
│ └── index.js # 路由配置文件
└── components # 組件目錄
├── App.vue # 根組件
├── Home.vue # 大的框架結構組件
├── ChangeBox.vue
└── ChangeBoxArea.vue
文件也不多,只要有兩個(ChangeBox.vue 和 ChangeBoxArea.vue)下面我們就來看看實現這個示例的代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>changebox</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
本示例主要用到了 bootstrap ,所以我們就在 index.html 中引入了 bootstrap 的 cdn。然后我們就可以直接在示例中使用 bootstrap 給我們提供的 UI 了。
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
}]
})
在這里我們直接把 / 路徑的配置到 Home 組件。
<template>
<div id="app">
<Home></Home>
</div>
</template>
<script>
import Home from "@/components/Home";
export default {
name: "App",
components: { Home }
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin-top: 60px;
}
</style>
在根組件中,我們只是做了一件很簡單的事,就是引入Home 組件。
<template>
<div>
<change-box></change-box>
</div>
</template>
<script>
import ChangeBox from "@/components/ChangeBox";
export default {
name: "Home",
components: {
ChangeBox
}
};
</script>
Home.vue 組件代碼非常地簡單,這里其實也可以直接寫到根組件上的,但為了養成良好的習慣,我們還是有必要把示例的整體結構往寫成更接近實戰一些。
好了,上面的基本功做好了之后,我就可以開始這個時空穿梭框的主要代碼了的展示了。
<template>
<div class="container">
<div class="row">
<div class="col-md-5">
<change-box-area :title="sourceTitle" :data="sourceList"></change-box-area>
</div>
<div class="col-md-2 text-center">
<p><button :disabled="sourceList.length === 0 || sourceRefNum === 0" class="btn btn-primary" @click="toTarget()">》</button></p>
<p><button :disabled="targetList.length === 0 || targetRefNum === 0" class="btn btn-primary" @click="toSource()">《</button></p>
</div>
<div class="col-md-5">
<change-box-area :title="targetTitle" :data="targetList"></change-box-area>
</div>
</div>
</div>
</template>
<script>
import ChangeBoxArea from "./ChangeBoxArea";
// 這里的 isSeleted 屬性可以不用添加,可以在 JS 中進行處理,一般情況下后端返回的數據也不會帶有類似這種靜態狀態的屬性
let dataList = [
{ id: 1, name: "HTML5", isSelected: false },
{ id: 2, name: "CSS3", isSelected: false },
{ id: 3, name: "Angular", isSelected: false },
{ id: 4, name: "Vue", isSelected: false },
{ id: 5, name: "Linux", isSelected: false },
{ id: 6, name: "JavaScript", isSelected: false }
];
export default {
components: {
ChangeBoxArea
},
name: "ChangeBox",
data() {
return {
sourceTitle: "請選擇",
targetTitle: "已選擇",
sourceList: dataList,
targetList: []
};
},
methods: {
exchange(fd, td) {
let selectedItem = fd.filter(item => item.isSelected).map(item => {
return {
...item,
isSelected: false
};
});
td.push(...selectedItem);
return fd.filter(item => !item.isSelected);
},
// 把選擇數據轉移到目標(右框)
toTarget() {
this.sourceList = this.exchange(this.sourceList, this.targetList);
},
// 把選擇數據轉回到源(左框)
toSource() {
this.targetList = this.exchange(this.targetList, this.sourceList);
}
},
computed: {
// 源數據中選中的數量
sourceRefNum() {
return this.sourceList.filter(item => item.isSelected).length;
},
// 目標數據中選中的數量
targetRefNum() {
return this.targetList.filter(item => item.isSelected).length;
}
}
};
</script>
接下來我們再來看看最后一個
<template>
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="pull-left">
<div class="checkbox">
<label>
<input :disabled="data.length === 0" type="checkbox" @click="toggleAll()" :checked="selectedAllStatus"><span>{{title}}</span>
</label>
</div>
</div>
<span class="pull-right">{{selectItemNumber}}/{{data.length}}</span>
</div>
<div class="panel-body">
<ul>
<li v-for="item in data" :key="item.id">
<div class="checkbox">
<label>
<input type="checkbox" v-model="item.isSelected"> {{item.name}}
</label>
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: "ChangeBox",
props: ["title", "data"],
computed: {
// 選擇的數量
selectItemNumber() {
return this.data.filter(item => item.isSelected).length;
},
// 全選狀態
selectedAllStatus() {
if (
this.selectItemNumber === this.data.length &&
this.selectItemNumber !== 0
) {
return true;
} else {
return false;
}
}
},
methods: {
// 全選及反選
toggleAll() {
let len = this.data.length;
let slen = this.data.filter(item => item.isSelected).length;
if (len !== slen) {
this.data.map(item => (item.isSelected = true));
} else {
this.data.map(item => (item.isSelected = false));
}
}
}
};
</script>
<style scoped>
ul {
list-style: none;
padding: 0;
}
.checkbox {
margin: 0;
}
</style>
在上面的代碼中,有一個地圖需要特別的注意下:在全選的input 中我們要使用 :checked 來綁定 selectedAllStatus,而不用 v-model,因為我們的 selectedAllStatus 是一個計算屬性,如果把它綁定到 v-model,會報錯的:
報錯
[Vue warn]: Computed property “selectedAllStatus” was assigned to but it has no setter.
大概意思是說 selectedAllStatus 沒有 setter 方法,不能給它賦值,當然,你可以把給這個屬性添加 setter 方法,但這樣做好像又有點累贅了。為此我們直接使用 :checked 來綁定 selectedAllStatus 屬性。
Vue 實現時空穿梭框功能模塊就分享到這里,其實這樣的需求示例在真實的項目中是有可能出現的,但在項目中這個很有可能會更加復雜,比如:
- 1、左邊的框不是平鋪的,而是多級,可展開收縮,勾選了的項才會出現在右邊的框中
- 2、搜索功能
- 3、不用點中間的兩個箭頭,而是勾選后,就會自動穿梭到右邊去。
這樣的需求極為常見,所以你有必要在平時的學習中,把這些東西都自己整理出來,或者把常用的功能模塊封裝成通用組件,這個對於提高工作效率是非常有用的。花個兩三天把它封裝成一個常用的組件,以后開發起來,只要遇到這種的基本都可以搬過來用,頂多改改樣式,所以也有必要給組件添加必要的屬性為定制提供可能。又或者更過分點的,追加一些功能。