|
|
@@ -1,366 +1,147 @@
|
|
|
/**
|
|
|
- * Created by zhuth on 2019/07/08.
|
|
|
+ * Created by zhuth on 2019/08/22.
|
|
|
*/
|
|
|
+const XlsxPopulate = require('xlsx-populate');
|
|
|
+
|
|
|
;exports = module.exports = (function () {
|
|
|
- // "use strict";
|
|
|
- let tmplWorkbookXML = '<?xml version="1.0"?><?mso-application progid="Excel.Sheet"?>'
|
|
|
- + '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">'
|
|
|
- + '<DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">'
|
|
|
- + '<Author>{author}</Author>'
|
|
|
- + '<Created>{created}</Created>'
|
|
|
- +'</DocumentProperties>'
|
|
|
- + '<Styles>{styles}</Styles>'
|
|
|
- + '{worksheets}'
|
|
|
- + '</Workbook>',
|
|
|
- tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{columnWidth}{rows}</Table></Worksheet>',
|
|
|
- tmplColumnWidthXML = '<Column ss:Width="{width}"/>',
|
|
|
- tmplTableHeaderXML = '<Row><Cell ss:StyleID="{styleID}" ss:MergeAcross="{columnCount}"><Data ss:Type="String">{header}</Data></Cell></Row>',
|
|
|
- tmplRowXML = '<Row>{cells}</Row>',
|
|
|
- tmplCellXML = '<Cell{attributeStyleID}{attributeFormula}><Data ss:Type="{nameType}">{data}</Data></Cell>',
|
|
|
- _defaultStyleIDs = {
|
|
|
- DateTime: 'date',
|
|
|
- },
|
|
|
- tmplStyleXML = '<Style ss:ID="{styleID}">{styleNode}</Style>',
|
|
|
- tmplStyleNodeXML = '<{nodeName}{attributes}>{children}</{nodeName}>',
|
|
|
- tmplStyleNodeAttributeXML = 'ss:{attributeName}="{attributeValue}"',
|
|
|
- styleConfig = [{
|
|
|
- name: "date",
|
|
|
- tags: [{
|
|
|
- tag: "NumberFormat",
|
|
|
- attributes: {
|
|
|
- Format: "yyyy-mm-dd"
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "bold",
|
|
|
- tags: [{
|
|
|
- tag: "Font",
|
|
|
- attributes: {
|
|
|
- Bold: "1"
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "center",
|
|
|
- tags: [{
|
|
|
- tag: "Alignment",
|
|
|
- attributes: {
|
|
|
- Horizontal: "Center",
|
|
|
- Vertical: "Center",
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "header",
|
|
|
- tags: [{
|
|
|
- tag: "Font",
|
|
|
- attributes:{
|
|
|
- Color: "#FFFFFF"
|
|
|
- }
|
|
|
- }, {
|
|
|
- tag: "Interior",
|
|
|
- attributes: {
|
|
|
- Color: "#70AD47",
|
|
|
- Pattern: "Solid"
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "even",
|
|
|
- tags: [{
|
|
|
- tag: "Interior",
|
|
|
- attributes: {
|
|
|
- Color: "#C6E0B4",
|
|
|
- Pattern: "Solid"
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "odd",
|
|
|
- tags: [{
|
|
|
- tag: "Interior",
|
|
|
- attributes: {
|
|
|
- Color: "#A9D08E",
|
|
|
- Pattern: "Solid"
|
|
|
- }
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "default",
|
|
|
- tags: [{
|
|
|
- tag: "Borders",
|
|
|
- tags: [{
|
|
|
- tag: "Border",
|
|
|
- attributes: {
|
|
|
- Position: "Right",
|
|
|
- LineStyle: "Continuous",
|
|
|
- Weight: "1",
|
|
|
- Color: "#EAEAEA"
|
|
|
- }
|
|
|
- }]
|
|
|
- }]
|
|
|
- }, {
|
|
|
- name: "border",
|
|
|
- tags: [{
|
|
|
- tag: "Borders",
|
|
|
- tags: [{
|
|
|
- tag: "Border",
|
|
|
- attributes: {
|
|
|
- Position: "Top",
|
|
|
- LineStyle: "Continuous",
|
|
|
- Weight: "1",
|
|
|
- Color: "#EAEAEA"
|
|
|
- }
|
|
|
- }, {
|
|
|
- tag: "Border",
|
|
|
- attributes: {
|
|
|
- Position: "Right",
|
|
|
- LineStyle: "Continuous",
|
|
|
- Weight: "1",
|
|
|
- Color: "#EAEAEA"
|
|
|
- }
|
|
|
- }, {
|
|
|
- tag: "Border",
|
|
|
- attributes: {
|
|
|
- Position: "Bottom",
|
|
|
- LineStyle: "Continuous",
|
|
|
- Weight: "1",
|
|
|
- Color: "#EAEAEA"
|
|
|
- }
|
|
|
- }, {
|
|
|
- tag: "Border",
|
|
|
- attributes: {
|
|
|
- Position: "Left",
|
|
|
- LineStyle: "Continuous",
|
|
|
- Weight: "1",
|
|
|
- Color: "#EAEAEA"
|
|
|
- }
|
|
|
- }]
|
|
|
- }]
|
|
|
- }],
|
|
|
- _format = function(s, c) { return s.replace(/{(\w+)}/g, function(m, p) { return c[p]; }) },
|
|
|
- _export = function(workbookXML, wbname) {
|
|
|
- var eleLink = document.createElement('a');
|
|
|
- eleLink.download = wbname || 'Workbook.xls';
|
|
|
- eleLink.style.display = 'none';
|
|
|
- var blob = new Blob([workbookXML]);
|
|
|
- eleLink.href = URL.createObjectURL(blob);
|
|
|
- document.body.appendChild(eleLink);
|
|
|
- eleLink.click();
|
|
|
- document.body.removeChild(eleLink);
|
|
|
- };
|
|
|
|
|
|
- function Exportor(json, wbname) {
|
|
|
- this.init(json, wbname);
|
|
|
- this.workbookXML = this.generateWorkbookXML();
|
|
|
- }
|
|
|
+ function Exportor() {};
|
|
|
|
|
|
- Exportor.prototype.init = function (json, wbname) {
|
|
|
- this.json = json;
|
|
|
- this.wbname = wbname;
|
|
|
- this.workbookXML = '';
|
|
|
- this.styleID = 1;
|
|
|
- this.styles = [];
|
|
|
+ function Cell(str) {
|
|
|
+ str = str ? (str + '').toUpperCase() : 'A1';
|
|
|
+ this._colIdx = str.split(/\d+/)[0]; // 'A'
|
|
|
+ this._colIdxNum = this._colIdx.length === 1 ? this._colIdx.charCodeAt() : NaN; // 暂不支持超出Z
|
|
|
+ this._rowIdx = +str.split(/[a-z | A-Z]+/)[1]; // 1
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.generateWorkbookXML = function() {
|
|
|
- let ctx = {
|
|
|
- worksheets: this.generateWorksheetsXML(),
|
|
|
- styles: this.generateStylesXML(),
|
|
|
- author: 'usoftchina',
|
|
|
- created: new Date().getTime()
|
|
|
- };
|
|
|
- return _format(tmplWorkbookXML, ctx);
|
|
|
+ Cell.prototype.getRowIdx = function() {
|
|
|
+ return this._rowIdx;
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.generateStylesXML = function() {
|
|
|
- let str = '';
|
|
|
- for(let i = 0; i < this.styles.length; i++) {
|
|
|
- str += this.generateStyles(this.styles[i].styleID, this.styles[i].styleNames);
|
|
|
- }
|
|
|
- return str;
|
|
|
+ Cell.prototype.getColIdx = function() {
|
|
|
+ return this._colIdx;
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.generateWorksheetsXML = function() {
|
|
|
- let str = '',
|
|
|
- sheets = this.json.sheets;
|
|
|
-
|
|
|
- for(let i = 0; i < sheets.length; i++) {
|
|
|
- str += this.generateWorksheetXML(sheets[i]);
|
|
|
- }
|
|
|
- return str;
|
|
|
+ Cell.prototype.getColIdxNumber = function() {
|
|
|
+ return this._colIdxNum;
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.generateWorksheetXML = function(sheet) {
|
|
|
- const { name, header, columns, rows } = sheet;
|
|
|
- let widths = columns.map(c => c.width);
|
|
|
- let ctx = {
|
|
|
- nameWS: name,
|
|
|
- columnWidth: this.generateColumnWidth(widths),
|
|
|
- rows: this.generateTableHeaderXML(header, columns.length) + this.generateHeaderRowsXML(columns) + this.generateRowsXML(columns, rows)
|
|
|
- };
|
|
|
- let str = _format(tmplWorksheetXML, ctx);
|
|
|
-
|
|
|
- return str;
|
|
|
- };
|
|
|
+ Cell.prototype.toString = function() {
|
|
|
+ return this._colIdx + this._rowIdx;
|
|
|
+ }
|
|
|
|
|
|
- Exportor.prototype.generateTableHeaderXML = function(header, columnCount) {
|
|
|
- let str = '';
|
|
|
- if(!!header) {
|
|
|
- let styleID = this.addStyle(['center', 'header', 'border']);
|
|
|
- let ctx = {
|
|
|
- styleID: styleID,
|
|
|
- columnCount: columnCount - 1,
|
|
|
- header
|
|
|
- }
|
|
|
- str = _format(tmplTableHeaderXML, ctx);
|
|
|
+ Cell.prototype.addCol = function(num) {
|
|
|
+ num = +num;
|
|
|
+ if(this._colIdxNum + num < 91) {
|
|
|
+ this._colIdxNum = this._colIdxNum + num;
|
|
|
+ this._colIdx = String.fromCharCode(this._colIdxNum);
|
|
|
+ return this;
|
|
|
+ }else {
|
|
|
+ throw new Error({message: 'error...'});
|
|
|
}
|
|
|
- return str
|
|
|
- };
|
|
|
+ }
|
|
|
|
|
|
- Exportor.prototype.generateColumnWidth = function(widths) {
|
|
|
- let str = '';
|
|
|
- for(let i = 0; i < widths.length; i++) {
|
|
|
- let ctx = {
|
|
|
- width: widths[i] || 100
|
|
|
- };
|
|
|
- str += _format(tmplColumnWidthXML, ctx);
|
|
|
- }
|
|
|
- return str;
|
|
|
- };
|
|
|
+ Cell.prototype.addRow = function(num) {
|
|
|
+ num = +num;
|
|
|
+ this._rowIdx += num;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
|
|
|
- Exportor.prototype.generateHeaderRowsXML = function(columns) {
|
|
|
- let cells = columns.map(c => ({ type: 'String', value: c.name, styleNames: ['header', 'bold', 'default'] }));
|
|
|
- let ctx = {
|
|
|
- cells: this.generateCellsXML(cells)
|
|
|
- };
|
|
|
- let str = _format(tmplRowXML, ctx);
|
|
|
- return str;
|
|
|
+ Exportor.prototype.init = function (json, wbname) {
|
|
|
+ this._json = json;
|
|
|
+ this._filename = wbname;
|
|
|
+ return this._generateWorkbookBlob().then(blob => {
|
|
|
+ this._blob = blob;
|
|
|
+ return this;
|
|
|
+ });
|
|
|
};
|
|
|
-
|
|
|
- Exportor.prototype.generateRowsXML = function(columns, rows) {
|
|
|
- let cols = columns.map(c => ({ type: c.type, name: c.name, styleNames: c.styleNames }));
|
|
|
- let str = '';
|
|
|
- for(let i = 0; i < rows.length; i++) {
|
|
|
- let r = rows[i];
|
|
|
- let cells = [];
|
|
|
- let s = (i&1) === 0 ? 'even' : 'odd';
|
|
|
- for(let j = 0; j < cols.length; j++) {
|
|
|
- cols[j].styleNames = cols[j].styleNames && cols[j].styleNames.length>0 ? cols[j].styleNames: [];
|
|
|
- cells.push({
|
|
|
- type: cols[j].type,
|
|
|
- value: r[j],
|
|
|
- styleNames: ['default', s].concat(cols[j].styleNames.concat(_defaultStyleIDs[cols[j].type] ? [_defaultStyleIDs[cols[j].type]] : []))
|
|
|
- });
|
|
|
- }
|
|
|
- let ctx = {
|
|
|
- cells: this.generateCellsXML(cells)
|
|
|
- };
|
|
|
- str += _format(tmplRowXML, ctx);
|
|
|
- }
|
|
|
- return str;
|
|
|
+
|
|
|
+ Exportor.prototype._getWorkbook = function() {
|
|
|
+ return XlsxPopulate.fromBlankAsync();
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.generateCellsXML = function(cells) {
|
|
|
- let str = '';
|
|
|
- for(let i = 0; i < cells.length; i++) {
|
|
|
- let styleID = this.addStyle(cells[i].styleNames);
|
|
|
- let ctx = {
|
|
|
- attributeStyleID: cells[i].styleNames && cells[i].styleNames.length > 0 ? _format(' ss:StyleID="{styleID}"', {
|
|
|
- styleID: styleID
|
|
|
- }) : '',
|
|
|
- attributeFormula: '',
|
|
|
- nameType: cells[i].type || 'String',
|
|
|
- data: cells[i].value || ''
|
|
|
- };
|
|
|
- str += _format(tmplCellXML, ctx);
|
|
|
- }
|
|
|
- return str;
|
|
|
+ Exportor.prototype._generateWorkbookBlob = function() {
|
|
|
+ var me = this;
|
|
|
+ return this._getWorkbook()
|
|
|
+ .then(function(workbook) {
|
|
|
+ return me._fullWorkbook(workbook, me._json);
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
- Exportor.prototype.addStyle = function(styleNames) {
|
|
|
- if(!styleNames || styleNames.length === 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
- let s1 = styleNames.sort();
|
|
|
- let idx = -1; // 是否已存在
|
|
|
- outer:
|
|
|
- for(let i = 0; i < this.styles.length; i++) {
|
|
|
- let style = this.styles[i];
|
|
|
- let s2 = style.styleNames.sort();
|
|
|
- if(s1.length !== s2.length) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- for(let j = 0; j < s1.length; j++) {
|
|
|
- if(s1[j] !== s2[j]) {
|
|
|
- continue outer;
|
|
|
- }
|
|
|
+ Exportor.prototype._fullWorkbook = function(workbook, json) {
|
|
|
+ var index = 0;
|
|
|
+ do{
|
|
|
+ var sheetData = json.sheets[index],
|
|
|
+ sheetName = sheetData.name,
|
|
|
+ header = sheetData.header,
|
|
|
+ columns = sheetData.columns,
|
|
|
+ clen = columns.length,
|
|
|
+ rows = sheetData.rows,
|
|
|
+ sheet,
|
|
|
+ cell = new Cell('A1'); // 当前第一列
|
|
|
+
|
|
|
+ if(workbook.sheets().length - 1 < index) {
|
|
|
+ sheet = workbook.addSheet(sheetName)
|
|
|
+ }else {
|
|
|
+ sheet = workbook.sheet(index).name(sheetName);
|
|
|
}
|
|
|
- idx = i;
|
|
|
- }
|
|
|
- if(idx === -1) {
|
|
|
- let styleID = 's' + this.styleID++
|
|
|
- this.styles.push({
|
|
|
- styleID: styleID,
|
|
|
- styleNames: styleNames
|
|
|
- });
|
|
|
- return styleID;
|
|
|
- }else {
|
|
|
- return this.styles[idx].styleID;
|
|
|
- }
|
|
|
- };
|
|
|
|
|
|
- Exportor.prototype.generateStyles = function(styleID, styleNames) {
|
|
|
- let configs = styleNames.map(name => styleConfig.find(s => s.name === name)).filter(c => !!c);
|
|
|
- let arr = [];
|
|
|
- let str = '';
|
|
|
- for(let i = 0; i < configs.length; i++) {
|
|
|
- configs[i].tags.forEach(config => {
|
|
|
- arr.push(this.generateStyleNode(config));
|
|
|
- });
|
|
|
- }
|
|
|
- str = _format(tmplStyleXML, {
|
|
|
- styleID: styleID,
|
|
|
- styleNode: arr.join('')
|
|
|
- });
|
|
|
- return str;
|
|
|
- };
|
|
|
+ if(header) {
|
|
|
+ let startCell = cell,
|
|
|
+ endCell = new Cell('A1').addCol(clen - 1);
|
|
|
+ let r = sheet.range(startCell + ":" + endCell).merged(true);
|
|
|
+ r.value(header);
|
|
|
+ r.style({ fill: '70AD47', fontColor: 'ffffff', bold: true, horizontalAlignment: 'center', bottomBorder: true, bottomBorderColor: 'EAEAEA' });
|
|
|
+ cell.addRow(1);
|
|
|
+ }
|
|
|
|
|
|
- Exportor.prototype.generateStyleNode = function(styleConfig) {
|
|
|
- let str = '';
|
|
|
- let attributes = [''];
|
|
|
- let children = [];
|
|
|
- let ctx = {};
|
|
|
- if(!!styleConfig.tags) {
|
|
|
- for(let i = 0; i < styleConfig.tags.length; i++) {
|
|
|
- let c = styleConfig.tags[i];
|
|
|
- children.push(this.generateStyleNode(c));
|
|
|
+ if(clen > 0) {
|
|
|
+ let columnDatas = [columns.map(function(c) {
|
|
|
+ return c.name;
|
|
|
+ })];
|
|
|
+ let r = sheet.range(cell + ":" + new Cell(cell).addCol(clen - 1));
|
|
|
+ r.value(columnDatas);
|
|
|
+ let sheet_columns = r.cells()[0].map(function(c) {
|
|
|
+ return c.column();
|
|
|
+ });
|
|
|
+ columns.forEach(function(c, i) {
|
|
|
+ if(sheet_columns[i]) {
|
|
|
+ sheet_columns[i].width(c.width);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ r.style({ fill: '70AD47', fontColor: 'ffffff', bold: true, horizontalAlignment: 'center', rightBorder: true, rightBorderColor: 'EAEAEA' });
|
|
|
+ cell.addRow(1);
|
|
|
+ }
|
|
|
+ if(rows.length > 0) {
|
|
|
+ let r = sheet.range(cell + ":" + new Cell(cell).addRow(rows.length - 1).addCol(clen - 1));
|
|
|
+ r.value(rows);
|
|
|
+ r.style('fill', function(cell, ri, ci, range) {
|
|
|
+ return (ri&1) === 0 ? 'C6E0B4' : 'A9D08E';
|
|
|
+ })
|
|
|
+ .style({ rightBorder: true, rightBorderColor: 'EAEAEA' });
|
|
|
}
|
|
|
- }
|
|
|
- styleConfig.attributes = styleConfig.attributes || [];
|
|
|
- for(let k in styleConfig.attributes) {
|
|
|
- attributes.push(_format(tmplStyleNodeAttributeXML, {
|
|
|
- attributeName: k,
|
|
|
- attributeValue: styleConfig.attributes[k]
|
|
|
- }));
|
|
|
- }
|
|
|
- ctx.nodeName = styleConfig.tag;
|
|
|
- ctx.attributes = attributes.join(' ');
|
|
|
- ctx.children = children.join('');
|
|
|
- str = _format(tmplStyleNodeXML, ctx);
|
|
|
- return str;
|
|
|
- };
|
|
|
|
|
|
- Exportor.prototype.generateStyleNodeAttribute = function(styleNodeConfig) {
|
|
|
- let str = '';
|
|
|
- for(let k in styleNodeConfig) {
|
|
|
- str += _format(tmplStyleNodeAttributeXML, {
|
|
|
- key: k,
|
|
|
- value: styleNodeConfig[k]
|
|
|
- })
|
|
|
- }
|
|
|
- return str;
|
|
|
+ index++;
|
|
|
+ }while(!!json.sheets[index]);
|
|
|
+ console.log(workbook);
|
|
|
+
|
|
|
+ return workbook.outputAsync();
|
|
|
};
|
|
|
|
|
|
Exportor.prototype.export = function() {
|
|
|
- _export(this.workbookXML, this.wbname);
|
|
|
+ if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
|
|
+ window.navigator.msSaveOrOpenBlob(this._blob, this._filename + ".xlsx");
|
|
|
+ } else {
|
|
|
+ var url = window.URL.createObjectURL(this._blob);
|
|
|
+ var a = document.createElement("a");
|
|
|
+ document.body.appendChild(a);
|
|
|
+ a.href = url;
|
|
|
+ a.download = this._filename + ".xlsx";
|
|
|
+ a.click();
|
|
|
+ window.URL.revokeObjectURL(url);
|
|
|
+ document.body.removeChild(a);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
return Exportor;
|
|
|
-
|
|
|
})();
|