element ui transfer 穿梭框 分页实现,接口获取、更新数据

2023-10-30

使用穿梭框数据量大时需要分页,官方的组件并不能满足需求。数据大时不分页一次渲染页面卡。通过重新修改官方源码、封装实现分页。

1、新建TransferPanel 组件,根据官方源码修改适配

<template>
  <div class="el-transfer-panel" style="width: 17.5rem">
    <p class="el-transfer-panel__header">
      <el-checkbox
        v-model="allChecked"
        :indeterminate="isIndeterminate"
        @change="handleAllCheckedChange"
      >
        {{ title }}
        <span>{{ checkedSummary }}</span>
      </el-checkbox>
    </p>

    <div
      :class="['el-transfer-panel__body', 'is-with-footer']"
      style="height: 100%"
    >
      <el-input
        v-if="filterable"
        v-model="query"
        class="el-transfer-panel__filter"
        size="small"
        :placeholder="placeholder"
        @mouseenter.native="inputHover = true"
        @mouseleave.native="inputHover = false"
      >
        <i slot="prefix" :class="['el-input__icon', 'el-icon-search']" />
        <i
          v-if="query.length > 0 && inputHover"
          slot="suffix"
          :class="['el-input__icon', 'el-icon-circle-close']"
          @click="handleQuery('')"
        />
        <span
          v-if="query.length > 0 && inputHover"
          slot="suffix"
          class="pr-2 cursor-pointer text-blue-400 el-input__icon"
          @click="handleQuery(query)"
          >搜索</span
        >
      </el-input>
      <el-checkbox-group
        v-show="!hasNoMatch && data.length > 0"
        v-model="checked"
        :class="{ 'is-filterable': filterable }"
        class="el-transfer-panel__list"
        style=""
      >
        <el-checkbox
          v-for="item in data"
          :key="item[keyProp]"
          class="el-transfer-panel__item"
          :label="item[keyProp]"
          :disabled="item[disabledProp]"
        >
          <option-content :option="item"></option-content>
        </el-checkbox>
      </el-checkbox-group>
      <div
        v-show="hasNoMatch"
        v-loading="loading"
        element-loading-text="加载中"
        class="el-transfer-panel__empty"
        style="
          height: 12rem;
          display: flex;
          align-items: center;
          justify-content: center;
        "
      >
        {{ t('el.transfer.noMatch') }}
      </div>
      <p
        v-show="data.length === 0 && !hasNoMatch"
        class="el-transfer-panel__empty"
      >
        {{ t('el.transfer.noData') }}
      </p>
    </div>
    <div
      class="el-transfer-panel__footer"
      style="display: flex; align-items: center; justify-content: center"
    >
      <el-pagination
        small
        :page-size="pageSize"
        layout="prev, pager, next"
        :total="total"
        :pager-count="5"
        :current-page="page"
        @current-change="(val) => $emit('page-change', val)"
      />
    </div>
  </div>
</template>

<script>
import Locale from 'element-ui/src/mixins/locale'

export default {
  name: 'TransferPanel',
  components: {
    OptionContent: {
      props: {
        option: Object,
      },
      render(h) {
        const getParent = (vm) => {
          if (vm.$options.componentName === 'TransferPanel') {
            return vm
          } else if (vm.$parent) {
            return getParent(vm.$parent)
          } else {
            return vm
          }
        }
        const panel = getParent(this)
        const transfer = panel.$parent || panel
        return panel.renderContent ? (
          panel.renderContent(h, this.option)
        ) : transfer.$scopedSlots.default ? (
          transfer.$scopedSlots.default({ option: this.option })
        ) : (
          <span>
            {this.option[panel.labelProp] || this.option[panel.keyProp]}
          </span>
        )
      },
    },
  },
  mixins: [Locale],
  componentName: 'TransferPanel',
  props: {
    data: {
      type: Array,
      default() {
        return []
      },
    },
    renderContent: { type: Function, default: null },
    placeholder: { type: String, default: '' },
    title: { type: String, default: '' },
    filterable: { type: Boolean, default: false },
    loading: { type: Boolean, default: false },
    format: {
      type: Object,
      default() {
        return {}
      },
    },
    filterMethod: { type: Function, default: null },
    pageSize: { type: Number, default: 15 },
    page: { type: Number, default: 1 },
    total: { type: Number, default: 0 },
    defaultChecked: {
      type: Array,
      default() {
        return []
      },
    },
    props: {
      type: Object,
      default() {
        return {}
      },
    },
  },

  data() {
    return {
      checked: [],
      allChecked: false,
      query: '',
      inputHover: false,
      checkChangeByUser: true,
    }
  },

  computed: {
    checkableData() {
      return this.data.filter((item) => !item[this.disabledProp])
    },
    checkedSummary() {
      const checkedLength = this.checked.length
      const dataLength = this.data.length
      const { noChecked, hasChecked } = this.format
      if (noChecked && hasChecked) {
        return checkedLength > 0
          ? hasChecked
              .replace(/\${checked}/g, checkedLength)
              .replace(/\${total}/g, dataLength)
          : noChecked.replace(/\${total}/g, dataLength)
      } else {
        return `${checkedLength}/${dataLength}`
      }
    },

    isIndeterminate() {
      const checkedLength = this.checked.length
      return checkedLength > 0 && checkedLength < this.checkableData.length
    },
    hasNoMatch() {
      return this.data.length === 0
    },
    labelProp() {
      return this.props.label || 'label'
    },
    keyProp() {
      return this.props.key || 'key'
    },
    disabledProp() {
      return this.props.disabled || 'disabled'
    },
  },
  watch: {
    checked(val, oldVal) {
      this.updateAllChecked()
      if (this.checkChangeByUser) {
        const movedKeys = val
          .concat(oldVal)
          .filter((v) => !val.includes(v) || !oldVal.includes(v))
        this.$emit('checked-change', val, movedKeys)
      } else {
        this.$emit('checked-change', val)
        this.checkChangeByUser = true
      }
    },
    data() {
      const checked = []
      const dataKeys = this.data.map((item) => item[this.keyProp])
      this.checked.forEach((item) => {
        if (dataKeys.includes(item)) checked.push(item)
      })
      this.checkChangeByUser = false
      this.checked = checked
    },
    checkableData() {
      this.updateAllChecked()
    },
    defaultChecked: {
      immediate: true,
      handler(val, oldVal) {
        if (
          oldVal &&
          val.length === oldVal.length &&
          val.every((item) => oldVal.includes(item))
        )
          return
        const checked = []
        const checkableDataKeys = this.checkableData.map(
          (item) => item[this.keyProp]
        )
        val.forEach((item) => {
          if (checkableDataKeys.includes(item)) {
            checked.push(item)
          }
        })
        this.checkChangeByUser = false
        this.checked = checked
      },
    },
  },
  methods: {
    updateAllChecked() {
      const checkableDataKeys = this.checkableData.map(
        (item) => item[this.keyProp]
      )
      this.allChecked =
        checkableDataKeys.length > 0 &&
        checkableDataKeys.every((item) => this.checked.includes(item))
    },
    handleAllCheckedChange(value) {
      this.checked = value
        ? this.checkableData.map((item) => item[this.keyProp])
        : []
    },
    handleQuery(query) {
      this.query = query
      this.$emit('query-change', query)
    },
  },
}
</script>

2、封装Transfer组件,根据官方源码修改适配

<template>
  <div v-loading="loading" class="el-transfer" element-loading-text="更新中">
    <transfer-panel
      v-bind="$props"
      ref="leftPanel"
      :data="left.data"
      :title="titles[0] || t('el.transfer.titles.0')"
      :page-size="left.pageSize || 15"
      :page="left.pageNum"
      :total="left.total"
      :loading="left.loading"
      :default-checked="leftDefaultChecked"
      :placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
      @checked-change="(val) => $emit('checked-change', 'left', val)"
      @query-change="(val) => $emit('query-change', 'left', val)"
      @page-change="(val) => $emit('page-change', 'left', val)"
    >
      <slot name="left-footer"></slot>
    </transfer-panel>
    <div class="el-transfer__buttons">
      <div style="display: flex; flex-direction: column">
        <el-button
          type="primary"
          size="small"
          :class="[hasButtonTexts ? 'is-with-texts' : '']"
          :disabled="rightDefaultChecked.length === 0"
          @click.native="$emit('bind', 'right', false)"
        >
          <i class="el-icon-arrow-left"></i>
          <span v-if="buttonTexts[0] !== undefined">{{ buttonTexts[0] }}</span>
        </el-button>
        <el-button
          type="primary"
          size="small"
          style="margin: 0.5rem 0 0"
          :class="[hasButtonTexts ? 'is-with-texts' : '']"
          :disabled="leftDefaultChecked.length === 0"
          @click.native="$emit('bind', 'left', true)"
        >
          <span v-if="buttonTexts[1] !== undefined">{{ buttonTexts[1] }}</span>
          <i class="el-icon-arrow-right"></i>
        </el-button>
      </div>
    </div>
    <transfer-panel
      v-bind="$props"
      ref="rightPanel"
      :data="right.data"
      :title="titles[1] || t('el.transfer.titles.1')"
      :page-size="right.pageSize || 15"
      :page="right.pageNum"
      :total="right.total"
      :loading="right.loading"
      :default-checked="rightDefaultChecked"
      :placeholder="filterPlaceholder || t('el.transfer.filterPlaceholder')"
      @checked-change="(val) => $emit('checked-change', 'right', val)"
      @query-change="(val) => $emit('query-change', 'right', val)"
      @page-change="(val) => $emit('page-change', 'right', val)"
    >
      <slot name="right-footer"></slot>
    </transfer-panel>
  </div>
</template>

<script>
import TransferPanel from './TransferPanel'

export default {
  name: 'Transfer',
  components: { TransferPanel },
  props: {
    data: {
      type: Array,
      default() {
        return [[], []]
      },
    },
    titles: {
      type: Array,
      default() {
        return []
      },
    },
    buttonTexts: {
      type: Array,
      default() {
        return []
      },
    },
    filterPlaceholder: {
      type: String,
      default: '',
    },
    filterMethod: { type: Function, default: null },
    leftDefaultChecked: {
      type: Array,
      default() {
        return []
      },
    },
    rightDefaultChecked: {
      type: Array,
      default() {
        return []
      },
    },
    renderContent: { type: Function, default: null },
    format: {
      type: Object,
      default() {
        return {}
      },
    },
    left: {
      type: Object,
      default() {
        return { data: [], page: 1, size: 15, loading: false, total: 0 }
      },
    },
    right: {
      type: Object,
      default() {
        return { data: [], page: 1, size: 15, loading: false, total: 0 }
      },
    },
    filterable: { type: Boolean, default: false },
    loading: { type: Boolean, default: false },
    props: {
      type: Object,
      default() {
        return {
          label: 'label',
          key: 'key',
          disabled: 'disabled',
        }
      },
    },
    targetOrder: {
      type: String,
      default: 'original',
    },
  },
  computed: {
    hasButtonTexts() {
      return this.buttonTexts.length === 2
    },
  },
  methods: {
    // 清空查询
    clearQuery(which) {
      const { leftPanel, rightPanel } = this.$refs
      switch (which) {
        case 'left':
          leftPanel.query = ''
          break
        case 'right':
          rightPanel.query = ''
          break
      }
      this.$emit('query-change', which, '')
    },
  },
}
</script>

3、引用

<template>
  <AddBase
    :visible="visible"
    :title="`广告绑定[ ${name} ]`"
    width="750px"
    :footer="false"
    @close="$emit('close')"
    @closed="$emit('closed')"
    @submit="submit"
  >
    <div slot="form">
      <Transfer
        ref="transfer"
        filter-placeholder="请输入设备名称"
        :props="{ key: 'id', label: 'name' }"
        :left="left"
        :right="right"
        filterable
        :titles="['未绑定', '已绑定']"
        :button-texts="['解绑', '绑定']"
        :left-default-checked="left.checked"
        :right-default-checked="right.checked"
        :loading="loading"
        @page-change="handlePage"
        @query-change="handleQuery"
        @checked-change="handleCheck"
        @bind="updateBind"
      >
        <template slot-scope="{ option: { name: label, imei, actId: id } }">
          <el-tooltip
            class="item"
            effect="dark"
            :content="imei"
            placement="right"
          >
            <span
              ><span> {{ label }}</span>
              <span v-if="id && id != actId" class="text-red-300 text-xs">
                已绑其他</span
              ></span
            >
          </el-tooltip></template
        >
      </Transfer>
    </div>
  </AddBase>
</template>

<script>
import mixin from '@/mixin/form'
import Transfer from './Transfer'

export default {
  name: 'BindMachine',
  components: { Transfer },
  mixins: [mixin],
  props: {
    actId: {
      type: [Number, String],
      default: '',
    },
    name: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      left: {
        data: [], // 数据
        total: 0, // 数据总数
        loading: false, // 加载状态
        pageNum: 1, // 当前页
        pageSize: 100, // 分页大小
        name: '', // 查询条件
        checked: [], // 默认选中
      },
      right: {
        data: [],
        total: 0,
        loading: false,
        pageNum: 1,
        pageSize: 100,
        name: '',
        checked: [], // 默认选中
      },
      loading: false, // 更新时状态
    }
  },
  watch: {
    visible(val) {
      if (val) {
        this.handleSearch('left')
        this.handleSearch('right')
      }
    },
    'left.name'() {
      // 查询条件改变,当前页值为1 执行查询 否则通过当前页执行查询
      if (this.left.pageNum === 1) this.handleSearch('left')
      else this.left.pageNum = 1
    },
    'left.pageNum'() {
      this.handleSearch('left')
    },
    'right.name'() {
      if (this.right.pageNum === 1) this.handleSearch('right')
      else this.right.pageNum = 1
    },
    'right.pageNum'() {
      this.handleSearch('right')
    },
  },
  methods: {
    handleCheck(key, val) {
      this[key].checked = val
    },
    handlePage(key, val) {
      this[key].pageNum = val
    },
    handleQuery(key, val) {
      this[key].name = val
    },
    // 根据key值判断 left 左边 right 右边
    async handleSearch(key) {
      const { actId } = this // 广告id
      const { name, pageNum, pageSize } = this[key]
      this[key].data = [] // 数据置空
      this[key].loading = true // 显示加载
      const bind = key === 'right' ? 1 : 0 // 查询条件 1 绑定数据 0 未绑定数据
      const params = { pageNum, name, bind, actId, pageSize } // 查询条件
      const { count = 0, rows = [] } = await this.getData(params)
      this[key].data = rows // 数据
      this[key].total = count // 总数
      this[key].loading = false
    },
    // 数据获取,根据不同查询条件获取不同数据
    async getData(params) {
      const { code, msg, result = { count: 0, rows: [] } } = await this.get({
        url: '/api/poster/bindMachine',
        params,
      })
      if (code !== 0) this.$message.error(msg)
      return result
    },
   // 绑定、解绑
    async updateBind(key, bind) {
      const { actId } = this
      const ids = this[key].checked // 选中的数据
      const params = { actId, bind, ids }
      this.loading = true // 显示更新状态
      const { code, msg } = await this.post({
        url: '/api/post/poster/bind',
        params,
      })
      this.message(code, msg)
      // 更新成功,置空选中
      if (code === 0) this[key].checked = []
      this.loading = false
      // 重置查询条件查询,重新获取左右数据
      const { pageNum, name } = this[key]
      if (name) this.$refs.transfer.clearQuery()
      else if (pageNum > 1) this[key].pageNum = 1
      else if (pageNum === 1) await this.handleSearch(key)
      await this.handleSearch(key === 'left' ? 'right' : 'left')
    },
  },
}
</script>

在原有props基础上 添加了 left 左侧数据 right 右侧数据 事件  page-change(当前页改变)、query-change(查询条件改变)、checked-change(选中改变)、bind(绑定、解绑),返回的参数(key,val) key :left/right 区分左右 val: 改变的值。在bind中 val: true/false  绑定、解绑

示例是在弹框中引用的所以监听显示visible 值,显示时加载左右数据,如果是页面在mounted里加载左右数据

4、效果

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

element ui transfer 穿梭框 分页实现,接口获取、更新数据 的相关文章

  • c++string替换指定位置字符_小白打怪进阶之路:Day3 用字母位置替换字符串

    几句废话 周末愉快 如果周末你还在学习 那可以说是相当优秀 当一个人知道自己想要什么的时候 脚步也会越来越坚定 给奋斗中的你点赞 下面开始今天的打怪 请听题 这个题目的要求是 给定一个字符串 用它在字母表中的位置替换每个字母 如果文本中的任
  • blender快捷键

    1 shift a 添加物件 2 rgs 这三个按键 可以分别对模型进行旋转移动缩放 3 shift d 可以实现复制功能 alt d 关联复制 4 使用 z 键可以切换到线框模式 再按一次切换回来 5 数字键旋转 中心 6 tab 按键可
  • 数据的导入

    Excel数据的导入就是相当于批量的新增 可以快速的添加多条数据 给导入按钮一个点击事件 打开导入的模态框 下载模板文件 点击下载按钮可以下载文件的功能 操作如下 首先要把模板放到项目中对应的目录下 获取到文件的物理路径 定义一个方法 返回

随机推荐

  • 基于Hugging Face的transformers包的微调模型训练

    transformers API参考链接 https huggingface co docs transformers v4 21 2 en training train py from datasets import load datas
  • IPD产品开发流程详解

    为什么80 的码农都做不了架构师 gt gt gt 集成产品开发 Integrated Product Development 简称IPD 是一套产品开发的模式 理念与方法 IPD的思想来源于美国PRTM公司出版的 产品及生命周期优化法一书
  • MySQL基础------sql指令1.0(查询操作->select)

    目录 前言 单表查询 1 查询当前所在数据库 2 查询整个表数据 3 查询某字段 4 条件查询 5 单行处理函数 聚合函数 6 查询时给字段取别名 7 模糊查询 8 查询结果去除重复项 9 排序 升序和降序 10 分组查询 11 分页查询
  • SSM-easyui 添加多个tabs页面出现混乱

    框架是springMVC 用easyui穿件tabs 使用href创建两个tabs的话 第一个里面的内容部分会进入第二个tab 第二个里面的内容也会进入第一个
  • 个性化推荐系统该如何评估,四种不同策略的角度

    对推荐结果的评估一直都是十分重要的环节 一个推荐算法或者说排序的优劣直接体现在这些评估指标上 具体地 有三种方式 分别是产品数据层面 机器学习算法层面 用户体验层面 推荐系统产品上 最能体现系统 算法在业务层面的价值 因为一个再牛逼的产品
  • 开源免费的pdf文档编辑器LibreOffice

    前言 一般adobe rader 福昕的pdf工具只能读pdf文档 想要编辑pdf就要收费 哎 我是穷人啊 说多了都是泪啊 现在福利来了 免费开源好用的pdf编辑器哪家强 我给大家郑重 推荐LibreOffice LibreOffice下载
  • AssetBundle加载和销毁机制

    AssetBundle加载和销毁机制 AssetBundle内存管理 AssetBundle加载和销毁图 AssetBundle加载分为三个部分 AssetBundle相关对象销毁的几个方法 AssetBundle 和 Resource 的
  • 【LeetCode刷题】160 相交链表 java

    题目 给你两个单链表的头节点 headA 和 headB 请你找出并返回两个单链表相交的起始节点 如果两个链表不存在相交节点 返回 null 图示两个链表在节点 c1 开始相交 示例 方法一 先遍历两个链表得到各自长度 然后求出总节点的差值
  • 桂院校园导航

    这里记录开发校园导航小程序的一些技术问题及解决方案的文章 所有的说明文档都在当前的CSDN文章专栏里 点进去就可以看到所有的 可以点个关注不迷路 文章中有任何的步骤有问题可以私聊我说明问题情况 开源仓库 平台 仓库 Gitee 桂院校园导航
  • Linux命令总结大全,包含所有linux命令

    使用说明 此文档包含所有的Linux命令 只有你想不到的没有你看不到的 此文档共计10万余字 有8400多行 预计阅读时间差不多需要3个小时左右 所以要给大家说一说如何阅读此文档 为了方便大家阅读 我这里做了相关索引 建议使用搜索的方式阅读
  • 华为硬件工程师社招机考题库_华为电子软硬件工程师招聘笔试题

    华为面题 硬件 全都是几本模电数电信号单片机题目 1 用与非门等设计全加法器 2 给出两个门电路让你分析异同 3 名词 sram ssram sdram 4 信号与系统 在时域与频域关系 5 信号与系统 和4题差不多 6 晶体振荡器 好像是
  • 【云原生】Prometheus之图形化界面grafana与服务发现部署

    内容预知 前言 1 部署 Grafana 1 1 grafana的下载与安装 1 安装grafana 2 配置数据源 3 导入 grafana 监控面板 4 删除模板操作 4 2 grafana的中文插件安装 2 部署 Prometheus
  • Mybatis快速入门(XML,注解方式)

    简介 什么是Mybatis Mybatis是一个优秀的基于java的持久层框架 它内部封装了JDBC 使开发者只需要关注SQL语句本身 而不需要花费精力去处理加载驱动 创建连接 创建Statement等繁杂的过程 Mybatis通过XML或
  • js中把数字转换成汉字输出

    目录 背景 实现方法 方法1 方法二 支持9位以上也就是亿级别的 如果需要钱的那种单位 把注释放开就行 背景 我们日常开发中可能遇到这种场景 将js中的数字转化为中文的汉字 数字110 输出 一百一十 数字11 输出 十一 js中把数字转换
  • 一文看尽 2020 年谷歌 AI 重大突破

    2021 01 28 10 12 14 在二十年前刚刚加入谷歌时 我们关注的问题只有一个 如何面向这么多不同种类的联网计算机提供一整套质量出色且涵盖范围全面的网络信息搜索服务 到如今 尽管我们面临着各种各样的技术挑战 但谷歌已经基本达成了组
  • PowerDesigner 15.1 安装步骤详细图解及破解

    准备工作 下载 PowerDesigner 15 1 的安装文件和破解文件 PowerDesigner 15 1 下载地址 http pan baidu com share link shareid 177870 uk 3626956064
  • 数据分析处理之词频统计

    摘要 本次项目主要是对英文文献进行词频统计 利用给定的数据集中已分好的初级 中级 高级三个等级 对英文文献中的单词进行分级处理 并得到各个等级所占比重 画出统计图 饼图 此项目用到python的模块有 tkinter 用来搭建词频统计的前台
  • 【Java基础】接口和多态

    文章目录 1 接口 1 1 概述 1 2 抽象方法的定义和使用 1 3 默认方法的定义和使用 1 4 静态方法的定义和使用 1 5 私有方法的定义和使用 1 6 接口的常量定义和使用 1 7 总结 1 8 注意事项 1 9 接口之间的多继承
  • LeetCode 1828. 统计一个圆中点的数目

    给你一个数组 points 其中 points i xi yi 表示第 i 个点在二维平面上的坐标 多个点可能会有 相同 的坐标 同时给你一个数组 queries 其中 queries j xj yj rj 表示一个圆心在 xj yj 且半
  • element ui transfer 穿梭框 分页实现,接口获取、更新数据

    使用穿梭框数据量大时需要分页 官方的组件并不能满足需求 数据大时不分页一次渲染页面卡 通过重新修改官方源码 封装实现分页 1 新建TransferPanel 组件 根据官方源码修改适配