基于SVG的票面设计器开发总结

今天周六,原则上要休息,但想到下周还有一堆任务,还是先做一部分工作吧,就把之前做的票面设计器改了改,增加了上传图片和更换背景底图的功能。现在打算整理下这个设计器,也算对齐一个总结。不过这属于我们部门的产品,代码我使用截图的方式多一些。首先来看一下我做的这个票面设计器的最终效果图:

从截图中可以看到在页面里,专业点叫我在画布里增加了很多的元素,这些元素都与剧院票务相关包括项目、场次、地点,二维码等信息。上面的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定时器的代码如果没有时间就放在下周去整理。近期我还会阅读关于多线程方面的数据,有收获后也会写博客的,明天可能会去吃大虾,哈哈。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-20 19:05:27

基于SVG的票面设计器开发总结的相关文章

Stimulsoft Reports Designer.Silverlight是一个基于web的报表设计器控件

Stimulsoft Reports Designer.Silverlight是一个基于web的报表设计器控件,通过使用它您可以直接在web浏览器中更改您的报表控件.该产品使用Silverlight技术和ASP.NET开发.它不需要开发人员编写复杂的代码或很长的组件设置.您在服务器上使用的是一个简单的ASP.NET组件.Silverlight组件在客户端上运行.Stimulsoft Reports Designer.Silverlight拥有一个时尚的用户界面,加载迅速,运行速度快,并拥有丰富的

基于Qt的流程设计器(一)

一: 先来看一下界面的截图: 说明: 拖动节点的时候,与该节点相关的箭头连线也会跟着调整: 用户可以使用鼠标从一个节点拖出一个箭头到另一个节点(鼠标在空白区域点击一下,拖出的箭头消失) 这三个图标,手型图标处于选中状态的时候,节点可以拖动, 箭头图标处于选中状态的时候,可以使用鼠标绘制连线箭头 最后一个图标,用于在画布上创建一个节点方框 二: 关键代码文件如下图(用红框框住的为关键代码文件) 其他文件均为辅助代码文件(有些文件中的代码没有用到) 三: CustomView类是我们的绘图面板,这个

流程设计器jQuery + svg/vml(Demo7 - 设计器与引擎及表单一起应用例子)

去年就完成了流程设计器及流程引擎的开发,本想着把流程设计器好好整理一下,形成一个一步一步的开发案例,结果才整理了一点点,发现写文章比写代码还累,加上有事情要忙,结果就.. 明天要去外包驻场了,现把流程设计器最终实现的效果及应用案例放到网上(Demo),欢迎大家围观,欢迎园友们提出宝贵意见. 1.流程设计器整体外观 2.流程实例名称设置 3.活动审批人设置 4.条件公式设置 以上是流程设计器的实现效果. 接下来想向园友们推荐一下我这几年来用业余时间做的一个云考勤系统(大部分时间花在做流程设计器.流

基于Extjs的web表单设计器 第六节——界面框架设计

基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extjs的web表单设计器 第四节——控件拖放 基于Extjs的web表单设计器 第五节——数据库设计 基于Extjs的web表单设计器 第六节——界面框架设计 基于Extjs的web表单设计器 第七节——取数公式设计 基于Extjs的web表单设计器 第八节——表单引擎设计 这一节我给大家介绍一下表单设

基于vue开发的一款强大的表单设计器,支持element和antd-vue表单快速开发。

基于 vue 和 element-ui 实现的表单设计器,使用了最新的前端技术栈,内置了 i18n 国际化解决方案,支持生成element 和 antd-vue 表单,让表单开发简单而高效. 在线预览 使用文档 特性 可视化配置页面 提供栅格布局,并采用flex实现对齐 一键预览配置的效果 一键生成配置json数据 一键生成代码,立即可运行 提供自定义组件满足用户自定义需求 提供远端数据接口,方便用户需要异步获取数据加载 提供功能强大的高级组件 支持表单验证 快速获取表单数据 国际化支持 组件

基于Extjs的web表单设计器 第二节——表单控件设计

这一节介绍表单设计器的常用控件的设计. 在前面两章节的附图中我已经给出了表单控件的两大分类:区域控件.常用控件.这里对每个分类以及分类所包含的控件的作用进行一一的介绍,因为它们很重要,是表单设计器的基本元素,更是核心组成部门. 一.区域控件,它主要包含三个类型的控件:卡片区域.表格区域.混合区域.这三个控件是我们的其他控件的容器或者叫包装器,相当于VS里面的各种Panel.它们很重要,每种区域控件的作用都不一样,能够包含的控件类型也不大一样,它们三个区域相互配合使用,可以为我们的表单提供强大的支

基于Extjs的web表单设计器 第七节——取数公式设计之取数公式的使用

基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extjs的web表单设计器 第四节——控件拖放 基于Extjs的web表单设计器 第五节——数据库设计 基于Extjs的web表单设计器 第六节——界面框架设计 基于Extjs的web表单设计器 第七节——取数公式设计之取数公式定义 基于Extjs的web表单设计器 第七节——取数公式设计之取数公式的使用

.net web 开发平台- 表单设计器 一(web版)

如今为了适应需求的不断变化,动态表单设计器应运而生.它主要是为了满足界面的不断变化和提高开发速度.比如:一些页面客户可能也无法确定页面的终于布局,控件的位置,在哪种情况下显示或不显示等可能须要随时改动.为了应对这些需求而不去多次改动源码进行公布,就能够在项目中使用动态表单设计器.如今分享一下我做的动态表单设计器的设计思路,共同学习. 想做一个表单设计器,首先要确定是做c/s的还是b/s.我考虑到以后的发展方向是c/s向b/s转化,所以就选择了b/s的方向,并且做b/s比做c/s要简单非常多.在做

基于Extjs的web表单设计器 第三节——控件拖放

看过之前设计器截图的朋友应该有印象,可能会发觉我们的设计器UI设计布局其实类似Visual studio 的设计界面,采用的是左.中.右三个区域布局.左侧为控件区域.中间为表单的画布设区域.右侧为属性区域.这样的UI设计肯定就得支持控件的拖拽设计,用户只要拖放一个控件到我们的画布上,那么画布就应该立即能够看到我们拖放的控件在画布中的位置.大小.以及一些控件自带的默认信息.不用说这样的设计对于用户来说不论在操作体验上还是设计的感官上都更加直接和方便,因为我在设计阶段就可以知道我设计后的表单在系统运