需求:
在本项目中,添加选择票据信息时,每条票据信息都有一个指定的号码段,有起始号码,终止号码,隐藏的属性还有校验位信息,号码长度。
要求用户每次选择号段时,会根据用户已选票据,和用户当前选择的票据信息对比,若属于同一种票据,则需要根据用户已选的票据,和该号段下剩余的票据,自动进行选择。
选择规则:
若剩余的票据号段是一整段连续号段,则自动选择剩余的全部号段为一条票据信息。若剩余的票据号段有多个号段,则自动选择其中起始号和终止号最小的号段,作为本次选择的票据信息。
票据号码的处理规则:
若校验位信息checkMark为1,则表示该票据有校验位,那么最后一位号码属于校验位,计算票据号码需要截断最后一位来看。号码长度则代表当前票据的号码长度。校验位的计算规则是,传入除校验位之外的票号,根据函数计算得出,最后补齐校验位,并将剩余长度在左侧通过补零来补齐。
选择完票据信息之后,用户还可以手动修改票据号码,
修改规则:
1、若该票据有校验位,则永远认为用户输入的最后一位是校验位,每次用户输入之后都需要重新计算验证校验位,并根据票据长度来补零。
2、起始号码和终止号码,必须在所选择的票据信息的起始号和终止号范围之内。
3、修改起始号码,若起始号码小于终止号码,则根据终止号码减去其实号码,计算出本数。若起始号码大于终止号码,则根据本数,计算出新的终止号码。
4、修改终止号码,终止号码必须大于起始号码,不满足时要弹出友好提示。终止号码大于或等于起始号码时,则根据新输入的终止号码减去起始号码,计算出新的本数。
5、修改本数时,起始号码不变,根据新输入的本数加上起始号码,计算出新的终止号码。
以上规则均需同时满足。
解决
将以上规则整理成流程图,如下。
如图所示,号码效验联动可以单独处理,因此本次文章主要处理选号流程。
第一步,选择号段。
为选择号段事件绑定处理函数:
// 选择票证种类信息
basBillInfoSelect(datas) {
console.log('选择的数据是:', datas)
}
第二步,判断所选项目和已存在项目是否相同。
首先确定目标billid,即当前选择的行数据的billid,只要表格中存在billid相同的项目,就可能是同一号段。为什么是可能呢?因为这里所选的号段,可能存在多个号段。
例如0000000001——000100000,可能分割成0000000001——0000500000和0000600000——000090000,这样就要以billid为基准,进一步判断起始号码和终止号码。若billid相同,且项目的起始号码大于或等于所选号段的起始号码,且项目的终止号码小于或等于所选号段的终止号码,则认为该项目从属于所选号段。由于对比号码还涉及到校验位的问题,所以这里多做了几个判断。
var targetBillId = datas.currentRow.billid
// 相同找到tabledata表格数组中相同billid的项目
var sameBillIdItemList = this.tableData.filter(item => {
// return item.billid === targetBillId
if (item.billid === targetBillId) {
if (+item.checkmark === 0) {
// 外面无校验位
if (+datas.currentRow.checkmark === 0) {
// 里面无校验位,外面无。
return +item.startnumber >= +datas.currentRow.startnumber && +item.endnumber <= +datas.currentRow.endnumber
} else {
// 里面有校验位,外面无。表现形式不一致
return false
}
} else {
// 外面有校验位
if (+datas.currentRow.checkmark === 0) {
// 里面无校验位,外面有校验位。表现形式不一致
return false
} else {
// 里面有校验位,外面也有校验位。
return +item.startnumber.slice(0, -1) >= +datas.currentRow.startnumber.slice(0, -1) && +item.endnumber.slice(0, -1) <= +datas.currentRow.endnumber.slice(0, -1)
}
}
} else {
return false
}
})
若项目中,与所选号段有所属关系的号段数组sameBillIdItemList 的长度为零,说明不存在已选择的号段,那么直接将该选择号段全部添加进去就行了。
第三步,对已选择的票据进行排序
若存在相同号段票据,那么首先对sameBillIdItemList这个数组进行排序,根据起始号码来排序。
同样,由于涉及到校验位,这里多做一步判断。
第四步,根据已排序的票据号段,计算出剩余未选中的票据号段。
排序完成之后,需要根据排序完之后的数组,整个所选票据的起始号码,终止号码,校验位信息,来获取到剩余的没有被选择的数据组成的数组,并且按升序排列。
首先定义一个函数getRestNumberArray,将上面提到的4个信息全部作为参数传递进来。然后在函数内部定义一个空数组,准备接受待会传递进来的号段信息。
export function getRestNumberArray(ascTableDataArray, startNumber, endNumber, checkMark) {
var restNumberArray = []
}
然后就需要考虑已选择的号段,可能出现的几种情况。
情况1:已选择的号段都是间断的,不连续的,且头尾都不靠近边界。
情况2:已选择的号段,起始号码是从票据号段头部开始,即头部靠近边界。
情况3:已选择的号段,终止号码正好是票据号段的尾部,即尾部靠近边界。
情况4:已选择的号段,中间的部分有连续,又有间隔,即中间部分可能连续也可能间断。
分析完以上4种情况后,应该可以得出这样的结论:
一、如果已选择号段的头部(左边第一个号段),它的起始号码大于整个号段的左边界,那么从左边界开始,到已选择号段的头部的号码 - 1 的号段,全部是未选中的号段,可以直接push到剩余号段中。(为了简单起见,这里暂时没有考虑校验位的情况。后面全部代码会写上)
// 开头
if (+ascTableDataArray[0].startnumber > +startNumber) {
const newNumberItem = {}
newNumberItem.startnumber = startNumber
newNumberItem.endnumber = ascTableDataArray[0].startnumber - 1
console.log('有一个大于左边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
二、如果已选择号段的尾部(右边最后一个号段),它的终止号码要小于整个号段的右边界,那么从右边界开始,往前一直到已选择号段的尾部的终止号码 - 1的号段,全部是未选中的号段,可以直接push到剩余号段中。
// 结尾
if (+ascTableDataArray[ascTableDataArray.length - 1].endnumber < +endNumber) {
const newNumberItem = {}
newNumberItem.startnumber = +ascTableDataArray[ascTableDataArray.length - 1].endnumber + 1
newNumberItem.endnumber = endNumber
console.log('有一个小于右边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
三、前面把头和尾的情况都处理了,接下来处理中间的情况。中间的情况主要有两种,连续和不连续。
这里判断连续的原理是:如果后面一段票据的起始号码减一,等于前面一段票据号码的终止号码,那么就认为这两段号码连续。
如果两段号码连续,那么两个号段中间无剩余票据,所以不做任何处理。
如果两段号码不连续,则说明两个号段中间有剩余票据。而剩余票据的起始号码,等于前面一段票据的终止号码加一;剩余票据的终止号码,等于后面一段票据的起始号码减一。
最后将该号码段push到剩余号段中。
这样,就获得了全部的剩余未选中号段!
// 中间
for (let i = 0; i < ascTableDataArray.length - 1; i++) {
if (ascTableDataArray[i + 1].startnumber - 1 === +ascTableDataArray[i].endnumber) {
// 说明连续。
} else {
// 说明中断了,区间数+1
const newNumberItem = {}
newNumberItem.startnumber = +ascTableDataArray[i].endnumber + 1
newNumberItem.endnumber = ascTableDataArray[i + 1].startnumber - 1
console.log('有一个中间号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
}
全部代码如下,这是考虑了校验位的情况。cutoffCheckMark是另外一个函数,用来截掉校验位并返回除去校验位后的号码,其实就是slice(0,-1)的封装。
export function getRestNumberArray(ascTableDataArray, startNumber, endNumber, checkMark) {
var restNumberArray = []
console.log('收到的排序后的数组为:', ascTableDataArray)
if (+checkMark === 0) {
// 无校验位时
// 开头
if (+ascTableDataArray[0].startnumber > +startNumber) {
const newNumberItem = {}
newNumberItem.startnumber = startNumber
newNumberItem.endnumber = ascTableDataArray[0].startnumber - 1
console.log('有一个大于左边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
// 中间
for (let i = 0; i < ascTableDataArray.length - 1; i++) {
if (ascTableDataArray[i + 1].startnumber - 1 === +ascTableDataArray[i].endnumber) {
// 说明连续。
} else {
// 说明中断了,区间数+1
const newNumberItem = {}
newNumberItem.startnumber = +ascTableDataArray[i].endnumber + 1
newNumberItem.endnumber = ascTableDataArray[i + 1].startnumber - 1
console.log('有一个中间号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
}
// 结尾
if (+ascTableDataArray[ascTableDataArray.length - 1].endnumber < +endNumber) {
const newNumberItem = {}
newNumberItem.startnumber = +ascTableDataArray[ascTableDataArray.length - 1].endnumber + 1
newNumberItem.endnumber = endNumber
console.log('有一个小于右边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
} else {
// 有校验位时
// 开头
if (+cutoffCheckMark(ascTableDataArray[0].startnumber) > +cutoffCheckMark(startNumber)) {
console.log(+cutoffCheckMark(ascTableDataArray[0].startnumber))
console.log(+cutoffCheckMark(startNumber))
const newNumberItem = {}
newNumberItem.startnumber = cutoffCheckMark(startNumber)
newNumberItem.endnumber = cutoffCheckMark(ascTableDataArray[0].startnumber) - 1
console.log('有一个大于左边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
// 中间
for (let i = 0; i < ascTableDataArray.length - 1; i++) {
if (cutoffCheckMark(ascTableDataArray[i + 1].startnumber) - 1 === +cutoffCheckMark(ascTableDataArray[i].endnumber)) {
// 说明连续。
} else {
// 说明中断了,区间数+1
const newNumberItem = {}
newNumberItem.startnumber = +cutoffCheckMark(ascTableDataArray[i].endnumber) + 1
newNumberItem.endnumber = cutoffCheckMark(ascTableDataArray[i + 1].startnumber) - 1
console.log('有一个中间号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
}
// 结尾
if (+cutoffCheckMark(ascTableDataArray[ascTableDataArray.length - 1].endnumber) < +cutoffCheckMark(endNumber)) {
const newNumberItem = {}
newNumberItem.startnumber = +cutoffCheckMark(ascTableDataArray[ascTableDataArray.length - 1].endnumber) + 1
newNumberItem.endnumber = cutoffCheckMark(endNumber)
console.log('有一个小于右边界的号段被push进入', newNumberItem)
restNumberArray.push(newNumberItem)
}
}
return restNumberArray
}
这样,就获取到了按升序排列,且没有被选中的所有号段,所组成的数组。
第五步,取剩余号段中,最左的那条号段作为本次选择结果,并将该数据从剩余号段数组中剔除。
这里添加选择结果之前,还需要判断一下,如果剩余票据号段长度为零,说明该票种下面的全部号段已经添加完毕,给个友好提示即可。
如果不为零,则每次取剩余号段中的第一个,取完之后记得使用shift方法删除剩余号段中的第一条数据。
这样,票据选择时,根据票据分段自动选择数据的功能就基本实现了。