(长文预警) 你还在烦工作中碰到的拖拽问题?一个框架解决你的疑问__Vue.js
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利
Sortablejs 简介
Sortable —是一个JavaScript库,用于在现代浏览器和触摸设备上对拖放列表进行重新排序。无需jQuery。 支持Meteor,AngularJS,React,Polymer,Vue,Ember,Knockout和任何CSS库,例如Bootstrap
一看这解释感觉就是很棒的感觉
特征
支持触摸设备和现代浏览器(包括IE9) 可以从一个列表拖动到另一个列表或在同一列表内 支持拖动手柄和可选文本(比voidberg的html5sortable更好) 智能自动滚动 高级交换检测 流畅的动画 多拖动支持 支持CSS转换 使用原生HTML5拖放API构建
支持
Meteor Angular2.0+1.* React ES2015+Mixin Knockout Polymer Vue Ember 支持任何CSS库,例如Bootstrap 简单的API 支持插件 CDN 不需要jQuery(但有支持) typscript定义在 [@types](/user/types)/sortablejs
文章
Dragging Multiple Items in Sortable (April 26, 2019) Swap Thresholds and Direction (December 2, 2018) Sortable v1.0 — New capabilities (December 22, 2014) Sorting with the help of HTML5 Drag'n'Drop API (December 23, 2013)
安装
npm install sortablejs --save
导入
// Default SortableJS
import Sortable from 'sortablejs';
// Core SortableJS (without default plugins)
import Sortable from 'sortablejs/modular/sortable.core.esm.js';
// Complete SortableJS (with all plugins)
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
插件导入
// Cherrypick extra plugins
import Sortable, { MultiDrag, Swap } from 'sortablejs';
Sortable.mount(new MultiDrag(), new Swap());
// Cherrypick default plugins
import Sortable, { AutoScroll } from 'sortablejs/modular/sortable.core.esm.js';
Sortable.mount(new AutoScroll());
基本模板以及样式代码
<template>
<div @click="reportClick" class="report-wrap">
<!-- v-if="ufd.isInit" v-loading="!ufd.isInit" -->
<div class="report-container">报表</div>
<div class="select-container">
<div class="key-list-content">
<h3 class="key-list-title">字段列表</h3>
<div class="key-list">
<p class="key-list-tip">将字段拖动到数据透视区域</p>
<ul :style="styleUl" @mouseenter="ulEnter" @mouseleave="ulLeave" class="key" id="key">
<!-- <li
:data-id="li['tableColumnCtlId']"
:data-label="li['columnName']"
:data-value="li['dbFieldName']"
:key="index"
v-for="(li,index) in ufd.columns.order"
>{{li['columnName']}}</li>-->
<li
:data-id="li['id']"
:data-label="li['labelKey']"
:data-value="li['valueKey']"
:key="index"
data-fun-name="sum"
v-for="(li,index) in options"
>{{li['labelKey']}}</li>
</ul>
</div>
</div>
<div class="data-view-content">
<h3 class="key-list-title">数据透视区域</h3>
<div class="data-view">
<p class="data-view-tip">在下面区域拖动字段</p>
<div class="view">
<div class="select-view-wrap same-wrap">
<p class="select-view-title">过滤器</p>
<div class="select-view same">
<ul @click.stop="select" class="select" data-id="filter"></ul>
</div>
</div>
<div class="row-view-wrap same-wrap">
<p class="row-view-title">行</p>
<div class="row-view other">
<ul @click.stop="row" class="row" data-id="row"></ul>
</div>
</div>
<div class="what-view-wrap same-wrap">
<p class="what-view-title">值</p>
<div class="what-view same">
<ul @click.stop="what" class="what" data-id="value"></ul>
</div>
</div>
</div>
</div>
</div>
<!-- 更新按钮 start -->
<div class="update">
<button @click="update">更新</button>
</div>
<!-- 更新按钮 end -->
</div>
<!-- 右键 出现 menu 菜单 start -->
<div id="menu" v-show="menuShow">
<div
:class="'menu'"
:key="index"
@click.stop="menuClick($event, index, item)"
v-for="(item, index) in menus"
>{{item.value}}</div>
</div>
<!-- 右键 出现 menu 菜单 end -->
<!-- 字段设置 start -->
<div @click.stop class="setting" v-if="settingShow">
<p class="title">字段设置</p>
<div class="setting-content">
<p class="origin-name">
原名称:
<span>{{originName}}</span>
</p>
<p class="setting-name">
自定义名称:
<input type="text" v-model="settingName" />
</p>
<h5 class="setting-select-title">分类汇总</h5>
<div @click.stop class="select-wrap">
<span>选择一个函数:</span>
<div class="select-content">
<select class="select" v-model="selects">
<option
:data-id="item.id"
:data-label="item.label"
:data-value="item.value"
:value="item.label"
v-for="(item, index) in settings"
>{{item.value}}</option>
</select>
</div>
</div>
<div class="select-button">
<button @click.stop="confirm" class="confirm">确定</button>
<button @click.stop="cancel" class="cancel">取消</button>
</div>
</div>
</div>
<!-- 字段设置 end -->
</div>
</template>
<style lang="scss" scoped>
.report-wrap {
width: 100vw;
height: 100vh;
display: flex;
.report-container {
width: 70vw;
height: 100%;
background-color: aqua;
}
.select-container {
box-sizing: border-box;
padding: 12px;
width: 25vw;
height: 100%;
h3.key-list-title {
color: rgb(56, 141, 145);
margin-bottom: 4px;
}
.key-list {
ul.key {
background-color: #fff;
height: 355px;
overflow: hidden;
margin-top: 6px;
display: flex;
flex-flow: column;
li {
padding: 6px;
cursor: -webkit-grab;
}
li:hover {
background-color: rgb(230, 230, 230);
}
}
}
.data-view-content {
margin-top: 20px;
box-sizing: border-box;
.view {
box-sizing: border-box;
display: flex;
justify-content: space-around;
.other {
height: 150px;
background-color: #fff;
overflow-y: auto;
ul {
box-sizing: border-box;
padding: 2px;
li {
position: relative;
box-sizing: border-box;
padding: 2px;
border: 1px solid rgb(230, 230, 230);
background-color: #fff;
margin-bottom: 2px;
color: rgb(102, 102, 102);
font-size: 14px;
}
li:last-child {
margin-bottom: 0;
}
}
}
.other:last-child {
margin-bottom: 0;
}
.same {
height: 150px;
background-color: #fff;
overflow-y: auto;
ul {
box-sizing: border-box;
padding: 2px;
li {
position: relative;
box-sizing: border-box;
padding: 2px;
border: 1px solid rgb(230, 230, 230);
background-color: #fff;
margin-bottom: 2px;
color: rgb(102, 102, 102);
font-size: 14px;
&:after {
content: '';
position: absolute;
top: 10px;
right: 5px;
width: 0;
height: 0;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
border-top: 4px solid rgb(102, 102, 102);
}
}
li:last-child {
margin-bottom: 0;
}
}
}
.same:last-child {
margin-right: 0;
}
.same-wrap {
margin-right: 10px;
}
.same-wrap:last-child {
margin-right: 0;
}
.select-view-wrap {
width: calc(50%);
}
.row-view-wrap {
width: calc(50%);
}
.what-view-wrap {
width: calc(50%);
}
}
}
.update {
padding: 10px;
}
}
/*css代码*/
#menu {
width: 0; /*设置为0 隐藏自定义菜单*/
height: auto;
overflow: hidden; /*隐藏溢出的元素*/
box-shadow: 0 1px 1px #888, 1px 0 1px #ccc;
position: absolute; /*自定义菜单相对与body元素进行定位*/
background-color: #fff;
z-index: 100;
.menu {
width: 130px;
height: 25px;