Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
下圖是一個產品開發中非常常見的大屏展示界面示例。
通過Vue提供的Vuex,上方三個儀表板以及下方的表格組件共享同一個數據源,已經實現了數據改變後同步響應更新。
“很棒的大屏展示功能,能支持Excel數據的導入導出嗎,表格數據可以實時編輯更新嗎?”
如果你已經開發軟件很長時間,可能不止一次地從最終客戶或者產品經理那裏聽到過這個靈魂拷問。對於非技術人羣來說,覺得要求 Excel 導入/導出/展示是一個非常正常且容易實現的需求。
但實際上,這個問題常常讓前端開發人員感到恐懼。處理 Excel 文件需要大量工作。
這個問題通過前端表格可以變得簡單,將電子表格嵌入Web 應用程序。同時和其他的組件進行交互。 這篇博客將研究如何使用現有的這個大屏展示 Vue 應用作爲基礎,使用前端電子表格對其進行增強。
本文假定你已經瞭解 HTML、CSS 和 JavaScript。以及Vue的基礎應用。如果你有使用過Vuex ,當然會更容易理解,如果還沒有,也不用擔心。VueX在這個項目中的應用很簡單。
關於VueX,可以在Vue官網瞭解更多信息
本文將分爲下面的幾個部分
- Vuex的原始應用
- 給應用添加實時編輯功能
- 添加 Excel 數據導入功能
- 添加導出爲Excel功能
包含Vuex的原始應用
如上圖看到的,將要使用的 Vue 應用程序是一個簡單的大屏展示界面,帶有幾個彙總信息儀表板和一個數據表。
可以通過下面的附件獲取這個Vue應用項目代碼,然後運行“npm install”以及 “npm run serve”即可啓動應用 。
附件下載地址:
https://gcdn.grapecity.com.cn/forum.php?mod=attachment&aid=MjI1NzA1fDNkMDNjNjQ2fDE2NjAxMTUxMjF8NjI2NzZ8OTk3MTg%3D
原始的Vue 應用代碼結構如下:
- Vuex 和 Vue 應用程序都定義在main.js中。
- 有幾個單文件 Vue 組件,位於該components文件夾中。
Vuex store代碼如下,初始狀態只有一個設置爲recentSales 的值,表示近期銷售記錄 :
const store = new Vuex.Store({
state: {
recentSales
}
});
通過recentSales這一個數據,如何生成三個統計表和一個表格?打開 Dashboard.vue 組件。在其中,可以看到基於 Vuex 存儲中的數據生成了幾個計算屬性:
<template>
<div style="background-color: #ddd">
<NavBar title="銷售儀表板"/>
<div class="container">
<div class="row">
<TotalSales :total="totalSales"/>
<SalesByCountry :salesData="countrySales"/>
<SalesByPerson :salesData="personSales"/>
<SalesTableBySpreadjs :tableData="salesTableData"/>
<SalesTable :tableData="salesTableData"/>
</div>
</div>
</div>
</template>
<script>
import NavBar from "./NavBar";
import TotalSales from "./TotalSales";
import SalesByCountry from "./SalesByCountry";
import SalesByPerson from "./SalesByPerson";
import SalesTable from "./SalesTable";
import SalesTableBySpreadjs from "./SalesTableBySpreadjs";
import { groupBySum } from "../util/util";
export default {
components: { NavBar, SalesByCountry, SalesByPerson, SalesTable, TotalSales ,SalesTableBySpreadjs},
computed: {
totalSales() {
const total = this.$store.state.recentSales.reduce(
(acc, sale) => (acc += sale.value),
0
);
return parseInt(total);
},
countrySales() {
const items = this.$store.state.recentSales;
const groups = groupBySum(items, "country", "value");
return groups;
},
personSales() {
const items = this.$store.state.recentSales;
const groups = groupBySum(items, "soldBy", "value");
return groups;
},
salesTableData() {
return this.$store.state.recentSales;
}
}
};
</script>
因此recentSales這個單個數據集目前能爲這個大屏展示的幾個儀表板和表格提供一致數據。由於數據位於Vuex store中,那麼如果數據更新,所有儀表板面板都會自動更新。
當我們用可以編輯的電子表格替換現有的表格來進行編輯時,這種特性將派上用場。
將前端電子表格添加到您的 Vue 應用程序
我們要用前端電子表格替換這個html表格,在component文件夾新建一個vue文件,命名爲SalesTableBySpreadjs.vue,然後在其中添加一個template:
<template>
<TablePanel title="近期銷售額">
<gc-spread-sheets
:hostClass="hostClass"
@workbookInitialized="workbookInit"
style="height: 300px"
>
<gc-worksheet
:dataSource="tableData"
:autoGenerateColumns="autoGenerateColumns"
>
<gc-column
:width="50"
:dataField="'id'"
:headerText="'ID'"
:visible="visible"
:resizable="resizable"
>
</gc-column>
<gc-column
:width="300"
:dataField="'client'"
:headerText="'Client'"
:visible="visible"
:resizable="resizable"
>
</gc-column>
<gc-column
:width="350"
:headerText="'Description'"
:dataField="'description'"
:visible="visible"
:resizable="resizable"
>
</gc-column>
<gc-column
:width="100"
:dataField="'value'"
:headerText="'Value'"
:visible="visible"
:formatter="priceFormatter"
:resizable="resizable"
>
</gc-column>
<gc-column
:width="100"
:dataField="'itemCount'"
:headerText="'Quantity'"
:visible="visible"
:resizable="resizable"
>
</gc-column>
<gc-column
:width="100"
:dataField="'soldBy'"
:headerText="'Sold By'"
:visible="visible"
:resizable="resizable"
></gc-column>
<gc-column
:width="100"
:dataField="'country'"
:headerText="'Country'"
:visible="visible"
:resizable="resizable"
></gc-column>
</gc-worksheet>
</gc-spread-sheets>
</TablePanel>
</template>
其中,gc-spread-sheets元素創建了一個電子表格並定義瞭如何顯示數據列。gc-column 中的dataField 屬性告訴該列應該顯示底層數據集的哪個屬性。
接下來是js部分:
import "@grapecity/spread-sheets/styles/gc.spread.sheets.excel2016colorful.css";
_// SpreadJS imports_
import "@grapecity/spread-sheets-vue";
import Excel from "@grapecity/spread-excelio";
import TablePanel from "./TablePanel";
export default {
components: { TablePanel },
props: ["tableData"],
data(){
return {
hostClass:'spreadsheet',
autoGenerateColumns:true,
width:200,
visible:true,
resizable:true,
priceFormatter:"$ #.00"
}
},
methods: {
workbookInit: function(_spread_) {
this._spread = spread;
}
}
};
只需很少的代碼即可完成。其中的幾個數據屬性和方法,是綁定到純前端電子表格組件的配置選項,workbookInit 方法是SpreadJS在初始化工作表時調用的回調。
回到Dashboard.vue文件,加入剛剛創建的SalesTableBySpreadjs組件。
然後重新運行,即可顯示電子表格數據:
<template>
<div style="background-color: #ddd">
<NavBar title="銷售儀表板"/>
<div class="container">
<div class="row">
<TotalSales :total="totalSales"/>
<SalesByCountry :salesData="countrySales"/>
<SalesByPerson :salesData="personSales"/>
<SalesTableBySpreadjs :tableData="salesTableData"/>
<SalesTable :tableData="salesTableData"/>
</div>
</div>
</div>
</template>
<script>
import NavBar from "./NavBar";
import TotalSales from "./TotalSales";
import SalesByCountry from "./SalesByCountry";
import SalesByPerson from "./SalesByPerson";
import SalesTable from "./SalesTable";
import SalesTableBySpreadjs from "./SalesTableBySpreadjs";
import { groupBySum } from "../util/util";
export default {
components: { NavBar, SalesByCountry, SalesByPerson, SalesTable, TotalSales ,SalesTableBySpreadjs},
computed: {
totalSales() {
const total = this.$store.state.recentSales.reduce(
(acc, sale) => (acc += sale.value),
0
);
return parseInt(total);
},
countrySales() {
const items = this.$store.state.recentSales;
const groups = groupBySum(items, "country", "value");
return groups;
},
personSales() {
const items = this.$store.state.recentSales;
const groups = groupBySum(items, "soldBy", "value");
return groups;
},
salesTableData() {
return this.$store.state.recentSales;
}
}
};
</script>
現在我們已經用一個完整的電子表格替換了原來的html table,接下來可以對電子表格中的金額列中顯示的金額進行編輯。比如將第6行的金額從 35,000 美元更改爲 3500 美元,可以看到上面三個儀表板顯示的內容同時也進行了更新。
原因是SpreadJS被編輯後同步更新了它的數據源=>VUEX store中的recentSales。
到這裏我們已經有了一個可以隨着數據變化而實時更新的增強型儀表板。下一步我們可以通過導出導入 Excel 數據的功能來做進一步增強。
導出爲Excel文件
將 Excel 導出功能添加到工作表很容易。首先,在儀表板中添加一個導出按鈕。把它放在表格面板的底部,在 gc-spread-sheets 結束標記之後:
</gc-spread-sheets>
<div class="row my-3">
<div class="col-sm-4">
<button class="btn btn-primary mr-3" @click="exportSheet">
導出文件
</button>
</div>
</div>
</TablePanel>
</template>
接下來添加點擊時觸發的 exportSheet方法,從名爲 file-saver 的 NPM 包中導入一個函數:
import { saveAs } from 'file-saver';
然後將 exportSheet 添加到組件的方法對象中:
exportSheet: function () {
const spread = this._spread;
const fileName = "SalesData.xlsx";
//const sheet = spread.getSheet(0);
const excelIO = new IO();
const json = JSON.stringify(
spread.toJSON({
includeBindingSource: true,
columnHeadersAsFrozenRows: true,
})
);
excelIO.save(
json,
function (blob) {
saveAs(blob, fileName);
},
function (e) {
console.log(e);
}
);
},
運行測試點擊按鈕,即可直接獲取到導出的excel文件。
需要注意的是,我們設置了兩個序列化選項:includeBindingSource 和 columnHeadersAsFrozenRows。以確保綁定到工作表的數據被正確導出,且工作表包含列標題,。
Excel 數據導入
在template中,添加以下代碼添加一個file類型的input用於導入文件:
<div class="col-sm-8">
<button class="btn btn-primary float-end mx-2">導入文件</button>
<input
type="file"
class="fileSelect float-end mt-1"
@change="fileChange($event)"
/>
</div>
然後將fileChange方法添加到組件的method對象中:
fileChange: function (e) {
if (this._spread) {
const fileDom = e.target || e.srcElement;
const excelIO = new IO();
//const spread = this._spread;
const store = this.$store;
excelIO.open(fileDom.files[0], (data) => {
const newSalesData = extractSheetData(data);
store.commit("updateRecentSales", newSalesData);
});
}
},
選擇文件後,使用SpreadJS中的 ExcelIO 導入它。獲取其中的json數據。傳入自定義的函數extractSheetData,從中提取需要的數據,然後將其提交回 Vuex store,來更新recentSales數據。
extractSheetData 函數可以在 src/util.util.js 文件中找到。extractSheetData函數假定導入工作表中的數據與原始數據集具有相同的列。如果有人上傳的電子表格不符合此要求,將無法解析。這個應該是大多數客戶可以接受的限制。數據不符時,也可以嘗試給客戶一個提示信息。
另外,還需要在main.js中爲Vuex store添加updateRecentSales來更新數據,
修改後的store如下:
const store = new Vuex.Store({
state: {
recentSales
},
mutations: {
updateRecentSales (state,param) {
let sales=state.recentSales;
let arr=sales.map(function(o){return o.id});
param.forEach((newsale)=>{
if(arr.indexOf(newsale.id)>0){
console.log("update");
state.recentSales[arr.indexOf(newsale.id)]=newsale;
}
else{
console.log("add");
state.recentSales.push(newsale);
}
});
console.log(state.recentSales);
}
},
actions: {
updateRecentSales ({commit},param) {
commit('updateRecentSales',param)
}
}
});
可以看到,Vuex store調用 commit後,會觸發updateRecentSales方法對recentSales進行更新,id相同時進行更新, 有新的id時進行新增。
最後,SpreadJS工作表和所有儀表板面板都會同步更新以反映新數據。
Vue、Vuex 和 SpreadJS 的配合使用讓這個應用的增強開發變的非常方便。藉助 Vue 的模板和數據綁定、Vuex 的管理共享狀態,響應式數據存儲和純前端的交互式電子表格,可以在很短內創建複雜的企業 JavaScript 應用程序。
大家如果感興趣可以訪問更多在線實例:
https://demo.grapecity.com.cn/spreadjs/gc-sjs-samples/index.html