背景
在重构中遇到了一个比较复杂的排序功能,里面有同组项,拖动时要考虑同组项拖动其中一个时其他同组项的排序处理问题,且有同组项拖动到另外一个同组项之间等问题的处理,整个排序处理逻辑比较复杂,因此研究了一下小清的写法后,值得分享
访问地址
http://192.168.3.103:8099/#/login
线上访问地址为:crm.mytijian.com -> 维护后台 -> 单项管理 -> 排序
页面展示

核心功能概述
- 当拖动非同组项到同组项A中间时,采用置换方式将此非同组项依次向上或向下置换,直至挪出此同组项A之间
- 当拖动同组项A的其中一个小项A1移动到非同组项位置时,首先把同组项A全部找到并放入一个数组,然后以A1为分界线,将A1上下两层中除去同组项A后剩下的数据分别放入start和end两个数组,然后以start + A + end方式重新组装并排序
- 当拖动同组项A的其中一个小项A1移动到同组项B中间时,首先把同组项A和同组项B分别放入A、B两个数组,然后以A1为分界线,将A1上下两层中除去同组项A和同组项B的数据分别放入start和end两个数组,然后以start + A + B + end 或者start + B + A + end方式重新组装并排序
代码
<template><div class="p10"><div class="lh30"><div class="dib">项目名称:<i-input v-model.trim="filterWord" icon="ios-search-strong" placeholder="请输入项目名称" style="width: 260px" class="fr" @on-enter="filterItem" @on-click="filterItem" @on-change="filterItem"></i-input></div><span class="c-rd ml10">鼠标拖动可排序,双击单项可单个排序(注:当输入搜索条件时不可拖动)</span></div><div><div class="df"><div><div class="mt10 vertical-table overflow" v-bind:style="{'max-height': height, 'min-height': '300px', 'max-width': '900px'}"><table class="mt-table"><tr><th class="w60">序号</th><th class="w300">项目名称</th><th class="w150">所有类别</th></tr><draggable element="tbody" id="dragList" v-model="itemList" @end="end" @change="change" :options="{disabled: !!filterWord}"><tr v-for="(item, index) in itemList" :key="item.id" :class="{'c-fail': !item.show}" v-show="!item.hide" @dblclick="openSingleSort(item, index)"><td>{{item.sequence}}</td><td><span v-if="item.groupId"> {{item.groupId}} <i class="iconfont icon-fenzu c-primary"></i></span> {{item.name}}</td><td>{{item.speciesName}}</td></tr></draggable></table><mt-none class="pt20" :show="itemList.length == 0" /></div><div class="mt10"><Button type="ghost" class="fr" @click="cancel">取消</Button><Button type="primary" class="fr mr10" @click="ok">保存</Button></div></div><div class="mt10 ml10"><table class="mt-table w200 table-radius"><tr><th>类别顺序</th></tr><tr class="pointer" v-for="(species, index) in speciesList" :key="index"><td>{{species.name}}</td></tr></table><Button type="primary" class="mt15" @click="sortBySpecies">按以上类别排序</Button></div></div></div><!-- 弹窗 --><Modal v-model="isShowModal" width="400px" title="修改" :mask-closable="false"><div v-if="isShowModal"><Form :label-width="100"><FormItem label="修改"><div class="c-or">{{selectItem.name}}</div></FormItem><FormItem label="序号为:"><div class="rel"><InputNumber class="w150" v-model.trim="newSequence" @on-change="filterInput" :precision="0" :step="1"></InputNumber></div></FormItem></Form></div><div slot="footer"><span class="c-gr" v-if="successMsg">{{successMsg}}</span><Button type="text" @click="clearModalData">取消</Button><Button type="primary" @click="changeSequence">确定</Button></div></Modal></div></template><script>import { mapState } from 'vuex'import api from '@/api/m-itemmanage'import _cloneDeep from 'lodash/cloneDeep'import Draggable from 'vuedraggable'import { fullTohalf, arrayToHash, contains } from '@/common/utils'export default {name: 'manage-itemmanage-sort',data() {return {filterWord: '',itemList: [],itemListBak: [],itemListBak2: [],oldItemList: [],speciesList: [],height: '',startGroupItem: [],endGroupItem: [],isShowModal: false,selectItem: [],selectIndex: '',newSequence: null,successMsg: ''}},computed: {...mapState({hospitalId: state => state.user.hospital.id})},components: {draggable: Draggable},methods: {filterInput() {this.newSequence = parseInt(this.newSequence)},changeSequence() {if (this.newSequence || this.newSequence === 0) {let newIndex = parseInt(this.newSequence)if (newIndex < 0) {newIndex = 0}if (newIndex >= this.itemList.length - 1) {newIndex = this.itemList.length - 1}console.log(this.selectIndex, newIndex)if (newIndex != this.selectIndex) {this.singleSort(newIndex, this.selectIndex)} else {this.clearModalData()}} else {this.clearModalData()}},clearModalData() {this.isShowModal = falsethis.newSequence = nullthis.successMsg = ''},singleSort(newIndex, index) {let sorted = []let startGroupItem = this.getWholeGroup(index)let endGroupItem = this.getWholeGroup(newIndex)let insertArray = []let selectItem = this.itemList[index]let dropItem = this.itemList[newIndex]if (selectItem.groupId && dropItem.groupId && selectItem.groupId == dropItem.groupId) {//如果是组内移动,不做处理return}if (selectItem.groupId) {insertArray = insertArray.concat(startGroupItem)} else {insertArray.push(selectItem)}if (dropItem.groupId) {insertArray = insertArray.concat(endGroupItem)} else {insertArray.push(dropItem)}let beforeGroupItems = []let afterGroupItems = []for (let i = 0; i < newIndex; i++) {if (!this.containsItem(insertArray, this.itemList[i])) {beforeGroupItems.push(this.itemList[i])}}for (let i = newIndex; i < this.itemList.length; i++) {if (!this.containsItem(insertArray, this.itemList[i])) {afterGroupItems.push(this.itemList[i])}}sorted = sorted.concat(beforeGroupItems).concat(insertArray).concat(afterGroupItems)this.itemList = sortedfor (let i = 0; i < this.itemList.length; i++) {this.itemList[i].sequence = i}this.ok('single')},containsItem(array, item) {for (let i = 0; i < array.length; i++) {if (array[i].id == item.id) {return true}}return false},ok(type) {this.newItemList = arrayToHash(this.itemList)let changedItem = [] //排序后,sequence发生变化的项目for (let index in this.newItemList) {if (this.oldItemList[index].sequence != this.newItemList[index].sequence) {changedItem.push({ id: this.oldItemList[index].id, sequence: this.newItemList[index].sequence, hisItemId: this.newItemList[index].hisItemId })}}console.log(5, changedItem)if (changedItem.length > 0) {api.updateSequence({ data: changedItem }).then(data => {if (type === 'single') {this.successMsg = '保存成功'let timer = setTimeout(() => {this.clearModalData()clearTimeout(timer)}, 1000)} else {this.$Message.success('保存成功!')}this.getItem()})} else {this.clearModalData()}},cancel() {this.itemList = _cloneDeep(this.itemListBak2)},openSingleSort(item, index) {this.selectItem = itemthis.selectIndex = indexthis.isShowModal = true},// step1change(evt) {// 对应原来老版crm中的sort方法的 update 方法this.startGroupItem = this.getWholeGroup(evt.moved.oldIndex)this.endGroupItem = this.getWholeGroup(evt.moved.newIndex)},end(evt) {evt.oldIndex // 可以知道拖动前的位置evt.newIndex // 可以知道拖动后的位置this.sort(evt.newIndex, evt.oldIndex)},getWholeGroup(index) {let len = this.itemList.lengthlet groupItems = []if (this.itemListBak[index].groupId) {for (let i = -index; i < len - index; i++) {// console.log(i, len, len-index, index+i, index)/**-73 267 194 0 73*-72 267 194 1 73*...*192 267 194 265 73*193 267 194 266 73*/// 整个遍历一遍数组并将同组项全部挑选出来if (this.itemListBak[index + i] && this.itemListBak[index + i].groupId == this.itemListBak[index].groupId) {groupItems.push(this.itemListBak[index + i])}}}return groupItems},sort(currIndex, oldIndex) {if (currIndex == undefined) {return}if (this.inOneGroup(currIndex) || !this.itemList[currIndex].groupId) {// 1:同组项组内拖动 || 不是同组项拖动console.log(-1)this.isSortMiddleOfGroup(currIndex, oldIndex)} else {// 同组项拖动且非组内拖动console.log(-2)this.sortGroupItem(currIndex, oldIndex)}for (let i = 0; i < this.itemList.length; i++) {this.itemList[i].sequence = i}// 重新排序后保存当前快照,为了之后在拖动时准确找出牵连的同组项this.itemListBak = _cloneDeep(this.itemList)},sortGroupItem(currIndex, oldIndex) {console.log(this.startGroupItem, this.endGroupItem)let currItem = this.itemList[currIndex]let beforeGroupItems = []let afterGroupItems = []let endGroupIdif (this.endGroupItem.length > 0) {endGroupId = this.endGroupItem[0].groupId}let sorted = []// 以被拖动的项目为分界线,从上下两层中分别剔除与拖动相关的同组项并分为上下两个大集合for (let i = 0; i < currIndex; i++) {if (this.itemList[i].groupId != currItem.groupId) {if (endGroupId) {if (this.itemList[i].groupId != endGroupId) {beforeGroupItems.push(this.itemList[i])}} else {beforeGroupItems.push(this.itemList[i])}}}for (let i = currIndex + 1; i < this.itemList.length; i++) {if (this.itemList[i].groupId != currItem.groupId) {if (endGroupId) {if (this.itemList[i].groupId != endGroupId) {afterGroupItems.push(this.itemList[i])}} else {afterGroupItems.push(this.itemList[i])}}}// 若有A同组项向下拖动到B同组项之间,则在重新排序时B在A之上// 若有A同组项向上拖动到B同组项之间,则在重新排序时A在B之上if (currIndex > oldIndex) {sorted = sorted.concat(beforeGroupItems).concat(this.endGroupItem).concat(this.startGroupItem).concat(afterGroupItems)} else {sorted = sorted.concat(beforeGroupItems).concat(this.startGroupItem).concat(this.endGroupItem).concat(afterGroupItems)}this.itemList = sorted},inOneGroup(dropIndex) {let maxInd = this.itemList.length - 1if (dropIndex == 0 && this.itemList[0].groupId && this.itemList[1].groupId && this.itemList[0].groupId == this.itemList[1].groupId) {console.log(1, true, '被拖动到第一个,整个组被拖动到最上方且是组内拖动')return true}if (dropIndex == maxInd && this.itemList[maxInd].groupId && this.itemList[maxInd - 1].groupId && this.itemList[maxInd].groupId == this.itemList[maxInd - 1].groupId) {console.log(2, true, '被拖动到最后一个,整个组被拖动到最后一个且是组内拖动')return true}if (dropIndex == maxInd) {console.log(3, false, '拖动到最后一个')return false}if (dropIndex == 0) {console.log(4, false, '拖动到第一个')return false}if (this.itemList[dropIndex].groupId && (this.itemList[dropIndex].groupId == this.itemList[dropIndex + 1].groupId || this.itemList[dropIndex].groupId == this.itemList[dropIndex - 1].groupId)) {console.log(5, true, '被拖动项目是同组项,且拖动后的位置紧挨着一个id相同的同组项,简称组内拖动')} else {console.log(6, '非被拖动到第一个且最后一个,且非组内拖动')}return this.itemList[dropIndex].groupId && (this.itemList[dropIndex].groupId == this.itemList[dropIndex + 1].groupId || this.itemList[dropIndex].groupId == this.itemList[dropIndex - 1].groupId)},isSortMiddleOfGroup(dropIndex, oldIndex) {if (this.inOneGroup(dropIndex)) {//组内拖动除外(组内拖动不需要再次重新排序)console.log(7, '组内拖动')return}if (!this.itemList[dropIndex - 1] || !this.itemList[dropIndex + 1]) {console.log(8, '非同组项 且 被拖动到第一个或最后一个')return}if (!(this.itemList[dropIndex - 1].groupId && this.itemList[dropIndex + 1].groupId && this.itemList[dropIndex - 1].groupId == this.itemList[dropIndex + 1].groupId)) {console.log(9, '非同组项之间拖动')return}console.log(10, '非同组项被拖动到同组项之间')if (oldIndex < dropIndex) {//向下拖// 当前被拖动项目 与 下方第一个元素互换位置 并继续循环执行此步骤this.exchangeIndex(dropIndex, dropIndex + 1)dropIndex = dropIndex + 1this.isSortMiddleOfGroup(dropIndex, dropIndex - 1)} else if (oldIndex > dropIndex) {//向上拖this.exchangeIndex(dropIndex, dropIndex - 1)dropIndex = dropIndex - 1this.isSortMiddleOfGroup(dropIndex, dropIndex + 1)}},exchangeIndex(index1, index2) {let temp = this.itemList[index1]this.itemList[index1] = this.itemList[index2]this.itemList[index2] = temp},getItem() {api.getExamItemInSort({ data: { hospitalId: this.hospitalId } }).then(res => {this.itemList = resthis.itemListBak = _cloneDeep(this.itemList)this.itemListBak2 = _cloneDeep(this.itemList)this.oldItemList = arrayToHash(_cloneDeep(this.itemList))this.filterItem()})},filterItem() {this.filterWord = this.filterWord.trim()this.filterItemByQianDuan()this.srollTo()},filterItemByQianDuan() {let word = fullTohalf(this.filterWord)this.itemList.forEach((item, index) => {if (contains(item.name, word) || contains(item.pinyin, word) || contains(item.hisItemId, word)) {item.hide = false} else {item.hide = true}})},srollTo() {for (let i = 0; i < this.itemList.length; i++) {if (this.itemList[i].foc) {document.getElementById('dragList').scrollTop = 40 * i - 350return}}document.getElementById('dragList').scrollTop = 0},sortBySpecies() {this.$Modal.confirm({title: '提示',content: '你确定按类别重新排序?排序后不可撤销!',onOk: () => {this.speciesSort()}})},speciesSort() {let noSpeciesList = []let perSpeciesItems = {}for (let j = 0; j < this.speciesList.length; j++) {perSpeciesItems[this.speciesList[j].id] = []}for (let i = 0; i < this.itemList.length; i++) {let hasSpecies = falsefor (let j = 0; j < this.speciesList.length; j++) {let speId = this.speciesList[j].idif (speId == this.itemList[i].speciesId) {perSpeciesItems[speId].push(this.itemList[i])hasSpecies = truebreak}}if (!hasSpecies) {noSpeciesList.push(this.itemList[i])}}let sortedItems = []for (let i in perSpeciesItems) {sortedItems = sortedItems.concat(perSpeciesItems[i])}sortedItems = sortedItems.concat(noSpeciesList)this.itemList = sortedItemsfor (let i = 0; i < this.itemList.length; i++) {this.itemList[i].sequence = i}this.ok()}},mounted() {this.height = window.innerHeight - 350 + 'px'this.getItem()api.getSpeciesList({ data: { hospitalId: this.hospitalId, type: 1 } }).then(res => {this.speciesList = res})}}</script><style lang="less" scoped src="../style/index.less"></style>
文档更新时间: 2019-05-10 14:29