2015年2月28日星期六

angularjs与bootstrap搭配,radio标签的checked无法更新到ngModel的问题

最近系统有很大的架构调整,组内没有专业前端,于是我这个半桶水被派去做前端任务,而且在我刚刚熟悉knockout的时候放弃,使用从未用过的angular框架,看着那道高高的学习曲线苦不堪言。
任务中涉及到大量的表单提交,而且同页面可能根据选项的不同而得到不同的表单项,对于这个我提出动态表单的想法,通过后端接口得到具体表单中的项类型、名称、属性、长度、正则等信息,然后前端通过类型判断生成不同的html。一查文档angular中的directive好像就是干这个事的,但是基本上都是一个标签,比如一个<icon> </icon>。而我的目标则是整个<form> </form>
想法不错,可是由于学艺不精,这个用angular我实现不出来,但是C#咱们熟啊,先用C#代码实现动态表单的工作,在使用ng-model绑定模型时发现radio类型的标签模型中获取不到值,经过测试发现还是bootstrap的老问题,该$('input')[0].checked=true,但是attr('checked')却是空,想了几个办法,后来发现一个最为简单暴力的,就是在外层的label上添加一个ng-click方法,直接将该值赋给对应的model。 
ng-click="formData.@input.Code=@radioData.Value"
@foreach (var radioData in input.InputSource)
{
    <label class="btn btn-primary" ng-click="formData.@input.Code=@radioData.Value">
        <input type="radio" name="@input.Code" value="@radioData.Value" autocomplete="off" ng-model="formData.@input.Code">@radioData.Name
    </label>
}
现在学angular貌似有点晚了,最近都是推崇其他的前端框架,像react、vue等,今天居然看到google自己也推了另一个框架叫polymer,感觉有点完全学不过来的节奏啊,不过先学通一个再学别的应该不难,如果熟练掌握了angular我也可以说自己是个基本合格的前端了吧。那个动态的form如果我能实现一定放到github上,希望有朝一日那么牛逼。

2015年1月17日星期六

以json格式发送对象集合作为参数的方法

一个页面,要同时修改多条数据,我们不可能向后端post多次来实现,可以一次post一个列表的形式将所有的数值都传给后端,然后后端统一处理。
先说前端,使用ajax技术post一个json对象是一个不错的方式,而我们的前端使用了knockout,viewModel正好也是json格式,而且knockout中还有一个ko.toJSON(viewModel)方法,非常简单,唯一需要注意的是在请求头中添加Content-Type: application/json,后端即可以自动反序列化为对象列表
var data = ko.toJSON(viewModel);
$.ajax('url',{
    'data': data,
    'type': 'POST',
    'contentType': 'application/json' 
});

再说后端,只要定义的接口参数中放入结构相同的列表就可以了
[HttpPost]
public ActionResult AfterCreditRepayNotifyDetailEdit(IList<AfterCreditRepayNotifyDetail> details)

以上过程在postman中测试是这样的: 
使用postman通过post方式传送json参数

2015年1月10日星期六

2014年总结与2015年目标

2014年已经过去了,这一年对我来说变化特别大,由于最后两个月换了工作,比原来忙了不少,所以也很少写博客了。在别人纷纷回望自己的一年时,我却累的不想打字,这个周末简单罗列一点,算是对自己过去的一年有个交代吧。
2014年经历的: 
1. 职业转型,没能转为希望的产品经理,却成了个专职程序员; 
2. 这一年,自己写过文档、画过原型、架设过服务器、接触云、从数据库到前端js代码,学了不少东西,感谢老叶的放纵,让我野蛮成长,也感谢老蒋的看重,将到带到新的平台; 
3. 自己写了个微信服务号程序,毫无宣传的情况下居然还有好几十号人订阅; 
4. 收入增加,能稍微改善点生活; 
5. 离开待了9年的公司,没有收到一句祝福与挽留,最后跟一个贱人吵了一架; 
6. 过年时带丁丁去了东北,丁丁很喜欢那里,也出奇的适应; 
7. 听了一场逼哥演唱会; 
8. 感谢所有容忍我一年的人;
现在的自己,年轻的面容早已不在,每天坐地铁时对着玻璃中自己大大的眼袋,心里也万般无奈,再该打拼的年纪自己选择了玩,现在只能还债,还好老天还算公平,晚努力总比不努力好,希望自己新的一年能有新的进步。
2015年计划如下: 
1. 购买VPS并搭建vpn与shadowsocks; 
2. 深入学习javascript,熟悉一个以上前端框架,比如Angular或React; 
3. 学会一门新的编程语言,比如php或swift; 
4. 入个MBP高配并掌握Mac系统; 
5. 锻炼身体; 
6. 每个月写一到两篇博客; 
7. 技术上继续学学学; 
8. 重构公司的系统,如果可能加入自己的产品想法; 
9. 忘记该忘记的,记住该记住的;

2015年1月5日星期一

javascript通过UserAgent判断访问设备

最近做的手机分享html页面中,有下载APP的按钮,需要判断访问设备是ios还是android系统,从而到对应的市场下载应用。
方法其实很简单,js判断userAgent中是否包含iPhone即可,再准确一点再判断个android还有分辨率之类的,但是我图省事就没做,代码如下:
function appDownload(){
    if(navigator.userAgent.indexOf("iPhone")>=0){
        location.href="https://itunes.apple.com/us/app/jin-rong-guan-jia100/id920332764?l=zh&ls=1&mt=8";
    }
    else{
        location.href="http://www.rongyu100.com/Uploads/Apps/FinaManager.apk";
    }
}
按钮点击时调用appDownload函数即可:
<a href="#" onClick="appDownload();" class="down pt-page-scaleUp">立即下载</a>

2014年11月7日星期五

比较两个数据库的不同

由于正式库与测试库有些改动未能同步修改,导致结构上有不同,安装了数据库比较工具,可是不怎么好用,而且正版需要付费,试用版功能又不全,搜索stackoverflow时发现了这个,感觉简单好用,自己之前怎么就没想到呢。
--比较两个数据库的不同
DECLARE @Sourcedb sysname 
DECLARE @Destdb sysname 
DECLARE @SQL varchar(max) 

SELECT @Sourcedb = 'AAA' 
SELECT @Destdb = 'BBB' 

SELECT @SQL = ' SELECT ISNULL(SoSource.name,SoDestination.name) ''Object Name'' ,
                CASE WHEN SoSource.object_id IS NULL THEN SoDestination.type_desc +  '' source库中不存在 -- ' + @Sourcedb + ''' COLLATE database_default 
                     WHEN SoDestination.object_id IS NULL THEN SoSource.type_desc +  '' Destination库中不存在 -- ' + @Destdb + ''' COLLATE database_default 
                     ELSE SoDestination.type_desc + '' 双方一致'' COLLATE database_default END ''Status'' 
                FROM (  SELECT  *
                        FROM ' + @Sourcedb + '.SYS.objects  
                        WHERE   Type_desc not in (''INTERNAL_TABLE'',''SYSTEM_TABLE'',''SERVICE_QUEUE'')) SoSource FULL OUTER JOIN
                     (  SELECT * 
                        FROM    ' + @Destdb + '.SYS.objects  
                        WHERE   Type_desc not in (''INTERNAL_TABLE'',''SYSTEM_TABLE'',''SERVICE_QUEUE'')) SoDestination ON SoSource.name = SoDestination.name COLLATE database_default AND SoSource.type = SoDestination.type COLLATE database_default 
                WHERE   SoSource.object_id IS NULL OR SoDestination.object_id IS NULL
                ORDER BY isnull(SoSource.type,SoDestination.type)' 
EXEC (@Sql)
发现sys.objects表中不包含索引,于是再写了一个查询sys.indexes的,具体需要哪些字段还需要修改。
DECLARE @SQL VARCHAR(max)
DECLARE @ADB sysname
DECLARE @BDB sysname

SET @ADB = 'AAA'
SET @BDB = 'BBB'

SET @SQL = 'SELECT  CASE WHEN A.object_id IS NULL THEN ''' + @BDB + ''' + CONVERT(VARCHAR(100),B.object_id) + ''' ''' + B.name
            WHEN B.object_id IS NULL THEN ''' + @ADB + ''' + CONVERT(VARCHAR(100),A.object_id) + A.name
            ELSE A.name + '''双方一致''' END AS Result
    FROM    ''' + @ADB + '''.SYS.indexes A FULL OUTER JOIN
            ''' + @BDB + '''.SYS.indexes B ON A.name = B.name
    WHERE   A.object_id IS NULL
            OR B.object_id IS NULL'
EXEC (@Sql)

悬浮按钮

需要在一个浏览页面添加编辑的按钮,看了下页面,有点长,右侧比较空,所以想在页面右侧添加一个悬浮按钮,由于对css不熟,在网上搜了一下,都是特别费事的。自己冷静下来想了想,不就是对上方和右方的相对位置吗,css完全可控制,按钮就用a好了,直接链接到编辑页面去,最省事。
<div style="position: fixed; z-index: 999; right: 15px; top: 150px; ">
</div>
z-index是控制层的,数值越大越靠上,最后div里面加个a就好了,就是这么简单。 
另外发现css中选择器会有选不中的情况,这时带上上层结构就可以了,可能是css之间有冲突。

2014年10月22日星期三

坑爹的azure - windows 2012打开framework3.5功能

早就听闻azure坑,今天总算被坑到了。
新建了个Windows 2012 R2的虚拟机,在系统中需要安装framework3.5功能,可是2012又不像2008那样直接选中打开此功能就可以,微软你确定这样出尔反尔毫无立场的变化好吗,于是我花了一下午的时间在试验各种方法上。
经测试以下方法都是错误的: 
- 指定本地安装源 
- 修改DNS 
- powershell安装
最后我打开了windows update,打过补丁之后再打开framework3.5功能居然奇迹般的好了。

2014年9月30日星期二

One Flew Over the Great Fucking Wall

昨天名为instagram的著名图片社交app被墙,据我猜测可能是因为最近instagram上直播香港关于人权、普选的抗议活动越来越多,为了防止广大墙内的国人被恶意境外势力影响愚弄,伟大的中央政府紧急在GFW上屏蔽了这个全球最火的社交应用,再一次捍卫了我人民民主专政的纯洁。

首先我得承认我是instagram的重度用户,如果不是特别累每天都要刷个至少3次,我喜欢上面全世界各地的人们分享的美图,有专业的摄影师用专业的器材拍出令人震撼的照片,更多的是普通人用手机拍的生活,微信的朋友圈就是copycat这个(PS:总有人说微信是个伟大的产品,我真不觉得,里面的每一个功能都是照搬过来的,如果没有腾讯强大的用户数量和伟大的墙,微信真的做不大,这个以后再说)。
我最喜欢在insta上看冒险、生活、风景、人像这几类的照片,通过这些照片感受自己可能一生都没有机会到达的地方、一辈子无法遇见的人,体会其他人的生活,学习摄影的构图、光线等等。而这个app做的又非常棒,简洁、高效,每一个功能都不可或缺,每一个操作都非常自然,从产品与技术的角度来说,这是一款非常棒的产品,从内容的角度来说,可以说insta是twitter的图片版,而图片更能令人产生深刻的印象,这样一款产品没法让人不喜欢。
可就在twitter、facebook、google+这些社交网站一个个被墙之后,instagram也加入了这个行列,正面上说明其已经具有可以与一线公司竞争的实力,可也注定失去了中国的份额,墙内市场肯定会被模仿者瓜分掉,LOFTER虽然用户体验非常不错,但是从见识上来说我更倾向于instagram。当然这些是次要的,主要的是墙内政府的态度,记得习大大在上台之前外媒普遍叫好,上台后实行的一些改革也有很多鼓掌者,我不懂政治,不懂得这里面的利弊,但是作为一个人,我可以感受到自己的生活,现在与过去并没有实质性的转变,广东一个人告中国联通不让访问google,意料之中的败诉之后回到家中马上就来了police将其带走;李大眼因为批评政府被迫到美国躲避;侩子手赵白鸽堂而皇之的去赴任红会,墙内的愚民每天更多的是在微博上狂欢,而不是探索这个未知的世界,当“老公,艹我”大行其道,而“正义、真理、民主、自由”这些词再也不见时,这个国家这个民族还有未来吗。
从接触到的香港人来看,他们自信、干净、小气,对内地人有点优越感,虽然有时候对他们有些不爽,但是对于他们游行的活动,我完全支持,任何反民主反自由反人类的事物最终都将被推翻,希望港人自强不息,这次社交网络被墙,肯定是有一些行动不希望墙内人看到,愿港人平安。最有引用飞越疯人院的名字,愿我们都有One Flew Over the Great Fucking Wall。

2014年9月26日星期五

谈谈iphone6的大屏对用户使用习惯与APP交互方式的改变

这个月初,大屏iphone终于千呼万唤始出来,一口气推出了4.7和5.5寸屏两个版本。在乔老爷坚持了几年“手机好不好,不能比大小”的原则之后,库房总管终于向市场妥协,顺势而为推出大屏手机。虽然现在的iphone已经走下神坛,不再是手机业界无法企及的高度,但每次推出的新品仍然是东西两半球最好用的智能手机,没有之一。随着越来越多的人选择大屏肾6,那么它也将会改变人们的一些手机使用习惯,以及一些app的交互方式。

更多的双手操作

虽然现在每个大屏手机厂商都考虑到了单手操作的问题,但是真正完美解决的可以说没有,苹果也一样。男性尚且无法完全hold住,手小的女性又喜欢用大屏看韩剧的就更无法驾驭了,于是双手操作肯定成为必然,当然这对于那些从安卓转移过来的用户说基本不是问题,因为他们已经过了适应期。双手操作多了也会有一些麻烦,比如公交车上不好抓扶手了,比如拍照或视频时一只手抓不住,希望以后不要听到车主因为发信息双手脱离方向盘发生事故,罪魁祸首竟是大屏iphone6的新闻吧。

更多的下半屏的交互

很多app中的一些菜单,比如分享功能,有的多的可能几乎占满整个屏幕,如果在大屏iphone上估计这样的交互是不合理的,毕竟人家官方都把电源键挪到侧面了。所以以后这类菜单应该尽量只占用下半屏就好,因为屏幕宽了,每排可以多放几个,所以改起来应该很简单。而我最希望的是上拉的快捷工具可以放置更多。

更多的手势操作

苹果的Magic Mouse是个怪物,喜欢的人非常喜欢,厌恶的人非常厌恶,但是无论多么厌恶的人都必须承认它上面强大手势操作,这也是这款鼠标最大的特点。而且苹果笔记本的触摸区,以及Magic Trackpad都可以使用同样的交互方式。如果大屏手机上也能够实现一部分手势操作,对于重度苹果用户来说是非常舒服的事情,而且大屏也有这个条件。之前的优酷视频上已经支持一些手势操作,所以用户体验得到质的提升,但那还是停留在app层面的,如果系统层面直接支持的话,相信很多app的界面都会做很大的调整,而使用者用起app来都像是在“打飞机”。

更多的分屏应用

记得以前安卓刚出来3.0版本时是为了大屏机器推出的,当时很多应用都有一个对应的大屏版。ipad推出后,ios的应用也有iphone版和ipad版,比如qq,在ipad上就是左边显示好友列表,右边显示聊天窗口,比手机qq方便,这就是典型的分屏应用。而iphone6尤其是plus的尺寸已经允许做这种尝试,比如邮件app、笔记app等等,在横屏时就可以作为分屏显示,用户则会获得更少的操作及更多的信息。

更多用于存放大手机的空间

以前iphone都是直接插裤子口袋里的,换成大屏之后会很不舒服,而且很多网站都评测了iphone6更容易变弯,如果不想让其他厂商用户开启群嘲模式,那么最好把手机放在包里的专有空间,据小道消息以后的哎呦喂和哭去都会加装一个内袋,按照iphone的屏幕大小分为3个版本,对于这个本人不负半点责任。


不管怎么说,iphone都是一个好产品,一个改变世界的产品,一个能让大部分用户满意的产品,时代总会发展,也许未来的手机又会回到一个百家争鸣的时代,让我们一起拥抱变化吧。

2014年9月16日星期二

使用godaddy+dnspod注册域名并解析的过程


由于公司就是有这种业务,以前朋友购买域名空间邮箱什么的,我都是直接在公司注册的,但是国内注册域名有很多阻碍,程序员们基本都不会选择国内注册服务商,于是狗爹和name成了首选,还有一些直接走淘宝的,都不需要任何证件和备案,麻烦与风险少了很多,今天自己试着走了一下注册全过程,还是很简单快速的,记录下来给需要的朋友使用。

godaddy注册域名

  1. godaddy注册;
  2. 使用搜索框搜索自己心仪的域名;
  3. 添加购物车然后付款,付款时会遇到点小麻烦,因为godaddy目前不翻墙没法访问,而翻了墙又是国外的IP地址,于是狗爹就认为不是中国的用户,所以支付宝会不成功
  4. 在此网站可以拿到狗爹的优惠码,使用这里的优惠码就可以使用支付宝付款了

dnspod添加解析

  1. dnspod注册;
  2. 添加域名;
  3. 在域名下面添加记录;
  4. godaddy登录后点击domain下的Lauch按钮 图片描述在对应域名右方的下拉菜单中选择“Set Nameservers” 图片描述弹出层中选择“Custom”后,下面点击“Add Nameserver”按钮 图片描述  在这里将dnspod默认的@记录填上即可,之后的一切解析都在dnspod中操作即可,比如像这个博客添加blog域名就是添加CNAME记录。


整个注册+解析过程不超过两个小时,如果是熟手的话估计10分钟就可以搞定,比国内供应商注册快了不知道多少倍,而且价格也便宜,以后是不是可以开个代理呢。。。

2014年9月11日星期四

自己做的查询数据网页改进

程序员应该对自己做过的项目进行迭代,相对于新写代码那种假想的用户需求,维护旧代码会更多的接触到用户真正的需求,每次修改之后得到的都是用户更好的体验,而且维护旧代码可以让我们从更多方面去思考问题,写不同的代码以及更努力地优化。

经过一段时间,界面到实现都修改了一些,原来使用了.net控件,本着向优秀程序员学习的态度,也实现了前后端分离,目前外观如下:
热情指数查询

修改点:

  • 前端 
    1. 按钮图标与文字对齐
    2. input与button组成group
    3. 下方表格使用bootstrap样式,thead自定义了背景色
    4. jquery使用ajax方式调用后端api取数据
    5. 增加按月查询方式
    6. 输入格式判断、日期处理、回到顶部、进度条等都在js层做
  • 后端 
    1. 取电话数据程序与SQL自动作业合并
    2. SQL存储过程合并为一个
    3. api使用缓存
    4. 一些小bug修改

具体实现代码:

按钮图标与文字对齐

原来的按钮我使用的是`i`标签,改为`button`后自动对齐。

jquery使用ajax方式调用后端api取数据

function getHotData(url, data) {
    $.ajax({
        url: apiurl,
        dataType: "json",
        data: data
    }).done(function (json) {
        if (json.Table.length === 0) {
            content1 = '<span class=\"err\">没有找到数据</span>';
            $('#tamiba').html(content1);
            return;
        }

        content1 = '<thead><tr><th>工号</th><th>姓名</th><th>部门</th><th>电话</th><th>回访数</th><th>回访附件</th><th>客户方案</th><th>外出</th><th>线索</th><th>来访</th><th>呼入量</th><th>呼入时长</th><th>呼出量</th><th>呼出时长</th><th>新增乙级</th><th>新增甲级</th><th>幸福指数</th></tr></thead>';
        content1 += '<tbody>'
        $.each(json.Table, function (idx, item) {
            content1 += '<tr>';
            for (var i = 0; i < Object.keys(item).length; i++) {
                var person = item[Object.keys(item)[i]];
                content1 += '<td>' + person + '</td>';
            }
            content1 += '</tr>'
        });
        content1 += '</tbody><tfoot><tr></tr></tfoot>';
        $('#tperson').html(content1);

        content2 = '<thead><tr><th>阿米巴</th><th>回访数</th><th>回访附件</th><th>客户方案</th><th>外出</th><th>线索</th><th>来访</th><th>呼入量</th><th>呼入时长</th><th>呼出量</th><th>呼出时长</th><th>新增乙级</th><th>新增甲级</th><th>幸福指数</th></tr></thead><tbody><tr></tr></tbody>';
        content2 += '<tbody>'
        $.each(json.Table1, function (idx, item) {
            content1 += '<tr>';
            for (var i = 0; i < Object.keys(item).length; i++) {
                var person = item[Object.keys(item)[i]];
                content2 += '<td>' + person + '</td>';
            }
            content2 += '</tr>'
        });
        content2 += '</tbody><tfoot><tr></tr></tfoot>';
        $('#tamiba').html(content2);
    });
};

js日期处理

var date = new Date;
var today = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();

//格式化日期
function formatDate(date) {
    return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
}

//两种查询得到起始日期,因为firefox和safari等浏览器不认-连接的日期,只能改成/
function getDate(date, type) {
    date = date.replace('-', '/').replace('-', '/');
    var rdate = new Date();
    if (type == "day")
        rdate.setTime(Date.parse(date + " 00:00:00"));
    if (type == "month")
        rdate.setTime(Date.parse(date + "/1 00:00:00"));
    return formatDate(rdate);
}

//按日期查询得到结束日期
function addDate(date, n) {
    date = date.replace('-', '/').replace('-', '/');
    var rdate = new Date();
    rdate.setTime(Date.parse(date))
    rdate.setDate(rdate.getDate() + n);
    return formatDate(rdate);
}

//按月查询得到结束日期
function addMonth(date, n) {
    date = date.replace('-', '/').replace('-', '/');
    var rdate = new Date();
    rdate.setTime(Date.parse(date));
    rdate.setMonth(rdate.getMonth() + n);
    return formatDate(rdate);
}

spin进度条

这个第三方进度条简约又可以自定义,非常喜欢,引用官方js既可使用
//定义进度条外观
var opts = {
    lines: 11, // The number of lines to draw
    length: 5, // The length of each line
    width: 2, // The line thickness
    radius: 11, // The radius of the inner circle
    corners: 1, // Corner roundness (0..1)
    rotate: 0, // The rotation offset
    direction: 1, // 1: clockwise, -1: counterclockwise
    color: '#000', // #rgb or #rrggbb or array of colors
    speed: 1, // Rounds per second
    trail: 60, // Afterglow percentage
    shadow: false, // Whether to render a shadow
    hwaccel: false, // Whether to use hardware acceleration
    className: 'spinner', // The CSS class to assign to the spinner
    zIndex: 2e9, // The z-index (defaults to 2000000000)
    top: '50%', // Top position relative to parent
    left: '50%' // Left position relative to parent
};

//使用时
var spinner = new Spinner(opts).spin($('#spinner')[0]);
setTimeout(function(){
        spinner.stop();
    }, 500);
PS:jquery的id选择器和js原声的方法getElementById得到的结果是不一样的,前者是个集合,而后者则是一个对象。

jquery回到顶部

先在html中创建一个div:
<div id="gotop" title="回到顶部"><span></span></div>
再在css中定义gotop的样式:
#gotop{
    display:none;
    width:35px;
    height:35px;
    position:fixed;
    right:30px;
    bottom:50px;
    /*border-radius:5px;*/
    text-decoration:none;
    background-color:#BBBBBB;
}
#gotop span{ 
    display:block;
    width:35px;
    color:#FFFFFF;
    font-size:30px;
    text-align:center;
    margin-top:6px;
}
#gotop:hover{ 
    cursor:pointer;
    background-color:#666666;
}
juery控制效果
//距离顶部超过200时,渐显,否则渐隐
$(window).scroll(function () {
    if ($(window).scrollTop() > 200) {
        $('#gotop').fadeIn(100);
    }
    else {
        $('#gotop').fadeOut(100);
    }
});

//点击时回到顶部,100是用时,单位ms
$('#gotop').click(function () {
    $('body, html').animate({ scrollTop: 0 }, 100);
});

api使用缓存

先定义一个helper,封装了`System.Runtime.Caching中的方法:
public class CacheHelper
{
    public bool Contains(string key)
    {
        MemoryCache mc = MemoryCache.Default;
        return mc.Contains(key);
    }

    public object GetValue(string key)
    {
        MemoryCache mc = MemoryCache.Default;
        return mc.Get(key);
    }

    public bool Add(string key, object value, DateTimeOffset absExpiration)
    {
        MemoryCache mc = MemoryCache.Default;
        return mc.Add(key, value, absExpiration);
    }
}
然后调用
CacheHelper cache = new CacheHelper();
if (cache.Contains(key))
{
    json = cache.GetValue(key).ToString();
}
else
{
    //...执行查询

    DateTime dtBegin = DateTime.Parse(beginDate);
    DateTime dtEnd = DateTime.Parse(endDate);
    if (dtBegin.AddMonths(1) == dtEnd)  //按月查询
    {
        cache.Add(key, json, new DateTimeOffset(DateTime.Today.AddMonths(2)));
    }
    else
    {
        if(dtBegin == DateTime.Today)   //按日查询 - 查询当日
        {
            cache.Add(key, json, new DateTimeOffset(DateTime.Today.AddDays(1)));
        }
        else   //按日查询且不是当日
        {
            cache.Add(key, json, new DateTimeOffset(DateTime.Today.AddDays(2)));
        }
    }
}
使用缓存效果非常明显,经过测试,在站点第一次启动时查询数据大约耗时280ms,之后不使用缓存大概20-30ms,使用缓存后0-1ms。
此外还有gzip可以提高性能,IIS里设置一下就可以了,很简单,不描述了。

好了,目前就这些,写出来做个总结,还有一项没有实现,就是表格的thead在向下滚动时悬浮。