Переглянути джерело

报表导出功能重新设计,改为前台导出

zhuth 6 роки тому
батько
коміт
37dff67078

+ 2 - 0
src/components/common/echarts/resolveChartThumbnailOption.js

@@ -16,6 +16,8 @@ export default (option) => {
     _option.animation = false;
     // 悬浮提示
     _option.tooltip = undefined;
+    // 工具箱
+    _option.toolbox = undefined;
     // 图例
     _option.legend = undefined;
     // 容器

+ 181 - 16
src/models/dashboardDesigner.js

@@ -6,6 +6,7 @@ import moment from 'moment'
 import URLS from '../constants/url'
 import CHART_TYPE from './chartType.json'
 import { ArrayEquals } from '../utils/baseUtils.js'
+import Exportor from '../utils/exportor'
 
 /**
  * 获得报表中图表的真实过滤规则
@@ -711,29 +712,193 @@ export default {
                 yield put({ type: 'dataList/setField', name: 'loading', value: false });
             }
         },
+        // *exportToExcel(action, { select, call, put }) {
+        //     const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
+        //     const { code, name, dataSources, filters } = dashboardDesigner;
+        //     try {
+        //         let body = {
+        //             dashboardId: code,
+        //             dashboardName: name,
+        //             data: dataSources.map(d => {
+        //                 let { code } = d;
+        //                 return {
+        //                     dataSourceId: code,
+        //                     filter: getBodyFilters(getTrueFilters({ dataSourceCode: code }, filters))
+        //                 }
+        //             }),
+        //         }
+        //         yield call(service.fetch, {
+        //             url: URLS.DASHBOARD_EXPORT,
+        //             requestType: 'file',
+        //             fileName: name,
+        //             body,
+        //         })
+        //     }catch(e) {
+        //         message.error('报表导出错误: ' + e);
+        //     }
+        // },
         *exportToExcel(action, { select, call, put }) {
             const dashboardDesigner = yield select(state => state.present.dashboardDesigner);
-            const { code, name, dataSources, filters } = dashboardDesigner;
+            const { name, creatorCode, filters, items } = dashboardDesigner;
             try {
-                let body = {
-                    dashboardId: code,
-                    dashboardName: name,
-                    data: dataSources.map(d => {
-                        let { code } = d;
-                        return {
-                            dataSourceId: code,
-                            filter: getBodyFilters(getTrueFilters({ dataSourceCode: code }, filters))
+                yield put({ type: 'dashboardDesigner/setField', name: 'loading', value: true });
+
+                let sheets = [];
+                for(let i = 0; i < items.length; i++) {
+                    let item = items[i];
+                    let { chartCode, name: itemName, chartOption, viewType, chartType, content } = item;
+                    let option = chartOption ? (chartOption.baseOption || {}) : {};
+                    let { originConfig, xAxis, series } = option;
+                    let { xAxis: oxAxis, yAxis: oyAxis, groupBy: ogroupBy } = (originConfig || {});
+                    let header;
+                    let columns = [];
+                    let rows = [];
+                    const TYPES = {
+                        time: 'DateTime',
+                        categorical: 'String',
+                        scale: 'Number',
+                        string: 'String'
+                    };
+                    if(viewType === 'chart') {
+                        if(chartType === 'bar' || chartType === 'line') {
+                            columns = [{
+                                name: oxAxis.column.label,
+                                width: 150,
+                                type: 'String'
+                            }, {
+                                name: oyAxis.column.label,
+                                width: 100,
+                                type: 'Number'
+                            }];
+                            if(ogroupBy && ogroupBy.key) {
+                                columns.push({
+                                    name: ogroupBy.label,
+                                    width: 150,
+                                    type: 'String'
+                                });
+                            }
+                            xAxis[0].data.forEach((d, i) => {
+                                if(ogroupBy && ogroupBy.key) {
+                                    series.forEach(xs => {
+                                        rows.push([d, xs.data[i], xs.name]);
+                                    })
+                                }else {
+                                    rows.push([d, series[0].data[i]])
+                                }
+                            })
+                        }else if(chartType === 'pie') {
+                            columns = [{
+                                name: oxAxis.column.label,
+                                width: 150,
+                                type: 'String'
+                            }, {
+                                name: oyAxis.column.label,
+                                width: 100,
+                                type: 'Number'
+                            }];
+                            rows = series[0].data.map(d => ([d.name, d.value]))
+                        }else if(chartType === 'scatter') {
+                            columns = [{
+                                name: oxAxis.column.label,
+                                width: 80,
+                                type: 'Number'
+                            }, {
+                                name: oyAxis.column.label,
+                                width: 80,
+                                type: 'Number'
+                            }];
+                            if(ogroupBy && ogroupBy.key) {
+                                columns.push({
+                                    name: ogroupBy.label,
+                                    width: 100,
+                                    type: 'String'
+                                });
+                            }
+                            series.forEach(xs => {
+                                xs.data.forEach(xd => {
+                                    if(ogroupBy && ogroupBy.key) {
+                                        rows.push([xd[0], xd[1], xs.name]);
+                                    }else {
+                                        rows.push([xd[0], xd[1]]);
+                                    }
+                                });
+                            })
+                        }else if(chartType === 'dataView') {
+                            let { viewColumns } = chartOption.originConfig || {};
+                            columns = viewColumns.map(c => ({
+                                name: c.label,
+                                width: 100,
+                                type: TYPES[c.type]
+                            }));
+                            const res = yield call(service.fetch, {
+                                url: URLS.CHART_OPTION,
+                                allow: true,
+                                body: {
+                                    dashboardCreatorId: creatorCode,
+                                    chartId: chartCode,
+                                    filters: getBodyFilters(getTrueFilters(item, filters)),
+                                    testPage: {
+                                        pageNum: 1,
+                                        pageSize: 99999, 
+                                    } 
+                                },
+                                timeout: 30000
+                            });
+                            if(res.code > 0) {
+                                res.data.valueList.list.forEach(l => {
+                                    let r = [];
+                                    viewColumns.forEach(c => {
+                                        r.push(l[c.name]);
+                                    });
+                                    rows.push(r);
+                                })
+                            }else {
+                                console.error(res.msg);
+                            }
+                        }else if(chartType === 'aggregateTable') {
+                            let { originConfig, data, group1Name, group2Name, group1s, group2s } = chartOption;
+                            let { targetColumn, groupBy, statistics } = (originConfig || {});
+                            header = `分析目标:${targetColumn.label}`
+                            columns = groupBy.map(g => ({ name: g.label, width: 100, type: TYPES[g.type] })).concat(statistics.map(s => ({ name: s.label, width: 100, type: 'Number' })));
+
+                            if(!group1Name) { // 无分组
+                                rows = [statistics.map(s => data[s.name])];
+                            }else {
+                                if(!group2Name) { // 只有一个分组
+                                    rows = group1s.map((g, i) => [g].concat(statistics.map(s => data[i][s.name])));
+                                }else { // 有两个分组
+                                    group1s.forEach((g1, i1) => {
+                                        let d = data[i1];
+                                        group2s.forEach((g2, i2) => {
+                                            let arr = statistics.map(s => d.data[i2][s.name]);
+                                            rows.push([g1, g2].concat(arr));
+                                        })
+                                    });
+                                }
+                            }
                         }
-                    }),
+                    }else if(viewType === 'richText') {
+                        columns = [{
+                            name: content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/\'/g, '&apos;'),
+                            width: 500,
+                            type: 'String'
+                        }];
+                        rows = [];
+                    }
+                    sheets.push({
+                        name: itemName || new Date().getTime(),
+                        header,
+                        columns,
+                        rows
+                    });
                 }
-                yield call(service.fetch, {
-                    url: URLS.DASHBOARD_EXPORT,
-                    requestType: 'file',
-                    fileName: name,
-                    body,
-                })
+                console.log(sheets)
+                let e = new Exportor({ sheets }, `${name}.xls`);
+                e.export();
             }catch(e) {
                 message.error('报表导出错误: ' + e);
+            }finally {
+                yield put({ type: 'dashboardDesigner/setField', name: 'loading', value: false });
             }
         }
     },

+ 158 - 0
src/utils/base64.js

@@ -0,0 +1,158 @@
+/**
+*
+*  Base64 encode / decode
+*  http://www.webtoolkit.info
+*
+**/
+var Base64 = {
+
+    // private property
+    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
+
+    // public method for encoding
+    , encode: function (input)
+    {
+        var output = "";
+        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+        var i = 0;
+
+        input = Base64._utf8_encode(input);
+
+        while (i < input.length)
+        {
+            chr1 = input.charCodeAt(i++);
+            chr2 = input.charCodeAt(i++);
+            chr3 = input.charCodeAt(i++);
+
+            enc1 = chr1 >> 2;
+            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+            enc4 = chr3 & 63;
+
+            if (isNaN(chr2))
+            {
+                enc3 = enc4 = 64;
+            }
+            else if (isNaN(chr3))
+            {
+                enc4 = 64;
+            }
+
+            output = output +
+                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+        } // Whend 
+
+        return output;
+    } // End Function encode 
+
+
+    // public method for decoding
+    ,decode: function (input)
+    {
+        var output = "";
+        var chr1, chr2, chr3;
+        var enc1, enc2, enc3, enc4;
+        var i = 0;
+
+        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+        while (i < input.length)
+        {
+            enc1 = this._keyStr.indexOf(input.charAt(i++));
+            enc2 = this._keyStr.indexOf(input.charAt(i++));
+            enc3 = this._keyStr.indexOf(input.charAt(i++));
+            enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+            chr1 = (enc1 << 2) | (enc2 >> 4);
+            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+            chr3 = ((enc3 & 3) << 6) | enc4;
+
+            output = output + String.fromCharCode(chr1);
+
+            if (enc3 !== 64)
+            {
+                output = output + String.fromCharCode(chr2);
+            }
+
+            if (enc4 !== 64)
+            {
+                output = output + String.fromCharCode(chr3);
+            }
+
+        } // Whend 
+
+        output = Base64._utf8_decode(output);
+
+        return output;
+    } // End Function decode 
+
+
+    // private method for UTF-8 encoding
+    ,_utf8_encode: function (string)
+    {
+        var utftext = "";
+        string = string.replace(/\r\n/g, "\n");
+
+        for (var n = 0; n < string.length; n++)
+        {
+            var c = string.charCodeAt(n);
+
+            if (c < 128)
+            {
+                utftext += String.fromCharCode(c);
+            }
+            else if ((c > 127) && (c < 2048))
+            {
+                utftext += String.fromCharCode((c >> 6) | 192);
+                utftext += String.fromCharCode((c & 63) | 128);
+            }
+            else
+            {
+                utftext += String.fromCharCode((c >> 12) | 224);
+                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+                utftext += String.fromCharCode((c & 63) | 128);
+            }
+
+        } // Next n 
+
+        return utftext;
+    } // End Function _utf8_encode 
+
+    // private method for UTF-8 decoding
+    ,_utf8_decode: function (utftext)
+    {
+        var string = "";
+        var i = 0;
+        var c, c2, c3;
+        c = c2 = 0;
+
+        while (i < utftext.length)
+        {
+            c = utftext.charCodeAt(i);
+
+            if (c < 128)
+            {
+                string += String.fromCharCode(c);
+                i++;
+            }
+            else if ((c > 191) && (c < 224))
+            {
+                c2 = utftext.charCodeAt(i + 1);
+                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+                i += 2;
+            }
+            else
+            {
+                c2 = utftext.charCodeAt(i + 1);
+                c3 = utftext.charCodeAt(i + 2);
+                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+                i += 3;
+            }
+
+        } // Whend 
+
+        return string;
+    } // End Function _utf8_decode 
+
+}
+exports = module.exports = Base64;

+ 158 - 0
src/utils/exportor.js

@@ -0,0 +1,158 @@
+/**
+ * Created by zhuth on 2019/07/08.
+ */
+let Base64 = require('./base64.js');
+;exports = module.exports = (function () {
+    // "use strict";
+    let uri = 'data:application/vnd.ms-excel;base64,',
+    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}'
+            + '{worksheets}'
+        + '</Workbook>',
+    tmpStyleXML = '<Styles>'
+        + '<Style ss:ID="Bold"><Font ss:Bold="1"/></Style>'
+        + '<Style ss:ID="Currency"><NumberFormat ss:Format="Currency"></NumberFormat></Style>'
+        + '<Style ss:ID="Date"><NumberFormat ss:Format="Medium Date"></NumberFormat></Style>'
+        + '<Style ss:ID="Center"><Alignment ss:Horizontal="Center" ss:Vertical="Center"/></Style>'
+    + '</Styles>',
+    tmplWorksheetXML = '<Worksheet ss:Name="{nameWS}"><Table>{columnWidth}{rows}</Table></Worksheet>',
+    tmplColumnWidthXML = '<Column ss:Width="{width}"/>',
+    tmplTableHeaderXML = '<Row><Cell ss:StyleID="Center" 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>',
+    _format = function(s, c) { return s.replace(/{(\w+)}/g, function(m, p) { return c[p]; }) },
+    _export = function(workbookXML, wbname) {
+        let link = document.createElement("A");
+        link.href = uri + Base64.encode(workbookXML);
+        link.download = wbname || 'Workbook.xls';
+        link.target = '_blank';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+ 
+    };
+
+    function Exportor(json, wbname) {
+        this.init(json, wbname);
+        this.workbookXML = this.generateWorkbookXML();
+    }
+
+    Exportor.prototype.init = function (json, wbname) {
+        this.json = json;
+        this.wbname = wbname;
+        this.workbookXML = '';
+    };
+
+    Exportor.prototype.generateWorkbookXML = function() {
+        let ctx = {
+            worksheets: this.generateWorksheetsXML(),
+            styles: tmpStyleXML,
+            author: 'zhuth',
+            created: 'zhuth'
+        };
+        return _format(tmplWorkbookXML, ctx);
+    };
+
+    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;
+    };
+
+    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;
+    };
+
+    Exportor.prototype.generateTableHeaderXML = function(header, columnCount) {
+        let str = '';
+        if(!!header) {
+            let ctx = {
+                columnCount: columnCount - 1,
+                header
+            }
+            str = _format(tmplTableHeaderXML, ctx);
+        }
+        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;
+    };
+
+    Exportor.prototype.generateHeaderRowsXML = function(columns) {
+        let cells = columns.map(c => ({ type: 'String', value: c.name, styleID: 'Bold' }));
+        let ctx = {
+            cells: this.generateCellsXML(cells)
+        };
+        let str = _format(tmplRowXML, ctx);
+        return str;
+    };
+
+    Exportor.prototype.generateRowsXML = function(columns, rows) {
+        let cols = columns.map(c => ({ type: c.type, name: c.name }));
+        let str = '';
+        for(let i = 0; i < rows.length; i++) {
+            let r = rows[i];
+            let cells = [];
+            for(let j = 0; j < cols.length; j++) {
+                cells.push({
+                    type: cols[j].type,
+                    value: r[j]
+                });
+            }
+            let ctx = {
+                cells: this.generateCellsXML(cells)
+            };
+            str += _format(tmplRowXML, ctx);
+        }
+        return str;
+    };
+
+    Exportor.prototype.generateCellsXML = function(cells) {
+        let str = '';
+        for(let i = 0; i < cells.length; i++) {
+            let ctx = {
+                attributeStyleID: cells[i].styleID ? _format(' ss:StyleID="{styleID}"', {
+                    styleID: cells[i].styleID
+                }) : '',
+                attributeFormula: '',
+                nameType: cells[i].type || 'String',
+                data: cells[i].value || ''
+            };
+            str += _format(tmplCellXML, ctx);
+        }
+        return str;
+    };
+
+    Exportor.prototype.export = function() {
+        _export(this.workbookXML, this.wbname);
+    }
+
+    return Exportor;
+
+})();