业务背景:
项目上有一个价格库的概念,对应于某个地区的某个时间段。即:在某年的某个季度或者月份,项目公司会对各个地区建立该地区的人工、材料、机械的价格库。
呈现视图如下:
数据库设计
对于上面的业务,由于地区和价格期数是作为字典维护的,有独立的前端模块进行维护,所以需要独立建表。
同时,出于效率方面的考虑,未采用物理外键,使用了逻辑外键,由程序全权保证业务逻辑的正确性和完整性。
最终的表结构如下图所示:
后台接口
项目采用前后端分离的结构,后台提供CRUD接口,前台进行逻辑呈现。后端采用SpringBoot构建微服务体系,前台为使用vue框架的SPA应用。
后台提供Restful API接口,并通过Swagger提供前端开发人员进行查询和联调的接口文档界面及联调工具。
地区接口
价格期数接口
价格库接口
价格库中,除了基础的增删改查接口之外,还有带字典获取列表的接口(即一个请求同时获取到价格库及地区字典、价格期数字典);另外,还有按照价格期数执行使能切换的接口。
前端呈现实现
前端呈现的一个复杂之处在于:
1. 将原本的无层级记录列表呈现为带层级的逻辑视图;
2. 一级记录为从原本记录中构造出来的交集;
3. 二级记录需要与一级记录构建层级关系;
4. 存在过滤状态(即按照关键字进行过滤的状态);
为此,作如下的处理:
1 三级请求获取数据(鉴于带字典请求数据的接口与目前的前端公共接口不兼容,因此暂时采用三级串行请求的方式):
function(currentItemId) {
var vueObj = this;
// 一级请求,请求价格期数数据
periodModel().getTableDatas(function(items) {
vueObj.periods = items;
items.forEach(function(item) {
vueObj.periodMap[item.id] = item.description;
});
// 二级请求,请求地区数据
regionModel().getTableDatas(function(items) {
vueObj.regions = items;
items.forEach(function(item) {
vueObj.regionMap[item.id] = item.description;
});
// 三级请求,请求价格库数据
resPriceDBModel().getTableDatas(function(items) {
vueObj.moving = false;
vueObj.doFormatDisplayData(items);
// 数据获取完成并进行修饰构造后,通过handsonTable进行呈现
vueObj.$refs.table.$emit('show-grid', vueObj.tableData(), vueObj.fieldList, vueObj.getEditStyle, true, vueObj.getAlignment, 'all');
vueObj.setCurrentRow(currentItemId);
}, function(errMsg) {
vueObj.moving = false;
vueObj.$message({message: errMsg, type: 'error'});
});
}, function(errMsg) {
vueObj.moving = false;
vueObj.$message({message: errMsg, type: 'error'});
});
}, function(errMsg) {
vueObj.moving = false;
vueObj.$message({message: errMsg, type: 'error'});
});
}
2 其中,最关键的方式是doFormatDisplayData,该方法构造出最终呈现的数据视图;
算法逻辑为:
① 遍历获取到的价格库记录数组,按照需要填充前台呈现所需数据(采用数组的map方法);
② 如果处于过滤状态,则对构造后的数组执行过滤处理(采用数组的filter方法);
③ 对于构造后的数组执行排序(按照价格期数排序,采用数组的sort方法);
④ 通过构造后的数组,提取出一级记录的map(采用数组的reduce方法);
⑤ 将构造后的数据填充到handsonTable呈现对应的数组对象,对于一级记录直接压入(采用数组的push方法);
⑥ 对于二级记录,则将原数据数组进行slice处理提取出一级记录对应的子记录,然后调用forEach方法将各记录压入数组(采用数组的slice及forEach方法)。
function(items) {
var vueObj = this;
var keyword = vueObj.keyword;
var datas = [];
if (vueObj.inSearch) {
datas = items.map(vueObj.makeupResPriceDB).filter(function(item) {
return (item.period && item.period.indexOf(keyword) >= 0) ||
(item.region && item.region.indexOf(keyword) >= 0) ||
(item.auditor && item.auditor.indexOf(keyword) >= 0);
}).sort(vueObj.doCompareResPriceDB);
} else {
datas = items.map(vueObj.makeupResPriceDB).sort(vueObj.doCompareResPriceDB);
}
var periodMap = datas.reduce(function (prevResult, currItem) {
if (prevResult.has(currItem.period_id)) {
prevResult.get(currItem.period_id).childCount += 1;
} else {
var periodObj = vueObj.makeupParentPeriodRec(currItem.period_id, currItem.enabled);
prevResult.set(currItem.period_id, periodObj);
}
return prevResult;
}, new Map());
dataStore[vueObj._uid].tableData = [];
var startIndex = 0;
for (var value of periodMap.values()) {
dataStore[vueObj._uid].tableData.push(value);
datas.slice(startIndex, startIndex + value.childCount).forEach(function(data) {
dataStore[vueObj._uid].tableData.push(data);
});
startIndex += value.childCount;
}
}
如上可知,该部分视图的构造逻辑还是有一些复杂的,其中综合运用了数组的各个方法,也算是对于JS数组的一次学习应用了。
JS数组方法总结
XMind总结数组方法,如下三张图: