今天周六,原则上要休息,但想到下周还有一堆任务,还是先做一部分工作吧,就把之前做的票面设计器改了改,增加了上传图片和更换背景底图的功能。现在打算整理下这个设计器,也算对齐一个总结。不过这属于我们部门的产品,代码我使用截图的方式多一些。首先来看一下我做的这个票面设计器的最终效果图:
从截图中可以看到在页面里,专业点叫我在画布里增加了很多的元素,这些元素都与剧院票务相关包括项目、场次、地点,二维码等信息。上面的word截图是我今天做的功能,背景图我也换成了自定义的图片。光看这个页面其实不难做,因为页面只是票面设计的一部分,还有后台的支持,票面保存与获取,打印等一系列的流程,本篇我侧重于前端的开发,也就是基于SVG如何完成一个简单票面设计器。
关于SVG的基础,这里我就不再讲了,因为之前我也写过关于SVG入门的文章,传送门:http://blog.csdn.net/qq522935502/article/details/45073897
首先我们来分析下这个票面设计器有哪些功能:
(1)工具栏点击后,需要在票面增加对应的元素,放置在初始的坐标。
(2)点击元素本身,可以对其进行拖拽移动,字体调整,自动对齐等功能。
(3)每个元素都应该有自己的属性包括:类型,ID,名称,标识等。
讲解之前,大家还是要对SVG的基础知识有一些了解,例如:
(1)组的概念。在本篇中,我们会使用很多组,因为组可以很简单的将一组元素合为一个组,进行同一个样式修改或者旋转移动比较方便。
(2)创建元素的方法,例如创建一个组:var group = document.createElementNS(globalVb.svgNS,"g");(svgNS = svgNS: "http://www.w3.org/2000/svg")
创建一个文本元素:document.createElementNS(globalVb.svgNS, "text");等。
(3)坐标系:关于SVG的坐标系统其实是比较复杂的,默认情况下SVG坐标系的原点(0,0)与左上角,与屏幕的左上角是完全重合的,此时SVG坐标系同用户坐标系保持一致没有发生变形移位的现象。SVG的坐标系统也是由横X,纵Y和原点组成,SVG坐标系统称作为工作区坐标系统或者矩阵坐标系统。在实际开发中由于我们会将我们的画布放置在屏幕中心,坐标原点(0,0)也不就在屏幕左上角了,这个大家要明白。
(4)关于元素移动,有两种方式,一个是用绝对坐标,一个是用相对坐标。从实现角度来讲后者更简单,但从扩展角度或者说后期想做一些更高级的元素的话绝对坐标就比较有优势了。但目前咱们没有特别高级的功能就是移动所以使用后者也就是用相对坐标,那相对于谁的坐标呢,就是元素原始的绝对坐标。另外我们还需要明确的一个问题就是如果一个元素的初始坐标x是100 ,我通过方法对元素进行移动,使其像右边移动了20像素。到最后我们计算这个元素的坐标的时候要记得要将原始坐标与移动坐标相加。也就是100+20是120,如果我们使其向左移动的20,也就是相对于100,我们减少了20,它的最终绝对坐标是80.
好,说明白了这些问题以后,我们开始看看,怎么完成这样一个设计器。
1、第一步,我们要定义一个常量,这些常量的类型主要包括以下:
(1)系统支持哪些业务变量,这些业务变量是否可以分类,是的,我们目前支持关于项目的、关于订单的、关于场馆的元素:generalKeyId:"projectKey,orderKey,siteKey",具体包括:generalKeys:"projectName,showTime,priceMoney,priceName,ticketCodeId,orderId,placeName,siteName,floorName,boundName,seatRow,seatNo",这些元素我不一一解释了就,除了这些变量还有一些常量例如:imageKey,在image里有3中类型:imageKeys:"qrcode,image,map",
分别是二维码,图片和底图。每个元素的初始位置是:transform:"translate(0,0) rotate(0) scale(1)", 我们将这些信息统一放置在常量里:
(2)我们需要在系统中增加一些鼠标操作,比如鼠标点击,鼠标划过,鼠标放下等操作,此时我们需要根据元素的一些特殊信息来判断元素属于哪种类型,一般我们会根据元素的class样式来判断,所以为每一个元素都定义了对应的class名字,另外,这些元素在初始、选中等状态下都有不同的边框颜色,他们在页面里显示的类型共计5种分别是静态文本,动态文本,二维码,图片以及底图:
2、第二步,进入票面设计器,我们需要对票面设计器进行一些初始化的工作,例如如果当前我们是编辑状态,那需要通过后台加载票面并通过SVG解析到页面来显示,如果是新建状态,则我们需要初始化工具栏的这些下拉框,选择框等UI组件的状态等等。所以在进入票面设计器时,我们设计了这样一个方法:
function initTicketFace(data,info,clear),它接受3个参数<1>data 数据,主要是指票面数据,一遍是用户点击编辑时才有数据 <2> info 主要是一些辅助信息 例如票面的名称等 <3>clear是一个命令 意味是否清除当前的画布信息:
/** * 初始化方法 */ function initTicketFace(data,info,clear){ console.log("-----------initTicketFace------------"); try { $scope.tplName = "["+data.name+"]"; $scope.currentTpl = data; if(clear){ jquerySvg.empty(); } if(data == null){ return; } baseInfo = info; initEventListener();//初始化工具栏的鼠标事件等 resolveTicketFace(data);//如果票面数据不为空 则进行解析回显 } catch (e) { console.log(e); } };
3、忘记了截图我们的工具栏是怎么写的,这些工具栏的按钮其实就是一些select 下拉框,在form里放置着例如:
<select class="form-control btn btn-default mr10" id="imageKey"> <option value="tip">+图片元素</option> <option value="qrcode">+二维码</option> <option value="map">+背景底图</option> <option value="image">+自定义图片</option> </select> <select class="form-control btn btn-default mr10" id="projectKey"> <option value="tip">+项目元素</option> <option value="projectName">+项目名称</option> <option value="showTime">+演出日期</option> <option value="priceMoney">+票价</option> <option value="priceName">+票档</option> </select>
通过为这些select组件增加点击事件,来触发对应的方法,来完成图形的创建以及往画布上添加。这些事件我们是如何进行添加的呢?看下面的代码:
/**项目键发生变化*/ $("select").unbind("change"); $("select").change(keyChangeFun);
截图里我也写的很清除了,点击了元素后,元素会根据各自的类型初始化对应的信息,包括每个元素都有自身的XY坐标位置,class,文本示例内容等信息,接下来我们就来看一下这个commonAdd方法都做了什么,这个方法是一个核心的方法。
4、commonAdd方法,向画布添加元素:
var commonAdd = function(loadGroup,element,loadRect) 首先 这个方法我们接受3个参数,第一个loadGroup 指所添加元素的组; element 是指元素本身,loadRect是指元素的辅助矩形。如果我们是新增一个元素自然第一个参数和第三个参数传null,如果是回显元素,这3个参数都是有值的。
进入方法:
(1)如果参数类型是“背景底图”。 则我们无须将其添加到画布里,而是直接将图片填充到背景色里即可,这里我们使用的方案是填充图片base64编码,现在HTML都是支持的,例如:
这里我们需要了解的是,base64简单来说,它把一些8-bit数据翻译成了标准的ASCII字符,网上有很多免费的base64编码和解码的工具,目前IE8,Firfox,chirome,苹果浏览器等都支持。而且我们也需要了解这种格式:在上面的Data URI中,data表示取得数据的协定名称,image/jpg是指数据类型名称,base64是数据的编码方法,逗号后面就是这个image文件base64编码后的数据。
(2)如果参数类型是非背景底图,则我们需要为其增加一个组,即每个元素都规定在一个组下,然后给这个组设置它的class,transform,以及组ID等信息。当然这里要区分是新增还是解析:
(3)如果参数类型是静态文本或者动态文本(静态文本是指写死的信息,动态文本是指会根据具体实际的票打印不同的内容)。则我们创建一个TEXT的文本节点,设置它的相关参数:
二维码和图片这里我们也不重点解说。完成上述的节点设置后,我们要对他们的一些共同属性进行设置,包括坐标,文本内容等:
这样我们的元素就构造结束了,下一步,就是将元素放置在组里,再将组添加到画布中。完成上述工作后,我们再需要为这个元素增加一个辅助的矩形便于我们进行拖拽,因为之后我们要进行鼠标的拖拽,矩形的感知力要比文本好很多。
这里注意,我们将矩形的透明度设置为了0.意思就是其实用户看到这个矩形,它只是辅助的作用,不过边框我们可以看到。然后依次的添加到画布中:
group.appendChild(keyElement); group.appendChild(assistRect); scriptSvg.appendChild(group);
这样其实就完成了我们的一个核心功能:如果向SVG画布里添加元素信息。
5、元素的鼠标点击事件。
首先我们需要为画布添加一个鼠标按下事件:
jquerySvg.unbind("mousedown"); jquerySvg.bind("mousedown",mousedownFun);
function mousedownFun(evt){ hideDialog(); var target = evt.target; curLocation.x = evt.clientX; curLocation.y = evt.clientY; var className = target.getAttribute("class");
通过上述代码,我们可以获取鼠标目前的坐标位置,以及选中的图形的class属性,这个class属性我们之前设置过,不同的元素有不同的classname,所以只要我们判断不同的classname就可以做对应的操作处理了:
再看一下我们的辅助线是怎么做的:
关于PATH:
6、接下来我们节约时间,再讲一个拖拽功能,刚才我们在选中图形后,为发布增加一个鼠标移动的事件,其实就是为拖动图形做准备。通过监听鼠标的位置,动态的改变图形的x和y坐标来实现图形的平移,不过这里我们需要注意的是,我们最开始说了我们使用的是相对坐标,即是通过修改图形的transform属性中的translate参数来进行平移的。关于transform属性的其他特性,我们可以参考这个网址看一下:http://blog.163.com/asdf_zhy/blog/static/274596872010102224240426/
7、鼠标抬起事件。当鼠标抬起后,我们要做的工作是将图形的相对坐标换算为绝对坐标。这么做的好处是方便下次对图形进行移动时进行计算。如果我们不去计算,则图形再进行集体平移,或者自动对齐功能时,它的计算公式将非常复杂,如果还原了则会简单很多。
8、功能部分其实就差不多这些,相信如果你和我是同事,看了我这些代码是不是理解起来就简单多了。再讲一个对齐吧,对齐其实我采用了一种简单的方法,即直接修改元素的绝对坐标。因为我们在鼠标松开的时候还原了元素的transform属性,这是我们修改绝对坐标不需要考虑transform。比较方便。例如左对齐:
9、保存当前的票面信息。我们写了这么多代码最终的目的就是为了将其保存起来,在需要的时候再读取并打印。如何保存呢,没有用数据库,而是放置在了磁盘里,将这些信息转化为JSON保存在了txt里,需要的时候直接使用jackson的方法,readWithFile就可以了:
/** * 读取票面方法 */ function readTicketFace(){ console.log("------------readStart------------"); var groupList = jquerySvg.find("."+classes.groupClass); var ticketGroupList = new Array(); //返回值 for(var i=0;i<groupList.length;i++){ var group = groupList[i]; var groupObj = { "clazz":group.getAttribute("class"), "transform":group.getAttribute("transform"), "groupId":group.getAttribute("groupId") }; var rect = $(group).children("rect")[0]; groupObj.elementRect = { "clazz":rect.getAttribute("class"), "x":rect.getAttribute("x"), "y":rect.getAttribute("y"), "width":rect.getAttribute("width"), "height":rect.getAttribute("height"), "fillOpacity":rect.getAttribute("fill-opacity"), "stroke":"none", "rectId":rect.getAttribute("rectId") }; var text = $(group).children("text")[0]; var image = $(group).children("image")[0]; if(text){ groupObj.elementKey = { "width":"", "height":"", "x":text.getAttribute("x"), "y":text.getAttribute("y"), "fontWeight":text.getAttribute("fontWeight") ? text.getAttribute("fontWeight"):"", "fontItalic":text.getAttribute("fontItalic") ? text.getAttribute("fontItalic"):"", "style":text.getAttribute("style")? text.getAttribute("style"):"", "format":text.getAttribute("format"), "content":text.getAttribute("content"), "fontSize":text.getAttribute("font-size"), "fontFamily":text.getAttribute("font-family"), "textAnchor":text.getAttribute("text-anchor"), "clazz":text.getAttribute("class"), "elementType":text.getAttribute("elementType"), "isStatic":text.getAttribute("isStatic"), "keyName":text.getAttribute("keyName"), "signId":text.getAttribute("signId"), "keyId":text.getAttribute("keyId") }; }else if(image){ groupObj.elementKey = { "x":image.getAttribute("x"), "y":image.getAttribute("y"), "height":image.getAttribute("height"), "width":image.getAttribute("width"), "content":image.getAttribute("content"), "clazz":image.getAttribute("class"), "elementType":image.getAttribute("elementType"), "isStatic":image.getAttribute("isStatic"), "keyName":image.getAttribute("keyName"), "signId":image.getAttribute("signId"), "keyId":image.getAttribute("keyId") }; } ticketGroupList.push(groupObj); } console.log("------------readEnd------------"); return ticketGroupList; };
后台很简单:
/** * 另存为模版 * @return */ public static CommonResult saveAsTpl(SVGTicketFace svgTicketFace){ CommonResult commonResult = new CommonResult(); try { /** * 另存 */ String time = STARTWITHS + DateUtil.getNowByFormat("ddHHmmss"); svgTicketFace.setId(time); if(StringUtils.isBlank(svgTicketFace.getName())){ svgTicketFace.setName("模版"+time); } commonResult = isExistByName(svgTicketFace.getName()); if(!commonResult.isOk()){ return commonResult; } String fileName = configPath + time +".txt"; File file = new File(fileName); file.createNewFile(); objectMapper.writeValue(file,svgTicketFace); /** * 插入记录 */ svgTicketFace.setVersion(EDIT_VERSION); insert(svgTicketFace); commonResult.setOk(true); commonResult.setDesc("保存成功,票面名称["+svgTicketFace.getName()+"]"); commonResult.setData(time); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return commonResult; }
10、读取票面并解析回显。这个代码我就不贴了,很简单其实,直接调现成的commonAdd就可以了。
附加-上传图片功能:
将图片的BASE64编码放置在文本框内,点击上传,创建对应的元素信息,直接添加到画布即可,代码如下:
if(type == elementType.image){ keyElement = document.createElementNS(globalVb.svgNS, "image"); keyElement.setAttributeNS(null,"height",element.height); keyElement.setAttributeNS(null,"width",element.width); keyElement.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",element.content); checkFlag = 2; }
如此,整个基于SVG的票面设计器也就这么讲完了,相对如果你对svg和javascript比较熟悉一定不会觉得很难,甚至会发现我写的代码其实很矬,请见谅。最近公司来了几个新同事,可以看看我最近整理的这些总结的笔记,会很快上手工作的。看明天是否有时间,如果有就整理关于quartz定时器的代码如果没有时间就放在下周去整理。近期我还会阅读关于多线程方面的数据,有收获后也会写博客的,明天可能会去吃大虾,哈哈。
版权声明:本文为博主原创文章,未经博主允许不得转载。