教你从零写vue穿梭框

1. 前言

最近项目中遇到一个需求:制作一个组件,左侧类似一个禁用的input输入框,右侧有一个选择按钮,点击后打开一个vue穿梭框,可以进行人员的选择,选择完成后,关闭弹框,选择人员的名单显示在input中。因为项目中多处用到,因此选择将其注册成全局组件。
在这里插入图片描述
在这里插入图片描述

2. 制作选择组件(select-input)

效果图:
在这里插入图片描述
在这里插入图片描述

Input.vue:

<template>
  <div>
    <div class="select-input">
      <span v-for="(item, index) in list" :key="index">{
    
    {
    
    
        index === list.length - 1 ? item[field] : item[field] + '、'
      }}</span>
    </div>
    <a-button type="link">选择</a-button>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    // 数组
    list: {
    
    
      type: Array,
      default: () => {
    
    
        return [
          {
    
    
            name: '张三',
            no: 'P001',
            tel: '13876787675',
            department: '工程部'
          }
        ]
      }
    },
    // 显示的数组字段
    field: {
    
    
      type: String,
      default: 'name'
    }
  },
  data() {
    
    
    return {
    
    }
  },
  created() {
    
    }
}
</script>
<style lang="less" scoped>
.select-input {
    
    
  display: inline-block;
  width: calc(100% - 70px);
  border: 1px solid #ccc;
  padding: 6px 11px;
  border-radius: 4px;
  height: 36px;
}
</style>

上面的input输入框,实际上只是个div设置了边框,并不具备真实的输入功能。
右侧的选择按钮作为备用,先放着,用于点击显示vue穿梭框使用。
为了演示效果,props.list设置了初始值,实际使用时应该设置为空数组[ ]。
index.js:

import Input from './Input.vue'
const SelectInput = {
    
    
  // install 为Vue实例上的一个方法
  install: function(Vue) {
    
    
    // 注册全局组件,此时MyBreadcrumb为使用的组件名称
    Vue.component('SelectInput', Input)
  }
}
// 导出组件
export default SelectInput

在main.js中全局挂载该组件:

import SelectInput from './components/select-input'
Vue.use(SelectInput)

之后在其他页面中通过标签<select-input></select-input>即可使用。

<template>
  <a-row>
    <a-col :span="5">
      <select-input></select-input>
    </a-col>
  </a-row>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    }
  },
  created() {
    
    }
}
</script>
<style lang="less" scoped></style>

2. 制作vue穿梭框组件(table-transfer)

效果图:
在这里插入图片描述
一共两个表格,上面一个表格带有查询条件,是通过接口查询获取的所有人员数据;下面一个表格是已经选中的人员表格,通过中间的两个按钮,可以实现选中人员的增加和删除。
在这里插入图片描述
Transfer.vue:

<template>
  <!-- 人员选择弹框 -->
  <a-modal
    :title="title"
    :visible="modalVisible"
    @ok="comfirm()"
    @cancel="cancel()"
    :width="1400"
    cancelText="取消"
    okText="选择"
  >
    <!-- 查询条件区域 -->
    <a-form class="query-con" :label-col="labelCol" :wrapper-col="wrapperCol">
      <a-row type="flex">
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="姓名">
            <a-input v-model="query.name"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="员工编号">
            <a-input v-model="query.no"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="联系电话">
            <a-input v-model="query.tel"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="部门">
            <a-select
              default-value="0"
              style="width: 120px"
              @change="handleChange"
            >
              <a-select-option
                v-for="(item, index) in department"
                :key="index"
                :value="item.value"
              >
                {
    
    {
    
     item.text }}
              </a-select-option>
            </a-select>
          </a-form-item>
        </a-col>
        <a-col :span="4" class="query-btn">
          <a-button type="primary">查询</a-button>
          <a-button>重置</a-button>
        </a-col>
      </a-row>
    </a-form>
    <!-- 查询表格区域 -->
    <a-table
      :row-selection="rowSelection"
      :columns="columns"
      :data-source="list"
      :rowKey="rowKey"
      :pagination="pagination"
      @change="changePage"
    >
    </a-table>
    <!-- 穿梭按钮区域 -->
    <a-row class="query-btn" type="flex" justify="center">
      <a-button @click="addSelectedRow()" type="primary">
        <a-icon type="down" />
      </a-button>
      <a-button @click="deleteSelectedRow()" type="primary">
        <a-icon type="up" />
      </a-button>
    </a-row>
    <!-- 选中表格区域 -->
    <a-table
      :row-selection="rowSelection2"
      :columns="columns"
      :data-source="selectedList"
      :rowKey="rowKey"
    >
    </a-table>
  </a-modal>
</template>

<script>
import _ from 'lodash'
export default {
    
    
  props: {
    
    
    // 弹框标题
    title: {
    
    
      type: String,
      default: '选择人员'
    },
    // 表格支持两种选择模式,复选框checkbox || 单选框radio
    type: {
    
    
      type: String,
      default: 'checkbox'
    },
    // 选中表格列表
    selectedList: {
    
    
      type: Array,
      default: () => {
    
    
        // return []
        return [
          {
    
    
            name: '张三',
            no: 'P001',
            tel: '13876787675',
            department: '工程部'
          }
        ]
      }
    },
    // 列表唯一标识rowKey
    rowKey: {
    
    
      type: String,
      default: 'no'
    }
  },
  data() {
    
    
    return {
    
    
      labelCol: {
    
    
        span: 8
      },
      wrapperCol: {
    
    
        span: 16
      },
      // 弹框是否可见
      modalVisible: true,
      query: {
    
    
        name: '',
        no: '',
        tel: ''
      },
      selectedRowKeys: [],
      selectedRows: [],
      selectedRowKeys2: [],
      selectedRows2: [],
      department: [
        {
    
    
          value: '0',
          text: '工程部'
        },
        {
    
    
          value: '1',
          text: '运维部'
        },
        {
    
    
          value: '2',
          text: '财务部'
        },
        {
    
    
          value: '3',
          text: '人事部'
        }
      ],
      // 待办列表表格列
      columns: [
        {
    
    
          title: '序号',
          dataIndex: 'index',
          key: 'index',
          align: 'center',
          width: 100,
          customRender: (text, record, index) => `${
      
      index + 1}`
        },
        {
    
    
          title: '姓名',
          dataIndex: 'name'
        },
        {
    
    
          title: '员工编号',
          dataIndex: 'no'
        },
        {
    
    
          title: '联系电话',
          dataIndex: 'tel'
        },
        {
    
    
          title: '部门',
          dataIndex: 'department'
        }
      ],
      list: [
        {
    
    
          name: '张三',
          no: 'P001',
          tel: '13876787675',
          department: '工程部',
          disabled: true
        },
        {
    
    
          name: '李四',
          no: 'P002',
          tel: '13876787675',
          department: '工程部'
        },
        {
    
    
          name: '王五',
          no: 'P003',
          tel: '13876787675',
          department: '工程部'
        }
      ],
      // 表格分页器配置
      pagination: {
    
    
        defaultCurrent: 1,
        defaultPageSize: 5,
        pageSizeOptions: ['2', '5', '10', '20', '50'],
        showSizeChanger: true,
        showQuickJumper: true,
        showTotal: total => {
    
    
          return `${
      
      total}`
        },
        total: 0
      }
    }
  },
  created() {
    
    },
  methods: {
    
    
    // 显示选择人员弹框
    show() {
    
    
      this.modalVisible = true
    },
    // 点击选择按钮,抛出选中行
    comfirm() {
    
    
      // this.modalVisible = false
      this.$emit('comfirm', this.selectedList)
    },
    cancel() {
    
    
      this.modalVisible = false
    },
    handleChange(value) {
    
    
      console.log(`selected ${
      
      value}`)
    },
    changePage(pagination, filters, sorter, {
     
      currentDataSource }) {
    
    },
    // 将查询表格选中的行添加到选中表格中
    addSelectedRow() {
    
    
      for (let i = 0; i < this.selectedRows.length; i++) {
    
    
        // 先判断选中表格中是否已经有该行,如果有跳出本次循环
        let flag = false
        for (let j = 0; j < this.selectedList.length; j++) {
    
    
          if (
            this.selectedRows[i][this.rowKey] ===
            this.selectedList[j][this.rowKey]
          ) {
    
    
            flag = true
          }
        }
        if (flag) {
    
    
          continue
        }
        const selectedRow = _.cloneDeep(this.selectedRows[i])
        console.log('hahah', selectedRow)
        // 如果是单选模式,需要先删除第一个元素,因为是prop元素,所以不能直接赋值
        if (this.type === 'radio') {
    
    
          this.selectedList.splice(0, 1)
        }
        this.selectedList.push(selectedRow)
      }
    },
    // 将选中表格的选中行删除
    deleteSelectedRow() {
    
    
      for (let i = 0; i < this.selectedRows2.length; i++) {
    
    
        for (let j = 0; j < this.selectedList.length; j++) {
    
    
          if (
            this.selectedRows2[i][this.rowKey] ===
            this.selectedList[j][this.rowKey]
          ) {
    
    
            this.selectedList.splice(j, 1)
            break
          }
        }
      }
    }
  },
  computed: {
    
    
    // 查询表格选择框设置
    rowSelection() {
    
    
      return {
    
    
        type: this.type,
        // 选中变化事件
        onChange: (selectedRowKeys, selectedRows) => {
    
    
          this.selectedRowKeys = selectedRowKeys
          this.selectedRows = selectedRows
        }
      }
    },
    // 选中表格选择框设置
    rowSelection2() {
    
    
      return {
    
    
        type: this.type,
        onChange: (selectedRowKeys, selectedRows) => {
    
    
          this.selectedRowKeys2 = selectedRowKeys
          this.selectedRows2 = selectedRows
        }
      }
    }
  }
}
</script>
<style lang="less" scoped>
.query-con {
    
    
  .ant-form-item .ant-form-item-control-wrapper {
    
    
    display: block;
    box-sizing: border-box;
    width: 66.66666667%;
  }
  .ant-select {
    
    
    width: 100% !important;
  }
  .query-btn {
    
    
    display: flex;
    margin-top: 4px;
    .ant-btn {
    
    
      margin-left: 10px;
    }
  }
}
</style>

因为这个穿梭框组件是整个项目通用的人员选择弹框,所以我直接将查询条件和表格数据直接写进了组件中。为了更加方便理解,同学们在看这段代码时,可以先忽略或者注释查询条件区域的代码。
如果需要更加通用的表格穿梭框组件,可以将其中的columns和list抽离出来作为props的属性。
index.js:

import Transfer from './Transfer.vue'
const TableTransfer = {
    
    
  // install 为Vue实例上的一个方法
  install: function(Vue) {
    
    
    // 注册全局组件,此时MyBreadcrumb为使用的组件名称
    Vue.component('TableTransfer', Transfer)
  }
}
// 导出组件
export default TableTransfer

在main.js中全局挂载该组件:

import TableTransfer from './components/table-transfer'
Vue.use(TableTransfer)

之后在其他页面中通过标签<table-transfer></table-transfer>即可使用。

<template>
  <a-row>
    <!-- <a-col :span="5">
      <select-input></select-input>
    </a-col> -->
    <table-transfer></table-transfer>
  </a-row>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    }
  },
  created() {
    
    }
}
</script>
<style lang="less" scoped></style>

4. 将选择组件和穿梭框组件结合使用

4.1 点击选择组件按钮,弹出穿梭框

(1)在select-input组件中添加点击事件,并且暴露出去

在这里插入图片描述
(2)在页面中通过ref引用穿梭框的显示方法
在这里插入图片描述
在这里插入图片描述

4.2 将select-input组件中的数据显示在弹框中的已选表格中

4.2.1 全局引入lodash

在终端通过npm i lodash下载lodash
在main.js中全局引入:

import _ from 'lodash'
Vue.prototype._ = _

4.2.2 初始化input-select组件list数据和穿梭框的selectList(已选中的人员)

在这里插入图片描述
在这里插入图片描述
点击选择按钮:
在这里插入图片描述

4.3 将重新选择后的表格数据显示在select-input组件中

在这里插入图片描述
在这里插入图片描述
重新选择人员后,点击确认:
在这里插入图片描述
在这里插入图片描述

4.3 单选模式

在这里插入图片描述
在使用穿梭框时,只需要设置 type 为 radio 即可切换成单选模式
在这里插入图片描述

5. 完整代码:

5.1 select-input 组件代码:

<template>
  <div class="select-input-con">
    <div class="select-input">
      <span v-for="(item, index) in list" :key="index">{
    
    {
    
    
        index === list.length - 1 ? item[field] : item[field] + '、'
      }}</span>
    </div>
    <a-button @click="showSelectModal()" type="link">选择</a-button>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    // 数组
    list: {
    
    
      type: Array,
      default: () => {
    
    
        return []
      }
    },
    // 显示的数组字段
    field: {
    
    
      type: String,
      default: 'name'
    }
  },
  data() {
    
    
    return {
    
    }
  },
  created() {
    
    },
  methods: {
    
    
    showSelectModal() {
    
    
      this.$emit('showSelectModal')
    }
  }
}
</script>
<style lang="less" scoped>
.select-input-con {
    
    
  display: flex;
  align-items: center;
  .select-input {
    
    
    display: inline-block;
    width: calc(100% - 70px);
    border: 1px solid #ccc;
    padding: 6px 11px;
    border-radius: 4px;
    height: 36px;
  }
}
</style>

5.2 table-transfer 组件代码:

<template>
  <!-- 人员选择弹框 -->
  <a-modal
    :title="title"
    :visible="modalVisible"
    @ok="comfirm()"
    @cancel="cancel()"
    :width="1400"
    cancelText="取消"
    okText="选择"
  >
    <!-- 查询条件区域 -->
    <a-form class="query-con" :label-col="labelCol" :wrapper-col="wrapperCol">
      <a-row type="flex">
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="姓名">
            <a-input v-model="query.name"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="员工编号">
            <a-input v-model="query.no"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="联系电话">
            <a-input v-model="query.tel"></a-input>
          </a-form-item>
        </a-col>
        <a-col :span="5">
          <a-form-item :wrapper-col="wrapperCol" label="部门">
            <a-select
              default-value="0"
              style="width: 120px"
              @change="handleChange"
            >
              <a-select-option
                v-for="(item, index) in department"
                :key="index"
                :value="item.value"
              >
                {
    
    {
    
     item.text }}
              </a-select-option>
            </a-select>
          </a-form-item>
        </a-col>
        <a-col :span="4" class="query-btn">
          <a-button type="primary">查询</a-button>
          <a-button>重置</a-button>
        </a-col>
      </a-row>
    </a-form>
    <!-- 查询表格区域 -->
    <a-table
      :row-selection="rowSelection"
      :columns="columns"
      :data-source="list"
      :rowKey="rowKey"
      :pagination="pagination"
      @change="changePage"
    >
    </a-table>
    <!-- 穿梭按钮区域 -->
    <a-row class="query-btn" type="flex" justify="center">
      <a-button @click="addSelectedRow()" type="primary">
        <a-icon type="down" />
      </a-button>
      <a-button @click="deleteSelectedRow()" type="primary">
        <a-icon type="up" />
      </a-button>
    </a-row>
    <!-- 选中表格区域 -->
    <a-table
      :row-selection="rowSelection2"
      :columns="columns"
      :data-source="selectedList"
      :rowKey="rowKey"
    >
    </a-table>
  </a-modal>
</template>

<script>
import _ from 'lodash'
export default {
    
    
  props: {
    
    
    // 弹框标题
    title: {
    
    
      type: String,
      default: '选择人员'
    },
    // 表格支持两种选择模式,复选框checkbox || 单选框radio
    type: {
    
    
      type: String,
      default: 'checkbox'
    },
    // 选中表格列表
    selectedList: {
    
    
      type: Array,
      default: () => {
    
    
        return []
      }
    },
    // 列表唯一标识rowKey
    rowKey: {
    
    
      type: String,
      default: 'no'
    }
  },
  data() {
    
    
    return {
    
    
      labelCol: {
    
    
        span: 8
      },
      wrapperCol: {
    
    
        span: 16
      },
      // 弹框是否可见
      modalVisible: false,
      query: {
    
    
        name: '',
        no: '',
        tel: ''
      },
      selectedRowKeys: [],
      selectedRows: [],
      selectedRowKeys2: [],
      selectedRows2: [],
      department: [
        {
    
    
          value: '0',
          text: '工程部'
        },
        {
    
    
          value: '1',
          text: '运维部'
        },
        {
    
    
          value: '2',
          text: '财务部'
        },
        {
    
    
          value: '3',
          text: '人事部'
        }
      ],
      // 待办列表表格列
      columns: [
        {
    
    
          title: '序号',
          dataIndex: 'index',
          key: 'index',
          align: 'center',
          width: 100,
          customRender: (text, record, index) => `${
      
      index + 1}`
        },
        {
    
    
          title: '姓名',
          dataIndex: 'name'
        },
        {
    
    
          title: '员工编号',
          dataIndex: 'no'
        },
        {
    
    
          title: '联系电话',
          dataIndex: 'tel'
        },
        {
    
    
          title: '部门',
          dataIndex: 'department'
        }
      ],
      list: [
        {
    
    
          name: '张三',
          no: 'P001',
          tel: '13876787675',
          department: '工程部',
          disabled: true
        },
        {
    
    
          name: '李四',
          no: 'P002',
          tel: '13876787675',
          department: '工程部'
        },
        {
    
    
          name: '王五',
          no: 'P003',
          tel: '13876787675',
          department: '工程部'
        }
      ],
      // 表格分页器配置
      pagination: {
    
    
        defaultCurrent: 1,
        defaultPageSize: 5,
        pageSizeOptions: ['2', '5', '10', '20', '50'],
        showSizeChanger: true,
        showQuickJumper: true,
        showTotal: total => {
    
    
          return `${
      
      total}`
        },
        total: 0
      }
    }
  },
  created() {
    
    },
  methods: {
    
    
    // 显示选择人员弹框
    show() {
    
    
      this.modalVisible = true
    },
    // 点击选择按钮,抛出选中行
    comfirm() {
    
    
      // this.modalVisible = false
      this.$emit('comfirm', this.selectedList)
    },
    cancel() {
    
    
      this.modalVisible = false
    },
    handleChange(value) {
    
    
      console.log(`selected ${
      
      value}`)
    },
    changePage(pagination, filters, sorter, {
     
      currentDataSource }) {
    
    },
    // 将查询表格选中的行添加到选中表格中
    addSelectedRow() {
    
    
      for (let i = 0; i < this.selectedRows.length; i++) {
    
    
        // 先判断选中表格中是否已经有该行,如果有跳出本次循环
        let flag = false
        for (let j = 0; j < this.selectedList.length; j++) {
    
    
          if (
            this.selectedRows[i][this.rowKey] ===
            this.selectedList[j][this.rowKey]
          ) {
    
    
            flag = true
          }
        }
        if (flag) {
    
    
          continue
        }
        const selectedRow = _.cloneDeep(this.selectedRows[i])
        console.log('hahah', selectedRow)
        // 如果是单选模式,需要先删除第一个元素,因为是prop元素,所以不能直接赋值
        if (this.type === 'radio') {
    
    
          this.selectedList.splice(0, 1)
        }
        this.selectedList.push(selectedRow)
      }
    },
    // 将选中表格的选中行删除
    deleteSelectedRow() {
    
    
      for (let i = 0; i < this.selectedRows2.length; i++) {
    
    
        for (let j = 0; j < this.selectedList.length; j++) {
    
    
          if (
            this.selectedRows2[i][this.rowKey] ===
            this.selectedList[j][this.rowKey]
          ) {
    
    
            this.selectedList.splice(j, 1)
            break
          }
        }
      }
    }
  },
  computed: {
    
    
    // 查询表格选择框设置
    rowSelection() {
    
    
      return {
    
    
        type: this.type,
        // 选中变化事件
        onChange: (selectedRowKeys, selectedRows) => {
    
    
          this.selectedRowKeys = selectedRowKeys
          this.selectedRows = selectedRows
        }
      }
    },
    // 选中表格选择框设置
    rowSelection2() {
    
    
      return {
    
    
        type: this.type,
        onChange: (selectedRowKeys, selectedRows) => {
    
    
          this.selectedRowKeys2 = selectedRowKeys
          this.selectedRows2 = selectedRows
        }
      }
    }
  }
}
</script>
<style lang="less" scoped>
.query-con {
    
    
  .ant-form-item .ant-form-item-control-wrapper {
    
    
    display: block;
    box-sizing: border-box;
    width: 66.66666667%;
  }
  .ant-select {
    
    
    width: 100% !important;
  }
  .query-btn {
    
    
    display: flex;
    margin-top: 4px;
    .ant-btn {
    
    
      margin-left: 10px;
    }
  }
}
</style>

5.3 结合select-input和table-transfer 完整代码:

<template>
  <a-row>
    <a-col :span="5">
      <select-input
        :list="list"
        @showSelectModal="showSelectModal"
      ></select-input>
    </a-col>
    <table-transfer
      :selectedList="selectedList"
      @comfirm="comfirm"
      type="radio"
      ref="staffmodal"
    ></table-transfer>
  </a-row>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      list: [],
      selectedList: []
    }
  },
  created() {
    
    },
  methods: {
    
    
    showSelectModal() {
    
    
      // 深拷贝给已选择人员
      this.selectedList = this._.cloneDeep(this.list)
      this.$refs.staffmodal.show()
    },
    comfirm(selectList) {
    
    
      this.list = selectList
      this.selectedList = []
      this.$refs.staffmodal.cancel()
    }
  }
}
</script>
<style lang="less" scoped></style>

猜你喜欢

转载自blog.csdn.net/qq_39055970/article/details/123899894