vuedraggable实现树形拖拽组件

开发可视化项目而做的树形拖拽组件。需要拖入为子元素需要children并且为树形展开的情况。单击可选中,shift+单击可选中一片区域,ctrl+单击多选。

效果图

在这里插入图片描述
在这里插入图片描述

属性

  • itemStyle:单个item项的样式,更推荐使用类名drag-item去更改基本样式。被选中的会被添加active类名;
  • data:数据,注意需要有id;
  • iconSize:树形节点图标的大小;
  • iconColor:树形节点图标的颜色;
  • select:默认选中的数据;
  • dragCheck:拖拽进入为子组件的触控区域;
  • styleId:自定义样式的类名所在id,id匹配会增加类名self_style;
  • isdbCancel:是否按两下取消选中。

方法

  • dragEnd:拖拽完成之后的回调,第一个参数是事件对象e,第二个参数是改变后的数据;
  • selectItem:选中的回调,第一个参数是事件对象e,第二个参数是当前选中的数据;
  • selectItems:选中数据改变的回调;
  • hoverItem:悬浮的回调,第一个参数是事件对象e,第二个参数是当前悬浮的数据;
  • moveItem:鼠标离开的回调,第一个参数是事件对象e,第二个参数是当前离开元素的数据;
  • handleDbClick:双击的回调,第一个参数是事件对象e,第二个参数是当前双击元素的数据;
  • onRightClick:右键鼠标的回调,第一个参数是事件对象e,第二个参数是当前右键元素的数据;
  • getSelectedList:组件方法,获取所有选中的数据;
  • setCheckedKeys:设置当前组件哪些元素被选中;
  • setShowChild:设置当前组件哪些元素应该被展开子元素。
  • changeshowChild:改变当前组件某个节点的孩子是否展示。

组件目录

在这里插入图片描述

组件封装

index.vue

<template>
    <yhdrag ref="yhDrag" :data="data" :drag-check="dragCheck" :showchilds="showchilds" @dragEnd="dragEnd">
        <template #content="{ scope }">
            <div
                :class="getClass(scope.data)" :style="getStyle(scope.$level)"
                @mouseenter="hoverItem($event, scope.data)" @mousemove="moveItem($event, scope.data)"
                @dblclick="handleDbClick($event, scope.data)" @contextmenu.prevent="onRightClick($event, scope.data)"
                @click.exact="selectItem($event, scope.data)" @click.ctrl="selectItems($event, scope.data)"
                @click.shift="addItems($event, scope.data)">
                <span :style="getIconStyle()">
                    <template v-if="scope.data.children">
                        <CaretRight
                            v-if="!showchilds.includes(scope.data.id)" style="width: 1em; height: 1em;"
                            :color="iconColor" @click.stop="changeshowChild(scope.data)" />
                        <CaretBottom
                            v-else style="width: 1em; height: 1em;" :color="iconColor"
                            @click.stop="changeshowChild(scope.data)" />
                    </template>
                </span>
                <slot name="content" :data="scope.data"></slot>
            </div>
        </template>
    </yhdrag>
</template>

<script setup>
/**
 * ============================================info==================================================>
 * author:yihua                                        
 * date:2022/10/26                                          
 * describe:树形可拖拽组件,单击可选中,shift+单击可选中一片区域,ctrl+单击多选。
 * <=================================================================================================
 * 
 * ===========================================属性================================================>
 * itemStyle:单个item项的样式,更推荐使用类名drag-item去更改基本样式。被选中的会被添加active类名;
 * data:数据,注意需要有id;
 * iconSize:树形节点图标的大小;
 * iconColor:树形节点图标的颜色;
 * select:默认选中的数据;
 * dragCheck:拖拽进入为子组件的触控区域;
 * styleId:悬浮的id,悬浮的item会增加类名self_style;
 * isdbCancel:是否按两下取消选中。
 * <================================================================================================
 * 
 * ===========================================方法================================================>
 * dragEnd:拖拽完成之后的回调,第一个参数是事件对象e,第二个参数是改变后的数据;
 * selectItem:选中的回调,第一个参数是事件对象e,第二个参数是当前选中的数据;
 * selectItems:选中数据改变的回调;
 * hoverItem:悬浮的回调,第一个参数是事件对象e,第二个参数是当前悬浮的数据;
 * moveItem:鼠标离开的回调,第一个参数是事件对象e,第二个参数是当前离开元素的数据;
 * handleDbClick:双击的回调,第一个参数是事件对象e,第二个参数是当前双击元素的数据;
 * onRightClick:右键鼠标的回调,第一个参数是事件对象e,第二个参数是当前右键元素的数据;
 * getSelectedList:组件方法,获取所有选中的数据;
 * setCheckedKeys:设置当前组件哪些元素被选中;
 * setShowChild:设置当前组件哪些元素应该被展开子元素;
 * changeshowChild:改变当前组件某个节点的孩子是否展示;
 * goToItem:滑动到某一项。
 * <================================================================================================
 */
import {
    CaretRight, CaretBottom } from '@element-plus/icons-vue'
import {
    computed, defineEmits, defineProps, ref, defineExpose, nextTick } from 'vue'
import yhdrag from './yhdrag.vue';

const props = defineProps({
   
    itemStyle: {
   
        type: Object,
        default: () => {
    }
    },
    data: {
   
        type: Array,
        default: () => []
    },
    iconSize: {
   
        type: Number,
        default: () => 14
    },
    iconColor: {
   
        type: String,
        default: () => '#FFFFFF'
    },
    select: {
   
        type: Array,
        default: () => []
    },
    dragCheck: {
   
        type: Number,
        default: () => 20
    },
    styleId: {
   
        type: String,
        default: () => ''
    },
    isdbCancel: {
   
        type: Boolean,
        default: () => false
    }
})

// 获取样式
const getStyle = level => ({
   
    ...props.itemStyle,
    paddingLeft: `${
     (level + 1) * 20}px`
})

// icon样式
const getIconStyle = () => ({
   
    display: 'inline-block',
    fontSize: `${
     props.iconSize}px`,
    width: `${
     props.iconSize}px`,
    height: '10px',
    marginRight: '5px'
})

const showchilds = ref([])
// 改变子集节点的显隐
const changeshowChild = data => {
   
    const LEN = showchilds.value.length
    showchilds.value = showchilds.value.filter(item => item !== data.id)
    if (LEN === showchilds.value.length) {
   
        showchilds.value.push(data.id)
    }
}

// 选中的数组
const selectList = ref([])
// eslint-disable-next-line vue/no-setup-props-destructure
selectList.value = props.select

// 获取已选中的数据
const getSelectedList = () => selectList.value;
// 设置选中
const setCheckedKeys = arr => {
    selectList.value = arr }
// 设置展开子节点
const setShowChild = data => {
    showchilds.value = data.map(item => item.id) }

// 注册事件
const emits = defineEmits(['dragEnd', 'selectItem', 'hoverItem', 'handleDbClick', 'moveItem', 'onRightClick', 'selectItems'])

// 初始化类名
const getClass = computed(() => data => {
   
    const isHover = data.id === props.styleId ? 'self_style' : ''
    if (selectList.value.findIndex(i => i.id === data.id) > -1) {
   
        return `drag-item active ${
     isHover}`
    }
    return `drag-item ${
     isHover}`

})

/**
 * @param {Array} arr
 * @param {String} id1
 * @param {String} id2
 */
const findtwoIdsData = (arr, id1, id2) => {
   
    const rst = [];
    const hitId = (id) => id === id1 || id === id2;
    let hitTimes = 0;

    const addItem = (list) => {
   
        list.forEach((item) => {
   
            if (hitTimes === 0 && hitId(item.id)) {
   
                // 命中第一次
                rst.push(item);
                hitTimes++;
                if (item.children && item.children.length) {
   
                    addItem(item.children, hitTimes);
                }
            } else if (hitTimes === 1 && hitId(item.id)) {
   
                // 命中第二次
                rst.push(item);
                hitTimes++;
                // eslint-disable-next-line no-useless-return
                return;
            } else if (hitTimes === 1) {
   
                // 命中第一次中间的
                rst.push(item);
                if (item.children && item.children.length) {
   
                    addItem(item.children, hitTimes);
                }
            } else if (item.children && item.children.length) {
   
                addItem(item.children, hitTimes);
            }
        });
    };
    addItem(arr);
    return rst;
};

const preId = ref('')
// 按住ctrl点击选中
const selectItems = (e, data) => {
   
    
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值