特性:
- 表格宽度可以自定义
- 翻页器显示控件可以自定义
- 列配置项可以设置显示字段列名称、宽度、字段名
- 可以配置搜索框提示文本,支持搜索过滤
- 穿梭框顶部标题可以自定义
- 左右箭头按钮文本可以设置
sgTransfer源码
<template>
<div :class="$options.name">
<div class="sg-start " :style="{ width: width }">
<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="'300px'" style="width: 100%" stripe @selection-change="selection_start_change"
:row-class-name="row_class_name" @row-click="row_click_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 }">
<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="'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',
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,//主键
}
},
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) {
this.inputSearchValue_start = '';
this.inputSearchValue_end = '';
this.startPage.currentPage = 1;
this.endPage.currentPage = 1;
this.tableData_end_bk = d || [];
this.initListStart();
}, deep: true, immediate: true,
},
data: {
handler(d) {
if (d) {
d.width && (this.width = d.width);
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.startPage.total = (d.startPage || {}).total || 0;
}
}, 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_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(); this.$nextTick(() => { this.refreshCheckStatus() }); },
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));
},
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 = 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',//表格宽度
layout: `total, sizes, prev, next, jumper`,//翻页器显示控件
// 列配置项
tableItems: [
{ prop: 'ID', label: '工号', minWidth: '50', mainKey: true },//设置主键
{ prop: 'XM', label: '姓名', minWidth: '50' },
{ 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>