序言
當我的團隊進行稅務系統模組開發的時候,我發現他們需要花費80%的時間去解決計算問題,尤其體現在表格(Grid)中的計算,這些時間花在:
寫前台js程式碼(因為使用者在表格中的輸入會影響其他儲存格,所以需要即時將運算後的新值呈現給使用者看) 寫後台程式碼(因為使用者對表格資料的變更會影響其他表格,所以要在使用者點擊儲存時更新受影響表格的資料) 實施修改計算方法,導致開發者需要修改程式碼於是我研究了稅務其他模組的功能,發現稅務系統大量使用表格控件,而其中或多或少都會涉及到計算問題。而處理計算的方法,都是採用硬編碼。
計算,這個習以為常的編碼動作,其實很容易讓人聯想到Excel中的公式,更何況需求文件本身就是以Excel的形式提供的。當我們在使用Excel的時候,可以在儲存格中設定公式,透過改變來源儲存格的值,Excel會自動計算儲存格公式,將結果值賦予目標儲存格。那麼,我們是否可以參考這種模式,開發者不再需要寫複雜難懂的計算邏輯,只需要根據實作提供的公式,將它們轉成某種格式的語句,再調用某種計算引擎產出結果,將結果呈現給使用者看或持久化到資料庫?答案是肯定的,而這一切的核心就是自動運算引擎-AutoCalculate。
作用
AutoCalculate是表格複雜運算的解決方案,可以讓你省掉成百上千行的計算邏輯程式碼,從此寫程式就像寫Excel公式一般簡單。
適用範圍
前台:
適用於ElementUI表格、EasyUI Grid控制項、ParamQuery Grid等所有js表格控制項中帶有公式的複雜運算
後台:
適用,需要V8引擎
前台用法
AutoCalculate由兩部分組成,分別是公式和計算引擎,公式是就是根據特定語法編寫的字串,如:[Month12,1]#3 = [Month11,1] * 10,計算引擎即是AutoCalculate.js,負責解析公式。以下開始介紹如何書寫公式。
單元格
假設有這樣的場景,單元格①=單元格②+單元格③,對應的公式是:
[Month1,1] = [Month1,2] + [Month1,3]
先來看看[Month1,1]
代表什麼,首先,中括號[ ]
代表一個單元格,Month1即「1月」對應的列名,緊接著是一個逗號,
,後面的1代表RowNo = 1,以此類推,
[Month1,2]
代表列為「1月」且RowNo = 2的單元格
[Month1,3]
代表列為「1月」且RowNo = 3的單元格
所以我們可以用[y,x]
來代表一個單元格,y即列名,也稱為縱座標, x即RowNo的值,也稱為橫座標
如果表格沒有RowNo列怎麼辦?如想找答案,請繼續往下閱讀
讓公式生效
//首先引入AutoCalculate.js import AutoCalculate from '../components/AutoCalculate'; …… //定義一個AutoCalculate實例,formulas為公式陣列let autoCal = new AutoCalculate(formulas); /* 呼叫cal方法* gridDatas(必填):表格資料* refField(必填):參考字段,即單元格[y,x]中x是哪個字段的值*/ autoCal.cal(gridDatas, refField);
區域公式
實際上,除了1月,2月,3月…10月也存在類似的公式,即:
[Month1,1] = [Month1,2] + [Month1,3] [Month2,1] = [Month2,2] + [Month2,3] [Month3,1] = [Month3,2] + [Month3,3] …… …… …… [Month10,1] = [Month10,2] + [Month10,3]
也就是說我們需要寫10個這樣的公式,對於簡單的場景來說,這不成問題,但是對於某些包含大量公式的表格,這種寫法存在一些弊端,比如容易寫錯,還有,公式長的時候也需要花較多時間才能寫完。所以,便有了區域公式。
觀察上面的公式可以發現,其實每個公式都可以用一條公式來代替,例如以下公式:
[@,1] = [@,2] + [@,3]
這裡沒有明確的列名,只是用了一個佔位符@,但它足以代表以上10條公式。這時候,我們只需要在適當的位置補上列名就可以了,所以,最終的公式就是:
{Month1, Month2, Month3, Month4, Month5, Month6, Month7, Month8, Month9, Month10}[@,1] = [@,2] + [@,3]
你需要將列名用,
分開,並放置在大括號{ }
內,如此,1條公式便相當於10條公式。
佔位符不僅可以用於縱座標,還可用於橫座標,如下公式:
//公式1: [YearTotal,3] = [Month1,3] + [Month2,3] + [Month3,3] + [Month4,3] + [Month5,3] + [Month6,3] + [Month7,3] + [Month8 ,3] + [Month9,3] + [Month10,3] //公式2: [YearTotal,4] = [Month1,4] + [Month2,4] + [Month3,4] + [Month4,4] + [Month5,4] + [Month6,4] + [Month7,4] + [Month8 ,4] + [Month9,4] + [Month10,4] //公式3: [YearTotal,5] = [Month1,5] + [Month2,5] + [Month3,5] + [Month4,5] + [Month5,5] + [Month6,5] + [Month7,5] + [Month8 ,5] + [Month9,5] + [Month10,5] //公式4: [YearTotal,6] = [Month1,6] + [Month2,6] + [Month3,6] + [Month4,6] + [Month5,6] + [Month6,6] + [Month7,6] + [Month8 ,6] + [Month9,6] + [Month10,6] //公式5: [YearTotal,2] = [Month1,2] + [Month2,2] + [Month3,2] + [Month4,2] + [Month5,2] + [Month6,2] + [Month7,2] + [Month8 ,2] + [Month9,2] + [Month10,2] //公式6: [YearTotal,7] = [Month1,7] + [Month2,7] + [Month3,7] + [Month4,7] + [Month5,7] + [Month6,7] + [Month7,7] + [Month8 ,7] + [Month9,7] + [Month10,7] //公式7: [YearTotal,9] = [Month1,9] + [Month2,9] + [Month3,9] + [Month4,9] + [Month5,9] + [Month6,9] + [Month7,9] + [Month8 ,9] + [Month9,9] + [Month10,9] //公式8: [YearTotal,12] = [Month1,12] + [Month2,12] + [Month3,12] + [Month4,12] + [Month5,12] + [Month6,12] + [Month7,12] + [Month8 ,12] + [Month9,12] + [Month10,12] //公式9: [YearTotal,13] = [Month1,13] + [Month2,13] + [Month3,13] + [Month4,13] + [Month5,13] + [Month6,13] + [Month7,13] + [Month8 ,13] + [Month9,13] + [Month10,13]
使用區域公式,可以寫成:
{2, 3, 4, 5, 6, 7, 9, 12, 13}[YearTotal,@] = [Month1,@] + [Month2,@] + [Month3,@] + [Month4,@] + [ Month5,@] + [Month6,@] + [Month7,@] + [Month8,@] + [Month9,@] + [Month10,@]
由此可見,區域公式為公式的書寫帶來了極大的便利。
支援js語法
在實際場景中,我們常常會碰到一些複雜的公式,如下圖,單元格公式使用了Excel自帶的Max函數,對於這樣的公式,我們可以這樣寫:
[Month1,9] = ([Month1,6] - [Month1,7] - [Month1,8] > 0 ? [Month1,6] - [Month1,7] - [Month1,8] : 0) + [Month1 ,5]
如你所見,公式支援js語法,你可以在公式等號右邊放入js變量,甚至js函數,只要是js解析引擎認識的語法,都被支援。
這裡有個需要注意的地方,就是不可以將數組元素放入公式中,因為js的數組元素通常帶有“[ ]”符號,這與公式當中的單元格表示符”[ ]”產生衝突,所以數組元素被禁止使用,請留意這一點。
[y]公式
接下來,帶大家來看另一種場景,如圖,有這樣的關係:
單元格① = 單元格② - 單元格③
你可能很快就寫出了以下公式:
[column3,1] = [column2,1] - [column1,1] [column3,2] = [column2,2] - [column1,2]
這樣寫本身沒有錯,但是我得提醒你,這裡的行是不固定的,也就是說表格有多少行完全取決於當時的資料庫情況,有可能今天只有3行數據,明天會有5行,後天會有50行。我們不可能隨著行數增加而增加公式,所以對於這種行數不確定的表格,我們有一種新的寫法,我將它稱為[y]公式,因為跟普通公式相比,它沒有橫座標:
[column3] = [column2] - [column1]
只需要一行公式,AutoCalculate便會將公式套用於指定列名下的所有行。
合計列與小數位數
有時候,我們需要求某一列的和,雖然求某一列的和可能不是我們的最終目的,但卻是我們完成計算的必要步驟,如存在以下關係:
單元格③ = 單元格① / 單元格②
單元格②是GroupApprovedTotal
列的合計值,我們用<列名>
來表示,即: <GroupApprovedTotal>
。加上這裡的行不固定,需要用到[y]公式,所以公式要寫成:
[GroupApprovedTotalPercent] = [GroupApprovedTotal] / <GroupApprovedTotal>
我們知道,在除法中,除數是不可以是0的,所以正確的寫法應該是:
[GroupApprovedTotalPercent] = <GroupApprovedTotal> === 0 ? 0 : [GroupApprovedTotal] / <GroupApprovedTotal>
當你將這個公式放你的程式碼,並啟動程式後,聰明的你應該很快就會發現,你得到的值不夠精確,如上面單元格③顯示的數值是66.91%,如果你的單元格①和單元格②跟上圖的數值相同,你的單元格③很可能是67%,這是為什麼呢?
預設的,AutoCalculate會將計算結果保留2位小數,67%,即0.67,如果想得到66.91%,即0.6691,那就是需要保留4位小數,這時,你需要告訴AutoCalculate,你需要保留4位小數,所以,完整的寫法應該是:
[GroupApprovedTotalPercent]#4 = <GroupApprovedTotal> === 0 ? 0 : [GroupApprovedTotal] / <GroupApprovedTotal>
在公式的等號左邊,被賦值單元格的右邊,加上「#」號,緊跟著寫上小數位數,注意,「#」和小數位數之間不能有空格,前後可以有空格。
沒有RowNo的表格
終於到了回答這個問題的時候,我想問大家,我們是如何在一個平面上找到一個點的?答案就是需要這個點的橫座標和縱座標,同樣的,在一個表中,如何找到一個單元格?首先我們可以確定縱座標,因為所有的列名都是已知的,關鍵在於橫座標的決定。採用RowNo來定位,大家一定會覺得似曾相識,因為它跟Excel左側的序號很像,但不代表只有數字才能當橫座標。只要值具有唯一性,即不重複,就可以作為橫座標。
舉個例子,假設以下的表格是固定兩行,沒有RowNo,但是可以看出公司編號(BuCode)具有唯一性,那麼BuCode就可以作為參考字段,BuCode的值就是橫座標,那麼公式就可以寫成:
[SumDiffMonth1,F1136] = [GroupApprovalMonth1,F1136] - [Month1,F1136] [SumDiffMonth1,F2056] = [GroupApprovalMonth1,F2056] - [Month1,F2056]
如果有RowNo,用RowNo做參考欄位時這樣寫:
[SumDiffMonth1,2] = [GroupApprovalMonth1,2] - [Month1,2] [SumDiffMonth1,3] = [GroupApprovalMonth1,3] - [Month1,3]
跨資料來源計算
何為跨資料來源計算?用過Excel公式的朋友應該可以看懂下面這個單元格的公式代表的意思。很明顯這個單元格的值是其他Sheet的資料經過運算後的值,跨資料來源計算就是專門處理這樣的場景。
我們很少甚至不會在前台做跨資料來源計算,這裡是想告訴大家如何書寫公式及調用AutoCalculate的方法,以便在「後台用法」這一章節真正使用到它。
首先,為了取得其他資料來源單元格的數據,我們需要拓展一下單元格,之前,我們的單元格是這樣的:[y,x],暫且稱為二元單元格吧,還有這樣的單元格:[y],成為一元單元格,現在,你會看到這樣的單元格:[外部資料來源,y,x],即三元單元格,三元單元格的出現令到AutoCalculate定位單元格的能力從二維拓展到三維,即不管你有多少表,AutoCalculate都能找到你要的數據。
這是一條使用了三元單元格的公式:
[Month1,4] = [OutputTax,Month1,7]
其中OutputTax是某個資料來源的名稱,你可以任意取名,越簡潔越好,否則複雜的公式會寫得很長,難以閱讀。
下面這條公式會從兩個資料來源OutputTax和TaxRate取值:
[Month1,5] = [OutputTax,Month1,10] * (1 + [TaxRate,Month1,1] / 100)
我相信透過閱讀前面章節的內容,你已經能夠看懂下面公式的意思,其中前三行公式使用了外部資料來源,並結合了區域公式的寫法。
是時候呼叫我們的計算方法了,為了演示效果,我添加了一個按鈕,並將方法寫在按鈕事件中
看看我們做了什麼:
① 取得某個外部資料來源outputTaxDatas
② 取得目前表格的資料來源payableTaxDatas
③ 從資料庫取得另一個外部資料來源taxRateDatas
④ 這裡是重點,先來看看AutoCalculate 的建構函數,這裡有兩個參數:
formulas:公式,一個陣列
options:可選參數,一個object對象
options有個屬性externalDatas,表示外部資料來源,是一個數組,因為資料可能有多個,每個數組元素都是一個對象,有3個屬性:
name:外部資料來源名稱,這裡取什麼名稱,對應公式中的外部資料來源名稱
refField:參考字段
datas:資料來源
實例化AutoCalculate後,這裡呼叫了一個新的方法calculate,它有2個參數:
gridDatas:需要重新計算的表格數據,是一個數組
refField:參考字段
AutoCalculate之所有支援所有的js表格控件以及能被後台調用,就是藉助於這個方法,因為不論是哪種js表格控件,都能夠提取出表格數據(純數據),數據通常是數組形式,只要將這個數組傳進來就可以了。
⑤ 呼叫calculate後,payableTaxDatas的值已經是運算過的最新值,現在將它綁定到目前的表格即可。
運行程式後的介面:
點擊取得數據後:
後台用法
後台呼叫AutoCalculate,我們需要用到V8引擎,還有一點很重要,後台呼叫AutoCalculate也需要用到公式,我們之前的做法是將所有公式放在Extjs的Controller檔案中,如下圖:
為了方便後台調用,我們將公式提取出來作為一個單獨的文件
專案中對AutoCalculate後台呼叫進行了封裝,使用非常簡單。
調用方法如圖:
還是分步解析:
① 儲存目前表格的數據
② 取得公式所在js檔案的目錄
③ 取得兩個外部資料來源
④ 呼叫封裝後的後台方法,使用了第②步和第③步驟獲取的數據,其中FormulaExpression是公式表達式,即透過這個表達式是來找到你提供的js檔案中的公式
⑤ 上一步驟傳回的newDatas已經是經過運算的最新數據,現在將這些數據儲存到資料庫
注意事項
書寫公式時有兩點要注意:
儲存格中不允許出現空格
/正確寫法: [Month12,1] = [Month11,1] * 10 //錯誤寫法: [Month12,1 ] = [ Month11, 1] * 10
小數位數標記與小數位數之前不能有空格
//正確寫法: [Month12,1] #3 = [Month11,1] * 10 //錯誤寫法: [Month12,1] # 3 = [Month11,1] * 10
到此這篇關於告別硬編碼讓你的前端表格自動計算的文章就介紹到這了,更多相關前端表格自動計算內容請搜索downcodes.com以前的文章或繼續瀏覽下面的相關文章,希望大家以後多多支援downcodes.com!