做复杂一点的需求首先需要理清思路,这块功能有自动排序和手动排序两大块。
手动排序功能实现要点:
- 拖动时div的位置鼠标跟随-每个请求产生的div都需要绑定一个ref(div的宽度根据请求的时间段长短决定),并且只做y轴的位置改变,x轴不变(onmousemove事件、mousedown事件、mouseup事件)
- 位置吸附功能,更靠近哪一个通道(不用考虑更靠近哪一个半小时格子,请求的时间段不能改变)
- div替换功能,当放下位置有安排请求时(宽度上有接触),将原来的请求在该通道显示取消
- 是否可替换的判断,待审核区域会显示审核通过的请求,该状态请求不可再被替换,即当mouseup时,不能放下
- 两个区域的动态渲染,待审核区域的通道数由后台决定,冲突公司的行数是无限向下扩展的,但一个公司会占据完整一行
自动排序功能实现要点:
- 进入这个页面的时候就会自动排序一次
- 优先排列该天已经审核通过的请求,已通过的需要显示则后台数据将当天审核已通过的请求单独放到一块,未审核的单独放到一块
- 在进入页面自动排序后仍然还有冲突请求的情况下,可手动排序,也可以将冲突区域某个申请加锁再自动排序,这时,加锁的请求(单独存放在一个数组中,该数组还需要按照申请时间排序)会优先排列。点击自动排序时检测该数组是否有值
还需考虑的问题:
- 滚动条出现时,能否跟随鼠标滚动。
- 怎么把每个申请渲染到对应的格子上(已实现)。
做需求的时候合适的数据结构非常重要,也应该是优先考虑的问题,以下是我构建数据结构时的思路:
数据结构:每个格子应该有一个对象记录自己的时间属性,是否被占有,可以是二维数组,一维代表通道数。
进入页面获取数据后需要先在js中做一个计算(着重点),根据给出的通道数和申请信息做计算,区分出排在待审核区域的申请数组和排在冲突区域的申请数组。在这个计算过程中还需要给通道中的被占用cell做出标记,方便之后的手动排序。
自动排序计算思路:
- 根据拿到的通道数,每个通道初始化48个小格子,带有时间信息和占用状态。
- 遍历每个申请,对比时间段内每个半小时格子从第一个通道开始是否都可用,当能完全放下时,push进top数组(还需记录能放下的通道index)。当放不下时push进bottom数组。
- 全部计算完毕后,根据两个数组渲染出两组ref,下方通道数根据bottom数组的长度进行动态变化。
- (拖动时还需要一个临时数组,被拖动起来的div代表的申请不属于上方所说的任一数组,直到放下时才做判断,push进哪一个)
第二步才能是根据这两个数组动态渲染出位于两个区域的申请div
`模板:
待审核
{{item.company}}
{{n}}
冲突公司
{{item.company}}
{{n}}
{{n-1}}:00
script:
export default {
name: "pipeArea",
data(){
return {
totalPipe:5, //总pipe数
pipeInfoInit:[ //单独一个pipe内的cell信息
{time:'00:00',available:true},
{time:'00:30',available:true},
{time:'01:00',available:true},
{time:'01:30',available:true},
{time:'02:00',available:true},
{time:'02:30',available:true},
{time:'03:00',available:true},
{time:'03:30',available:true},
{time:'04:00',available:true},
{time:'04:30',available:true},
{time:'05:00',available:true},
{time:'05:30',available:true},
{time:'06:00',available:true},
{time:'06:30',available:true},
{time:'07:00',available:true},
{time:'07:30',available:true},
{time:'08:00',available:true},
{time:'08:30',available:true},
{time:'09:00',available:true},
{time:'09:30',available:true},
{time:'10:00',available:true},
{time:'10:30',available:true},
{time:'11:00',available:true},
{time:'11:30',available:true},
{time:'12:00',available:true},
{time:'12:30',available:true},
{time:'13:00',available:true},
{time:'13:30',available:true},
{time:'14:00',available:true},
{time:'14:30',available:true},
{time:'15:00',available:true},
{time:'15:30',available:true},
{time:'16:00',available:true},
{time:'16:30',available:true},
{time:'17:00',available:true},
{time:'17:30',available:true},
{time:'18:00',available:true},
{time:'18:30',available:true},
{time:'19:00',available:true},
{time:'19:30',available:true},
{time:'20:00',available:true},
{time:'20:30',available:true},
{time:'21:00',available:true},
{time:'21:30',available:true},
{time:'22:00',available:true},
{time:'22:30',available:true},
{time:'23:00',available:true},
{time:'23:30',available:true},
],
cellInfo:[],
applyInfo:[
{company:'宇宙第一大合奏公司',startTime:'05:30',endTime:'10:30',half:10},
{company:'站在世界中心呼唤爱公司',startTime:'09:30',endTime:'13:00',half:7},
{company:'高冷王子忧郁帅哥公司',startTime:'07:30',endTime:'11:30',half:8},
{company:'大脑当机公司',startTime:'06:30',endTime:'11:30',half:10},
{company:'玛丽学院公司',startTime:'12:30',endTime:'16:30',half:8},
{company:'浪子回头公司',startTime:'18:30',endTime:'20:30',half:4},
{company:'亲爱的无情孙小美公司',startTime:'10:30',endTime:'16:30',half:12},
{company:'阿土伯合资公司',startTime:'12:30',endTime:'16:30',half:8},
{company:'声声慢合资集团',startTime:'04:30',endTime:'16:30',half:24},
{company:'齐桓公公司',startTime:'06:30',endTime:'13:30',half:14},
{company:'鲁庄公公司',startTime:'06:30',endTime:'15:30',half:18},
],
applyInTop:[], //待审核区域的申请信息
applyInBottom:[], //冲突区域的申请信息
}
},
mounted(){
this.InitCellInfo();
// this.InitApplyInfo();
this.WhoIsTop();
},
updated(){
this.RenderApply();
},
methods:{
//根据拿到的通道数,每个通道初始化48个小格子,带有时间信息和占用状态。(二维数组)
InitCellInfo(){
for(let i = 0;i{
this.SplitTime2Half(item.startTime,item.endTime);
});
},
//进入页面获取数据后,根据给出的通道数和申请信息做计算,区分出排在待审核区域的申请数组和排在冲突区域的申请数组,并给通道中的被占用cell做出标记
WhoIsTop(){
this.applyInfo.map(item=>{ //遍历申请列表
for(let pipeIndex in this.cellInfo){ //遍历通道
let pipeNow = this.cellInfo[pipeIndex]; //记录当前通道
let cellIndex = pipeNow.findIndex((cell)=>cell.time == item.startTime);
item['startCell'] = cellIndex; //记录绘制时的cell位置
if(this.IsPipeAvailable(item,pipeNow)){
for(let n = 0;n < item.half;n++){
pipeNow[cellIndex+n].available = false; //将当前通道上该申请的时间段设为被占用
}
item['pipeIndex'] = pipeIndex; //记录该申请放置的通道
this.applyInTop.push(item); //将该申请存放入待审核数组
break;
}
}
console.log(this.applyInTop)
});
let topSet = new Set([...this.applyInTop]);
let allSet = new Set([...this.applyInfo]);
let bottomSet = new Set([...allSet].filter(item => !topSet.has(item)));
this.applyInBottom = Array.from(bottomSet)
},
//申请在一个通道是否可放置
IsPipeAvailable(apply,pipeNow){
let canPut = true;
for(let cellIndex in pipeNow){
if(pipeNow[cellIndex].time == apply.startTime){ //在当前通道找到申请开始时间
if(pipeNow[cellIndex].available){ //若当前通道上该申请开始时间可用(需要开始时间之后到结束时间之前的每一个格子都可用)
for(let n = 0; n < apply.half; n++){
let index = Number(cellIndex) + n;
if(!pipeNow[index].available){ //该通道在申请时间段内有格子被占用
canPut = false;
break;
}
}
}else{ //若当前通道上该申请开始时间不可用
canPut = false;
}
}
}
return canPut;
},
//自动排序将请求渲染到对应位置
RenderApply(){
let halfHourWidth = 18; //半小时宽度为16+2px边框 => 18px
let pipeHight = 34; //一个通道的高度为32+2px => 34px
let indexCellWidth = 34+20-1; //每个通道index的宽度 + pipeArea的padding - 1px留给每个申请box绘制的左border
for(let index in this.applyInTop){ //渲染待审核申请
let item = this.applyInTop[index]; //ref名相同时会自动归到一个数组中
this.$refs.topItemList[index].style.top = pipeHight*item.pipeIndex + 'px';
this.$refs.topItemList[index].style.width = halfHourWidth*item.half - 6*2 +'px'; //减去padding6*2
this.$refs.topItemList[index].style.left = indexCellWidth + halfHourWidth*item.startCell + 'px';
}
for(let index in this.applyInBottom){ //渲染待审核申请
let item = this.applyInBottom[index];
this.$refs.bottomItemList[index].style.top = pipeHight*index + 'px';
this.$refs.bottomItemList[index].style.width = halfHourWidth*item.half - 6*2 +'px'; //减去padding6*2
this.$refs.bottomItemList[index].style.left = indexCellWidth + halfHourWidth*item.startCell + 'px';
}
},
}
}