Vue項目中導(dǎo)出Excel表格并帶上圖表
1.需求場景:
我們在開發(fā)過程中會遇到一些需求就是將表格中的數(shù)據(jù)導(dǎo)出到excel文件中并下載至本地,但目前基本上下載的excel文件只有簡單的表格數(shù)據(jù),沒有相關(guān)的圖表(如餅圖,柱狀圖等),當(dāng)我們excel文件中帶上了這些圖表,可以幫助用戶更加簡潔明了地看到數(shù)據(jù)間的關(guān)系;
2.使用工具:
xslx-chart插件,由于xslx-chart是一個node環(huán)境下的一個插件所以我們可以使用兩種方法來使用它,下文就如何使用xslx-cahrt進行介紹(著重介紹方法二)
3.開始使用:
方法一:基于node環(huán)境使用xslx-chart工具;
注意:由于xslx-chart是在nodejs環(huán)境下運行的,所以我們要在項目本地跑一個node服務(wù)來使用xslx-cahrt,
思路:
1.我們在運行項目的同時,在本地跑一個nodejs服務(wù)(xlsx-chart-serve),在該nodejs服務(wù)中處理表格的操作,處理完之后再將文件發(fā)送到前端,前端再進行文件下載;
如圖:我們跑項目時直接執(zhí)行
npm run dev
就會同時執(zhí)行serve和xlsx;xlsx作用就是將導(dǎo)出表格的服務(wù)跑起來;
注意:
同時執(zhí)行多個指令需要concurrently這個工具,使用 && 連接兩個指令是串行的,會中斷執(zhí)行命令
2.我們服務(wù)跑起來之后,前端需要導(dǎo)出表格時,將數(shù)據(jù)發(fā)送給nodejs服務(wù),交由xlsx這個服務(wù)處理,處理完之后將文件發(fā)給前端
注意:
由于node服務(wù)和我們項目自身的端口不一致,所以會導(dǎo)致跨域的問題,這個需要注意
方法二(推薦):基于xlsx-chart進行二次開發(fā),直接在前端中使用
思路:
我們看xlsx-chart工具的源碼不難發(fā)現(xiàn),它生成的圖表是在本地讀取模板文件然后再拿最新的數(shù)據(jù)去替換模板表格中的數(shù)據(jù)生成的新表格和新圖表,它在node環(huán)境中使用的是fs工具讀取文件,那我們可以在前端使用axios請求本地文件,只要讀取文件內(nèi)容和類型一致就可以了
重點: axios請求中響應(yīng)數(shù)據(jù)類型需要配置;將responseType設(shè)置為’arraybuffer’
然后生成的最新文件使用file-saver下載下來即可
修改后的源碼文件如下:
第一步:準備源碼:
base.js
var _
= require("underscore");
var Backbone
= require("backbone");
var JSZip
= require("jszip");
var xml2js
= require("xml2js");
var VError
= require("verror");
var axios
= require('axios');
var async
= require("async");
var Chart
= Backbone
.Model
.extend({read: function (opts, cb) {var me
= this;var t
= me
.zip
.file(opts
.file
).asText();var parser
= new xml2js.Parser({ explicitArray: false });parser
.parseString(t
, function (err, o) {if (err
) {return new VError(err
, "getXML");}cb(err
, o
);});},write: function (opts) {var me
= this;var builder
= new xml2js.Builder();var xml
= builder
.buildObject(opts
.object
);me
.zip
.file(opts
.file
, new Buffer(xml
), { base64: true });},getColName: function (n) {var abc
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";n
--;if (n
< 26) {return abc
[n
];} else {return abc
[(n
/ 26 - 1) | 0] + abc
[n
% 26];}},getStr: function (s) {var me
= this;if (!me
.str
.hasOwnProperty(s
)) {throw new VError("getStr: Unknown string: " + s
);}return me
.str
[s
];},writeTable: function (cb) {var me
= this;me
.read({ file: "xl/worksheets/sheet2.xml" }, function (err, o) {if (err
) {return cb(new VError(err
, "writeTable"));}o
.worksheet
.dimension
.$
.ref
= "A1:" + me
.getColName(me
.titles
.length
+ 1) + (me
.fields
.length
+ 1);var rows
= [{$: {r: 1,spans: "1:" + (me
.titles
.length
+ 1)},c: _
.map(me
.titles
, function (t, x) {return {$: {r: me
.getColName(x
+ 2) + 1,t: "s"},v: me
.getStr(t
)}})}];_
.each(me
.fields
, function (f, y) {var r
= {$: {r: y
+ 2,spans: "1:" + (me
.titles
.length
+ 1)}};var c
= [{$: {r: "A" + (y
+ 2),t: "s"},v: me
.getStr(f
)}];_
.each(me
.titles
, function (t, x) {c
.push({$: {r: me
.getColName(x
+ 2) + (y
+ 2)},v: me
.data
[t
][f
]});});r
.c
= c
;rows
.push(r
);});o
.worksheet
.sheetData
.row
= rows
;console
.log('o',o
,rows
)me
.write({ file: "xl/worksheets/sheet2.xml", object: o
});cb();});},writeStrings: function (cb) {var me
= this;me
.read({ file: "xl/sharedStrings.xml" }, function (err, o) {if (err
) {return cb(new VError(err
, "writeStrings"));}o
.sst
.$
.count
= me
.titles
.length
+ me
.fields
.length
;o
.sst
.$
.uniqueCount
= o
.sst
.$
.count
;var si
= [];_
.each(me
.titles
, function (t) {si
.push({ t: t
});});_
.each(me
.fields
, function (t) {si
.push({ t: t
});});me
.str
= {};_
.each(si
, function (o, i) {me
.str
[o
.t
] = i
;});o
.sst
.si
= si
;me
.write({ file: "xl/sharedStrings.xml", object: o
});cb();});},removeUnusedCharts: function (o) {var me
= this;if (me
.tplName
!= "charts") {return;};var axId
= [];function addId (o) {_
.each(o
["c:axId"], function (o) {axId
.push(o
.$
.val
);});};_
.each(["line", "radar", "area", "scatter", "pie"], function (chart) {if (!me
.charts
[chart
]) {delete o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart
+ "Chart"];} else {addId(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart
+ "Chart"]);};});if (!me
.charts
["column"] && !me
.charts
["bar"]) {delete o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"];} elseif (me
.charts
["column"] && !me
.charts
["bar"]) {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0];addId(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]);} elseif (!me
.charts
["column"] && me
.charts
["bar"]) {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"] = o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1];addId(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]);} else {addId(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]);addId(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]);};var catAx
= [];_
.each(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"], function (o) {if (axId
.indexOf(o
["c:axId"].$
.val
) > -1) {catAx
.push(o
);};});o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:catAx"] = catAx
;var valAx
= [];_
.each(o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"], function (o) {if (axId
.indexOf(o
["c:axId"].$
.val
) > -1) {valAx
.push(o
);};});o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:valAx"] = valAx
;},writeChart: function (cb) {var me
= this;var chart
;me
.read({ file: "xl/charts/chart1.xml" }, function (err, o) {if (err
) {return cb(new VError(err
, "writeChart"));}var ser
= {};_
.each(me
.titles
, function (t, i) {var chart
= me
.data
[t
].chart
|| me
.chart
;var r
= {"c:idx": {$: {val: i
}},"c:order": {$: {val: i
}},"c:tx": {"c:strRef": {"c:f": "Table!$" + me
.getColName(i
+ 2) + "$1","c:strCache": {"c:ptCount": {$: {val: 1}},"c:pt": {$: {idx: 0},"c:v": t
}}}},"c:cat": {"c:strRef": {"c:f": "Table!$A$2:$A$" + (me
.fields
.length
+ 1),"c:strCache": {"c:ptCount": {$: {val: me
.fields
.length
}},"c:pt": _
.map(me
.fields
, function (f, j) {return {$: {idx: j
},"c:v": f
};})}}},"c:val": {"c:numRef": {"c:f": "Table!$" + me
.getColName(i
+ 2) + "$2:$" + me
.getColName(i
+ 2) + "$" + (me
.fields
.length
+ 1),"c:numCache": {"c:formatCode": "General","c:ptCount": {$: {val: me
.fields
.length
}},"c:pt": _
.map(me
.fields
, function (f, j) {return {$: {idx: j
},"c:v": me
.data
[t
][f
]};})}}}};if (chart
== "scatter") {r
["c:xVal"] = r
["c:cat"];delete r
["c:cat"];r
["c:yVal"] = r
["c:val"];delete r
["c:val"];r
["c:spPr"] = {"a:ln": {$: {w: 28575},"a:noFill": ""}};};ser
[chart
] = ser
[chart
] || [];ser
[chart
].push(r
);});_
.each(ser
, function (ser, chart) {if (chart
== "column") {if (me
.tplName
== "charts") {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][0]["c:ser"] = ser
;} else {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser
;};} elseif (chart
== "bar") {if (me
.tplName
== "charts") {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"][1]["c:ser"] = ser
;} else {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:barChart"]["c:ser"] = ser
;};} else {o
["c:chartSpace"]["c:chart"]["c:plotArea"]["c:" + chart
+ "Chart"]["c:ser"] = ser
;};});me
.removeUnusedCharts(o
);if (me
.chartTitle
) {me
.writeTitle(o
, me
.chartTitle
);};me
.write({ file: "xl/charts/chart1.xml", object: o
});cb();});},writeTitle: function (chart, title) {var me
= this;chart
["c:chartSpace"]["c:chart"]["c:title"] = {"c:tx": {"c:rich": {"a:bodyPr": {},"a:lstStyle": {},"a:p": {"a:pPr": {"a:defRPr": {}},"a:r": {"a:rPr": {$: {lang: "ru-RU"}},"a:t": title
}}}},"c:layout": {},"c:overlay": {$: {val: "0"}}};chart
["c:chartSpace"]["c:chart"]["c:autoTitleDeleted"] = {$: {val: "0"}};},setTemplateName: function () {var me
= this;var charts
= {};_
.each(me
.data
, function (o) {charts
[o
.chart
|| me
.chart
] = true;});me
.charts
= charts
;if (charts
["radar"]) {me
.tplName
= "radar";return;};if (charts
["scatter"]) {me
.tplName
= "scatter";return;};if (charts
["pie"]) {me
.tplName
= "pie";return;};if (_
.keys(charts
).length
== 1) {me
.tplName
= _
.keys(charts
)[0];return;};me
.tplName
= "charts";},generate: function (opts, cb) {var me
= this;opts
.type
= opts
.type
|| "nodebuffer";_
.extend(me
, opts
);async
.series([function (cb) {me
.zip
= new JSZip();me
.setTemplateName();let path
= me
.templatePath
? me
.templatePath
: (__dirname
+ "template/" + me
.tplName
+ ".xlsx");console
.log("path:", path
);axios({url: path
, method: 'GET',responseType: 'arraybuffer', }).then((response) => {console
.log('response',response
)me
.zip
.load(response
.data
);cb();});},function (cb) {me
.writeStrings(cb
);},function (cb) {_
.each(me
.titles
, function (t) {me
.data
[t
] = me
.data
[t
] || {};_
.each(me
.fields
, function (f) {me
.data
[t
][f
] = me
.data
[t
][f
] || 0;});});me
.writeTable(cb
);},function (cb) {me
.writeChart(cb
);}], function (err) {if (err
) {return cb(new VError(err
, "build"));}var result
= me
.zip
.generate({ type: me
.type
});cb(null, result
);});}
});
module
.exports
= Chart
;
注意事項:
由于我們發(fā)送axios請求修改了responseType:‘a(chǎn)rraybuffer’,所以我們需要保證此項配置成功,如果項目中使用了mock并引用了就要注意了,因為mock會將全局的axios的responseType設(shè)置為 ’ ';導(dǎo)致我們發(fā)請求無法獲取excel的模板文件內(nèi)容,所以我們需要保證項目中沒有使用mock
第二步:準備模板文件
我們將所有的模板文件都放到項目中的public目錄下即可通過axios直接請求到了(模板文件可以在先安裝xlsx-chart,然后在node_modules中里面拿出來,然后把xlsx-chart卸載掉) ;模板我們只需要基礎(chǔ)的即可(columnAvg.xlsx,columnGroup.xlsx,columnGroupAvg.xlsx用不上)
第三步:在vue中使用
xxx.vue文件
<template><div id="app"><img alt="Vue logo" src="./assets/logo.png" /><br /><button @click="getChartExcel" style="margin: 0 auto">導(dǎo)出帶圖標的excel表格
</button></div>
</template><script>
import XLSXChart from "./utils/base";
import FileSaver from "file-saver";export default {name: "App",methods: {getChartExcel () {let xlsxChart = new XLSXChart();let opts = {chart: "bar",titles: ["Price"],fields: ["Apple", "Blackberry", "Strawberry", "Cowberry", "jasonchen"],data: {Price: {Apple: 10,Blackberry: 5,Strawberry: 15,Cowberry: 20,jasonchen: 2000},},chartTitle: "Area chart",};xlsxChart.generate(opts, function (err, data) {if (err) {console.error(err);} else {let blob = new Blob([data]);FileSaver.saveAs(blob, "chart.xlsx");}});}},
};</script><style>
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;
}
</style>
第四步:最終效果
第五步: 進階使用:
思路:我們使用過后不難了解到,xlsx-chart這個工具的實現(xiàn)思路就是在需要導(dǎo)出excel時,通過讀取本地的excel模板文件,然后使用我們想要展示的數(shù)據(jù)去替換模板中表格的數(shù)據(jù),最終達到圖表生成的效果;但是我們能不能在已有模板的基礎(chǔ)上多加幾個圖表呢,答案是可以的而且非常簡單
1.我們打開其中一個模板文件column.xlsx;選中我們想要生成圖表的列(第一列必選),注意要選中全列
2.生成圖表
3.重復(fù)上述操作生成多個圖表后,把這些生成的圖表剪切到Chart的sheet中,并保存
4.再次導(dǎo)出表格(使用保存后的模板)
文章項目源碼地址:
https://github.com/Jason-chen-coder/Xlsx-EasyExportExcelWithChars
寫在最后:
1.以上就是基于xlsx-chart開發(fā)excel導(dǎo)出表格數(shù)據(jù)帶圖表的功能,及簡單的拓展,如果你有更好的思路可以直接修改base.js的源碼和修改excel模板的圖表樣式甚至往模板中新增excel相關(guān)的更多功能等等;
2.文檔中的項目鏈接:https://github.com/Jason-chen-coder/exportExcelForChart
3.xlsx-chart非常強大,我只是基于他base的源碼修改了一下,大佬們可以修改他完整版的源碼來開發(fā)更多的功能
總結(jié)
以上是生活随笔為你收集整理的【xlsx-chart】Vue项目中导出Excel表格并带上图表的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。