基於vue作一個禪道主頁拖拽效果

不bb先看效果

預覽圖

源碼地址javascript

bb兩句

最近在作一個基於vue的後臺管理項目。平時項目進度統計就在上禪道上進行。so~ 而後領導就感受這個拖拽效果還行,能不能加到我們項目裏面。 既然領導發話,那就開幹。。css

全部技術:vue + vuedraggablehtml

拖動的實現基於 vuedraggable的插件開發。前端

主頁爲兩欄流式佈局,每個組件能夠在上下拖動,也能夠左右拖動。vue

頁面佈局

基本步驟

佈局

這塊佈局爲最爲普通的兩欄佈局,這裏採用flex佈局。左邊自適應,右邊爲固定寬。html5

.layout-container {
  display: flex;
  .left {
    flex: 1;
    margin-right: 40px;
  }
  .right {
    width: 550px;
  }
}  
複製代碼

拖拽實現

這裏使用 vuedraggable 插件。須要在組件裏面引入使用。draggable 至關於拖拽容器,這塊很明顯須要兩個拖拽的容器。因此分別在 .left .right中添加兩個拖拽容器。在默認狀況下,這裏已經能夠進行拖拽了。插件的效果仍是很強大。java

<div class="layout-container">
     <!--左欄-->
    <div class="left">
        <draggable
          v-bind="dragOptions"
          class="list-group"
          :list="item"
        >
         // ... 拖拽元素或組件
        </draggable>
      </div>
      <!--右欄-->
    <div class="right">
        <draggable
          v-bind="dragOptions"
          class="list-group"
          :list="item"
        >
          // ... 拖拽元素或組件
        </draggable>
    </div>
</div>
<script>
import draggable from "vuedraggable";
export default {
  components: {draggable},
  computed: {
    dragOptions() {
      return {
        animation: 30,
        handle: ".drag-handle",
        group: "description",
        ghostClass: "ghost",
        chosenClass: "sortable",
        forceFallback: true
      };
    }
  }
};
</script>
複製代碼

可是, 和我想要的效果仍是相差一點。git

左右拖動 與 僅標題欄拖動

這塊只須要配置相關的配置項就能夠比較簡單。 左右拖動須要給拖拽容器指定相同的 group 屬性。指定標題元素拖動須要配置 handle 爲可拖動元素的選擇器名稱。github

下面簡單介紹下經常使用的配置項:數據庫

  • disabled:boolean 定義是否此sortable對象是否可用,爲true時sortable對象不能拖放排序等功能,爲false時爲能夠進行排序,至關於一個開關;
  • group: 用處是爲了設置能夠拖放容器時使用,若兩個容器該配置項相同,則能夠相互拖動;
  • animation:number 單位:ms,定義排序動畫的時間;
  • handle:selector 格式爲簡單css選擇器的字符串,使列表單元中符合選擇器的元素成爲拖動的手柄,只有按住拖動手柄才能使列表單元進行拖動;
  • filter:selector 格式爲簡單css選擇器的字符串,定義哪些列表單元不能進行拖放,可設置爲多個選擇器,中間用「,」分隔;
  • draggable:selector 格式爲簡單css選擇器的字符串,定義哪些列表單元能夠進行拖放
  • ghostClass:selector 格式爲簡單css選擇器的字符串,當拖動列表單元時會生成一個副本做爲影子單元來模擬被拖動單元排序的狀況,此配置項就是來給這個影子單元添加一個class,咱們能夠經過這種方式來給影子元素進行編輯樣式;
  • chosenClass:selector 格式爲簡單css選擇器的字符串,當選中列表單元時會給該單元增長一個class;
  • forceFallback:boolean 若是設置爲true時,將不使用原生的html5的拖放,能夠修改一些拖放中元素的樣式等;
  • fallbackClass:string 當forceFallback設置爲true時,拖放過程當中鼠標附着單元的樣式;

採用相關配置以下:

computed: {
    dragOptions() {
      return {
        animation: 30,
        handle: ".drag-handle",
        group: "description",
        ghostClass: "ghost",
        chosenClass: "sortable",
        forceFallback: true
      };
    }
  }
複製代碼

拖動時樣式調整

在拖動的時候,咱們須要作三個事情。拖動時,拖動元素只顯示標題欄,兩欄內列表只顯示標題元素以及將要移動的位置變灰。

  1. 拖動元素只顯示標題欄: 在默認狀況下,會開啓 html5 元素的拖動效果。這裏明顯不須要。forceFallback 改成 false 則能夠關閉 html5 的默認效果。順便經過 chosenClass: "sortable" 修改拖動元素class 類名。直接用css進行隱藏

    .sortable {
      .component-box {
        display: none;
        height: 0;
      }
    }
    複製代碼
  2. 兩欄內列表只顯示標題元素 這裏我藉助兩個事件實現。

    • onStart:function 列表單元拖動開始的回調函數
    • onEnd:function 列表單元拖放結束後的回調函數
    <div class="layout-container" :class="{drag:dragging}">
        //...
    </div>
    複製代碼
    data() {
        return {
          dragging: false
        };
    },
    methods: {
        onStart() {
          this.dragging = true;
        },
        onEnd() {
          this.dragging = false;
        }
     }
    複製代碼
    .drag {
      .component-box {
        display: none;
      }
    }
    複製代碼

    在開始拖動的時候給 .layout-container 添加 .drag 的 class 名。拖動結束時,移除class名。

  3. 將要移動的位置變灰
    這裏須要用到上面 ghostClass: "ghost"配置項。並添加相應的css。

    .ghost {
      .drag-handle {
        background: rgb(129, 168, 187);
      }
    }
    複製代碼

    好了基本已經實現了。。。

展現動態組件

接下來就是數據的動態展現了。 這裏須要vue中的動態組件了。。附上官方文檔鏈接點擊查看

而後裏面每一個拖動的元素的內容都寫成組件,搭配動態組件實現自由拖動。

// 將所用組件引入
import {
  timeline,
  calendar,
  welcome,
  carousel,
  imgs,
  KonList
} from "@/components/DragComponents";

components: {
    draggable,
    timeline,
    calendar,
    welcome,
    carousel,
    imgs,
    KonList
}
複製代碼

配合 v-for 對數據進行循環,而後進行動態展現。

<component :is="element.name"/>
複製代碼

這塊涉及到數據格式相關的,能夠直接看文末的代碼。。。 這裏就就不展開說了。。

數據保持

在拖動結束後,咱們須要將拖動的順序緩存在前端,當下次進入後,能夠繼續使用拖動後的數據。

// 獲取新的佈局
 getLayout() {
      let myLayout = JSON.parse(window.localStorage.getItem("kon"));
      if (!myLayout || Object.keys(myLayout).length === 0)
        myLayout = this.layout;
      const newLayout = {};
      for (const side in myLayout) {
        newLayout[side] = myLayout[side].map(i => {
          return this.componentList.find(c => c.id === i);
        });
      }
      this.mainData = newLayout;
},
// 設置新的佈局
setLayout() {
    const res = {};
    for (const side in this.mainData) {
        const item = this.mainData[side].map(i => i.id);
        res[side]=item;
    }
    window.localStorage.setItem("kon", JSON.stringify(res));
}
複製代碼

這樣我只須要在 mounted 中獲取新的佈局。。

mounted() {
    this.getLayout();
 }
複製代碼

在拖動結束後,設置新的佈局

onEnd() {
    this.dragging = false;
    this.setLayout();
}
複製代碼

在項目中,仍是建議配合後端進行用戶佈局的數據存儲,每次拖動後將新的佈局數據請求接口保存在數據庫,同時存入緩存中。當再次進入頁面的時候,讀取緩存中的數據,沒有的話請求後端的接口拿到用戶的佈局,而後再次存入緩存中。有的話直接讀取緩存中的數據。

最後說兩句

其實上面的效果也不是特別難,簡單花點時間,看看相關文檔,就能作出來,,記錄在掘金上面,只是想和你們分享個人思路。同時但願和你們一塊兒交流,一塊兒進步。

生活不易,你們加油

附上源碼: 項目地址

<template>
  <div :class="{drag:dragging}">
    <div class="layout-container">
      <div :class="key" v-for="(item, key) in mainData" :key="key">
        <draggable v-bind="dragOptions" class="list-group" :list="item" @end="onEnd" @start="onStart" >
          <transition-group name="list">
            <div class="list-group-item" v-for="(element, index) in item" :key="index">
              <div class="drag-handle">{{ element.title }}</div>
              <div class="component-box">
                <component :is="element.name"/>
              </div>
            </div>
          </transition-group>
        </draggable>
      </div>
    </div>
  </div>
</template>
<script> import draggable from "vuedraggable"; import { timeline, calendar, welcome, carousel, imgs, KonList } from "@/components/DragComponents"; export default { components: { draggable, timeline, calendar, welcome, carousel, imgs, KonList }, data() { return { dragging: false, componentList: [ { name: "KonList", title: "追番地址", id: "5" }, { name: "imgs", title: "五月最強新番", id: "4" }, { name: "timeline", title: "日程組件", id: "2" }, { name: "carousel", title: "走馬燈組件", id: "1" }, { name: "calendar", title: "日曆組件", id: "3" } ], layout: { left: ["5", "4"], right: ["2", "1", "3"] }, mainData: {} }; }, computed: { dragOptions() { return { animation: 30, handle: ".drag-handle", group: "description", ghostClass: "ghost", chosenClass: "sortable", forceFallback: true }; } }, mounted() { this.getLayout(); }, methods: { onStart() { this.dragging = true; }, onEnd() { this.dragging = false; this.setLayout(); }, getLayout() { let myLayout = JSON.parse(window.localStorage.getItem("kon")); if (!myLayout || Object.keys(myLayout).length === 0) myLayout = this.layout; const newLayout = {}; for (const side in myLayout) { newLayout[side] = myLayout[side].map(i => { return this.componentList.find(c => c.id === i); }); } this.mainData = newLayout; }, setLayout() { const res = {}; for (const side in this.mainData) { const item = this.mainData[side].map(i => i.id); res[side]=item; } window.localStorage.setItem("kon", JSON.stringify(res)); } } }; </script>
<style lang="scss" scoped> .layout-container { height: 100%; display: flex; .left { flex: 1; margin-right: 40px; } .right { width: 550px; } .list-group-item { margin-bottom: 20px; border-radius: 6px; overflow: hidden; background: #fff; } .component-box { padding: 20px; } .drag-handle { cursor: move; height: 40px; line-height: 40px; color: #fff; font-weight: 700; font-size: 16px; padding: 0 20px; background: #6cf; } } .drag { .component-box { display: none; } } .list-enter-active { transition: all .3s linear; } .list-enter, .list-leave-to { opacity: .5; } .sortable { .component-box { display: none; height: 0; } } .list-group { > span { display: block; min-height: 20px; } } .ghost { .drag-handle { background: rgb(129, 168, 187); } } </style>
複製代碼
相關文章
相關標籤/搜索