最近做东西,问题一个接着一个,心里着急,明明很简单的,我却发现诸多问题,写个总结,关于项目模块碰到的问题、解决方法以及用到的相关知识点。
一直想写总结,但是比较费时间,断断续续写着把。
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
属性,更方便。
- 两者效果相同,但后者
点击文字也可勾选
,更方便:
4. 去掉字符串第一个字符
具体场景,接口返回的数据,每个都带有特定意义的字母缩写开头,但是对于页面用户展示并不需要他们看到这个标志,就需要去掉开头。
做东西过程中发现,掉接口返回的结果,应用系统名称列表都开头带了C,我并不需要,然后就需要for循环一下,将每一个开头字母C去掉。
- javascript String类型提供了很多的方法,以下是个小栗子:
- 注意:结果并不改变原字符串。
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);
注意:
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]);
}
}
前台自己过滤 使用match
、test
、或者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
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
批量下载时,请允许此网页上的弹出式窗口。
门户common 与 portal-core 字库问题
之前门户portal-core,直接上传font文件夹即可,后来将公共部分抽离,字库也放到了common工程里面。
更新字库要到common工程,同时下载最下得common架包,替换到本地portal-core target目录下的一个lib下。
但是死活不出来,解决如下:
- 更新配置文件,重新编译(把gulp、tomcat停掉),tomcat restar
-
再不行就清浏览器缓存
以下是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;
}
先写到这,有空在更...