前言
这篇文章主要是分享一个时空穿梭框功能,也就是我们平时用的选择功能。勾选了的项就会进入到另一个框中。
时空穿梭框之旅
示例演示:

这个时空穿梭框实现了:
- 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、不用点中间的两个箭头,而是勾选后,就会自动穿梭到右边去。
这样的需求极为常见,所以你有必要在平时的学习中,把这些东西都自己整理出来,或者把常用的功能模块封装成通用组件,这个对于提高工作效率是非常有用的。花个两三天把它封装成一个常用的组件,以后开发起来,只要遇到这种的基本都可以搬过来用,顶多改改样式,所以也有必要给组件添加必要的属性为定制提供可能。又或者更过分点的,追加一些功能。