Vue 拖拽选择列表

2,867 阅读1分钟

前言

最近因为某个需求,简单做了一个小拖拽选择列表 demo,这篇文章记录如何从零到一完成。

展示

1.gif

技术

Vue + vex + vuedraggable + element-ui

Demo

目录

image.png

布局

布局采用 element-ui 的 Container 布局

image.png

Data/index.vue

<template>
  <div class="app-wrapper">
    <el-container>
      <el-aside class="aside-position"><Aside /></el-aside>
      <el-container>
        <el-header class="header-position" height="155px">
          <Header />
        </el-header>
        <el-main>
          <Main />
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script>
  import Aside from '@/views/Data/Layout/Aside'
  import Main from '@/views/Data/Layout/Main'
  import Header from '@/views/Data/Layout/Header'

  export default {
    name: 'Index',
    components: {
      Header,
      Aside,
      Main
    }
  }
</script>

<style lang="scss" scoped>

.app-wrapper {
  position: relative;
  height: 100%;
  width: 100%;
  .aside-position {
    width: 28% !important;
  }
  .header-position {
    width: 100%;
    height: 100%;
    margin: 10px 0;
  }
}
</style>

Aside

image.png

data/Layout/Aside.vue

<template>
  <div class="aside-main">
    <div class="data-container">
      <el-divider />
      <DataList :col-type="0" :group="'attrCol'" />
      <el-divider />
      <DataList :col-type="1" :group="'numCol'" />
      <el-divider />
    </div>
  </div>
</template>

<script>
  import DataList from '@/views/Data/Layout/Components/DataList'
  export default {
    name: 'Aside',
    components: {
      DataList
    }
  }
</script>

<style lang="scss" scoped>
.aside-main {
  margin: 3px;
  padding: 0 5%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-radius: 4px;
  background-color: #fafbfb;
  .data-container {
    margin-top: 20px;
    width: 100%;
  }
}
</style>

DataList 组件

Data/Layout/Components/DataList.vue

封装 DdtaList 组件,也就是每一分类的数据列表。使用了 draggable 进行拖拽和 el-tag 数据标签样式。

<template>
  <div class="data-list-container">
    <div class="col-title data-title-position">{{ title[colType] }}(可选)</div>
    <draggable :group="group" :list="list" @start="dragging = true" @end="dragging = false">
      <el-tag v-if="list.length === 0" type="info">暂无数据</el-tag>
      <span v-for="{value, id} in list" v-else :key="id" class="item">
        <el-tag :type="colType ? 'primary' : 'success'">{{ value }}</el-tag>
      </span>
    </draggable>
  </div>
</template>

<script>
  import draggable from 'vuedraggable'
  export default {
    name: 'HeaderSlot',
    components: {
      draggable
    },
    props: {
      colType: {
        type: Number,
        default: 0
      },
      group: {
        type: String,
        required: true,
        default: ''
      }
    },
    data() {
      return {
        title: ['属性列', '度量列'],
        list: [],
        attrList: [
          { value: '商家姓名', id: 0 },
          { value: '地址', id: 1 },
          { value: '公告', id: 2 }
        ],
        numList: [
          { value: '起送费', id: 0 },
          { value: '配送费', id: 1 },
          { value: '抽成比例', id: 2 }
        ],
        dragging: false
      }
    },
    created() {
      this.list = this.colType === 1 ? this.numList : this.attrList
    },
    methods: {
    }
  }
</script>
<style scoped lang="scss">
.data-list-container {
  width: 100%;
  height: 100%;
  .data-title-position {
    margin-bottom: 15px;
  }
  .item {
    display: inline-block;
    margin-right: 5px;
    margin-bottom: 10px;
  }
}
</style>

Header

Data/Layout/Header.vue

image.png

用于拖拽选择列表数据

<template>
  <el-card class="box-card card-container" shadow="never">
    <div class="card-item-container">
      <span class="col-title">属性列</span>
      <el-divider direction="vertical" />
      <HeaderTag :col-type="'attrCol'" :group="'attrCol'" />
    </div>
    <el-divider />
    <div class="card-item-container">
      <span class="col-title">度量列</span>
      <el-divider direction="vertical" />
      <HeaderTag :col-type="'numCol'" :group="'numCol'" />
    </div>
  </el-card>
</template>

<script>
  import HeaderTag from './Components/HeaderTag'

  export default {
    name: 'Header',
    components: {
      HeaderTag
    }
  }
</script>

<style scoped>
</style>

HeaderTag 组件

Data/Layout/Components/HeaderTag.vue

<template>
  <span class="header-tag-container">
    <draggable :group="group" :list="list" @start="dragging = true" @end="dragging = false">
      <el-tag v-if="list.length === 0" type="info">暂无数据</el-tag>
      <span v-for="{value, id} in list" v-else :key="id" class="header-item">
        <el-tag :type="colType === 'attrCol' ? 'success' : 'primary'">{{ value }}</el-tag>
      </span>
    </draggable>
  </span>
</template>

<script>
  import draggable from 'vuedraggable'
  import { mapGetters } from 'vuex'

  export default {
    name: 'HeaderSlot',
    components: {
      draggable
    },
    props: {
      colType: {
        type: String,
        default: ''
      },
      group: {
        type: String,
        required: true,
        default: ''
      }
    },
    data() {
      return {
        list: [],
        dragging: false
      }
    },
    computed: {
      ...mapGetters([
        'attrCol',
        'numCol'
      ])
    },
    watch: {
      list: function(data) {
        this.$store.dispatch('data/updateColumns', { data, type: this.colType })
      }
    }
  }
</script>
<style scoped lang="scss">
.header-tag-container {
  display: inline-block;
  height: 100%;
  width: 80%;
  .header-item {
    display: inline-block;
    margin-right: 10px;
  }
}
</style>

通过 veux 来暂存数据

src/store/modules/data.js

const state = {
  attrCol: [],
  numCol: []
}

const mutations = {
  SET_COLUMNS: (state, { data, type }) => {
    console.log(type)
    state[type] = data
  }
}

const actions = {
  updateColumns({ commit }, { data, type }) {
    commit('SET_COLUMNS', { data, type })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

MAIN

Data/Layout/Main.vue

展示已拖拽选择的列表数据

<template>
  <div class="pane-container">
    <el-card class="box-card" shadow="hover">
      <div slot="header" class="clearfix">
        <span>已选属性列</span>
      </div>
      <span v-show="!attrCol.length" class="no-data">暂无数据</span>
      <span v-for="{value, id} in attrCol" :key="id" class="get-data">
        {{ value }}
        <el-divider direction="vertical" />
      </span>
    </el-card>

    <el-divider><i class="el-icon-lollipop" /></el-divider>

    <el-card class="box-card" shadow="hover">
      <div slot="header" class="clearfix">
        <span>已选度量列</span>
      </div>
      <span v-show="!numCol.length" class="no-data">暂无数据</span>
      <span v-for="{value, id} in numCol" :key="id" class="get-data">
        {{ value }}
        <el-divider direction="vertical" />
      </span>
    </el-card>
  </div>
</template>

<script>
  import { mapGetters } from 'vuex'

  export default {
    name: 'Main',
    components: { },
    data() {
      return {
      }
    },
    computed: {
      ...mapGetters([
        'attrCol',
        'numCol'
      ])
    }
  }
</script>
<style lang="scss" scoped>
.no-data {
  color: darkgray;
}
.get-data {
  font-size: 16px;
  color: cadetblue;
  font-weight: 600;
}
</style>

获取 vuex 数据

src/store/getters.js

const getters = {
  attrCol: state => state.data.attrCol,
  numCol: state => state.data.numCol
}
export default getters

  1. 实现不同组件中的拖拽

draggable 中需要定义相同的 group 属性值。

  1. aside 列表拖拽不上 header 中
  • 因为 element-ui 中 el-header 设置了默认值 60 px,当 header 上的 draggable 拖拽区域超过了 60 px,则出现 bug。所以需要将最外层容器的大小铺满整个 draggable 区域image.png

后言

最后完成了开头的效果 1.gif

一个简单的拖拽小 demo,为数据可视化的 BI 工具做小小的铺垫。觉得有些许用处就点赞哦~