純前端處理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數據。很大程度上節省了流程以及人力成本。

兼容低版本瀏覽器可引入以下文件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM