NLifeBill第四章添加页面
这一节主要记录添加页面的功能,其实在做这个添加页面浪费了我不少时间,因为这个里面我遇到了几个问题,不过最后都一一解决了。
阅读目录:
1.主要问题
2.解决第一个问题:Tags分类显示
3.解决第二个问题:使用事务
4.前端angularjs处理
主要问题
这做添加页面的时候遇到的第一个问题是tags分类显示的问题,问题描述如下:
1 id name pid 2 1 AA 0 3 2 BB 0 4 3 CC 0 5 4 DD 1 6 5 EE 1 7 6 FF 3 8 7 GG 2 9 8 HH 3 10 9 II 2 11 ................. 12 这个是表结构,总共也就几十条数据。
因为mysql不支持迭代查询sql,所以数据读出来之后是没有分类的,所以这个地方分类要我自己处理。
我遇到的第二个问题就是我在添加数据的时候是一次添加多笔数据,也就是要批量处理,其实批量处理也还好,主要是我想使用事务,而nodejs这方面有关mysql使用事务的资料却不多,我也是瞎子摸象。
解决第一个问题:Tags分类显示
这个问题我在上面已经描述过了,我显示出来的效果是这样的:
因为前端是使用angularjs,那么对数据的格式多少是有点要求的,所以我要把数据库里面读出来的列表转为一个数组,然后数组里面每个分类又是一个数组,大分类在数组的第一个位置,如这样:
[ [object1, object2, object3......], //这里第一个object是衣 [object4, object5, object6............], //这里的第一个object是食 [object4, object5, object6............], ................ ]
我的做法是双层循环,拿第一个对象和后面的每个对象比较,如果自己的pid和别的pid相同并且pid不等于0的就是同一类,然后把找到的对象放到一个临时数组中并且做个标记,如果是自己的pid和别的id相同的,就说明这个是父类然后放到临时数组的第一个位置并且也做个标记。当内层循环第一次循环完之后,把整个临时数组都放到一个外围定义的数组里面然后就就开始做清除处理,把所有标记的对象都清除掉那么剩余的就是接下来其他的分类,然后再次进入循环。代码如下:
1 //temp就是数据库中读出的数组列表对象 2 function changeTags(temp){ 3 //存放结果的数组 4 var arr = new Array(); 5 6 //外围循环控制数组数量 7 for(var i=0, x=0;i<temp.length;i++){ 8 9 //临时数组存放每个单个数组项 10 var temparr = new Array(); 11 12 //查找相同对象并且标记 13 for(var j=x+1;j<temp.length;j++){ 14 15 //如果是父类就放到第一个位置 16 if(temp[x].pid == temp[j].id){ 17 temparr.unshift(temp[j]); 18 19 //标记这个项是处理过的 20 temp[j].mark = 'D'; 21 } 22 23 24 //如果是子类就放第一个的后面 25 if(temp[x].pid == temp[j].pid && temp[j].pid != 0){ 26 temparr[temparr.length] = temp[j]; 27 28 temp[j].mark = 'D'; 29 } 30 31 //本身也要放进数组 32 if(j == temp.length-1){ 33 temparr[temparr.length] = temp[x]; 34 35 temp[x].mark = 'D'; 36 } 37 } 38 39 //查找到的结果放到结果数组里 40 arr[arr.length] = temparr; 41 42 //清除标记的对象 43 for(var z=0, k=0;z<temp.length;z++){ 44 //判断前一个是否已经删除 45 if(k==0){ 46 z=0; 47 } 48 49 //如果是标记过的就删除 50 if(temp[z].mark == 'D'){ 51 //这里没有多余,这个属性也用不着 52 delete temp[z].mark; 53 54 //清除标记过的项 55 temp.splice(z, 1); 56 57 //归零,方便下次从0开始 58 k=0; 59 60 //继续操作 61 continue; 62 }else{ 63 //如果没有标记就累加,用于跳过 64 k++; 65 } 66 } 67 } 68 69 return arr; 70 }
解决第二个问题:使用事务
第二个问题是批量处理的时候使用事务,这样可以保持数据的一致性。效果如下:
像这种一次就有3个分类的时候就要批量添加数据了,使用事务可以保持数据一致。最开始的时候我使用的是一个mysql事务库--》mysql-queues但是我配置好之后却一致没有办法成功,后来我又在node-mysql中看到另外一种方式处理事务,地址。我配置这种之后也还是一直不成功,不能说不成功,只能说成功了一半,以为它卡在第一次保存数据的地方之后就退不出来了,大概需要几分钟吧然后下面的程序也不执行,后来没有办法我又再次回头用第一种办法,结果发现我第一种办法是方式没有处理对,后来修改了下代码就正常了,下面是处理代码:
1 Bills.addBills = function(master, details, callback){ 2 //从连接池中获取一个连接 3 db.getConnection(function(err, connection) { 4 5 queues(connection, config.mysql_queues_debug); 6 7 //开启事务 8 var trans = connection.startTransaction(); 9 10 //拼接主档sql 11 var sql = "insert into billmaster(years, months, days, revenue, outlay, addtime, userid) values(?, ?, ?, ?, ?, now(), 1)"; 12 var inserts = [master.years, master.months, master.days, master.revenue, master.outlay]; 13 sql = connection.format(sql, inserts); 14 15 //先插入主档 16 trans.query(sql, function(err, info) { 17 if(err){ 18 //callback(err, null); 19 trans.rollback(); 20 }else{ 21 //trans.commit(); 22 } 23 }); 24 25 //查询主档ID 26 var cql = "(select id from billmaster where years=? and months=? and days=?)"; 27 var csert = [master.years, master.months, master.days]; 28 cql = connection.format(cql, csert); 29 30 for(var i=0;i<details.length;i++){ 31 (function(detail, index, leng){ 32 33 var dql = "insert into billdetail(masterid, tagid, price, notes, addtime) values("+cql+", ?, ?, ?, now())"; 34 var dsert = [detail.tagid, detail.price, detail.notes]; 35 dql = connection.format(dql, dsert); 36 37 //插入明细档 38 trans.query(dql, function(err, info) { 39 if(err){ 40 //callback(err, null); 41 trans.rollback(); 42 }else{ 43 //如果是最后一次就提交事务断开连接 44 if(index == leng-1){ 45 trans.commit(function(err, infos){ 46 47 callback(null, info); 48 connection.release(); //使用完之后断开连接,放回连接池 49 }); 50 } 51 // 52 } 53 }); 54 })(details[i], i, details.length); 55 } 56 57 //提交执行 58 trans.execute(); 59 }); 60 };
前端angularjs处理
整个数据的整理基本都是在前端的angularjs处理的,所以看整个项目的代码前端的代码是多余后端的。添加页面的流程是,当用户点击分类的时候如果是小分类就可以在输入框中输入数据失去焦点事件之后就会记录在输入框下面,如果点击的是大分类就会提示用户不能输入。当当天的账记录完之后点击添加入账就会记录到数据库,整个添加页面是一个controller,下面是整个addcontroller的代码:
1 //添加分部视图的控制器 2 app.controller('addctrl', ['$scope', '$http', '$routeParams', '$location', function($scope, $http, $routeParams, $location){ 3 var params = { 4 year: $routeParams.year, 5 month: $routeParams.month, 6 day: $routeParams.day 7 }; 8 9 $scope.params = params; 10 11 var cont = { 12 result: [], //用户存储添加的信息 13 ashow: false, //用于控制添加按钮是否显示 14 inkey:[] //用于存放时收入的分类 15 }; 16 17 $scope.cont = cont; 18 19 //从服务器获取所有类别事件 20 $http.get('/getTags').success(function(data, status, headers, config){ 21 $scope.tags = data.tag; 22 23 //因为传过来的数据时数组套数组对象 24 for(var i=0;i<data.tag.length;i++){ 25 var obj = data.tag[i]; 26 //内层数组循环 27 for(var j=0;j<obj.length;j++){ 28 //如果类型是进账行就保存下来 29 if(obj[j].tagtype == 'I'){ 30 cont.inkey.push(obj[j].tagname); 31 } 32 } 33 } 34 }); 35 36 //点击类别事件 37 $scope.show = function(name){ 38 //判断是否有几个大分类,大分类不计入 39 if (name == '衣' || name == '食' || name == '它' || name == '住' || name == '行'){ 40 cont.error = "你所点击分类:'" + name + "' 不支持入账;"; 41 cont.eshow = true; //错误信息框打开 42 }else{ 43 cont.tagname = name; 44 cont.eshow = false; //错误信息框关闭 45 46 //循环判断是否是新的分类 47 for(var i=0, k=0;i<cont.result.length;i++){ 48 if(cont.result[i].name == name){ 49 cont.scontent = cont.result[i].value; 50 break; 51 }else{ 52 k++; 53 } 54 } 55 56 //如果是新的分类就清空输入框 57 if(k == cont.result.length){ 58 cont.scontent = ""; 59 } 60 } 61 }; 62 63 //输入框失去焦点事件 64 $scope.blur = function(){ 65 //1.0.7版本的blur不行的 66 67 var content = $scope.cont.scontent; 68 69 var val = content.replace(/,/g, ";").replace(/;/g, ";").replace(/,/g, ";").replace(/:/g, ":"); 70 71 //计算输入框中的总额 72 if(val.length > 0 && val != null && cont.tagname != null){ 73 74 var s = val; 75 var reg = /[+-]?\d+\.?\d*/g; 76 var sum = 0.0; 77 var expr; 78 var numbers = s.match(reg); 79 if (numbers != null) { 80 for (v in numbers) { 81 if (v == 0) 82 expr = numbers[v]; 83 else 84 expr += '+' + numbers[v]; 85 86 sum += parseFloat(numbers[v]); 87 } 88 } 89 90 var resulttype = 'O'; 91 92 //判断记账类型 93 for(var i =0;i<cont.inkey.length;i++){ 94 if(cont.tagname == cont.inkey[i]){ 95 resulttype = 'I'; 96 break; 97 } 98 } 99 100 //判断是否是第一次 101 if(cont.result.length == 0){ 102 cont.result[cont.result.length] = { name: cont.tagname, value: val, total: sum, type: resulttype }; 103 }else{ 104 //循环判断是否这个新增的tag是否已经存在 105 for(var i=0, k=0;i<cont.result.length;i++){ 106 if(cont.result[i].name == cont.tagname){ 107 cont.result.splice(i, 1, { name: cont.tagname, value: val, total: sum, type: resulttype }); 108 break; 109 }else{ 110 k++; 111 } 112 } 113 114 //如果是新的分类就添加 115 if(k == cont.result.length){ 116 cont.result[cont.result.length] = { name: cont.tagname, value: val, total: sum, type: resulttype }; 117 } 118 } 119 120 } 121 }; 122 123 //提交方法 124 $scope.submit = function(){ 125 var total = sum(); 126 127 //拼接主档 128 var master = { 129 years: params.year, 130 months: params.month, 131 days: params.day, 132 revenue: total.iTotal, 133 outlay: total.oTotal 134 }; 135 136 //明细档 137 var details = []; 138 //cont.result 139 140 var temptags = new Array(); 141 142 //循环拼接明细档 143 var tags = $scope.tags; 144 for(var i=0;i<tags.length;i++){ 145 var tag = tags[i]; 146 for(var j=0;j<tag.length;j++){ 147 temptags[temptags.length] = tag[j]; 148 } 149 } 150 151 //循环存储起来的数组 152 for(var i=0;i<cont.result.length;i++){ 153 //{ name: cont.tagname, value: val, total: sum, type: resulttype }; 154 var temp = cont.result[i]; 155 156 for(var j=0;j<temptags.length;j++){ 157 if(temp.name == temptags[j].tagname){ 158 details[details.length] = { 159 tagid: temptags[j].id, 160 price: temp.total, 161 notes: temp.value 162 }; 163 } 164 } 165 } 166 167 var data = {master: master, details: details}; 168 169 //把数据写入到数据库 170 $http.post('/addBill', data).success(function(data, status, headers, config){ 171 window.alert('添加成功'); 172 $location.path('/'); 173 }); 174 175 }; 176 177 //监视添加的账单的总额 178 $scope.$watch(function(){ 179 var total = sum(); 180 181 if(total.iTotal > 0 || total.oTotal > 0){ 182 cont.ashow = true; 183 }else{ 184 cont.ashow = false; 185 } 186 187 cont.total = '总进账: ' + total.iTotal + ' 总出账: ' + total.oTotal; 188 }); 189 190 //计算总额 191 function sum(){ 192 var total = { 193 iTotal: 0, 194 oTotal: 0 195 }; 196 var arr = $scope.cont.result; 197 for(var i=0;i<arr.length;i++){ 198 if(arr[i].type == 'I'){ 199 total.iTotal += arr[i].total; 200 }else{ 201 total.oTotal += arr[i].total; 202 } 203 } 204 return total; 205 } 206 207 }]);
基本上大部分的业务逻辑都在客户端。