项目-云服务报告模块,问题总结

最近做东西,问题一个接着一个,心里着急,明明很简单的,我却发现诸多问题,写个总结,关于项目模块碰到的问题、解决方法以及用到的相关知识点。

一直想写总结,但是比较费时间,断断续续写着把。


1. 第一个需求,日期控件只支持选年月

门户之前的日期控件,能够选起始和结束,但是要具体到日,这里并不需要。

基于门户引入的kendo ui这个框架,试着引入相应的kendo 日期插件。
个人觉得kendo 控件做的样式美观功能也很强大,就是文档不全,官方网站访问速度很慢,有时候根本打不开,反正我白天从来没打开过。

1.2 kendo kendoDatePicker日期控件
  • html内 设置控件容器
起始日期:
结束日期:
js 内初始化 配置

只能选到年月、配置中文化、回显的日期格式、禁止用户手动输入日期、起始与结束日期控件的级联

           var start = $("#sTime1").kendoDatePicker({
                start: "year",
                depth: "year",   // 设置年历深度,设置后只能选到年月
                format: "yyyy-MM", // 选中日期后,input框显示的日期格式,相当于回调,看个人需求
                culture:  'zh-CN',  // 默认是英文,设置中文化
                change: startChange // 触发级联函数
            }).data("kendoDatePicker");
            // 默认日期控件,支持用户自己手动输入,为了减少校验用户输入非法日期,直接禁用掉
            start.element[0].disabled=true;

            var end = $("#eTime1").kendoDatePicker({
                start: "year",
                depth: "year",
                format: "yyyy-MM",
                culture:  'zh-CN',
                change: endChange
            }).data("kendoDatePicker");
            end.element[0].disabled=true;
            // 起始日期与结束日期设置最大、最小的可选日期值,不在范围内的日期将自动禁用,不可点击
            start.max(end.value());
            end.min(start.value());
起始日期与结束日期的级联函数与重置
  • 若【先选了起始日期】,则结束日期最小日期为起始日期,即结束日期只能选起始日期以后的日期,起始日期以前的日期不可点击。

  • 同理若【先选了结束日期】,则起始日期最大日期为结束日期,即起始日期只能选结束日期以前的日期,结束日期以后的日期不可点击。

【重置日期】:若用户选择日期后想修改日期段,先点击【重置日期】按钮将起始日期与结束日期初始化,然后继续操作,否则用户再次选的日期受限,不能随意选择起始或结束日期。

【注意】:搜索功能【可以起始日期与结束日期都不选择】,也可以【只选择起始日期或结束日期】。

  • 若只选择起始日期,则过滤该租户下报告日期大于等于起始日期的报告。

  • 若只选择结束日期,则过滤改租户下报告日期小于等于结束日期的报告。

            //起始日期与结束日期级联
            function startChange() {
                //var startDate = start.value(),
                //    endDate = end.value();
                var startDate = $("#sTime1").val(),
                    endDate = $("#eTime1").val();
                if (startDate) {
                    startDate = new Date(startDate);
                    startDate.setDate(startDate.getDate());
                    end.min(startDate);
                } else if (endDate) {
                    start.max(new Date(endDate));
                } else {
                    endDate = new Date();
                    start.max(endDate);
                    end.min(endDate);
                }
            }
            function endChange() {
                //var endDate = end.value(),
                //    startDate = start.value();
                var endDate = $("#eTime1").val(),
                    startDate = $("#sTime1").val();
                if (endDate) {
                    endDate = new Date(endDate);
                    endDate.setDate(endDate.getDate());
                    start.max(endDate);
                } else if (startDate) {
                    end.min(new Date(startDate));
                } else {
                    endDate = new Date();
                    start.max(endDate);
                    end.min(endDate);
                }
            }
            // 置空日期
            function initDatePicker(){
                 $("#eTime1").val("");
                $("#sTime1").val("");
                var endDate = new Date();
                start.max(endDate);
                end.min(endDate);
            }
设置日期控件禁止用户输入的另一种写法:attr("readonly", "readonly")
            // 只选择年月 kendo 插件
            $("#sTime").kendoDatePicker({
                //设置只选年月 深度
                start: "year",
                depth: "year",
                //日期显示格式
                format: "yyyy-MM",
                //中文化
                culture:  'zh-CN',
                //设置禁用
            }).attr("readonly", "readonly");
            $("#eTime").kendoDatePicker({
                start: "year",
                depth: "year",
                format: "yyyy-MM",
                culture:  'zh-CN',
                //dateInput: true
            }).attr("readonly", "readonly");

2. angular 设置checkbox 的全选与反选 级联

可以利用angular的双向数据绑定,input设置type='checkbox', ng-model对应它选中的状态。

                

            // 默认选中租户级、应用级(全部)
           vm.tenantChecked = true;
           vm.checkedAllApp = true;
          // 租户级 是否选中过滤
           vm.ifSelectTenant = function(){
               vm.submit();
           }
           // 应用级全部 全选、反选
           vm.ifSelectAllApp = function(){
               if(vm.checkedAllApp){
                   for(var i = 0; i < vm.applist.length; i++){
                       vm.applist[i].ifChecked = true;
                   }
               }else{
                   for(var i = 0; i < vm.applist.length; i++){
                       vm.applist[i].ifChecked = false;
                   }
               }
               vm.submit();
               $scope.$evalAsync();
           }
            // 单个应用系统 是否选中
            vm.ifItemAppCheck = function(row){
                if(row.ifChecked){
                    for(var i = 0; i < vm.applist.length; i++){
                        if(!vm.applist[i].ifChecked){
                            vm.checkedAllApp = false;
                            return;
                        }
                    }
                    vm.checkedAllApp = true;
                }else{
                    vm.checkedAllApp = false;
                }
                //vm.submit();
            }

【注意】:设置checked属性也可以选中或取消勾选,但是checked='{{item.ifChecked}}',不行,因为checked只是一个属性,不是angular中的指令,并不会自动检测数据变化,所以即使数据变化了,checked属性后面的{{item.ifChecked}}也只是一次性的,不会跟着数据动态的选中或取消,可以使用ng-checked指令。

angular中若controller层数据已经发生改变,但响应到view层页面上有延迟,添加以下代码:
 $scope.$evalAsync();

3. label包裹checkbox,有利于鼠标操作

为了方便点击,最好将input使用label包裹,这样用户点击checkbox 或者 checkbox对应得文字都能将checkbox勾选上,但后者体验更好。

label的两种写法:使用for属性关联对应的input id,如果要动态生成checkbox或者checkbox很多的时候,id命名很烦,不建议这种写法。

你喜欢的水果?

label写法2:直接用label包裹checkbox不用指定for属性以及checkbox id属性,更方便。

你喜欢的水果1?

  • 两者效果相同,但后者点击文字也可勾选,更方便:
    label写法.png

4. 去掉字符串第一个字符

具体场景,接口返回的数据,每个都带有特定意义的字母缩写开头,但是对于页面用户展示并不需要他们看到这个标志,就需要去掉开头。

做东西过程中发现,掉接口返回的结果,应用系统名称列表都开头带了C,我并不需要,然后就需要for循环一下,将每一个开头字母C去掉。

  • javascript String类型提供了很多的方法,以下是个小栗子:

  • 注意:结果并不改变原字符串。
    去掉首字母.png

5.替换字符串中的所有-

时间日期格式为年-月,如2018-08,我在前台过滤日期的时候,不需要-,需要将所有'-'去掉,即替换为空即可。

  • String replace(要被替换的字符,替换的字符)方法,将指定字符替换为空“”即可。
var dateRange = "2018-06~2018-09";
      dateRange = dateRange.replace(/\-/g,""); 
var dateArr = dateRange.split("~");
var sDate = dateArr[0];
var eDate = dateArr[1];
console.log(sDate,eDate);
替换字符.png

注意:
replace 只能替换第一个匹配的字符。

str.replace("-", "");

java 中有replaceAll,但JavaScript没有replaceAll方法。

要想替换所有的,请使用正则表达式全局匹配

str.replace(/\-/g,"");

6.数组与字符串的转换

  • 数组----> 字符串, arr.join("指定分隔符");
  • 字符串--->数组, str.split("指定分隔符");
    注意
  • join-若不指定分隔符,默认以逗号隔开。
  • split-最好指定分隔符,如果不指定分隔符,只能得到包含一个元素的数组

7. 将字符串拼接成正则表达式

多个要匹配的字段,中间以|隔开,然后拼接成/**|**|.../包裹,指定匹配规则i、g等,最后使用eval()转换。

var searchStr = ["北京西0", "上海南1", "宝山2", "虹桥3"];
 var myReg = '/'+ searchStr.join("|")+"/ig";
      myReg = eval(str);
vm.reportList = [];
 for(var i = 0; i < vm.dataset.length; i++) {
         if (vm.dataset[i].name.match(myReg)) {
                   vm.reportList.push(vm.dataset[i]);
          }
}

前台自己过滤 使用matchtest、或者indexOf,如果是动态拼接很多关键字段的话,建议使用以上方法,拼接关键字段为正则,这样省去大量for循环。


8. linux命令

要在服务器上新建一个目录,然后上传文件,最后将文件信息读取成json。

  • cd 进入目录
  • cd .. 返回上级目录
  • cd ../.. 返回上两级目录
  • mkdir 新建文件夹
  • rmdir 删除文件夹
  • vi 新建文件,i 进入编辑,esc 退出编辑, :wq退出并保存
  • rm 删除文件
  • cat 文件名 : 查看文件内容
上传文件:

SecureCRT工具 使用rz命令,弹出上传文件的窗口。

cmd本地打包成压缩包:

本地对应目录,cmd回车,弹出命令行,使用tar -zcvf 压缩包名 文件路径名命令打包,解压使用tar -zxvf 压缩包名

tar -zcvf 69.tar.gz 69
tar -zxvf 69.tar.gz
tar.png

9. 本地上传的文件(带中文的文件名)上传到服务器上乱码

tar命令上传,但是感觉根本原因不在这里。

这个问题待续...


10.使用nodejs,读取本地文件的信息,然后组成json,放置到本地对应的json文件内。

使用的是nodejs 的fs模块,不懂得可以上官网看文档。

思路如下:

  • 传递一个路径,然后读取路径下得所有文件。
  • 遍历文件,如果是文件则不处理,如果是文件夹,就以租户id,即这个文件夹名称,拼接一个json文件。
  • 判断这个json文件是否存在,若存在就更新,若不存在就新建。
  • 拼接下一层路径,遍历该路径下得所有文件,如果是文件夹不处理,如果是文件且不是那个json文件,则截取文件名得信息,拼接出文件后缀、文件名、文件日期、文件url.
  • 将拼接得json写入json文件中即可。
/*
* @Author: adminZz
* @Date:   2018-08-09 13:08:13
* @Last Modified by:   adminZz
* @Last Modified time: 2018-08-09 15:28:22
*/
var fs = require('fs');
var path = require('path');

// var filePath = path.resolve(process.argv[2]);
var filePath = 'C:/Users/adminZz/Desktop/serviceReport';
// var icon_json = {"path": process.argv[3],"templates": []};
var icon_json = {"path": 'C:/Users/adminZz/Desktop/serviceReport',"templates": []};

createOrUpdateFileToSavaJson(filePath);  

function createOrUpdateFileToSavaJson(filePath){  
    //根据文件路径读取文件,返回文件列表  
    fs.readdir(filePath,function(err,files){  
        if(err){  
            console.warn(err)  
        }else{
            //遍历读取到的文件列表
            files.forEach(function(filename){
                //获取当前文件的绝对路径  
                var filedir = path.join(filePath,filename);  
                console.log("filedir--->",filedir);
                //根据文件路径获取文件信息,返回一个fs.Stats对象  
                fs.stat(filedir,function(eror,stats){  
                    if(eror){  
                         console.warn('获取文件stats失败');  
                    }else{  
                        var isFile = stats.isFile();//是文件  
                        var isDir = stats.isDirectory();//是文件夹 
                        console.log("filename--->",filename);
                        if(isFile){ 
                              // 是文件 不管,不处理
                        }  
                        if(isDir){  
                            console.log("filePath1",filePath);
                            var filedir2 = path.join(filePath,filename);  
                            console.log("filePath2",filedir2);
                            var jsonName = filename+".json";
                            var filedir3 = path.join(filedir2,jsonName); 
                            fs.exists(filedir3, function(exists) {
                                console.log(exists ? "文件存在,to更新" : "文件不存在,to新建");
                                if(!exists){
                                    fs.writeFile(filedir3, "", function(err) {
                                        if(err) {
                                            return console.log(err);
                                        }
                                        console.log("文件新建成功!");
                                    }); 
                                }
                            });
                            reportInfosToJson(filedir2,jsonName,filedir3);
                        } 
                    }
                })  
            });
        }  
    });  
} 

function reportInfosToJson(filePath,jsonName,jsonPath){  
    //根据文件路径读取文件,返回文件列表  
    fs.readdir(filePath,function(err,files){  
        if(err){  
            console.warn(err)  
        }else{
            //遍历读取到的文件列表
            var fileArray = [];
            files.forEach(function(filename){
                //获取当前文件的绝对路径  
                var filedir = path.join(filePath,filename);  
                //根据文件路径获取文件信息,返回一个fs.Stats对象  
                // console.log(filedir);
                fs.stat(filedir,function(eror,stats){  
                    if(eror){  
                         console.warn('获取文件stats失败');  
                    }else{  
                        var isFile = stats.isFile();//是文件  
                        var isDir = stats.isDirectory();//是文件夹 
                        if(isFile && filename!=jsonName){ 
                             console.log(filedir);                         
                             var filejson = {};
                             filejson.name = filename;
                             filejson.reportType = filename.substring(filename.indexOf(".")+1);
                             var fullName = filename.substring(0,filename.indexOf("."));
                             var arr = fullName.split("_");
                             filejson.reportDate = arr[0];
                             filejson.reportName = arr[1];
                             filejson.reportUseType = arr[2]?arr[2]:"";
                             fileArray.push(filejson);
                        }  
                
                         icon_json.templates = fileArray;
                         var t = JSON.stringify(icon_json);
         
                        //process.argv[4]
                        // fs.writeFileSync('/share/json/'+process.argv[4]+'.json',t);
                        fs.writeFileSync(jsonPath,t);
                    }
                })  
            });
        }  
    });  
} 




11.前台下载功能?

前台知道文件在服务器上的存放地址,可以使用a标签的href属性来实现文件的下载。

问题1:相对路径、绝对路径

此时的href属性不具备跳转功能,同时它的值是一个相对路径。如下载,添加download属性,会触发下载,只是会报404错误。

如果我本地用的是localhost:8080,但是文件放的地址是http:10.70.50.35:9090,就会导致问题,找不到这个文件。

href直接是http:10.70.50.35:9090/serviceReport/69/69.json, 只能预览,即使添加了download属性,也不能下载,即href只能是相对路径,download才起作用

问题2:浏览器直接预览要下载得文件,如txt、jpg等格式

用户在点击链接的时候,浏览器会直接下载这个文件,但是这里有个问题,
像txt,jpg,json等这些浏览器支持直接打开的文件是不会执行下载的,而是会直接打开,这显然不是我想要的效果。

解决办法:添加download属性,这个属性值可以是文件名,也可以不填,download不给值,会使用默认的文件名

该属性也可以设置一个值来规定下载文件的名称。所允许的值没有限制,浏览器将自动检测正确的文件扩展名并添加到文件 (.img, .pdf, .txt, .html, 等等)。

这样的话即使浏览器能直接打开,也会触发下载,而不是直接预览。

前提是href是相对路径

问题3:浏览器兼容问题,ie不支持download h5新属性,所以该方法并不通用。

12 批量下载功能

选择多项,组织url地址数组,然后for循环。

window.open(url,'_blank'); // 打开新窗口,指向文件链接

但是出于安全限制,浏览器会认为它是广告弹窗,就自动拦截了,导致后面的弹窗直接被阻止了。

解决方案:模拟a链接被触发。
如果是用户自己手动点击a链接,浏览器就不会认为它是广告弹框,也就不会去拦截。

有用是有用,但是第一个会弹出,后面得照样被拦截,很无奈啊...

多次触发a链接,存在问题,这里加了提示让用户允许此网页弹出式窗口

  • 频繁得创建节点插入节点删除节点,性能不好,推荐第一种,页面上放一个隐藏得a链接,改变其href值,手动触发跳转。
               for(var i = 0; i < vm.ar.length; i++){
                //方法1:页面上放一个a标签,隐藏,每次去修改它的href值,然后手动触发a链接即可
                   $("#wopen")[0].href = webUrl+vm.ar[i];
                   $("#wopen")[0].download = vm.ar[i];
                   $("#wopen")[0].click();

                // 方法2:每次新建一个a标签,然后设置href,手动触发后,在移除这个a链接
                   var eleLink = document.createElement('a');
                   eleLink.download = vm.ar[i];
                   eleLink.style.display = 'none';
                   eleLink.href = webUrl+vm.ar[i];
                   eleLink.target = '_blank';
                   // 触发点击
                   document.body.appendChild(eleLink);
                   eleLink.click();
                   // 然后移除
                   document.body.removeChild(eleLink);

               }

13.js和jq手动触发a链接

js中的document.getElementById('ID').click(); 会手动触发a链接。
如果使用jQuery的话,选取的dom,必须是dom[0],否则会报错。
我个人理解,click() 相当与原生的一个方法,用于手动触发a链接,但是如果你在使用jquery的话,相当于jQ对象去使用原生js的方法,这样肯定会有问题

原生对象只能使用原生的方法,不能使用jQuery对象的方法。

同理jQuery对象只能使用jQuery对象的方法,不能使用原生dom的方法,否则会报错。

如果想用,就要进行dom的转换


14. js对象与jQ对象的转换:

  • jQdom --> jsDom : get 索引 或者 数组下标
$('dd').get(index)  或这 $('dd')[index]
  • jsDom--->jQdom: 使用$()包裹一下即可。
$(jsDom)  

jQ解决方案:

$(...)[0].click();

用这个方法可以直接模拟点击href的效果,实现下载。

因为a标签的href属性是在他dom中的0里面,需要点击那个0才能实现,而数字型的属性名不能用“.”来获取,故写成[0]。


15. 关于前台排序

因为拿到得json是无序得,我在页面上展示要按报告日期降序排列展示,所以我自己写了一个函数。

       function sortReportByDate(arr){
                for(var j=0;j

这样没过滤一次本地数据,就执行一次这个方法,即可实现排序功能。

sortReportByDate(vm.reportList)

16. angular $http 请求我动态拼接得url,进入success、error回调

切换租户得时候,会不存在json文件,导致404、还有跨域,需要在error回调中也正确处理页面展示。

  // 获取服务器上的json-- 初始数据
            var foldId = _user.customerId;
            var url = 'http://10.70.50.35:9090/serviceReport/'+foldId+'/'+foldId+'.json';
            var webUrl = 'http://10.70.50.35:9090/serviceReport/'+foldId+'/';
          vm.reportList = [];
          $http.get(url).success(function(data){
                console.log("data---->"+JSON.stringify(data));
                vm.dataset = data.templates;
                //self.imageUrl = data.path;
                for(var i = 0; i < vm.dataset.length; i++){
                    vm.dataset[i].url = webUrl + vm.dataset[i].name;
                }
                vm.reportList = vm.dataset;
                sortReportByDate(vm.reportList);
                vm.updateGrid();
            }).error(function(data){
                //alert("error--->服务器上无此文件!")
                console.log(JSON.stringify(data))
                vm.updateGrid();
            });

$http.post 小结
$http.post('url',{},{})
    .success(function(data,status,headers,config){
    })
    .error(function(data,status,headers,config){
    })

参数说明:

1) url: 请求的路径

2) 请求发送的数据: json对象 {name:'zmh'}     (在后端应该用req.body接收)

3)请求配置的参数: json对象 {params: {id:123}}, 这样得到的实际路径就是url?id=5    (在后端应该用req.query接收)

回调函数(成功和失败):

1)success: 请求成功的回调

2)error: 请求失败的回调

这两个方法都有四个参数:

 ①data: 返回的数据(或错误)

 ②status: 响应的状态码

 ③headers: 一个函数
 ④congfig: 请求的配置对象

$http.get 小结
$http.get('url',{})
    .success(function(data,status,headers,config){
    })
    .error(function(data,status,headers,config){
    })

传递得参数:

1) url: 请求的路径

2)请求配置的参数:json对象 {params:{id:5}} , 这样得到的实际路径就是url?id=5   (在后端应该用req.query接收)

回调函数(成功和失败):

1)success: 请求成功的回调

2)error: 请求失败的回调

这两个方法都有四个参数:

 ①data: 返回的数据(或错误)

 ②status: 响应的状态码

 ③headers: 一个函数
 ④congfig: 请求的配置对象

两者得区别:

$http.post接收三个参数,$http.get接收两个参数,$http.post比$http.get多了一个“请求发送的数据”。

另外在后端接收“请求发送的数据”和“请求配置的参数”的时候略有区别,“请求发送的数据”是一个json对象,在后端应该用req.body接收;

“请求配置的参数”是一个嵌套的json对象必须是 {params: json对象}, 在后端应该用req.query接收。

17. 纯html弹窗提示,使用border画三角形,组合成气泡

使用border设置三角形:

原理:设置border-left/right/top/bottom,设置其中一个为有颜色得(三角形指向),其他三边设置透明即可。


设置气泡:
两个标签包裹,背景色设置相同,父标签相对定位,子标签绝对定位,设置top/left即可。

                        
                        

i 批量下载时,请允许此网页上的弹出式窗口。

弹窗提示.png

门户common 与 portal-core 字库问题

之前门户portal-core,直接上传font文件夹即可,后来将公共部分抽离,字库也放到了common工程里面。
更新字库要到common工程,同时下载最下得common架包,替换到本地portal-core target目录下的一个lib下。

但是死活不出来,解决如下:

  • 更新配置文件,重新编译(把gulp、tomcat停掉),tomcat restar
  • 再不行就清浏览器缓存


    字库问题.png

以下是kendo grid 问题总结

kendo grid 基本上靠瞎琢磨,官网我笔记本打不开。碰到得问题也比较多。

  • kendo grid 使用本地数据
  • 本地数据翻页、分页问题
  • 搜索后,本地数据改变,grid不会跟着变。
  • grid 添加 全选列,全选与子checkbox 得全选、反选、级联
  • grid 分页选全部,pagesize值并不是全部。
  • grid 首次加载,条数显示NaN
  • grid 上页、下页、首页、末页 是在最外层得容器包裹。
  • grid 每页展示多少条,不在最外层得容器包裹内。
  • grid 设置外层容器最大高度无效,出现双层滚动条
    设置最外层无效,要设置内层table content区域
 #sr_report_list_table > .k-grid-content{
                max-height: 359px;
}

先写到这,有空在更...

你可能感兴趣的:(项目-云服务报告模块,问题总结)