vue 實現拖拽動態生成組件的需求

產品需求

開完產品需求會議,遇到瞭一個需求,首先頁面分成兩欄佈局,左側展示數據組件,支持拖拽排序,點擊按鈕清除組件。右側支持將組件的縮略圖拖拽至左側生成一個新的組件。

思路

對於動態生成組件來說每一次都要是生成全新的一個組件,那麼就可以把 組件放進函數當中 return。在JSX中調用函數,每次調用函數都會返回一個全新的組件。這對React來說非常簡單,但是對於Vue來說,直接將組件返回是不可能的。盡管這個 return 寫法不適合Vue,但是我們不可否認,思路是非常正確的,所以我們應該考慮一個別的寫法。至於動態的生成組件,我們必須以數據來驅動組件的生成。對於拖拽組件的排序,直接使用拖拽庫就OK瞭!!

面臨的問題

  1. 拖拽庫的選擇
  2. 如何生成組件
  3. 以數據驅動動態生成組件

拖拽庫的選擇

拖拽庫在這裡我選擇的是項目中存在的一個拖拽庫 Vue.Draggable 點這裡鏈接查看 Start 14.9K 蠻不錯的。如果你們的Vue項目中沒有用到這個拖拽庫,你們可以自行參考本片文章的設計思路。

如何生成組件

在這裡我使用的是 Vue.extend() 不清楚如何使用的小夥伴請在官方文檔中查看過後再來學習這篇文章 Vue.extend 。 接下來我們創建一個js文件,用來書寫創建組件的代碼。

生成組件

/* generateComponents.js 文件名 */

import Vue from "vue";

// 想要動態生成的組件,先引入這個文件。
import components1 from "./components/TestCom1.vue";
import components2 from "./components/TestCom2.vue";

// 將組件的名稱和組件做一個對應Map
const comMap = {
  components1,
  components2,
};

// 接收生成組件需要的組件名稱,和想要傳遞給組件的
// props, 和 事件
const ReturnNewCom = function ({ props, on }) {
  const {
    comItem: { name },
  } = props;
  const newComponent = Vue.extend({
    render(createElement) {
      // 使用傳進來的組件name來決定渲染哪一個組件。
      return createElement(comMap[name], {
        props,
        on,
      });
    },
  });
  return new newComponent();
};

export default ReturnNewCom;

組件

在這裡我們書寫兩個組件,用來演示這個Demo,分別為components1.vue,components2.vue。

/*components1.vue*/
<template>
  <div class="widget-wrapper">
    <header class="header">{{ comDetail.name }}--{{ comDetail.id }}</header>
    <h1>查詢條件:{{ queryObj }}</h1>
    <button @click="handleDelete">清除</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      comDetail: this.comItem,
      _queryObj: this.queryObj,
    };
  },
  props: {
    comItem: {
      type: Object,
      default() {
        return {
          id: 0,
          name: "",
        };
      },
    },
    queryObj: {
      // 可以接收父組件傳遞的曬選條件,必須是Object
      type: Object,
      default() {
        // 定義默認的查詢條件。
        return {
          num: 0,
        };
      },
    },
  },
  watch: {
    comItem(val) {
      this.comDetail = val;
      return val;
    },
    queryObj(val) {
      this._queryObj = val;
      return val;
    },
  },
  created() {
    console.log("data -> this.comItem", this.comItem);
  },
  methods: {
    handleDelete() {
      // 刪除組件方法
      this.$el.remove();
      // 調用父組件的函數。修改父組件中的 leftComList 數組的數據。
      this.$emit("handleDelete", this.comDetail);
    },
  },
};
</script>
<style scoped>
.widget-wrapper {
  background: #ff7b7b;
  border-radius: 12px;
  overflow: hidden;
  width: 200px;
}
.header {
  height: 50px;
  padding: 0 15px;
}
</style>

其實components2.vue文件中的代碼和components1.vue文件的代碼類似,唯一不同的地方就是背景顏色不一樣。

以數據驅動動態組件的生成

接下來就得使用Vue.Draggable 這個拖拽庫進行拖拽和數據的修改。 我們可以直接在App.vue文件中直接書寫。

/* App.vue */
<template>
  <div class="dragCom">
    <h1>{{ leftComList }}</h1>
    <button @click="queryObj.num++">改變查詢條件</button>
    <div class="body">
      <div class="left">
        <draggable class="left" :list="leftComList" :group="'people'">
          <div
            ref="comBody"
            v-for="({ name, id }, index) in leftComList"
            :key="id"
            class="comCard"
          >
            <!-- 循環 leftComList 數組,利用數據來渲染組件, 
            將動態生成的數組添加到這個DOM元素當中。 -->
            {{
              handleAddCom({
                props: { comItem: { name, id }, queryObj },
                index,
              })
            }}
          </div>
        </draggable>
      </div>
      <div class="right">
        <draggable
          class="dragArea"
          :list="rightComList"
          :group="{ name: 'people', pull: 'clone', put: false }"
          :clone="handleCloneDog"
        >
          <div class="card" v-for="element in rightComList" :key="element.id">
            {{ element.name }}
          </div>
          <!-- 右側的 卡片 數據, rightComList 數組對象中的name就對應瞭generateComponents.js
          中的ComMap中的屬性 -->
        </draggable>
      </div>
    </div>
  </div>
</template>

<script>
import draggable from "vuedraggable";
import CreateCom from "./generateComponents";
export default {
  components: {
    draggable,
  },
  data() {
    return {
      rightComList: [
        {
          id: Math.random(),
          name: "components1",
        },
        {
          id: Math.random(),
          name: "components2",
        },
      ],
      leftComList: [], // 存儲驅動動態生成組件的數據。
      comMap: new Map(), // 主要的作用就是用來記錄 
      // 組件有沒有渲染到 class="comCard" 這個DOM中,
      // 如果渲染瞭就不能再往進添加子元素瞭。
      queryObj: {
        // 主要的作用就是向子組件傳遞查詢條件
        num: 0,
      },
    };
  },
  beforeDestroy() {
    // 清除 記錄 的數據
    this.comMap.clear();
  },
  methods: {
    handleAddCom({ index, on = {}, props = { comItem: { name: "", id: 0 } } }) {
      const {
        comItem: { id },
      } = props;
      this.$nextTick(() => {
        // 獲取該節點的子節點的長度
        const childNodesLength = this.$refs.comBody[index].childNodes.length;
        // 獲取comBody 這個DOM 數組的長度
        const comLine = this.$refs.comBody.length;
        if (!this.comMap.get(id)) {
          // 如果沒有渲染過組件

          // 1. 調用 CreateCom 方法 創建組件。 並傳遞 props 和 事件
          const com = CreateCom({
            props,
            on: {
              handleDelete: this.handleDeleteCom,
              ...on,
            },
          });
          // 2. 生成組件
          com.$mount();
          if (childNodesLength === 2) {
            // 如果要添加到兩個組件中間。那麼就將新生成的組件DOM位置進行修改放到中間。
            // 將最後的組件DOM添加到正確的位置
            this.$refs.comBody.splice(
              index,
              0,
              this.$refs.comBody[comLine - 1]
            );
          }
          // 3. 將生成的組件添加到改DOM中。
          this.$refs.comBody[index].appendChild(com.$el);
          // 4. 記錄該組件實現瞭渲染。
          this.comMap.set(id, true);
        } else {
          // 該位置的組件已經渲染,不需要再次渲染直接返回
          return;
        }
      });
    },
    handleDeleteCom({ id }) {
      // 傳遞給子組件刪除的方法,根據組件的id來刪除數據
      const index = this.leftComList.findIndex((item) => item.id === id);
      if (~index) {
        // 如果存在這個id的組件,就刪除
        this.leftComList.splice(index, 1);
      }
    },
    handleCloneDog(item) {
      // 給 leftComList 數組添加數據
      return {
        ...item,
        id: Math.random(),
      };
    },
  },
};
</script>

<style>
.dragCom {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.body {
  width: 100%;
  height: 800px;
  display: flex;
  justify-content: space-between;
}
.left {
  flex: 1;
  height: 800px;
  border: 1px solid pink;
}
.right {
  width: 20%;
  height: 800px;
}
.card {
  height: 50px;
  background-color: #40cec7;
  margin: 12px 0;
  font-size: 12px;
  line-height: 50px;
  cursor: pointer;
}
.comCard {
  margin: 12px;
  display: inline-block;
}
</style>


這樣就實現瞭動態的組件渲染和拖拽排序。

效果

 

源碼

想要嘗試的同學可以自行下載本文的代碼源碼github

以上就是vue 實現拖拽動態生成組件的需求的詳細內容,更多關於vue拖拽動態生成組件的資料請關註WalkonNet其它相關文章!

推薦閱讀: