和老it一起做的项目,第一期终于快要完成了,剩下两个比较麻烦的,其中之一是一个复杂的表单模块,用来记录被调查者的各类信息。
前两天老it循循善诱:“$140,要不要挑战下?”
我心想问题应该不大,就先接了,把需求整个弄清楚后,我日了。。。
简单来说:对方只给了一张人工输入的sheet表格,共350+个表单,表格中用文字列出每个表单的各种字段及显示隐藏条件,部分表单的值由前面表单的值通过某些公式计算。
而且某些行里对应的是若干个相关联的表单,比如下面的是在sheet中的一行:
Actual Field Name Type
-----------------------------
Address:
Address Line 1 Textfield
Address Line 2 Textfield
City Textfield
State Dropdown
Zip code Number
-----------------------------
所以我们采取的方式是:
1.先将sheet表格转一个JsonA
2.从JsonA生成一个 表单配置对象config,用来传给vue
3.表单中的各种dropdown、checkboxes选项来自另一个数据源,转为一个JsonB
4.将JsonB那些下拉菜单和选框通过匹配label配对到对应的表单配置项
5.生成的config中处理好每一个label的格式、要传到后台的name的格式
6.给那些有显示隐藏条件的表单的配置对象添加show函数来控制显示
7.完成后台的crud接口
这两天做也蛮痛苦,需求逐渐确认,各种小坑,单是排查都排查了7、8遍以上。但是多亏了正则表达式,情况没想象中那么恶心。以前我只是用正则来验证email、密码、身份证之类的,偶尔.replace(regxep,xx)来修葺文本,但这次真是帮了大忙。
1.分离JSON中复合的表单
第一个问题比较简单,sheet转json后要把每个表单独立出来,如遍历中的某个对象n:
{"Actual Field Name":"Address :\nAddress Line 1\nAddress Line 2\nCity\nState\nZip code","Visible/Hidden":"Visible","Type":"Textfield\nTextfield\nTextfield\nDropdown\nNumber","R/O":"Required","Format":"99999-9999(ZipCode)"}
像抽离出每个表单,可以像下面这样在for循环里:
let rawName = n["Actual Field Name"].split(/\n/).map(name=>name.trim());
let rawtype = n["Type"].split(/\n/).map(type=>type.trim());
let prefix = rawName[0].replace(/[:| ]/g,'');
let fields = rawName.slice(1).map((each,index)=>{
{...n,"Actual Field Name":prefix+each,"Type":rawtype[index]}
})
然后通过Array.splice(index,1,fields)就可以完成第一次改造了。
2.标准化label和name
任何项目中,命名风格必须统一,由于label由特定的要求,所以和表单的name分别改造,name标准为驼峰写法,去除特殊字符:
function name2Standard(str){
return str.split(/[^a-zA-Z0-9]+/).map(n=>n[0]&&(n[0].toUpperCase()+n.slice(1))).filter(n=>n!==undefined).join(' ').replace(/\s+/g,'').split('').map((n,i)=>i===0?n.toLowerCase():n).join('')
}
3.互相匹配
由于JsonB里的表单选项是"Actual Field Name":[]形式,所以可以在标准化"Actual Field Name"后,通过匹配Actual Field Name"来给表单配置填充对应的options。
function heading2Standard(str){
let l = str.length;
let ending = str[l-1];
return (str.slice(0,l-1).replace(/[[A-Z][a-z]|\d+]/g,function(old){return ' '+old})
.replace(/[\_|:+]/g,'')
.replace(/\s+/g,' ')
.replace(/\/ /g,'/')
.trim()+ending);
}
4.添加函数
很大的一个配置config,总不能一个个添加函数吧,这也太辛苦了。由于显示逻辑写在"Visible/Hidden"字段里,比如:
{"Visible/Hidden":"Hidden (DoesTheClientReportAPersonalHistoryOfEmotionalAbuse.Contains(\"reports having been emotionally abused in the past\")) or (DoesTheClientReportAPersonalHistoryOfPhysicalAbuse.Contains(\"reports having been physically abused in the past\")) or (DoesTheClientReportAPersonalHistoryOfSexualAbuse.Contains(\"reports having been sexually abused in the past\"))"}
我们可以通过正则来尽量匹配需要添加显示的表单:
const run = async function() {
Object.keys(gs).forEach(name => {
let rows = gs[name];
rows.filter(r => /Contains/.test(r["Visible/Hidden"])).forEach(r => {
let v = r["Visible/Hidden"];
let pair = v.split(") or ");
let groups = pair.map(p => {
let str = p.match(/\"(.*?)\"/);
let base = v.match(/[\b\.\()]([^\.]*?)\.Contains/);
if (!str || !base) {
debugger;
}
return { str: str[1], base: base[1] };
});
let result = groups.map(e => `/${e.str}/.test(model["${e.base[0].toLowerCase()+e.base.slice(1)}"])`).join(" || ");
var a = 1;
result = `aaafunction(model){ return ${result}}aaa`;
r["Visible/Hidden"] = result;
});
// is yes
rows.filter(r => /is yes$/.test(r["Visible/Hidden"])).forEach(r => {
let v = r["Visible/Hidden"];
let base = v.match(/ (.*?) is yes/i);
if (!base) {
debugger;
}
base = base[1];
let first = base[0].toLowerCase();
let remain = base.slice(1);
let result = `aaafunction(model){ return model["${first+remain}"]}aaa`;
r["Visible/Hidden"] = result;
});
});
}
run();
将目前的config传进去后,函数会尽量查找需要添加显示/隐藏逻辑的表单对象,并将“Visible/Hidden”的字符串值改写为一个
`aaafunction(model){ return model["${first+remain}"]}aaa`
方便我们在编辑器上control+F去批量处理,将这个'Visible/Hidden'改为方法。这时候已经大大减少我们的工作量了。对于剩下的,只能我们自己去排查。
至于后台CRUD部分,传给mongoose的schema和typescript的Interface,直接从这个config配置中通过编辑器批量操作再移植就好了。这些工作量,不是开发,更多是编辑。
目前这个任务已经完成得差不多,过程坑很多,比如sheet中好些地方字母是拼错的,但我们又改不了那张sheet;有些选项的数据是缺失的,需要自己去查询拷贝;或者sheet中没有告知隐藏的表单中有些button点击后需要动态生成一组新表单。 但是有正则帮忙,大大降低了模式匹配和修正的工作量。
239

被折叠的 条评论
为什么被折叠?



