纯前端处理excel数据

纯前端处理excel数据

问题所在

由于公司一直有关于活动、会议等专题前端页面的需求。并且之中会有会议议程,相关表格等。处理这类表格需求,之前的做法有两种:
1.直接使用设计师的设计切图
2.前端将会议议程写进页面中
其实两种方法都可以,但有个问题是这样的表格会进行频繁的更改。比如出席人员、会议项目等,随时有进行修改、删除的可能。这样每次修改都会牵涉相关的人力,每次的小修改会造成大量的人力浪费。
如果时第一种方式,前端开发人员会减少一定的工作量。但对于设计人员,据他们的发聩情况是说这样会非常麻烦。第二种方式前端修改也是相当繁琐。所以就想能不能想办法解决这个问题。

寻找解决方案

解决方案应该满足以下条件:

  • 表格/列表以代码方式实现,不再使用一张切图(这样也是前端偷懒的做法)
  • 设计只负责出设计效果图,前端根据设计搞实现效果
  • 将数据与UI主见相互分离,不会因为数据的更改而使UI不能使用

但是存在一个问题,后期数据的修改如何来处理。是需求修改后,产品再与开发人员沟通进行数据修改。这样其实就没任何的改进,怎样才能让产品可以轻松、方便的进行数据修改。这样就想到了一个方法——excel,产品对于excel的操作肯定是非常熟悉的了,也便于将这个数据修改的工作交到他们手中。

如何实现

首先,由于后端小伙伴工作的繁重。对于excel数据的处理没法让他们进行技术支持,所以只能由前端来处理了。
那我在浏览器的这个环境下如何来处理excel数据呢?javascript当然是可以处理excel数据文件的,但是在浏览器这样的环境下怎么来读取excel这种复杂的文件呢。所以只能去找找有没有这样的插件
在github上找到了一个开源库xlsx,可以通过npm方式来安装。

npm install xlsx --save

在自己的html文件里面添加对js文件的引用

<script src="./node_modules/xlsx/dist/jszip.js"></script>
<script src="./node_modules/xlsx/dist/xlsx.js"></script>

有一个问题,这种数据是希望将它持久化显示的。但又没有后台的支持,只能完全依靠表格文件。所以表格就是一个持久化的数据。
我的思路时用ajax异步去请求文件,相应地就可以读取表格文件数据。只要能拿到数据,进入到javascript环境就有接下来的故事。刚好开源库xlsx也支持这样的方式:

/* set up XMLHttpRequest */
var url = "test_files/formula_stress_test_ajax.xlsx";
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";

oReq.onload = function(e) {
  var arraybuffer = oReq.response;

  /* convert data to binary string */
  var data = new Uint8Array(arraybuffer);
  var arr = new Array();
  for(var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
  var bstr = arr.join("");

  /* Call XLSX */
  var workbook = XLSX.read(bstr, {type:"binary"});

  /* DO SOMETHING WITH workbook HERE */
}

oReq.send();

这样我就可以将表格数据拿出来处理了。
最终的UI活动议程可能是这样

也可能是这样的表格

所以不能固化到将数据仅仅整理成table表格数据,我们需要将数据处理成前端便于处理的json数据。这样就不怕设计的设计如何变化。

直接上代码

/**
 * [description]
 * excel配置会议议程
 * @author mao
 * @version 1
 * @date    2016-09-28
 */
var excel = (function(mod) {

	/**
	 * [checkColumn description]
	 * 检测是几列数据
	 * @author mao
	 * @version 1
	 * @date    2016-10-12
	 * @param   {object}   workbook excel数据
	 * @return  {[object]}            结果数据
	 */
	function checkColumn(workbook) {
		var result = workbook.Sheets[(workbook.SheetNames)[0]],
				column,
				data;

		//判断是列excel数据
		column = ((result['!ref']).split(':')[1]).charAt(0);

		//处理成hash结构
		data = processOrigin(result, column);

		return data;
	}

	/**
	 * [processOrigin description]
	 * @author mao
	 * @version 1
	 * @date    2016-10-12
	 * @param   {object}   result 待处理excel数据
	 * @param   {string}   column 有多少列数据
	 * @return   {Object}    结果数据
	 */
	function processOrigin(result, column) {
		var merges = result['!merges'] || [],  //合并表格的位置信息
				obj,response;

		//生成对应的几项hash数据
		switch(column) {
			case 'B' : 
				obj = {
					_1:{}
				};
				break;
			case 'C' :
			  obj = {
					_1:{},
					_2:{}
				};
			  break;
			case 'D' :
			  obj = {
					_1:{},
					_2:{},
					_3:{}
				};
			  break;
			case 'E' :
			  obj = {
					_1:{},
					_2:{},
					_3:{},
					_4:{}
				};
			  break;
			default: break;
		}

		//拿到excel初始的hash数据
		for(var i in result) {
			if(i.charAt(0) === '!' || i.charAt(0) === 'A') continue;
			switch(i.charAt(0)) {
				case 'B':{
					var key = i.slice(1,i.length);
					obj._1[key] = result[i].v;
					break;
				}
				case 'C':{
					var key = i.slice(1,i.length);
					obj._2[key] = result[i].v;
					break;
				}
				case 'D':{
					var key = i.slice(1,i.length);
					obj._3[key] = result[i].v;
					break;
				}
				case 'E':{
					var key = i.slice(1,i.length);
					obj._4[key] = result[i].v;
					break;
				}
				default:break;
			}
		}

		//合并项
		response = mergeColumn(obj, merges);

		return response;
	}

	/**
	 * [mergeColumn]
	 * description
	 * @author mao
	 * @version 1
	 * @date    2016-10-12
	 * @param   {obj}   obj    取出的excel数据
	 * @param   {Object}   merges 合并单元格的起始坐标
	 * @return  {Object}          补全后的单元格数据
	 */
	function mergeColumn(obj, merges) {
		//判断是否只为一列
		var _keys = [];
		for(var i in obj) {
			_keys.push(i);
		}
		if(_keys.length === 1) {
			return obj;
		}

		//验证是否有合并
		if(merges.length === 0) {
			console.log('merges is empty');
			return obj;
		}

		//将数据处理成全项目的hash
		for(var i = 0; i < merges.length; i++) { //纵向合并
			if(merges[i].e.c == merges[i].s.c) { 
				var start = merges[i].s.r + 1,
						end = merges[i].e.r + 1,
						sub = merges[i].e.c, //起点x坐标
						range = end - start,
						origin = obj['_' + sub][start];
				
				//起始点数据
				obj['_' + sub][start] = {
					_v: origin,
					_w: 'row',
					_s: true,
					_c: (range + 1)
				}

				//补全被合并项
				for(var j = 1; j <= range; j++) {
					start ++;
					obj['_' + sub][start] = {
						_v: origin,
						_w: 'row',
					  _s: false,
						_c: (range + 1)
					}
				}

			} else { //横向的合并
				var start = merges[i].s.c,
						end = merges[i].e.c,
						sub = merges[i].e.r + 1, //起点y坐标
						range = end - start,
						origin = obj['_' + start][sub];

				//起始点数据
				obj['_' + start][sub] = {
					_v: origin,
					_w: 'col',
					_s: true,
					_c: (range + 1)
				}

				//补全被合并项
				for(var j = 1; j <= range; j++) {
					start ++;
					obj['_' + start][sub] = {
						_v: origin,
						_w: 'col',
						_s: false,
						_c: (range + 1)
					}
				}
			}
		}

		return obj;
	}

	/**
	 * [toArray description]
	 * @author mao
	 * @version 1
	 * @date    2016-10-12
	 * @param   {Object}   obj 待处理的hash
	 * @return  {array}       处理成的数组
	 */
	function toArray(obj) {
		var keys = [],
				data = obj._1,
				res = [];
		//获取key值
		for(var i in obj) {
			keys.push(i);
		}

		//处理成数组
		for(var i in data) {
			var current = {};
			for(var j = 0; j < keys.length; j++) {
				current[keys[j]] = obj[keys[j]][i];
			}
			res.push(current);
		}
		return res;
	}


	/**
	 * [createXHR]
	 * 创建一个xhr
	 * @author mao
	 * @version 1
	 * @date    2016-09-26
	 * @return  {object}   xhr
	 */
	function createXHR() {
		if(window.XMLHttpRequest) {
	    return new XMLHttpRequest();
		} else if(window.ActiveXObject) {  //ie6
			return new ActiveXObject('MSXML2.XMLHTTP.3.0');
		} else {
			throw 'XHR unavailable for your browser';
		}
	}


	/**
	 * [transferData]
	 * 请求excel文件请求
	 * @author mao
	 * @version 1
	 * @date    2016-09-28
	 * @param   {Function} option.dataRender 回调函数,处理结果数据
	 * @param   {string}   option.url      xlsx文件请求地址
	 */
	mod.transferData = function(option) {
		//新建xhr
		var oReq = createXHR(),
				resultData;
		//建立连接
		oReq.open("GET", option.url, true);

		//判断是否为低版本的ie,处理返回
		if(typeof Uint8Array !== 'undefined') {
			oReq.responseType = "arraybuffer";
			oReq.onload = function(e) {
				if(typeof console !== 'undefined') console.log("onload", new Date());
				var arraybuffer = oReq.response;
				var data = new Uint8Array(arraybuffer);
				var arr = new Array();
				for(var i = 0; i != data.length; ++i) {
					arr[i] = String.fromCharCode(data[i]);
				}
				//处理数据
				var wb = XLSX.read(arr.join(""), {type:"binary"});

				//数据放入回调
				option.dataRender(toArray(checkColumn(wb)));
			};
		} else {
			oReq.setRequestHeader("Accept-Charset", "x-user-defined");	
			oReq.onreadystatechange = function() { 
				if(oReq.readyState == 4 && oReq.status == 200) {
					var ff = convertResponseBodyToText(oReq.responseBody);
					if(typeof console !== 'undefined') {
						console.log("onload", new Date());
					}

					//处理数据
					var wb = XLSX.read(ff, {type:"binary"});

					//数据放入回调
					option.dataRender(toArray(checkColumn(wb)));
				}
			};
		}

		//发送请求
		oReq.send();
	}

	/**
	 * [check_undefind description]
	 * @author mao
	 * @version 1
	 * @date    2016-10-13
	 * @param   {string}   data 数据
	 * @return  {string}        返回空或数据本身
	 */
	function check_undefind(data) {
		if(!data) {
			return '';
		} else {
			if(typeof data != 'number') {
				//检测特殊字符
				if(data.indexOf('&#10;') != -1) {
					return data.split('&#10;').join('<br/>');
				} else {
					return data;
				}
			} else {
				return data;
			}
		}
	}

	/**
	 * [renderHTML description]
	 * @author mao
	 * @version 1
	 * @date    2016-10-13
	 * @param   {object}   table 最终数据
	 * @return  {string}         渲染的dom
	 */
	mod.renderHTML = function(table) {
		var html = '';
		for(var i = 0; i <table.length; i++) {
			html += '<tr>';
			for(var j in table[i]) {
				var item = table[i][j];
				if(typeof item === 'object') {
					switch(item._w) {
						case 'col': {
							if(item._s) {
								html += '<td colspan="'+item._c+'">'+check_undefind(item._v)+'</td>';
							}
							break;
						}
						case 'row': {
							if(item._s) {
								html += '<td rowspan="'+item._c+'">'+check_undefind(item._v)+'</td>';
							}
							break;
						}
						default:break;
					}
				} else {
					html += '<td>'+check_undefind(item)+'</td>';
				}
			}
			html += '</tr>';
		}
		return html;
	}

	return mod;

})(excel || {})


处理出来的数据如图,

这样的结果数据可以很友好地装载入UI结构当中,这样最后议程修改就只需要产品修改excel数据。很大程度上节省了流程以及人力成本。

兼容低版本浏览器可引入以下文件

posted @ 2016-11-28 23:38  marvine  阅读(7812)  评论(2编辑  收藏  举报