背景
在重构中遇到了一个比较复杂的排序功能,里面有同组项,拖动时要考虑同组项拖动其中一个时其他同组项的排序处理问题,且有同组项拖动到另外一个同组项之间等问题的处理,整个排序处理逻辑比较复杂,因此研究了一下小清的写法后,值得分享
访问地址
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 = false
this.newSequence = null
this.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 = sorted
for (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 = item
this.selectIndex = index
this.isShowModal = true
},
// step1
change(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.length
let 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 endGroupId
if (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 - 1
if (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 + 1
this.isSortMiddleOfGroup(dropIndex, dropIndex - 1)
} else if (oldIndex > dropIndex) {
//向上拖
this.exchangeIndex(dropIndex, dropIndex - 1)
dropIndex = dropIndex - 1
this.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 = res
this.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 - 350
return
}
}
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 = false
for (let j = 0; j < this.speciesList.length; j++) {
let speId = this.speciesList[j].id
if (speId == this.itemList[i].speciesId) {
perSpeciesItems[speId].push(this.itemList[i])
hasSpecies = true
break
}
}
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 = sortedItems
for (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