定制触摸屏网页开发

来源:互联网 发布:怎么对数据进行标准化 编辑:程序博客网 时间:2024/05/16 11:53

 

零 背景

       此项目为XX市XX局的“流通环节食品安全监管系统”,该定制触摸屏需求主要基于监管系统采集的进销货台账数据,通过食品条形码与生产日期查询出食

品的详细信息、质量合格证明、生产厂家证照信息。界面专为触摸屏设备定制,面向用户为普通消费者。客户提出的最初需求如下:

1. 开发查询主页面及查询结果页面,查询主页面提供输入“食品条形码”、“生产日期”的录入框,此两项均为必填项;

  - 查询主页面中“食品条形码”的录入方式需兼容扫描枪与触摸键盘,“生产日期”的录入方式需使用日历选择框;

2. 查询结果页面中分为三大信息,分别为“食品明细信息”、“食品质量合格证明”、“生产厂家证照信息”;

  - “食品明细信息”包括“食品名称”、“食品条形码”、“食品品种”、“商标名称”、“包装规格”、“保质期”;

  - “质量合格证明”需使用列表的形式展现“证件类别”、“生产日期”、“证件图片”,其中“证件图片”这一字段使用图标显示(需区分WORD文档与JPG文档);

  - “生产厂家证照信息”包括“生产厂家名称”与“证照”,证照的需求与“质量合格证明”相同;

3. 查询结果页面中的“质量合格证明”、“生产厂家证照”的“证件图片”列,可通过点击弹出证照的大图预览,并且图片可进行放大缩小、旋转与拖动。

一 前期准备工作

1. 选定开发框架与规范

JQuery:由于项目中大部分使用前端js逻辑,JQuery是比较我较为熟悉且运行效率也符合要求的框架,有大量的插件资源,符合标准,兼容各浏览器。

项目中采用   jquery-1.6.2.min.js ,新版本中修复了旧版本的一些问题,改善了性能,所以经验是选择JQuery版本时最好选用较新的的版本。

AJAX:通过JQuery可以方便的使用,主要完成异步请求,通过后台处理后得到返回的JSON数据。

HTML/CSS:使用xhtml1-strict.dtd的DOCTYPE,目的是规范代码通过W3C验证,保证各浏览器的兼容。

JSP:由于后台逻辑较为简单,所以选择JSP直接处理前端通过AJAX发送的请求,并且构造结果JSON对象返回前端。

2. 运行流程

确定了开发框架和使用的工具后,就可以画一个初步的流程出来了。

- (首页)输入食品条码、选择生产日期,按查询按钮

- (首页)JS校验,提示错误信息,或下一步

-  使用AJAX传输查询条件至后台,JSP处理后返回JSON对象

- (首页)判断返回结果,输入错误信息或下一步

-  解析JSON,填充详细页,完成后隐藏首页显示详细页

由于整个过程只涉及两个页面,所以整个过程没有涉及页面跳转,所有操作都是在一个页面中完成,通过隐藏和显示进行切换。这样做是考虑到减少与

服务器的数据交换,只请求最需要的数据;另一方面减少页面被浏览器加载解析时间,使用一个页面在最初的时候一次性加载完成,而不必因跳转另外

加载解析一个页面。

3. 选定辅助插件

在插件的选择上,主要为 日期选择插件 和 flash播放插件。

- 经过查找资料,观察手机触摸屏的操作习惯,发现开源的触摸屏日期选择插件很少,并且是英文版本,通过API学习最后选定了一款方便自定义并且

功能也适用的插件。

项目中使用的为:mobiscroll-1.5.min.js(http://code.google.com/p/mobiscroll/)。

- flash 插件的选择是为了通过W3C验证,方便flash的嵌入,这个插件就很多了。

项目中使用的为:jquery.swfobject.1-1-1.min.jshttp://code.google.com/p/swfobject/)。

4. 简单制定计划

这项工作本来应该在前期准备工作之前就要制定,但现在进行到这步的时候才觉得有必要做一个计划,毕竟是第一次做触摸屏定制。

2day:设计界面

2day:界面框架布局

3day:前端JS实现、后台功能实现

1day:测试、兼容性验证、收尾

总计:8人天

加上前期准备工作花了1天,总计9人天。

二 开始设计页面

1. 设计思路

介于是政府部门的项目,首先不能太花哨,但也想改变一下政府严肃死板教条的界面风,既然是关乎食品安全,想尽量设计得有趣并且包含一定的宣传

效果,界面图标以简洁为主,圆角不宜使用太多,由于是触摸屏则考虑按钮和图片预览框要略大以便触摸使用。

2. 使用PS打稿

 - 打稿的过程是发挥想象力的,必须自己的设计思路,同时在过程中做微调和改进。

 - 配色方面主要使用到了两个个网站。

  (http://colorschemedesigner.com/)

  (http://kuler.adobe.com/#themes/newest?time=30)

(图 2.2.1 首页)

- 选择绿底色最初的想法是契合食品给人的积极感官,右上角体现日期时间主要方便消费者知道当前时间,算是附属功能。

- 中上部的FLASH LOGO是主网站首页移植的,主要图个方便,没有多余的时间做一个新的。

- 中部就是输入框和查询按钮,主要以简洁为主,“条形码”输入框当有输入时会出现右侧红色的叉叉按钮,点击后消失并且清空输入框值,点击查询时

  若条形码无输入,则弹出输入框上方的提示;“生产日期”输入框选择时弹出日期选择控件,该输入框默认是当前时间,所以不用考虑查询时空值。

- 下部是一个滚动的slide,上网搜索了与食品相关的三个标识,打算对应每个标识截取一些说明文字,在下方滚动显示。

详细页 (图 2.2.2 详细页)

- 详细页面除了体现需要反馈的食品信息外,主要是图片的展示。其中“质量合格证明”与“生产厂家证照信息”如图 2.2.2 中方块的形式展现,针对不同

  的文件类型,选择不同的图标,若是图片格式则在方块内显示图片的缩略图(图 2.2.2 中未展示)。

(图 2.2.3 图片预览页面)

- 点击图片预览的方块后,弹出遮罩层,页面下方显示工具条,分别对应左旋、右旋、放大、缩小。

- 右上方显示关闭按钮。

(实际工作量:2人天)

三 实现页面HTML CSS布局

1. 构建基础代码。参照打稿后的图片进行排版,使用div的方式,图片作为背景层,首页需要table的部分只有中部表单区域,详细页的明细信息使用table。

2. 与界面显示无关的图片(即与内容有关)使用img的方式,如首页下方的标识、详细页标明图片类型的图标。

3. 需要JS控制的图片(放大缩小显示隐藏位移)在原先的div外城wrap一层div,方便对背景div进行位移(left、right),而不是对背景position进行位移。这是

    由于之前发现JQUERY对background-position的改变支持度不好,兼容性方面有问题,且会有脚本错误,所以取而代之的是对含有该背景的div进行position

    的位移,如右上方时间的跳动。

4. 预先在body末尾加入遮罩层,因为这部分不使用插件,所以减少初始化时使用JS生成的HTML元素,直接写在页面上速度更快。遮罩层中把背景层(透明)

    单独出一个div,方便灵活使用;loading的div层;错误信息层;图片预览层。

(实际工作量:2.5人天)

四 加上JQUERY

注:以下各代码段为程序部分代码段,均放在$(document).ready内。

1. 嵌入flash、绑定日期选择事件。使用swfobject插件嵌入flash,使用mobiscroll插件进行日期选择,详见API。

2. 首页下方介绍循环滚动。每次取第一个div,从左侧消失,消失的同时第二个div从右侧进入,消失后把第一个div追加入末尾,从而原来的第二个元素变为第

一个,如此循环。

var isSlideScroll = true;$('#body_slide ul li').not(':first').hide();var slideScroll = function(){if(isSlideScroll){var lifirst = $('#body_slide ul li:first');var lisecond = lifirst.next();lifirst.animate({left: -600, opacity: 'hide'}, 'slow', function(){lifirst.appendTo($('#body_slide ul'));});lisecond.css('left', 600).animate({left: 0, opacity: 'show'}, 'slow');}setTimeout(slideScroll, 5000);}setTimeout(slideScroll, 5000);

PS:isSlideScroll 这个变量用处在于,当切换至查询结果页时,因为要隐藏首页,所以把isSlideScroll置为false以停止滚动。当返回首页时置为true继续滚动。

 

3. 时间跳动。使用div偏移的方式显示时间,首先要取得各时间数值的偏移量,div根据这个偏移量设置top值。

// 时间跳动var jumpFlash = function($po, offset, type){if(type == 'date'){$po.animate({top: -offset*20}, 'fast');}else if(type == 'time'){$po.animate({top: -offset*30-5}, 'fast').animate({top: '+=10'}, 'fast').animate({top: '-=5'}, 'fast');}else {$po.animate({top: -offset*30}, 'fast');}}var timeJump = function(){var now = new Date();var years = now.getFullYear();var months = now.getMonth() + 1;var days = now.getDate();var hours = now.getHours();var minutes = now.getMinutes();var seconds = now.getSeconds();var timeAP = hours>12?1:0; var y1 = years<1000?0:((years.toString().substr(0, 1))*1);var y2 = years<100?0:((years.toString().substr(1, 1))*1);var y3 = years<10?0:((years.toString().substr(2, 1))*1);var y4 = (years.toString().substr(3, 1))*1;var m1 = months<10?0:((months.toString().substr(0, 1))*1);var m2 = months<10?months:((months.toString().substr(1, 1))*1);var d1 = days<10?0:((days.toString().substr(0, 1))*1);var d2 = days<10?days:((days.toString().substr(1, 1))*1);var h1 = hours<10?0:(hours<=12?1:(hours<22?0:1));var h2 = hours<10?hours:(hours<12?12-hours:(hours==12?2:(hours-12<=10?hours-12:22-hours)));var mi1 = minutes<10?0:((minutes.toString().substr(0, 1))*1);var mi2 = minutes<10?minutes:((minutes.toString().substr(1, 1))*1);jumpFlash($('#head_body_time .y1'), y1, 'date');jumpFlash($('#head_body_time .y2'), y2, 'date');jumpFlash($('#head_body_time .y3'), y3, 'date');jumpFlash($('#head_body_time .y4'), y4, 'date');jumpFlash($('#head_body_time .m1'), m1, 'date');jumpFlash($('#head_body_time .m2'), m2, 'date');jumpFlash($('#head_body_time .d1'), d1, 'date');jumpFlash($('#head_body_time .d2'), d2, 'date');jumpFlash($('#head_body_time .h1'), h1, 'time');jumpFlash($('#head_body_time .h2'), h2, 'time');jumpFlash($('#head_body_time .mi1'), mi1, 'time');jumpFlash($('#head_body_time .mi2'), mi2, 'time');jumpFlash($('#head_body_time .ap'), timeAP, '');// 设置默认时间$('#dateofmanufacture').val(''+y1+''+y2+''+y3+''+y4+'-'+m1+''+m2+'-'+d1+''+d2);// 1min 跳动一次setTimeout(timeJump, 60000);}timeJump();

4. 监听窗口resize。主要解决当浏览器全屏时页面布局的自适应。

// 监听窗口大小改变时间var nowHeight = document.documentElement.clientHeight;$(window).resize(function(){var oldHeight = nowHeight;nowHeight = document.documentElement.clientHeight;var cv = (nowHeight-oldHeight)/2;// 改变body_form的上下边距var mtop = parseFloat($('#body_form').css('margin-top'));var mbottom = parseFloat($('#body_form').css('margin-bottom'));$('#body_form').css({'margin-top': cv+mtop});});


 

5. 使用ajax请求后台数据返回结果。构造图片缩略图,绑定事件。

$.ajax({type: 'post',dataType: 'json',url: 'TFoodDetail4Touch.jsp',data: {foodbarcode: foodbarcode, dateofmanufacture: dateofmanufacture},error: function(){// 显示错误信息showErrorMsg('很抱歉,连接超时,请稍后再试!');},success: function(json){if(json.error){// 返回错误信息showErrorMsg(json.error);}else {// 填充各字段$('#body_table_foodname').next('td').find('span').text(json.foodname);$('#body_table_foodbarcode').next('td').find('span').text(json.foodbarcode);$('#body_table_foodclass').next('td').find('span').text(json.foodclass);$('#body_table_brandname').next('td').find('span').text(json.brandname);$('#body_table_packingspec').next('td').find('span').text(json.packingspec);$('#body_table_shelflife').next('td').find('span').text(json.shelflife+'天');$('#body_table_szxx').next('td').find('span').text(json.sccjname);// 以表格形式显示图片//showPicTable(json);showPicDiv(json);// 显示结果PAGE$overlay.fadeOut('fast');$loading.hide();$('#header_body').css('background-position', '0 -1000px');// 停止滚动slideisSlideScroll = false;$('#body > div').not('#body_table').fadeOut('fast');$('#body_table').fadeIn();$('#head_body_back').fadeIn();// 开始加载预览图showWordPreview();showPicPreview();}}});


 

6. 加载预览图。主要在于计算预览图宽高,绑定事件。

function showPicPreview(){var $pics = $('#body_table').find('.body_table_szsp_div_item .licenseurl img').not('.isword');// 图像加载完成后显示图像var showPreview = function(event){var $obj = event.data.obj;var $objdiv = event.data.objdiv;var objDivWidth = getObjWidth($objdiv);var objDivHeight = getObjHeight($objdiv);var objWidth = getObjWidth($obj);var objHeight = getObjHeight($obj);// 绑定单击事件$objdiv.parent().click(function(){var $overlay = $('#overlay');$overlay.fadeIn('fast');$('#overlay_close').click(function(){$('#pic_preview').empty();$overlay.fadeOut('fast');$('#pic_toolbar').fadeOut('fast', function(){$(this).hide();});$(this).fadeOut('fast', function(){$(this).hide();});}).show();var $picview = $('<img/>');$picview.attr({'src': $obj.attr('src'), 'alt': 'no image'});// 显示大图showEnlargePic($picview);// 绑定拖拽事件bindMoveFollowEvent($picview);// 显示图片工具条showPicToolbar($picview);$('#pic_preview').empty().append($picview);});// 计算预览图宽高$obj.attr({'width': objWidth, 'height': objHeight});var testHeight = objDivWidth*objHeight/objWidth;if(testHeight < objDivHeight){// 以高度为准objWidth = Math.floor(objDivHeight*objWidth/objHeight);objHeight = objDivHeight;}else {// 以宽度为准objWidth = objDivWidth;objHeight = Math.floor(testHeight);}$objdiv.empty();$obj.css({'width': objWidth+'px', 'height': objHeight+'px'}).appendTo($objdiv);};// 每个图片调用预览生成事件$.each($pics, function(idx, item){var $this = $(item);var $objdiv = $this.parent().parent().prev();var $img = $('<img/>').attr('src', $this.attr('alt')).attr('alt', 'no image');// 显示正在加载中$('<span/>').text('加载中...').css({'position': 'relative','top': '60px'}).appendTo($objdiv);// 加载完成后显示预览图if($img[0].complete){var params = {data: {obj: $img, objdiv: $objdiv}};showPreview(params);}else {$img.bind('load', {obj: $img, objdiv: $objdiv}, showPreview);}});}

 

7. 绑定拖拽事件。获取event事件的坐标并根据其坐标改变目标元素的位置。

function bindMoveFollowEvent($obj){var drag = false;var nleft, ntop, nclientX, nclientY;$obj.mousedown(function(e){var $this = $(this);nleft = parseFloat($this.css('left'));ntop = parseFloat($this.css('top'));var ep = getEventPosition(e);nclientX = ep[0];nclientY = ep[1];drag = true;                e.preventDefault();        }).mousemove(function(e){if(drag){var eclientX = 0;var eclientY = 0;var ep = getEventPosition(e);eclientX = ep[0];eclientY = ep[1];var eleft = nleft + (eclientX - nclientX);var etop = ntop + (eclientY - nclientY);$obj.css({'left': eleft+'px', 'top': etop+'px'});e.preventDefault();}}).mouseup(function(e){drag = false;});}


8.  绑定旋转事件。为了兼容于各主流浏览器,使用了两种方式:

     -  针对IE使用的是css fliter的方式实现旋转

     -  针对非IE使用的是canvas的方式

var rotate = function($obj, angle, whence) {if (!whence) {$obj.attr('angle', (($obj.attr('angle')==undefined?0:parseFloat($obj.attr('angle'))) + angle) % 360);} else {$obj.attr('angle', angle);}var obj_angle = parseFloat($obj.attr('angle'));var rotation;if (obj_angle >= 0) {rotation = Math.PI * obj_angle / 180;} else {rotation = Math.PI * (360+obj_angle) / 180;}var costheta = Math.cos(rotation);var sintheta = Math.sin(rotation);var $canvas;if (document.all && !window.opera) {$canvas = $('<img />');var canvas = $canvas.get(0);var width = $obj.get(0).width;var height = $obj.get(0).height;var clientSize = getClientSize();var left = Math.floor((clientSize[0]-parseFloat(width))/2);var top = Math.floor((clientSize[1]-parseFloat(height))/2);$canvas.attr({'src': $obj.attr('src')}).css({'left': left+'px', 'top': top+'px'});canvas.width = width;canvas.height = height;$canvas.css('filter', "progid:DXImageTransform.Microsoft.Matrix(M11="+costheta+",M12="+(-sintheta)+",M21="+sintheta+",M22="+costheta+",SizingMethod='auto expand')");// 绑定拖拽事件bindMoveFollowEvent($canvas);} else {$canvas = $('<canvas />');var canvas = $canvas.get(0);var oImage;if(!$obj.get(0).oImage){oImage = new Image();oImage.src = $obj.attr('src');canvas.oImage = oImage;}else {oImage = $obj.get(0).oImage;canvas.oImage = oImage;}var cwidth = parseFloat($obj.css('width'));var cheight = parseFloat($obj.css('height'));// 每旋转一次对调宽高canvas.width = cheight;canvas.height = cwidth;var clientSize = getClientSize();var left = Math.floor((clientSize[0]-canvas.width)/2);var top = Math.floor((clientSize[1]-canvas.height)/2);$canvas.css({'width': canvas.width+'px', 'height': canvas.height+'px', 'left': left+'px', 'top': top+'px'});var context = $canvas.get(0).getContext('2d');context.save();if (rotation <= Math.PI/2) {context.translate(sintheta*cheight,0);} else if (rotation <= Math.PI) {context.translate(canvas.width,-costheta*cwidth);} else if (rotation <= 1.5*Math.PI) {context.translate(-costheta*cwidth,canvas.height);} else {context.translate(0,-sintheta*cwidth);}context.rotate(rotation);if(rotation % Math.PI == 0){context.drawImage(oImage, 0, 0, cheight, cwidth);}else {context.drawImage(oImage, 0, 0, cwidth, cheight);}context.restore();// 绑定拖拽事件bindMoveFollowEvent($canvas);}$canvas.attr('angle', obj_angle);$obj.replaceWith($canvas);}

PS:该方法参考了网上的各种资料,写的比较粗俗不优雅,只适用于90°的旋转,后可改进可升级至任意角度的旋转。

(实际工作量:3.5人天)

五 测试&验证

1. 由于业务逻辑较为简单,测试时主要针对几种错误类型对后台业务进行测试,前台是否会返回相应的信息。

2. 余下的关注点在于页面布局与JS的执行在各浏览器之间的兼容情况。

  - 使用IE9的开发人员工具进行调试,比较简陋。

  - firefox就好用多了,主要使用firebug进行调试,web develop调试及验证。

(图 5.2.1 HTML验证)

(图 5.2.2 CSS验证)

其中CSS中有93处错误均出现在mobiscroll-1.5.min.css,mobiscroll是引入的插件使用的样式表,无视。

(实际工作量:1人天)

六 总结

       整个开发过程实际做了9天,加上前期准备工作共计2个星期的工作日。时间有点长了,但还算顺利,在HTML/CSS上没花太多时间,时间用在JS的反复

调试上,尤其是兼容性的测试。经测试,这些代码在IE7+、firefox、chrome、opera上运行良好,速度上IE就显得慢了一点,取决于JS的执行速度,所以

chrome应该最快吧,但测试中chrome的图片拖拽的响应有点滞后,其他的不会有这种情况,原因不明。(关于运行速度没有经过测试,没有找到什么好用的

工具)。

七 不足

1. 界面图片的设计方面还比较稚嫩,透视关系、光线、颜色搭配等是外行,吃的是以前美术基础的老本。

2. css、js编写的比较随性,雪碧没用上,压缩混淆也没用上,在这一点上总是收尾收不干净。

3. 虽然也是参差在其他的工作下同步进行,但整体的工期还是拖得太长。

 

原创粉丝点击