【sgTransfer】自定义组件:带有翻页、页码、分页器的穿梭框组件,支持大批量数据的穿梭显示。

简介: 【sgTransfer】自定义组件:带有翻页、页码、分页器的穿梭框组件,支持大批量数据的穿梭显示。


特性:

  1. 表格宽度可以自定义
  2. 翻页器显示控件可以自定义
  3. 列配置项可以设置显示字段列名称、宽度、字段名
  4. 可以配置搜索框提示文本,支持搜索过滤
  5. 穿梭框顶部标题可以自定义
  6. 左右箭头按钮文本可以设置

sgTransfer源码

<template>
  <div :class="$options.name">
    <div
      class="sg-start"
      :style="{ width: width, height: height, ...style, ...style_start }"
    >
      <div class="sg-title" v-if="titles">
        {{ titles[0] }}
      </div>
      <div class="sg-search">
        <el-input
          style="width: 100%"
          v-model.trim="inputSearchValue_start"
          maxlength="20"
          :show-word-limit="false"
          :placeholder="filterPlaceholder || `请输入搜索内容...`"
          clearable
          @keyup.native.enter="initListStart"
          @clear="initListStart"
        >
          <el-button slot="append" icon="el-icon-search" @click="initListStart" />
        </el-input>
      </div>
      <div class="sg-table">
        <el-table
          ref="table_start"
          :data="tableData_start"
          :header-cell-style="{ background: '#f5f7fa' }"
          :height="height ? `calc(${height} - 150px)` : '300px'"
          style="width: 100%"
          stripe
          @selection-change="selection_start_change"
          :row-class-name="row_class_name"
          @row-click="row_click_start"
          @row-dblclick="row_dblclick_start"
        >
          <el-table-column type="selection" minWidth="50" :selectable="selectable" />
          <el-table-column
            v-for="(a, i) in tableItems_start"
            :key="i"
            :prop="a.prop"
            :label="a.label"
            :width="a.width || false"
            :minWidth="a.minWidth || false"
            show-overflow-tooltip
          />
        </el-table>
      </div>
      <div class="sg-pagination">
        <el-pagination
          background
          :hidden="startPage.total <= 10"
          :layout="layout"
          :page-sizes="[10, 20, 50]"
          :pager-count="5"
          :current-page.sync="startPage.currentPage"
          :page-size.sync="startPage.pageSize"
          :total="startPage.total"
          @size-change="pageChange"
          @current-change="pageChange"
        />
      </div>
    </div>
    <div class="sg-center">
      <el-button
        :disabled="disabledLeftButton"
        @click="remove"
        type="primary"
        icon="el-icon-arrow-left"
        >{{ buttonTexts ? buttonTexts[0] : "" }}</el-button
      >
      <el-button :disabled="disabledRightButton" @click="add" type="primary"
        >{{ buttonTexts ? buttonTexts[1] : ""
        }}<i class="el-icon-arrow-right" style="margin-left: 5px"></i
      ></el-button>
    </div>
    <div class="sg-end" :style="{ width: width, height: height, ...style, ...style_end }">
      <div class="sg-title" v-if="titles">
        {{ titles[1] }}
      </div>
      <div class="sg-search">
        <el-input
          style="width: 100%"
          v-model.trim="inputSearchValue_end"
          maxlength="20"
          :show-word-limit="false"
          :placeholder="filterPlaceholder || `请输入搜索内容...`"
          clearable
          @keyup.native.enter="initListEnd({ currentPage: 1 })"
          @clear="initListEnd"
        >
          <el-button
            slot="append"
            icon="el-icon-search"
            @click="initListEnd({ currentPage: 1 })"
          />
        </el-input>
      </div>
      <div class="sg-table">
        <el-table
          ref="table_end"
          :data="tableData_end"
          :header-cell-style="{ background: '#f5f7fa' }"
          :height="height ? `calc(${height} - 150px)` : '300px'"
          style="width: 100%"
          stripe
          @selection-change="selection_end_change"
          @row-click="row_click_end"
        >
          <el-table-column type="selection" minWidth="50" />
          <el-table-column
            v-for="(a, i) in tableItems_end"
            :key="i"
            :prop="a.prop"
            :label="a.label"
            :width="a.width || false"
            :minWidth="a.minWidth || false"
            show-overflow-tooltip
          />
        </el-table>
      </div>
      <div class="sg-pagination">
        <el-pagination
          background
          :hidden="endPage.total <= 10"
          :layout="layout"
          :page-sizes="[10, 20, 50]"
          :pager-count="5"
          :current-page.sync="endPage.currentPage"
          :page-size.sync="endPage.pageSize"
          :total="endPage.total"
          @size-change="initListEnd"
          @current-change="initListEnd"
        />
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "sgTransfer",
  data() {
    return {
      width: "200px", //穿梭框宽度
      height: null, //穿梭框高度
      style: {}, //全局穿梭框样式
      style_start: {}, //左侧穿梭框样式
      style_end: {}, //右侧穿梭框样式
      layout: `total, sizes, prev, pager, next, jumper`,
      disabledForm: false,
      inputSearchValue_start: "",
      inputSearchValue_end: "",
      tableItems_start: [], //表格列配置项
      tableItems_end: [], //表格列配置项
      tableData_start: [], //呈现的当前页数据
      tableData_end: [], //呈现的当前页数据
      tableData_end_bk: [], //最终选择的数据
      selection_start: [],
      selection_end: [],
      startPage: { currentPage: 1, pageSize: 10, total: 0 },
      endPage: { currentPage: 1, pageSize: 10, total: 0 },
      mainKey: null, //主键
      filterKey_end: null, //右侧穿梭表过滤关键词字段
    };
  },
  props: [
    "value",
    "data",
    /*格式说明
         data: {
              width: '400px',//表格宽度
              layout: `total, sizes, prev, next, jumper`,//翻页器显示控件
              // 列配置项
              tableItems: [
              { prop: 'ID', label: '工号', minWidth: '50' },
              { prop: 'XM', label: '姓名', minWidth: '50' },
              { prop: 'YHM', label: '用户名', minWidth: '50' },
              ],
              tableData: [],//表格显示内容
              startPage: { total: 0, },//实际总数
        }, */
    "titles",
    "buttonTexts",
    "filterPlaceholder",
  ],
  computed: {
    disabledLeftButton(d) {
      return this.selection_end.length === 0;
    },
    disabledRightButton(d) {
      // 在左边表格选中项里面,遍历每一项,如果在右侧表格中都能找到匹配项就true
      return this.selection_start.every((row) =>
        this.tableData_end_bk.some((v) => this.isSameItem(v, row))
      );
    },
  },
  watch: {
    value: {
      handler(d) {
        // 避免重复循环执行双向绑定
        if (
          d &&
          JSON.stringify(JSON.parse(JSON.stringify(this.tableData_end_bk)).sort()) !==
            JSON.stringify(JSON.parse(JSON.stringify(d)).sort())
        ) {
          this.inputSearchValue_start = "";
          this.inputSearchValue_end = "";
          this.startPage.currentPage = 1;
          this.endPage.currentPage = 1;
          this.tableData_end_bk = d || [];
          this.initListStart();
        } else this.initListStart();
      },
      deep: true,
      immediate: true,
    },
    data: {
      handler(d) {
        if (d) {
          d.width && (this.width = d.width);
          d.height && (this.height = d.height);
          d.style && (this.style = d.style);
          d.style_start && (this.style_start = d.style_start);
          d.style_end && (this.style_end = d.style_end);
          d.layout && (this.layout = d.layout);
          this.tableData_start = d.tableData;
          this.tableItems_start = d.tableItems_start || d.tableItems;
          this.tableItems_end = d.tableItems_end || d.tableItems;
          this.mainKey = (this.tableItems_start.find((v) => v.mainKey) || {}).prop; //主键
          this.filterKey_end = (
            this.tableItems_start.find((v) => v.filterKey_end) || {}
          ).prop; //右侧穿梭表过滤关键词字段
          this.startPage.total = (d.startPage || {}).total || 0;
          this.$nextTick(() => {
            this.refreshCheckStatus();
          }); // 刷新勾选状态
        }
      },
      deep: true,
      immediate: true,
    },
    tableData_end_bk: {
      handler(d) {
        this.$emit(`input`, d);
        this.initListEnd();
        if (this.tableData_end.length === 0) {
          this.inputSearchValue_end = "";
          this.endPage.currentPage = Math.round(
            this.tableData_end_bk.length / this.endPage.pageSize
          );
        }
      },
      deep: true,
      immediate: true,
    },
  },
  methods: {
    // 双击选中移动到右侧
    row_dblclick_start(row, column, event) {
      this.$refs.table_start.toggleRowSelection(row, true);
      this.add();
    },
    row_click_start(row, column, event) {
      this.$refs.table_start.toggleRowSelection(row);
    },
    row_click_end(row, column, event) {
      this.$refs.table_end.toggleRowSelection(row);
    },
    pageChange(d) {
      this.initListStart();
    },
    // 刷新勾选状态
    refreshCheckStatus() {
      this.tableData_start.forEach((row) =>
        this.$refs.table_start.toggleRowSelection(
          row,
          this.tableData_end_bk.some((v) => this.isSameItem(v, row))
        )
      );
    },
    selectable(row) {
      return !this.tableData_end_bk.some((v) => this.isSameItem(v, row));
    },
    row_class_name({ row, rowIndex }) {
      return this.tableData_end_bk.find((v) => this.isSameItem(v, row)) ? "selected" : "";
    },
    isSameItem(a_obj, b_obj) {
      let isSame = true;
      if (this.mainKey) {
        isSame = a_obj[this.mainKey] == b_obj[this.mainKey];
      } else {
        isSame = Object.keys(a_obj).every((k) => a_obj[k] == b_obj[k]);
      }
      return isSame;
    },
    remove(d) {
      if (this.mainKey) {
        let selection_end_mainKeys = this.selection_end.map((v) => v[this.mainKey]);
        this.tableData_end_bk = this.tableData_end_bk.filter(
          (v) => !selection_end_mainKeys.includes(v[this.mainKey])
        );
      } else {
        let selection_end = this.selection_end.map((v) => JSON.stringify(v));
        this.tableData_end_bk = this.tableData_end_bk.filter(
          (v) => !selection_end.includes(JSON.stringify(v))
        );
      }
      this.$nextTick(() => {
        this.refreshCheckStatus();
      }); // 刷新勾选状态
    },
    add(d) {
      this.selection_start.forEach(
        (row) =>
          this.tableData_end_bk.some((v) => this.isSameItem(v, row)) ||
          this.tableData_end_bk.push(row)
      );
      this.$nextTick(() => {
        this.refreshCheckStatus();
      }); // 刷新勾选状态
    },
    selection_start_change(selection) {
      this.selection_start = selection;
    },
    selection_end_change(selection) {
      this.selection_end = selection;
    },
    initListStart() {
      this.$emit("init", {
        keyword: this.inputSearchValue_start,
        currentPage: this.startPage.currentPage || 1,
        pageSize: this.startPage.pageSize,
      });
    },
    initListEnd({
      keyword = this.inputSearchValue_end,
      currentPage = this.endPage.currentPage || 1,
      pageSize = this.endPage.pageSize,
    } = {}) {
      this.endPage.currentPage = currentPage;
      this.endPage.pageSize = pageSize;
      let results = [];
      if (this.filterKey_end) {
        results = this.tableData_end_bk.filter((obj) =>
          keyword
            ? (obj[this.filterKey_end] || "")
                .toString()
                .toLocaleLowerCase()
                .includes(keyword.toString().toLocaleLowerCase())
            : true
        );
      } else {
        results = this.tableData_end_bk.filter((obj) =>
          keyword
            ? Object.keys(obj).some((k) =>
                (obj[k] || "")
                  .toString()
                  .toLocaleLowerCase()
                  .includes(keyword.toString().toLocaleLowerCase())
              )
            : true
        );
      }
      this.endPage.total = results.length;
      this.tableData_end = results.slice(
        (currentPage - 1) * pageSize,
        currentPage * pageSize
      );
    },
  },
};
</script>
<style lang="scss" scoped>
.sgTransfer {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  white-space: nowrap;
  & > .sg-start,
  & > .sg-end {
    border: 1px solid #ebeef5;
    border-radius: 4px;
    overflow: hidden;
    background: #fff;
    display: inline-block;
    vertical-align: middle;
    max-height: 100%;
    box-sizing: border-box;
    position: relative;
    .sg-title {
      height: 40px;
      line-height: 40px;
      background: #f5f7fa;
      margin: 0;
      padding-left: 15px;
      border-bottom: 1px solid #ebeef5;
      box-sizing: border-box;
      color: #000;
    }
    .sg-search {
      box-sizing: border-box;
      padding: 10px;
    }
    .sg-table {
    }
    .sg-pagination {
      height: 50px;
      display: flex;
      justify-content: center;
      width: 100%;
      box-sizing: border-box;
      padding: 10px;
    }
  }
  & > .sg-center {
    margin: 0 10px;
  }
  & > .sg-end {
  }
}
>>> .el-table {
  tr.selected {
    filter: brightness(0.95);
    pointer-events: none;
  }
  .el-table__cell.gutter {
    border-bottom: 1px solid #ebeef5;
    background-color: #f5f7fa;
  }
}
</style>

用例

<template>
  <div>
    <sgTransfer
      v-model="transferValue"
      :data="transferData"
      :titles="['所有用户', '本组成员']"
      :button-texts="['到左边', '到右边']"
      :filter-placeholder="`请输入工号、姓名…`"
      @init="initTransfer"
    />
    <hr />
    <div>
      <h1>勾选的数据transferValue:</h1>
      <div
        v-html="JSON.stringify(transferValue).replace(/\,\{/g, ',\n{')"
        style="word-wrap: break-word; word-break: break-all; white-space: break-spaces"
      ></div>
    </div>
  </div>
</template>
<script>
import sgTransfer from "@/vue/components/admin/sgTransfer";
export default {
  components: { sgTransfer },
  data() {
    return {
      // 穿梭框配置项
      transferValue: [],
      transferData: {
        width: "400px", //表格宽度
        height: "calc(100vh - 200px)", //表格高度
        layout: `total, sizes, prev, next, jumper`, //翻页器显示控件
        // 列配置项
        tableItems: [
          { prop: "ID", label: "工号", minWidth: "50", mainKey: true }, //设置主键
          { prop: "XM", label: "姓名", minWidth: "50", filterKey_end: true  },//设置右侧穿梭表过滤关键词字段
          { prop: "YHM", label: "用户名", minWidth: "50" },
        ],
        tableData: [], //表格显示内容
        startPage: { total: 0 }, //实际总数
      },
      // 渲染数据
      tableData: [],
      tableData_bk: [],
      userList: [
        { key: 1, label: "梁冰露" },
        { key: 2, label: "吴梵听" },
        { key: 3, label: "卢令美" },
        { key: 4, label: "韩宛曼" },
        { key: 5, label: "郝海冬" },
        { key: 6, label: "傅优悦" },
        { key: 7, label: "郝幻莲" },
        { key: 8, label: "江嘉云" },
        { key: 9, label: "梁秋芳" },
        { key: 10, label: "郝悦颖" },
        { key: 11, label: "廖芝蓉" },
        { key: 12, label: "胡傲丝" },
        { key: 13, label: "赵珺琦" },
        { key: 14, label: "石心诺" },
        { key: 15, label: "丁翠芙" },
        { key: 16, label: "李夏河" },
        { key: 17, label: "范水悦" },
        { key: 18, label: "郑凝雪" },
        { key: 19, label: "李亦玉" },
        { key: 20, label: "袁三春" },
        { key: 21, label: "赵红叶" },
        { key: 22, label: "曹安琪" },
        { key: 23, label: "谭琴音" },
        { key: 24, label: "钟湛蓝" },
        { key: 25, label: "陆之柔" },
        { key: 26, label: "吕孒凡" },
        { key: 27, label: "熊野雪" },
        { key: 28, label: "曹叶澜" },
        { key: 29, label: "韩粟梅" },
        { key: 30, label: "孔杏儿" },
        { key: 31, label: "宋若彤" },
        { key: 32, label: "于淼淼" },
        { key: 33, label: "潘欣跃" },
        { key: 34, label: "石雅辰" },
        { key: 35, label: "白念珍" },
        { key: 36, label: "文爱茹" },
        { key: 37, label: "王如曼" },
        { key: 38, label: "宋丝琪" },
        { key: 39, label: "王凝荷" },
        { key: 40, label: "郑雨雪" },
        { key: 41, label: "梁映阳" },
        { key: 42, label: "徐新雨" },
        { key: 43, label: "毛恬雅" },
        { key: 44, label: "侯若蕊" },
        { key: 45, label: "杨云蔚" },
        { key: 46, label: "史之卉" },
        { key: 47, label: "胡千束" },
        { key: 48, label: "冯冷荷" },
        { key: 49, label: "金语心" },
        { key: 50, label: "江恬默" },
        { key: 51, label: "高香馨" },
        { key: 52, label: "江凌晴" },
        { key: 53, label: "梁列琴" },
        { key: 54, label: "邹鸾瑶" },
        { key: 55, label: "夏素洁" },
        { key: 56, label: "范秋玉" },
        { key: 57, label: "钟北嘉" },
        { key: 58, label: "谭水云" },
        { key: 59, label: "顾山柏" },
        { key: 60, label: "龙曼蔓" },
        { key: 61, label: "钟双儿" },
        { key: 62, label: "林林娜" },
        { key: 63, label: "邹溪儿" },
        { key: 64, label: "顾妙彤" },
        { key: 65, label: "傅茵茵" },
        { key: 66, label: "卢念露" },
        { key: 67, label: "罗冷亦" },
        { key: 68, label: "胡秋颖" },
        { key: 69, label: "姜怡月" },
        { key: 70, label: "傅和暄" },
        { key: 71, label: "赖布凡" },
        { key: 72, label: "郝念蕾" },
        { key: 73, label: "邱天欣" },
        { key: 74, label: "汤莉莉" },
        { key: 75, label: "段靖易" },
        { key: 76, label: "周之云" },
        { key: 77, label: "董映秋" },
        { key: 78, label: "汤玲琅" },
        { key: 79, label: "田雁梅" },
        { key: 80, label: "石雨雪" },
        { key: 81, label: "任君雅" },
        { key: 82, label: "蔡小谷" },
        { key: 83, label: "孟忆之" },
        { key: 84, label: "姜闲丽" },
        { key: 85, label: "文忆香" },
        { key: 86, label: "戴运虹" },
        { key: 87, label: "王玄穆" },
        { key: 88, label: "刘绿柳" },
        { key: 89, label: "萧梦丝" },
        { key: 90, label: "谭忆山" },
        { key: 91, label: "方榕嫣" },
        { key: 92, label: "徐欣合" },
        { key: 93, label: "夏雨南" },
        { key: 94, label: "尹沙羽" },
        { key: 95, label: "万梦玉" },
        { key: 96, label: "谢灵枫" },
        { key: 97, label: "曾源源" },
        { key: 98, label: "赖谷枫" },
        { key: 99, label: "彭子童" },
      ],
    };
  },
  created() {
    this.createTableData();
  },
  methods: {
    // 初始化、翻页、切换每页显示数量的时候触发
    initTransfer({ keyword = "", currentPage = 1, pageSize = 10 } = {}) {
      // 模拟接口调用----------------------------------------
      let results = this.tableData_bk.filter((obj) =>
        keyword
          ? Object.keys(obj).some((k) =>
              obj[k]
                .toString()
                .toLocaleLowerCase()
                .includes(keyword.toString().toLocaleLowerCase())
            )
          : true
      );
      this.transferData.startPage.total = results.length;
      this.transferData.tableData = results.slice(
        (currentPage - 1) * pageSize,
        currentPage * pageSize
      );
      // ----------------------------------------
    },
    // 构建数据
    createTableData(d) {
      this.tableData_bk = this.userList.map((v) => {
        let ID = this.$g.getRandomID();
        return { ID, XM: v.label, YHM: `user${ID}` };
      });
      this.initTransfer();
    },
  },
};
</script>


相关文章
|
24天前
【sgSearch】自定义组件:常用搜索栏筛选框组件(包括表格高度变化兼容)。
【sgSearch】自定义组件:常用搜索栏筛选框组件(包括表格高度变化兼容)。
|
24天前
|
JavaScript
【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作
【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作
【sgExcelGrid】自定义组件:简单模拟Excel表格拖拽、选中单元格、横行、纵列、拖拽圈选等操作
|
24天前
|
搜索推荐
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。
【sgUploadTray_v2】自定义组件:升级版上传托盘自定义组件,可实时查看上传列表进度,可以通过选项卡切换上传中、成功、失败的队列,支持翻页,解决了列表内容太多导致卡顿的情况。(一)
|
7月前
|
敏捷开发 前端开发 开发者
【RaETable】🚀🚀🚀告别Form,RaETable表格列宽度支持拖动调整了,附带原理说明
【RaETable】🚀🚀🚀告别Form,RaETable表格列宽度支持拖动调整了,附带原理说明
|
9月前
6.3.2 可翻页检视
6.3.2 可翻页检视
23 0
|
JavaScript 前端开发 开发者
分类页 -iscroll 区域滚动|学习笔记
快速学习 分类页 -iscroll 区域滚动
82 0
|
设计模式 存储 前端开发
分页复选设计的坑
什么是分页复选设计呢?
|
前端开发
前端工作总结141-根据后台传值动态显示开关状态及文字说明(0为文字,1为图标)
前端工作总结141-根据后台传值动态显示开关状态及文字说明(0为文字,1为图标)
104 0
|
索引
好客租房147-渲染(渲染右侧索引列表
好客租房147-渲染(渲染右侧索引列表
81 0
好客租房147-渲染(渲染右侧索引列表
|
索引
好客租房148-渲染右侧索引列表(滚动城市列表高亮)
好客租房148-渲染右侧索引列表(滚动城市列表高亮)
98 0
好客租房148-渲染右侧索引列表(滚动城市列表高亮)