Bläddra i källkod

导出文件不能在office中打开,重写导出工具。

zhuth 6 år sedan
förälder
incheckning
8122d7a18f
4 ändrade filer med 141 tillägg och 345 borttagningar
  1. 2 1
      package.json
  2. 8 6
      src/models/chartDesigner.js
  3. 18 6
      src/models/dashboardDesigner.js
  4. 113 332
      src/utils/exportor.js

+ 2 - 1
package.json

@@ -31,7 +31,8 @@
     "react-router-dom": "^4.3.1",
     "redux": "^4.0.0",
     "redux-undo": "^0.6.1",
-    "wangeditor": "^3.1.1"
+    "wangeditor": "^3.1.1",
+    "xlsx-populate": "^1.20.1"
   },
   "devDependencies": {
     "babel-plugin-dva-hmr": "^0.3.2",

+ 8 - 6
src/models/chartDesigner.js

@@ -985,7 +985,7 @@ export default {
                                 series.forEach(xs => {
                                     dataSource.push({
                                         xAxis: d,
-                                        yAxis: xs.data[i],
+                                        yAxis: +xs.data[i],
                                         groupBy: xs.name
                                     });
                                 })
@@ -1014,7 +1014,7 @@ export default {
                         }];
                         dataSource = series[0].data.map(d => ({
                             xAxis: d.name,
-                            yAxis: d.value
+                            yAxis: +d.value
                         }))
                         break;
                     }
@@ -1088,6 +1088,8 @@ export default {
                                     let v = l[c.name];
                                     if(c.type === 'time') {
                                         v = moment(v).isValid() ? moment(v).format('YYYY-MM-DD'): v;
+                                    }else if(c.type === 'scale') {
+                                        v = +v;
                                     }
                                     r[c.name] = v;
                                 });
@@ -1118,7 +1120,7 @@ export default {
                         if(!group1Name) { // 无分组
                             let obj = {};
                             statistics.forEach(s => {
-                                obj[s.name] = data[s.name];
+                                obj[s.name] = +data[s.name];
                             });
                             dataSource.push(obj);
                         }else {
@@ -1127,7 +1129,7 @@ export default {
                                     let obj = {};
                                     obj['groupBy1'] = g;
                                     statistics.forEach(s => {
-                                        obj[s.name] = data[i][s.name];
+                                        obj[s.name] = +data[i][s.name];
                                     });
                                     return obj;
                                 });
@@ -1139,7 +1141,7 @@ export default {
                                         obj['groupBy1'] = g1;
                                         obj['groupBy2'] = g2;
                                         statistics.forEach(s => {
-                                            obj[s.name] = d.data[i2][s.name];
+                                            obj[s.name] = +d.data[i2][s.name];
                                         })
                                         dataSource.push(obj);
                                     })
@@ -1167,7 +1169,7 @@ export default {
                         dataSource = data.map(d => {
                             let obj = {};
                             obj['xAxis'] = d.name;
-                            obj['yAxis'] = d.value;
+                            obj['yAxis'] = +d.value;
                             d.others.forEach((o, i) => {
                                 obj[`other${i}`] = o.value;
                             })

+ 18 - 6
src/models/dashboardDesigner.js

@@ -729,7 +729,7 @@ export default {
                             let { columns: tcolumns, dataSource: tdatasource } = tableData;
                             columns = tcolumns.map(c => ({
                                 name: c.title,
-                                width: 120,
+                                width: c.width || 25,
                                 type: TYPES[c.type]
                             }));
                             rows = tdatasource.map(d => {
@@ -743,23 +743,35 @@ export default {
                         content = content || '';
                         columns = [{
                             name: content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/\'/g, '&apos;'),
-                            width: 500,
+                            width: 25,
                             type: 'String'
                         }];
                         rows = [];
                     }
                     // 处理重名情况
                     let name = itemName || `未命名`;
-                    let arr = sheets.filter(s => s.name === name);
+                    let new_name = name;
+                    let arr = sheets.filter(s => s.name === new_name);
+                    let idx = 1;
+                    while(arr.length > 0){
+                        new_name = `${name}(${idx})`;
+                        arr = sheets.filter(s => s.name === new_name);
+                        idx++;
+                    }
+
+                    name = new_name;
+                    
                     sheets.push({
-                        name: arr.length > 0 ? `${name}(${arr.length})` : name,
+                        name,
                         header,
                         columns,
                         rows
                     });
                 }
-                let e = yield new Exportor({ sheets }, `${name}.xls`);
-                yield e.export();
+                let e = yield new Exportor();
+                e.init({ sheets }, name).then(() => {
+                    e.export()
+                });
             }catch(e) {
                 message.error('报表导出错误: ' + e);
             }finally {

+ 113 - 332
src/utils/exportor.js

@@ -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;
-
 })();