exportor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. /**
  2. * Created by zhuth on 2019/07/08.
  3. */
  4. ;exports = module.exports = (function () {
  5. // "use strict";
  6. let tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?>'
  7. + '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
  8. + '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">'
  9. + '<Author>{author}</Author>'
  10. + '<Created>{created}</Created>'
  11. +'</DocumentProperties>'
  12. + '<Styles>{styles}</Styles>'
  13. + '{worksheets}'
  14. + '</Workbook>',
  15. tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{columnWidth}{rows}</Table></Worksheet>',
  16. tmplColumnWidthXML = '<Column ss:Width="{width}"/>',
  17. tmplTableHeaderXML = '<Row><Cell ss:StyleID="{styleID}" ss:MergeAcross="{columnCount}"><Data ss:Type="String">{header}</Data></Cell></Row>',
  18. tmplRowXML = '<Row>{cells}</Row>',
  19. tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>',
  20. _defaultStyleIDs = {
  21. DateTime: 'date',
  22. },
  23. tmplStyleXML = '<Style ss:ID="{styleID}">{styleNode}</Style>',
  24. tmplStyleNodeXML = '<{nodeName}{attributes}>{children}</{nodeName}>',
  25. tmplStyleNodeAttributeXML = 'ss:{attributeName}="{attributeValue}"',
  26. styleConfig = [{
  27. name: "date",
  28. tags: [{
  29. tag: "NumberFormat",
  30. attributes: {
  31. Format: "yyyy-mm-dd"
  32. }
  33. }]
  34. }, {
  35. name: "bold",
  36. tags: [{
  37. tag: "Font",
  38. attributes: {
  39. Bold: "1"
  40. }
  41. }]
  42. }, {
  43. name: "center",
  44. tags: [{
  45. tag: "Alignment",
  46. attributes: {
  47. Horizontal: "Center",
  48. Vertical: "Center",
  49. }
  50. }]
  51. }, {
  52. name: "header",
  53. tags: [{
  54. tag: "Font",
  55. attributes:{
  56. Color: "#FFFFFF"
  57. }
  58. }, {
  59. tag: "Interior",
  60. attributes: {
  61. Color: "#70AD47",
  62. Pattern: "Solid"
  63. }
  64. }]
  65. }, {
  66. name: "even",
  67. tags: [{
  68. tag: "Interior",
  69. attributes: {
  70. Color: "#C6E0B4",
  71. Pattern: "Solid"
  72. }
  73. }]
  74. }, {
  75. name: "odd",
  76. tags: [{
  77. tag: "Interior",
  78. attributes: {
  79. Color: "#A9D08E",
  80. Pattern: "Solid"
  81. }
  82. }]
  83. }, {
  84. name: "default",
  85. tags: [{
  86. tag: "Borders",
  87. tags: [{
  88. tag: "Border",
  89. attributes: {
  90. Position: "Right",
  91. LineStyle: "Continuous",
  92. Weight: "1",
  93. Color: "#EAEAEA"
  94. }
  95. }]
  96. }]
  97. }, {
  98. name: "border",
  99. tags: [{
  100. tag: "Borders",
  101. tags: [{
  102. tag: "Border",
  103. attributes: {
  104. Position: "Top",
  105. LineStyle: "Continuous",
  106. Weight: "1",
  107. Color: "#EAEAEA"
  108. }
  109. }, {
  110. tag: "Border",
  111. attributes: {
  112. Position: "Right",
  113. LineStyle: "Continuous",
  114. Weight: "1",
  115. Color: "#EAEAEA"
  116. }
  117. }, {
  118. tag: "Border",
  119. attributes: {
  120. Position: "Bottom",
  121. LineStyle: "Continuous",
  122. Weight: "1",
  123. Color: "#EAEAEA"
  124. }
  125. }, {
  126. tag: "Border",
  127. attributes: {
  128. Position: "Left",
  129. LineStyle: "Continuous",
  130. Weight: "1",
  131. Color: "#EAEAEA"
  132. }
  133. }]
  134. }]
  135. }],
  136. _format = function(s, c) { return s.replace(/{(\w+)}/g, function(m, p) { return c[p]; }) },
  137. _export = function(workbookXML, wbname) {
  138. var eleLink = document.createElement('a');
  139. eleLink.download = wbname || 'Workbook.xls';
  140. eleLink.style.display = 'none';
  141. var blob = new Blob([workbookXML]);
  142. eleLink.href = URL.createObjectURL(blob);
  143. document.body.appendChild(eleLink);
  144. eleLink.click();
  145. document.body.removeChild(eleLink);
  146. };
  147. function Exportor(json, wbname) {
  148. this.init(json, wbname);
  149. this.workbookXML = this.generateWorkbookXML();
  150. }
  151. Exportor.prototype.init = function (json, wbname) {
  152. this.json = json;
  153. this.wbname = wbname;
  154. this.workbookXML = '';
  155. this.styleID = 1;
  156. this.styles = [];
  157. };
  158. Exportor.prototype.generateWorkbookXML = function() {
  159. let ctx = {
  160. worksheets: this.generateWorksheetsXML(),
  161. styles: this.generateStylesXML(),
  162. author: 'usoftchina',
  163. created: new Date().getTime()
  164. };
  165. return _format(tmplWorkbookXML, ctx);
  166. };
  167. Exportor.prototype.generateStylesXML = function() {
  168. let str = '';
  169. for(let i = 0; i < this.styles.length; i++) {
  170. str += this.generateStyles(this.styles[i].styleID, this.styles[i].styleNames);
  171. }
  172. return str;
  173. };
  174. Exportor.prototype.generateWorksheetsXML = function() {
  175. let str = '',
  176. sheets = this.json.sheets;
  177. for(let i = 0; i < sheets.length; i++) {
  178. str += this.generateWorksheetXML(sheets[i]);
  179. }
  180. return str;
  181. };
  182. Exportor.prototype.generateWorksheetXML = function(sheet) {
  183. const { name, header, columns, rows } = sheet;
  184. let widths = columns.map(c => c.width);
  185. let ctx = {
  186. nameWS: name,
  187. columnWidth: this.generateColumnWidth(widths),
  188. rows: this.generateTableHeaderXML(header, columns.length) + this.generateHeaderRowsXML(columns) + this.generateRowsXML(columns, rows)
  189. };
  190. let str = _format(tmplWorksheetXML, ctx);
  191. return str;
  192. };
  193. Exportor.prototype.generateTableHeaderXML = function(header, columnCount) {
  194. let str = '';
  195. if(!!header) {
  196. let styleID = this.addStyle(['center', 'header', 'border']);
  197. let ctx = {
  198. styleID: styleID,
  199. columnCount: columnCount - 1,
  200. header
  201. }
  202. str = _format(tmplTableHeaderXML, ctx);
  203. }
  204. return str
  205. };
  206. Exportor.prototype.generateColumnWidth = function(widths) {
  207. let str = '';
  208. for(let i = 0; i < widths.length; i++) {
  209. let ctx = {
  210. width: widths[i] || 100
  211. };
  212. str += _format(tmplColumnWidthXML, ctx);
  213. }
  214. return str;
  215. };
  216. Exportor.prototype.generateHeaderRowsXML = function(columns) {
  217. let cells = columns.map(c => ({ type: 'String', value: c.name, styleNames: ['header', 'bold', 'default'] }));
  218. let ctx = {
  219. cells: this.generateCellsXML(cells)
  220. };
  221. let str = _format(tmplRowXML, ctx);
  222. return str;
  223. };
  224. Exportor.prototype.generateRowsXML = function(columns, rows) {
  225. let cols = columns.map(c => ({ type: c.type, name: c.name, styleNames: c.styleNames }));
  226. let str = '';
  227. for(let i = 0; i < rows.length; i++) {
  228. let r = rows[i];
  229. let cells = [];
  230. let s = (i&1) === 0 ? 'even' : 'odd';
  231. for(let j = 0; j < cols.length; j++) {
  232. cols[j].styleNames = cols[j].styleNames && cols[j].styleNames.length>0 ? cols[j].styleNames: [];
  233. cells.push({
  234. type: cols[j].type,
  235. value: r[j],
  236. styleNames: ['default', s].concat(cols[j].styleNames.concat(_defaultStyleIDs[cols[j].type] ? [_defaultStyleIDs[cols[j].type]] : []))
  237. });
  238. }
  239. let ctx = {
  240. cells: this.generateCellsXML(cells)
  241. };
  242. str += _format(tmplRowXML, ctx);
  243. }
  244. return str;
  245. };
  246. Exportor.prototype.generateCellsXML = function(cells) {
  247. let str = '';
  248. for(let i = 0; i < cells.length; i++) {
  249. let styleID = this.addStyle(cells[i].styleNames);
  250. let ctx = {
  251. attributeStyleID: cells[i].styleNames && cells[i].styleNames.length > 0 ? _format(' ss:StyleID="{styleID}"', {
  252. styleID: styleID
  253. }) : '',
  254. attributeFormula: '',
  255. nameType: cells[i].type || 'String',
  256. data: cells[i].value || ''
  257. };
  258. str += _format(tmplCellXML, ctx);
  259. }
  260. return str;
  261. };
  262. Exportor.prototype.addStyle = function(styleNames) {
  263. if(!styleNames || styleNames.length === 0) {
  264. return;
  265. }
  266. let s1 = styleNames.sort();
  267. let idx = -1; // 是否已存在
  268. outer:
  269. for(let i = 0; i < this.styles.length; i++) {
  270. let style = this.styles[i];
  271. let s2 = style.styleNames.sort();
  272. if(s1.length !== s2.length) {
  273. continue;
  274. }
  275. for(let j = 0; j < s1.length; j++) {
  276. if(s1[j] !== s2[j]) {
  277. continue outer;
  278. }
  279. }
  280. idx = i;
  281. }
  282. if(idx === -1) {
  283. let styleID = 's' + this.styleID++
  284. this.styles.push({
  285. styleID: styleID,
  286. styleNames: styleNames
  287. });
  288. return styleID;
  289. }else {
  290. return this.styles[idx].styleID;
  291. }
  292. };
  293. Exportor.prototype.generateStyles = function(styleID, styleNames) {
  294. let configs = styleNames.map(name => styleConfig.find(s => s.name === name)).filter(c => !!c);
  295. let arr = [];
  296. let str = '';
  297. for(let i = 0; i < configs.length; i++) {
  298. configs[i].tags.forEach(config => {
  299. arr.push(this.generateStyleNode(config));
  300. });
  301. }
  302. str = _format(tmplStyleXML, {
  303. styleID: styleID,
  304. styleNode: arr.join('')
  305. });
  306. return str;
  307. };
  308. Exportor.prototype.generateStyleNode = function(styleConfig) {
  309. let str = '';
  310. let attributes = [''];
  311. let children = [];
  312. let ctx = {};
  313. if(!!styleConfig.tags) {
  314. for(let i = 0; i < styleConfig.tags.length; i++) {
  315. let c = styleConfig.tags[i];
  316. children.push(this.generateStyleNode(c));
  317. }
  318. }
  319. styleConfig.attributes = styleConfig.attributes || [];
  320. for(let k in styleConfig.attributes) {
  321. attributes.push(_format(tmplStyleNodeAttributeXML, {
  322. attributeName: k,
  323. attributeValue: styleConfig.attributes[k]
  324. }));
  325. }
  326. ctx.nodeName = styleConfig.tag;
  327. ctx.attributes = attributes.join(' ');
  328. ctx.children = children.join('');
  329. str = _format(tmplStyleNodeXML, ctx);
  330. return str;
  331. };
  332. Exportor.prototype.generateStyleNodeAttribute = function(styleNodeConfig) {
  333. let str = '';
  334. for(let k in styleNodeConfig) {
  335. str += _format(tmplStyleNodeAttributeXML, {
  336. key: k,
  337. value: styleNodeConfig[k]
  338. })
  339. }
  340. return str;
  341. };
  342. Exportor.prototype.export = function() {
  343. _export(this.workbookXML, this.wbname);
  344. };
  345. return Exportor;
  346. })();