element ui transfer 穿梭框 分页实现,接口获取、更新数据
element
A Vue.js 2.0 UI Toolkit for Web
项目地址:https://gitcode.com/gh_mirrors/eleme/element
免费下载资源
·
使用穿梭框数据量大时需要分页,官方的组件并不能满足需求。数据大时不分页一次渲染页面卡。通过重新修改官方源码、封装实现分页。
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、效果
GitHub 加速计划 / eleme / element
54.06 K
14.63 K
下载
A Vue.js 2.0 UI Toolkit for Web
最近提交(Master分支:1 个月前 )
c345bb45
5 个月前
a07f3a59
* Update transition.md
* Update table.md
* Update transition.md
* Update table.md
* Update transition.md
* Update table.md
* Update table.md
* Update transition.md
* Update popover.md 5 个月前
更多推荐
已为社区贡献3条内容
所有评论(0)