背景
實現一個可以在wps、office的展示圖表、表格以及自定義頁眉、頁腳、字體大小以及圖片信息。
使用到的庫有 officegen 和 highchart-export-server
簡單介紹一下officegen
officegen 模塊可以爲Microsoft Office 2007及更高版本生成Office Open XML文件。此模塊不依賴於任何框架,您不需要安裝Microsoft Office,因此您可以將它用於任何類型的 JavaScript 應用程序。輸出也是流而不是文件,不依賴於任何輸出工具。officegen模塊應適用於支持任何支持Node的任何環境,包括Linux,OSX和Windows。
此模塊生成Excel(.xlsx),PowerPoint(.pptx)和Word(.docx)文檔。 Officegen還支持帶有嵌入數據的PowerPoint本機圖表對象。
使用officegen生成Word文件
const officegen = require('officegen')
const fs = require('fs')
// 創建一個空的word對象
let docx = officegen('docx')
// 當word文件完成創建後會調用該函數
docx.on('finalize', written => {
console.log('Finish to create a Microsoft Word document.')
})
// 創建一個段落,類似html裏面的p標籤
let pObj = docx.createP()
// 往標籤裏面添加內容
pObj.addText('Simple')
// 添加內容同事設置字體顏色,北京顏色
pObj.addText(' and back color.', { color: '00ffff', back: '000088' })
// 還可以往段落裏面添加圖片
pObj.addImage('some-image.png')
// 最後將docx對象塞到generate裏面生成名爲example.docx的文件
let out = fs.createWriteStream('example.docx')
docx.generate(out)
使用officegen生成PPT文件
const officegen = require('officegen')
let pptx = officegen('pptx')
// 創建一頁PPT,可以往裏面插圖表、文本、表格以及圖片
slide = pptx.makeNewSlide({
userLayout: 'title'
});
// 設置標題跟設置段落一樣,可以設置字體大小/顏色,font-family
slide.setTitle([
// This array is like a paragraph and you can use any settings that you pass for creating a paragraph,
// Each object here is like a call to addText:
{
text: 'Hello ',
options: {font_size: 56}
},
{
text: 'World!',
options: {
font_size: 56,
font_face: 'Arial',
color: 'ffff00'
}
}
]);
// 插入圖表,這裏的chartInfo有一定的數據編排格式,建議如果產品線需要很多圖表的話,這裏可以單獨抽出來維護
slide.addChart( {
title: 'Column chart',
renderType: 'column', // 這裏設置圖表的樣式,類似echart裏面的type
valAxisTitle: 'Costs/Revenues ($)',
catAxisTitle: 'Category',
valAxisNumFmt: '$0',
valAxisMaxValue: 24,
data: [{
name: 'Income',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [23.5, 26.2, 30.1, 29.5, 24.6],
color: 'ff0000'
},
{
name: 'Expense',
labels: ['2005', '2006', '2007', '2008', '2009'],
values: [18.1, 22.8, 23.9, 25.1, 25],
color: '00ff00' // optional
}]
})
// 創建一個table
let rows = [];
rows.push([
{
val: "Category",
opts: {
font_face : "Arial",
align : "l",
bold : 0
}
},
{
val :"Average Score",
opts: {
font_face : "Arial",
align : "r",
bold : 1,
font_color : "000000",
fill_color : "f5f5f5"
}
}
]);
slide.addTable(rows, {});
// 生成ppt
let out = fs.createWriteStream('example.pptx')
pptx.generate(out)
使用officegen生成Excel文件
const officegen = require('officegen')
const fs = require('fs')
let xlsx = officegen('xlsx')
let sheet = xlsx.makeNewSheet()
sheet.name = 'Officegen Excel'
// 往單元格里面插數據
sheet.setCell('E7', 42)
// 或者直接使用二維數組進行數據的插入
sheet.data[2][6] = 900
sheet.data[2][5] = 'more text'
let out = fs.createWriteStream('example.xlsx')
xlsx.generate(out)
以上就是officegen生成Word、Excel以及PPT的示例。如果你覺得本文已經結束了,那你太天真了
最近接到一個需求,要往word文檔裏面插入一個echart圖表(看清楚不是圖片),那就尷尬了,officegen沒有API支持,怎麼辦…焦頭爛耳中…兩小時的文檔查閱,發現一個突破點,就是pObj.addImage(‘some-image.png’),這個API,還記得嗎?如果我將echart圖生成圖片,然後以圖片的形式插進去起步就ok?
接下來就是考慮怎麼生成echart圖片的問題,接下來就是highchart-export-server的show-time。一款服務端的生成圖片的解決方案,而且只要你的系統支持Node它就能跑起來,這彷彿看到了曙光。
搭建highcharts-export-server
// 全局安裝highcharts-export-server
yarn global add highcarts-export-server
// 在highcharts-export-server安裝包的根目錄執行build.js
node build.js
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oR2wqg64-1583199017050)(./img/build.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FHGE1aSs-1583199017052)(./img/options.png)]
然後會彈出很多選項,根據自己的需求進行選擇
// 創建一個options.json配置文件,options.json 的內容爲
{"title":{"text":"不同城市的月平均氣溫","x":-20},"subtitle":{"text":"數據來源:WorldClimate.com","x":-20},"xAxis":{"categories":["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]},"yAxis":{"title":{"text":"溫度(°C)"},"plotLines":[{"value":0,"width":1,"color":"#808080"}]},"tooltip":{"valueSuffix":"°C"},"legend":{"layout":"vertical","align":"right","verticalAlign":"middle","borderWidth":0},"series":[{"name":"東京","data":[7,6.9,9.5,14.5,18.2,21.5,25.2,26.5,23.3,18.3,13.9,9.6]},{"name":"紐約","data":[-0.2,0.8,5.7,11.3,17,22,24.8,24.1,20.1,14.1,8.6,2.5]},{"name":"柏林","data":[-0.9,0.6,3.5,8.4,13.5,17,18.6,17.9,14.3,9,3.9,1]},{"name":"倫敦","data":[3.9,4.2,5.7,8.5,11.9,15.2,17,16.6,14.2,10.3,6.6,4.8]}]}
// 將圖表配置轉換成 PNG 圖片
highcharts-export-server --infile options.json --outfile chart.png
注意事項
// 一般發佈前的所有測試上線工作都是內網中進行,所以必須要把所有使用到的依賴和包都下載好放到本地
在開發環境一般是沒有網絡的,所以,提前將環境所需要的依賴、包都在有網的前提下佈置好,然後將那個包部署到對應的開發測試環境。對了,如果在linux環境下面輸出圖片,記得注意字體,因爲在window下面字體是微軟雅黑,然後在linux如果找不到就顯示不出對應的文字
// 把phantom放置到temp目錄下
C:\Users\youerName\AppData\Local\Temp\
// 當執行完node build.js的時候,會在當前目錄下生成phantom文件,裏面有一個export.html。注意裏面使用到了CDN
需要把對應的moment和timezone資源下載至本地,而且export.html需要在外網的環境下生成
翻譯映射表
在項目裏面,很多時候不僅僅需要簡體中文,還需要一些國外的翻譯,例如英語、韓語、繁體字等等,那麼怎樣做纔能有條不紊地,方便後人進行維護呢?
原理:以簡體中文爲key
值,目標翻譯爲value
,這樣每次新增一個國家的翻譯就可以通過創建多一個文件就行。考慮到翻譯工具整個項目都需要使用到,所以把_
掛載到全局,然後記得在package.json
裏面的eslintConfig
配上"globals": {"_": true}
,不然eslint
會報錯
# 想要達到的目標
在`js`代碼中可以通過下劃線進行翻譯_('{0}你好{1}', '小明', new Date().getYear())
在模板中可以通過<lang :date="_('中國{0}', 2019)">你好{date}<lang>標籤和屬性翻譯
// index.js
const LANGUAGE = {
'en_US': en // 這個en是一個翻譯文件
};
function _(str, ...args) {
let langMap = LANGUAGE[cur];
if (langMap && langMap.hasOwnProperty(str)) {
str = langMap[str];
}
return str.replace(/\{(\d+|#\w+#)\}/g, function (m, i) {
i = parseInt(i, 10);
if (isNaN(i)) {
return '';
}
if (i >= 0 && i < args.length) {
return args[i];
}
return m;
});
}
function initI18n () {
if (typeof window !== 'undefined') {
let old;
if (window._) {
old = window._;
window._ = (str, ...args) => _(old.apply(null, arguments), ...args);
} else {
window._ = _;
}
return true;
}
return false;
}
initI18n();
// 內部實現一個install函數
export default {
install (Vue, opt = { lang: 'zh_CN' }) {
Vue.component('lang', Lang); // 這裏面的Lang是一個組件
Vue.prototype._ = _;
cur = opt.lang;
}
};
// lang.vue
props: {
showTitle: {
type: Boolean,
default: false
}
},
methods: {
_renderHTML () {
let slotText = this.$slots.default[0].text;
if (!this.$isMounted) { // 注意這個isMounted這個是判斷是否已經mounted
return slotText;
}
let attributeMap = Array.from(this.$el.attributes).reduce((prev, attr) => {
prev[attr.nodeName] = attr.nodeValue;
return prev;
}, {});
slotText = _(slotText);
return slotText.replace(/\{(.+?)\}/g, function (match, i) {
return attributeMap[i] || '';
});
}
},
mounted () {
this.$isMounted = true;
this.$forceUpdate();
},
render () {
let me = this,
text = me._renderHTML();
return (
<span class="language-wrap" title={me.showTitle ? text : ''}>
{
text
}
</span>
);
}