一、简介

在实际业务中,可能需要一些拖拽的交互效果,vuedragable.js可以帮助我们实现。下面主要分享vue3项目中的应用,满满干货,赶紧上车!

二、示例环境

vite+ruoyi+vue3+elementPlus+vuedragable+iconfont

三、演示效果

链接:https://live.csdn.net/v/276228

四、实现步骤

1,下载依赖

yarn add vuedragable@next

npm i vuedragable@next

2,按照官网使用方法

(1)单列可拖拽示例:

<draggable
    :list="state.list" //绑定的数据
    :group="groupA" //绑定拖拽规则
    ghost-class="ghost"
    chosen-class="chosenClass"
    animation="300" //动画延时
    @start="onStart" //点击拖拽开始事件
    @end="onEnd" //松开拖拽结束事件
>
    <template #item="{ element }">
        <div class="item" style="background: #3480fb14">
            <i class="iconfont icon-xinxi" style="color: #3480fb"></i> //iconfont库标签
            {{ element.name }}
        </div>
    </template>
</draggable>
<script setup>
    const state = reative({
        list:[
          { name: "A组", id: 1 },
          { name: "库存", id: 2 },
          { name: "销量", id: 3 },
          { name: "日志", id: 4 },
        ]
    })
    //拖拽开始的事件
    const onStart = () => {
        console.log("开始拖拽");
    }
    //拖拽结束的事件
    const onEnd = () => {
        console.log("结束拖拽");
    }
    //移动事件
    const onMove = (e) => {
       console.log("结束拖拽");
    }
</script>

(2)双列可互相拖拽示例:

            <el-dialog v-model="isShow" width="859px">
                <template #header>
                  <div class="flex-header" style="color: #3480fb">
                    <span class="flex-title" style="borderbottomcolor: #3480fb"
                      >导航面板管理</span
                    >
                  </div>
                </template>
                <div
                  class="flex-tip"
                  style="color: #3480fb; background: #3480fb14"
                >
                  <el-icon><WarningFilled /></el-icon>
                  温馨提示:导航区域最多只可添加六个导航,并可拖拽该区域导航进行排序。
                </div>
                <div class="flex-dialog-body">
                  <div class="flex-six-menus">
                    <p>导航栏</p>
                    <div class="flex-six-menus-list">
                      <draggable
                        :list="state.list"
                        :group="groupA"
                        ghost-class="ghost"
                        chosen-class="chosenClass"
                        animation="300"
                        @start="onStart"
                        @end="onEnd"
                      >
                        <template #item="{ element }">
                          <div class="item" style="background: #3480fb14">
                            <i
                              class="iconfont icon-xinxi"
                              style="color: #3480fb"
                            ></i
                            >{{ element.name }}
                          </div>
                        </template>
                      </draggable>
                    </div>
                  </div>
                  <div class="flex-more-menus">
                    <p>更多导航</p>
                    <div class="flex-more-menus-list">
                      <draggable
                        :list="state.moreList"
                        group="itxst"
                        ghost-class="ghost"
                        chosen-class="chosenClass"
                        animation="300"
                        @start="onStart2"
                        @end="onEnd2"
                      >
                        <template #item="{ element }">
                          <div class="item">
                            <i
                              class="iconfont icon-xinxi"
                              style="color: #3480fb"
                            ></i>
                            {{ element.name }}
                          </div>
                        </template>
                      </draggable>
                    </div>
                  </div>
                </div>
                <template #footer>
                  <div class="dialog-footer">
                    <span style="textalign: center">
                      <el-button @click="cancel">取消</el-button>
                      <el-button type="primary" @click="confirm"
                        >保存</el-button
                      >
                    </span>
                  </div>
                </template>
              </el-dialog>
//弹窗取消按钮事件 ==》 关闭弹窗
const cancel = () => {
  isShow.value = false;
};
//需要拖拽的数据,拖拽后数据的顺序也会变化
const state = reactive({
  list: [],
  moreList: [],
});
//弹窗确认按钮事件
const confirm = () => {
  isShow.value = false;
  //visibleNumber.value = state.list.length;
  //moreMenus.value = state.moreList;
  //newTopMenus.value = [...state.list, ...state.moreList]
};

const groupA = reactive({
  name: "itxst",
  //允许拖入
  put: () => {
    //如果拖入数量超过6个,不允许拖入
    if (state.list.length < 6) {
      return true;
    } else {
      return false;
    }
  },
  //允许拖出
  pull: true,
});
//拖拽开始的事件[groupA]
const onStart = () => {
  console.log("onStart--开始拖拽");
};
//拖拽开始的事件[itxst]
const onStart2 = () => {
  console.log("onStart2--结束拖拽");
};
//拖拽结束的事件[groupA]
const onEnd = () => {
  console.log("onEnd--结束拖拽");
};
//拖拽结束的事件[itxst]
const onEnd2 = () => {
  console.log("onEnd2--结束拖拽");
};

根据官网,其实关键就在于group这个属性:

五、前端实现自定义菜单缓存

1,需求分析

先来看一下效果示例图:

(1)首先用户点击更多下拉框,并且点击设置图标按钮

(2)通过菜单导航面板设置自定义菜单顺序和数量,并且点击保存按钮
(3)最后顶部菜单实现用户自定义的菜单和顺序

👀忽略iconfont图标不齐全的缺点,多多包容;

2,实现步骤

0️⃣案例前提:顶部横向菜单的交互功能,不是左侧的导航菜单哦!

1️⃣功能需求简单来说:用户通过菜单导航面板自定义菜单顺序和数量;

2️⃣关键文件位置在:src/components/TopNav/index.vue

(1)步骤一:分析菜单渲染逻辑

若依框架渲染菜单的关键是在topMenus这个方法,可以打印一下,提醒computed打印需要topMenus.value;😂

另外一个就是visibleNumber,控制显示顶部菜单的数量,超出数量的菜单隐藏到更多的下拉框里;

在function setVisibleNumber里设置的visibleNumber的数量;

了解了上面的实现逻辑,我们就可以把更多的下拉框里的菜单模仿topMenus写一个moreMenus和visibleNumber去绑定,这样就可以初步实现顶部菜单由visibleNumber去控制数量;

// 顶部更多菜单 
const moreMenus = computed(() => {
  let moreMenus = [];
  //这里的意思是:截取顶部菜单的最大数量之后的12个菜单,多于12个的隐藏【当然你也可以根据自己需求更改】    
  moreMenus = topMenus.value.slice(
    visibleNumber.value,
    visibleNumber.value + 12
  );
  return moreMenus;
});

然后把moreMenus用v-for循环渲染上去即可;这样初步功能就可以实现了,可以试试更改visibleNumber,看看显示的菜单和下拉框的菜单是否有改变。

(2)步骤二:实现可拖拽面板

参考上文;

(3)步骤三:实现保存自定义菜单设置

我们先捋一下实现思路:用户在操作面板进行一些操作后,点击保存按钮,我们更新菜单;

所以在confirm事件里面,我们需要实现一些代码逻辑,如下所示:

//修改导航后的新菜单
const newTopMenus = ref([]);
//绑定拖拽列表的数据
const state = reactive({
  list: [], //groupA
  moreList: [], //itxist
});

//保存按钮点击事件
const confirm = () => {
  isShow.value = false;
  // 更新 visibleNumber 为用户调整的菜单数量
  visibleNumber.value = state.list.length;
  //设置更多下拉框的菜单是 itxist 调整的菜单
  moreMenus.value = state.moreList;
  //把调整后的两列拖拽列表的数据合并在一个新的数组里
  newTopMenus.value = [...state.list, ...state.moreList];
};
// 顶部显示菜单
const topMenus = computed(() => {
  //如果拖拽修改菜单的数组有值就返回调整后的 newTopMenus 新数组
  if (newTopMenus.value.length > 0) {
    return newTopMenus.value;
  } else {
  //如果刚开始加载或者没有调整就返回原来的 topMenus 数组
    let topMenus = [];
    console.log("topMenu===", routers);
    routers.value.map((menu) => {
      if (menu.hidden !== true) {
        // 兼容顶部栏一级菜单内部跳转
        if (menu.path === "/") {
          topMenus.push(menu.children[0]);
        } else {
          topMenus.push(menu);
        }
      }
    });
    return topMenus;
  }
});

注意:computed的值没办法在confirm事件里重新赋值,所以需要在原来的computed里面去判断返回的值;

这样就可以实现大体功能了,但是目前仅仅只是前端实现了交互功能,刷新后依然恢复原来的默认菜单,所以,如果有对接真实接口的话,我们在点击确认按钮的同时需要向后端传用户保留的菜单的id,这样通过后端数据的修改,就可以实现真实的效果情况了。

六、总结

以上大功告成,本篇主要提供思路,项目需求个不一致,大家可以灵活运用。

Logo

快速构建 Web 应用程序

更多推荐