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

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

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、效果

 

 

 

 

 

  • 15
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
Vue Element UI提供了el-pagination组件来实现表格数据分页功能。以下是实现步骤: 1. 在Vue组件中引入el-pagination组件并定义分页属性 ``` <template> <div> <el-table :data="tableData" border> //表格内容 </el-table> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 30, 40]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> </div> </template> <script> export default { data() { return { tableData: [], //表格数据 total: 0, //总记录数 pageSize: 10, //每页记录数 currentPage: 1 //当前页码 }; }, methods: { handleSizeChange(val) { this.pageSize = val; this.getData(); }, handleCurrentChange(val) { this.currentPage = val; this.getData(); }, getData() { //获取数据,并更新this.tableData和this.total } } }; </script> ``` 2. 在el-pagination组件中绑定事件和属性 - @size-change:当每页显示条数改变时触发的事件,调用handleSizeChange方法更新pageSize并重新获取数据。 - @current-change:当当前页码改变时触发的事件,调用handleCurrentChange方法更新currentPage并重新获取数据。 - :current-page:当前页码,绑定到currentPage属性。 - :page-sizes:每页显示条数数组,可以自定义,默认为[10, 20, 30, 40]。 - :page-size:每页显示条数,绑定到pageSize属性。 - layout:分页组件布局。其中total表示总记录数,sizes表示每页显示条数选择器,prev表示上一页按钮,pager表示页码按钮,next表示下一页按钮,jumper表示跳转输入和确定按钮。 - :total:总记录数,绑定到total属性。 3. 在getData方法中获取数据更新表格数据和总记录数 ``` getData() { //计算分页查询的参数 const start = (this.currentPage - 1) * this.pageSize; const limit = this.pageSize; //发起分页查询请求 axios.get('/api/data', { params: { start, limit } }).then(response => { this.tableData = response.data.rows; //更新表格数据 this.total = response.data.total; //更新总记录数 }); } ``` 以上就是使用Vue Element UI实现表格数据分页功能的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少十步

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值