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在向下滚动时悬浮。

2014年9月2日星期二

用python Tkinter制作窗口小程序

昨天发现以前用VB做的生成代码的小工具不能用了,可能是系统换成2008之后程序不好用了吧,闲着没事想试试用python重新做,因为很功能很简单,所以就用自带的Tkinter库写了一个。

  • 基本结构 
    Tkinter虽然不像.NET那样可以随意拖窗体,但是使用代码也可以很简单的构建出来:
代码块
#encoding=UTF-8
from Tkinter import *
root = Tk()     #窗体
root.mainloop()
  • 布局 
    我需要两个多行文本框,一个用于输入,一个用于输出;中间一个按钮,用于触发转换并输出事件。定义三个Frame,并左右排列side=LEFT,每个控件最后都要pack()后才能添加,其中expand='yes'是指窗体大小改变时允许自动填充,fill='y'代表只能y轴方向填充,而both就是两个方向都允许,padxpady同理是内部留的不同方向的距离。
代码块
frame1 = Frame(root, height=500, width=500)
frame1.pack(side=LEFT, fill='y', expand='yes', padx=5, pady=5)

frame2 = Frame(root, height=500, width=600)
frame2.pack(side=LEFT, fill='both', expand='yes', padx=5, pady=5)

frame3 = Frame(root)
frame3.pack(side=LEFT)
  • 添加控件 
    Tkinter中的多行文本是Text,有点类似于网页中的textarea,而按钮就是Button。为了区分输入与输出,我把输入的文本框设成了灰色。定义控件时可以添加回调函数,但是却不能写参数(如果需要写参数可以使用lambda表达式)。这里的Text控件读取有个坑,就是它会自己多添加出来一个空行,所以在get时指定结尾行要写成END+'-1c',否则做字符串处理时会出错,因为excel粘贴过来时末尾也会带一个换行符形成一个空行,于是循环时添加一个if判断,避免出错。
代码块
text1 = Text(frame1, bg='#D9D9D9')
text1.pack(side=LEFT, fill='y', expand='yes')

text2 = Text(frame2)
text2.pack(side=LEFT, fill='both', expand='yes')

btn = Button(frame3, text="-->", height=3, command=convert)
btn.pack(anchor='se', expand='yes', fill='y')

def convert():
    text = text1.get('0.0', END+'-1c')

    result = ""

    for line in text.split('\n'):
        if line == "":
            continue
        y = line.split('\t')
        result += '''/// <summary> %s </summary>
public const string %s = "%s";\n''' % (y[0], y[1], y[2])
    text2.delete('0.0', END)
    text2.insert('0.0', result)
  • 绑定全选事件 
    对于输出文本框,需要将结果拷贝出来,但是默认是不支持Control-A的,只能在代码中绑定,有一个简单的办法,可以用bind_class方法将所有的Text控件一起添加全选事件(此绑定有时会失灵,不知为何):
代码段
root.bind_class('Text', '<Control-a>', selectAll)
  • 滚动条 
    好了,基本功能已经完成,最后我给两个Text控件各添加了一个垂直的滚动条,看起来更习惯一些。
代码段
scroll1 = Scrollbar(frame1)
scroll1.pack(side=RIGHT, fill='y')

#添加滚动条后要与Text控件互相绑定
text1.config(yscrollcommand=scroll1.set)
scroll1.config(command=text1.yview)

  • 总结 
    用python开发窗体程序挺有意思的,虽然这不是python的主要用法,但是练练手也不错,而且Tkinter开发出的程序是跨平台的,这点比.NET要舒服很多。
效果图
PS:这次Markdown文档是用raysnote写的,作者还是一位仍在读书的学生,虽然仍然有一些瑕疵,但是已经非常不错了,真羡慕他。