vue3 tree树形穿梭框(封装组件)

效果图
代码
  • 子组件
// 封装组件
<template>
  <div class="treeTransfer">
    <!-- 左边 -->
    <div class="leftTree">
      <div class="list">
        <div class="left_lowline">
          <p class="left_title">用户列表</p>
        </div>
        <!-- 搜索 -->
        <div class="left_input">
          <el-input
            v-model="leftSearch"
            class="w-50 m-2"
            placeholder="搜索"
            clearable
            :prefix-icon="Search"
          />
        </div>
        <el-tree
          ref="treeRef"
          :data="props.fromData"
          show-checkbox
          :node-key="props.nodeKey"
          highlight-current
          :props="props.defaultProps"
          v-slot="{ node, data }"
        >
          <div>
            {{ data.corgName }} // 父节点
          </div>
          <div>
            {{ data.userName }}  // 子节点
          </div>
        </el-tree>
      </div>
    </div>
    <!-- 中间按钮 -->
    <div class="btnDiv">
      <div class="mg10" @click="toRight()">
        <el-button :icon="Right" circle />
      </div>
      <div class="mg10" @click="toLeft()">
        <el-button :icon="Back" circle />
      </div>
    </div>
    <!-- 右边 -->
    <div class="rightTree">
      <div class="list">
        <div class="left_lowline">
          <p class="left_title">已选列表</p>
        </div>
        <!-- 搜索 -->
        <div class="left_input">
          <el-input
            v-model="rightSearch"
            class="w-50 m-2"
            placeholder="搜索"
            clearable
            :prefix-icon="Search"
          />
        </div>

        <div
          class="right_item"
          :class="['item', { active: item.active }]"
          v-for="(item, index) in toData"
          :key="index"
          @click="checkNode(item)"
        >
          {{ item[props.defaultProps.label] }}
          <!-- 下拉框 -->
          <div>
            <el-select
              v-model="item.priority"
              class="m-2"
              placeholder="请选择"
              style="width: 100px"
            >
              <el-option
                v-for="item2 in options"
                :key="item2.value"
                :label="item2.label"
                :value="item2.value"
              />
            </el-select>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
//@ts-nocheck    解决ts类型报红
import { ref, onMounted } from 'vue';
import { Search } from '@element-plus/icons-vue';
import { useMainStore } from '@/store/index';
import { Right, Back } from '@element-plus/icons-vue';

const mainStore = useMainStore();
const props = defineProps(['nodeKey', 'fromData', 'toData', 'defaultProps']);

const leftSearch = ref('');
const rightSearch = ref('');
const options = [
  {
    value: '3',
    label: '高',
  },
  {
    value: '2',
    label: '中',
  },
  {
    value: '1',
    label: '低',
  },
];

// 定义emit
const emit = defineEmits(['checkVal']);
const treeRef = ref();

// 右侧数据
const toData = ref([]);

onMounted(() => {
  if (props.toData.length > 0) {
    toData.value = treeRef.value.getCheckedNodes(false, false);
    treeRef.value.setCheckedKeys([], false);
  }
});

// 去右边
const toRight = () => {
  const checkNodes = treeRef.value.getCheckedNodes(false, false);

  const newArr = toData.value.concat(checkNodes);
  let obj = {};
  let peon = newArr.reduce((cur, next) => {
    obj[next[props.nodeKey]]
      ? ''
      : (obj[next[props.nodeKey]] = true && cur.push(next));
    return cur;
  }, []); // 设置cur默认类型为数组,并且初始值为空的数组

  toData.value = peon;
  treeRef.value.setCheckedKeys([], false);
  checkVal();

  props.fromData.forEach((item: any) => {
    item.children.forEach((left: any) => {
      toData.value.forEach((right) => {
        if (left.userId == right.userId) {  // 已选的不可重复勾选
          left.disabled = true;
        }
      });
    });
  });
};
// 去左边
const toLeft = () => {
  for (var i = 0; i < toData.value.length; i++) {
    if (toData.value[i].active) {
      toData.value[i].active = false;
      toData.value.splice(i, 1);
      i -= 1;
    }
  }
  checkVal();

  props.fromData.forEach((item: any) => {
    item.children.forEach((left: any) => {
      left.disabled = false;
      toData.value.forEach((right) => {
        if (left.userId == right.userId) {
          left.disabled = true;
        }
      });
    });
  });
};
// 右侧item点击
const checkNode = (item: any) => {
  item.active = !item.active;
};
// 返回父组件
const checkVal = () => {
  emit('checkVal', toData.value);
};
</script>
  • 父组件
<div>
   <p class="select_meb">选择固定成员:</p>
            <div class="shuttle_frame">
              <tree-transfer
                ref="treeTransferRef"
                node-key="userId"
                :fromData="fromData"
                :toData="toData"
                :defaultProps="transferProps"
                @checkVal="checkVal"
              >
              </tree-transfer>
            </div>
            <!-- 底部按钮 -->
            <div class="modfiy_but">
              <el-button @click="saveModfiyManageMeb" type="primary"
                >确认</el-button
              >
              <el-button @click="cancelModfiyManage">取消</el-button>
            </div>
</div>
import { ref, onMounted } from 'vue';

let treeTransferRef = ref(); // 树形穿梭框
let fromData = ref([]); // 树形数据
let toData = ref([]); // 选中的ids数据
const transferProps = ref({
  label: 'userName',
  children: 'children',
  disabled: 'disabled',
});

// 子组件树形穿梭框返回
const checkVal = (val: any) => {
  toData.value = val;
};

// 获取左侧用户列表
async function getUserList() {
  let params = {
    userId: '',
  };
  const res = await getCompanyListById(params);
  if (res.code == 200) {
    if (res.data.result == 1) {
      fromData.value = res.data.returnMsg;
      // 根据返回数据处理.....
    } else {
      ElMessage.error(res.message);
    }
  }
}

********************************************************************
// 后端要求传参格式
[{"userId":"28586","priority":"2"},{"userId":"28588","priority":"3"}]

// 确认
function saveModfiyManageMeb() {
  let rightList: any[] = [];
  toData.value.forEach((element) => {
    let param = { userId: '', priority: '' }; 
    param.userId = element.userId;
    param.priority = element.priority;
    rightList.push(param);
  });
  var existUserStr = JSON.stringify(rightList);
  let params = {
    groupId: '',
    userDataStr: existUserStr,
  };
  saveAndModifyUserOfGroup(params).then((res) => {
    if (res.code == 200) {
      ElMessage.success('修改成功');
    } else {
      ElMessage.error(res.message);
    }
  });
}

参考了一位大佬的,当时忘记复制链接了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容