需求
左侧为树形组件,使用el-transfer无法满足需求,所以自己实现了一个transfer穿梭组件;
最终效果
HTML代码:
<div class="my-transfer" id="transfer">
<!-- 左边 -->
<div class="my-transfer-panel">
<p class="my-transfer-panel__header">
<span>未分配</span>
</p>
<div class="my-transfer-panel__body" v-loading="loadingTree">
<!-- 搜索框 -->
<el-select
v-model="leftFilter"
filterable
remote
clearable
reserve-keyword
placeholder="请输入内容按回车键(Enter)搜索"
@change="changeSelect"
:loading="loading"
:remote-method="remoteMethod"
class="my-transfer-panel__filter"
style="width:80%"
>
<template #prefix>
<el-icon><el-icon-search/></el-icon>
</template>
<el-option
v-for="item in leftOptions"
:key="item.userId"
:label="item.realName"
:value="{value:item.userId,item:item}"
:disabled='item.userId === leftFilter.userId'
/>
</el-select>
<!-- 树结构 -->
<el-tree
ref="tree"
:load="loadNode"
highlight-current
node-key="key"
@check-change="handleCheckChange"
lazy
:props="{label: 'label',children: 'children',isLeaf: 'leaf',disabled:'disabled'}"
show-checkbox />
</div>
</div>
<!-- 中间按钮 -->
<div class="my-transfer__buttons">
<el-button type="primary" style="margin-bottom:30px" :disabled="rightCheckListKey.length < 1 " @click="handleTransfer('toLeft')">
<template #default>
<span style="display: inline-flex;align-items: center;">
<el-icon ><el-icon-arrow-left /></el-icon>
</span>
</template>
</el-button>
<el-button type="primary" style="margin-left:0;" :disabled="leftCheckMapKey.size < 1" @click="handleTransfer()">
<template #default>
<span style="display: inline-flex;align-items: center;">
<!-- <span style="padding-right:5px">Toright</span> -->
<el-icon ><el-icon-arrow-right /></el-icon>
</span>
</template>
</el-button>
</div>
<!-- 右边 -->
<div class="my-transfer-panel">
<p class="my-transfer-panel__header">
<el-checkbox v-model="rightAllCheckbox" @change="changeRightAllCheckbox">
<template #default>
已分配
<span>{{rightList.length}}</span>
</template>
</el-checkbox>
</p>
<div class="my-transfer-panel__body">
<!-- 远程 搜索框 -->
<el-input v-model="userName" prefix-icon="el-icon-search" clearable @clear="resetQuery" @keyup.enter="getList" class="my-transfer-panel__filter" placeholder="请输入搜索内容" />
<!-- 按钮选择组 -->
<el-checkbox-group v-model="rightCheckListKey" class="my-transfer-panel__list">
<el-checkbox v-for="(i,index) in rightList" :key="index"
:label="i.key" class="my-transfer-panel__item">
{{i.label}}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<script>
import { defineComponent, reactive, toRefs,onMounted,getCurrentInstance } from "vue";
export default defineComponent({
setup() {
const { proxy } = getCurrentInstance();
const state = reactive({
// 存放左侧选中数据
leftCheckMapKey:new Map,
// 远程搜索字段
leftFilter:{userId:undefined},
// 远程搜索 选项
leftOptions:[],
// 右侧列表数据
rightList:[],
// 右侧列表数据 map格式
rightMapList:new Map,
// 右侧选中 数据列表
rightCheckListKey:[],
// 右侧全选 数据列表
rightAllCheckbox:false,
});
onMounted(() => {
getList()
})
/* 数据穿梭 */
function handleTransfer(str){
// 移动到左侧
if (str === 'toLeft') {
let transData = []
console.log(state.rightMapList);
// 选中的key
state.rightCheckListKey.forEach(i =>{
// 右侧全部数据map
if (state.rightMapList.has(i)) {
// 改变右侧数据列表
state.rightList = state.rightList.filter(v=>v.key != i)
let item = this.rightMapList.get(i)
transData.push(
{ key: item.key,
label: item.label,
})
}
})
// 更新树结构数据
proxy.$nextTick(()=>{
for (let index = 0; index < transData.length; index++) {
this.$refs['tree'].append(transData[index],transData[index].key)
}
})
// 清空 右侧选择的 key 完成移动
this.rightCheckListKey = []
state.rightAllCheckbox = false
}else{
// 获取 左侧树 选中的数据
let data = [...this.leftCheckMapKey.values()]
// 从树中 移除该数据
proxy.$nextTick(()=>{
for (let index = 0; index < data.length; index++) {
proxy._.refs['tree'].remove(data[index])
// 放入右侧数据列表
state.rightList.push(data[index])
state.rightMapList.set(data[index].key,data[index])
}
})
// 清空左侧选择的 key
state.leftCheckMapKey.clear()
proxy._.refs['tree'].setCheckedKeys([])
}
}
/* 左侧树形框 */
/* 树形结构 数据懒加载 */
async function loadNode(node, resolve){
if (!node) {
return;
}
let arr = []
for (let i = 1; i <= 4; i++) {
arr.push({
key: `${i}${node.level}`,
label: `Option ${i}${node.level}`,
disabled: i % 4 === 0,
leaf:node.level === 1
})
}
resolve(arr);
}
/* 树节点 复选框被点击 */
function handleCheckChange(val){
// 判断是否是子节点
if (val.leaf) {
// 当前 key 已存在 删除
if (state.leftCheckMapKey.size > 0 && state.leftCheckMapKey.has(val.key)) {
state.leftCheckMapKey.delete(val.key)
} else {
// key 不存在 添加
state.leftCheckMapKey.set(val.key,val)
}
}
}
/* 远程搜索请求 */
async function remoteMethod(){
/* let res = await this.$API.role.permission.unallocatedUserList.get({userName:str,roleId:this.queryParams.roleId}); */
/* this.leftOptions = res.rows */
}
/* 远程搜索完成 选择数据 */
async function changeSelect(val){
if (val.item) {
let data = {
key:val.item.userId,
userName:val.item.userName,
}
//将 选择的数据 放入 右侧列表
state.rightList.push(data)
}
}
/* 右侧 */
/** 请求右侧数据 */
async function getList() {
// 接口请求 数据
for (let i = 1; i <= 4; i++) {
state.rightList.push({
key: `${i}`,
label: `Option ${i}`,
disabled: i % 4 === 0,
})
}
// 数据转存map,方便取值
state.rightList.forEach(i=>{state.rightMapList.set(i.key,i)})
}
/* 全选 */
function changeRightAllCheckbox(val){
if (val) {
// 全选
state.rightList.forEach(item =>{
if (!state.rightCheckListKey.includes(item.key)) {
state.rightCheckListKey.push(item.key)
}
})
} else {
// 取消 全选
state.rightCheckListKey = []
}
}
return {
...toRefs(state),loadNode,remoteMethod,changeSelect,getList,handleCheckChange,
changeRightAllCheckbox,handleTransfer
};
}
})
</script>
<style lang="scss" scoped>
.my-transfer{
display: flex;
align-items: center;
width: fill-available;
width: -webkit-fill-available;
font-size: var(--el-font-size-base);
}
.my-transfer__buttons{
display: flex;
align-items: center;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
width: 85px;
}
.my-transfer-panel{
width: auto;
background: var(--el-bg-color-overlay);
display: inline-block;
text-align: left;
vertical-align: middle;
max-height: 100%;
box-sizing: border-box;
position: relative;
}
.my-transfer-panel__header{
display: flex;
align-items: center;
height:40px;
background: var(--el-fill-color-light);
margin: 0;
padding-left: 15px;
border: 1px solid var(--el-border-color-lighter);
border-top-left-radius: var(--el-border-radius-base);
border-top-right-radius: var(--el-border-radius-base);
box-sizing: border-box;
color: var(--el-color-black);
.el-checkbox{
position: relative;
display: flex;
width: 100%;
align-items: center;
:deep(.el-checkbox__label){
font-size: 16px;
color: var(--el-text-color-primary);
font-weight: 400;
}
}
.el-checkbox .el-checkbox__label span{
position: absolute;
right: 15px;
top: 50%;
transform: translate3d(0,-50%,0);
color: var(--el-text-color-secondary);
font-size: 12px;
font-weight: 400;
}
}
.my-transfer-panel__body{
border-bottom: 1px solid var(--el-border-color-lighter);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
height: 500px;
border-left: 1px solid var(--el-border-color-lighter);
border-right: 1px solid var(--el-border-color-lighter);
overflow: overlay;
.my-transfer-panel__filter{
text-align: center;
margin: 15px;
box-sizing: border-box;
width: 100%;
width:fill-available;
width: -moz-available;
width: -webkit-fill-available;
}
.my-transfer-panel__list{
height: calc(100% - 32px - 30px);
padding-top: 0;
margin: 0;
padding: 6px 0;
list-style: none;
overflow: auto;
box-sizing: border-box;
}
.my-transfer-panel__item{
height:30px;
line-height: 30px;
padding-left: 15px;
display: block!important;
}
}
</style>
组件功能还可以在完善,奈何水平有限🤣🤣🤣只能完成简单的操作,右侧内容穿梭到左侧的树时,树结构无法及时渲染视图,有清楚怎么解决的大佬请指教指教~~~ 谢谢谢谢谢谢谢谢啦😘