基於前端技術實現的全面預算編制系統

葡萄城技术团队發表於2024-03-25

前言

在現代商業環境中,預測銷售資料和實際成本是每個公司CEO和領導都極為重視的關鍵指標。然而,由於市場的不斷變化,準確地預測和管理這些資料變得愈發具有挑戰性。為了應對這一挑戰,建立一個高效的系統來管理和審查銷售資料的重要性不言而喻。今天小編就將為大家介紹一下如何使用葡萄城公司的純前端表格控制元件SpreadJS實現一個預算編制系統。

環境準備

Node.js

VSCode程式碼編輯器

完整程式碼Github地址(可在閱讀本文時配合參考使用)

使用程式碼實現的線上Demo地址(可在閱讀本文時配合參考使用)

實現步驟

1)自定義選單欄

上圖中紅色方框劃出來的選單欄叫做線上表格編輯器(Designer),Designer的選單提供了各種定製化的能力,如新增選單,修改選單執行的邏輯,修改圖示,修改文字以及刪除選單等功能。

觀察上圖中,首先新建了一個“預算操作(定製按鈕)”tab ,此tab內容包括了三部分,分別是“預算型別”、“預算編制”、“資料”。對應的程式碼如下:

let config = JSON.parse(JSON.stringify(GC.Spread.Sheets.Designer.DefaultConfig));
config.ribbon.push(
    {
    id: "fill-custom",
    text: "預算操作(定製按鈕)",
    buttonGroups: [
    {
        label:"預算型別",commandGroup:{}  
    },    
    {
        label: "預算編制", commandGroup:{}
       
    },
    {
        label: "資料", commandGroup:{}
      
    }]
})
designer.setConfig(config)

透過上述程式碼,我們來看看實現結果:

Ok ,發現新增了一個“預算操作(定製按鈕)”tab,點選此tab,已經有了基礎框架

接下來,繼續,我們設定當前tab為啟用狀態,加上active屬性,這樣子頁面初始化後看到的當前tab就是“預算操作(定製按鈕)”

{
    id: "fill-custom",
    text: "預算操作(定製按鈕)",
    active: true,
    buttonGroups: []
}

接下來,我們設定預算模型command, 我們再次看上面的第一張圖,發現預算型別只有一個節點,且該節點是一個下拉框。對應的程式碼實現方式如下:

{
    label:"預算型別",
    commandGroup: {
        children: ["selectBudgetType"]
    }
}, 

接下來定義“selectBudgetType”,程式碼如下所示:( 關於定義下拉框子選單的實現方法詳細解釋,可以參考此篇文章

const budgetType = {
    cost: 'cost' ,   //成本預算
    sales: 'sales'   //銷售預算
}
let selectBudgetType = {
    text: "選擇預算型別",
    comboWidth: 120,
    type:"comboBox",
    commandName: "selectBudgetType",
    dropdownList:[
        {
            text:"成本預算",
            value: budgetType.cost
        },{
            text:"銷售預算",
            value:budgetType.sales
        },
    ],
    execute:(context,propertyName) => {
        console.log('選擇',propertyName)
    },
}
config.commandMap = {selectBudgetType}
designer.setConfig(config)

上述程式碼為子選單“selectBudgetType”定義了text,type ,以及dropdownList以及點選事件。exexute方法中propertyName對應的是dropdownList中的value值。

結果如下:

上述程式碼已經熟悉瞭如何定義選單以及子選單,接下來的兩個子選單(預算編制和資料)就不重複詳細介紹,直接上程式碼:

config.ribbon.push(
    {
    id: "fill-custom",
    text: "預算操作(定製按鈕)",
    active: true,
    buttonGroups: [
    {
        label:"預算型別",
        commandGroup: {
            children: ["selectBudgetType"]
        }
    },    
    {
        label: "預算編制",
        thumbnailClass: "ribbon-thumbnail-editing",
        commandGroup: {
            children: [ "distributeTask"]
        }
    },
    {
        label: "資料",
        commandGroup: {
            children: ["clearLocalData"]
        }
    }]
})
config.commandMap = {
    selectBudgetType:{
        text: "選擇預算型別",
        comboWidth: 120,
        type:"comboBox",
        commandName: "selectBudgetType",
        dropdownList:[
            {
                text:"成本預算",
                value: budgetType.cost
            },{
                text:"銷售預算",
                value:budgetType.sales
            },
        ],
        execute:(context,propertyName) => {
              console.log('選擇',propertyName)
        }
    },
    distributeTask: {
        title: "下發預算任務",
        text: "預算編制",
        iconClass: "distribute-icon",
        bigButton: true,
        commandName: "distributeTask",
        execute: function (context) {
           
        }
    },
    clearLocalData: {
        title: "清除本地快取",
        text: "清除本地快取",
        iconClass: "clear-local-icon",
        bigButton: true,
        commandName: "clearLocalData",
        execute: function () {
            localStorage.clear()
        }
    },
}
designer.setConfig(config)

icon相關程式碼,注意iconClass要新增相應的背景圖片。

.clear-local-icon {
  background: url("../assets/clear.png");
  background-size: 35px 35px;
}
.distribute-icon {
  background: url("../assets/distribute.png");
  background-size: 35px 35px;
}

上述三個子選單中的execute方法需要自定義,如選擇選擇預算型別後,模板需要進行切換。

2)設定模板

當“選擇預算型別”選擇“成本預算”時,載入cost.json檔案

當“選擇預算型別”選擇“銷售預算”時,載入sales.json檔案

let selectBudgetType = {
    text: "選擇預算型別",
    comboWidth: 120,
    type:"comboBox",
    commandName: "selectBudgetType",
    dropdownList:[
        {
            text:"成本預算",
            value: budgetType.cost
        },{
            text:"銷售預算",
            value:budgetType.sales
        },
    ],
    execute:(context,propertyName) => {
        if(propertyName){
            selectedBudget.value = propertyName
            loadTemplate(context,propertyName,taskId)
         }  
    },
    getState:(context)=>{
        return selectedBudget.value
    },
}

const loadTemplate = async (designer,fileName,taskId) => {
    let templateStr = await BusinessType.getTemplate(fileName)
    let template = JSON.parse(templateStr)
    let spread = designer.getWorkbook()
    spread.fromJSON(template)  
}

上述程式碼介紹了【選擇預算型別】下拉框選中的事件,選中後,匯入對應的json檔案,透過fromJSON進行匯入。

對於需要設定的模板,可以透過Designer中選單快速設計,其選單基本與Excel一致,對於熟悉Excel的使用者來說,真的很友好。

3)設定資料來源

下面小編以“銷售預算”模板為例,介紹如何設定資料來源:

點選“資料”tab,接下來點選“工作表繫結”,此時出現右側欄位列表Panel。發現欄位列表中存在“id”和“name ",這是因為在模板(sales.json)中已經設定好欄位。

此時進行資料繫結setDataSource():

const bindInitialData = (spread,type,taskId) => {
    // 繫結初始資料
    let data = defaultBudgetData[type]
    let source = new GC.Spread.Sheets.Bindings.CellBindingSource(data)
    spread.suspendPaint()
    let sheetCount = spread.getSheetCount()
    for(let i=0; i<sheetCount;i++){
        let sheet = spread.getSheet(i)
        sheet.setDataSource(source)
    }
    spread.resumePaint()
    taskId.value = data.id
}
const defaultBudgetData = {
  [budgetType.cost]: {
    id:`成本NV-${getNowTime()}`,//專案編號
    name:'',    //專案名稱
    city: '',   //專案所在地
    customer: '',    //客戶名稱
    price: 0        //本次報價
},
  [budgetType.sales]:{
    id: `銷售NV-${getNowTime()}`,
    name:''
  }
}

4)任務下發

(1)在任務下發前 ,需要確認預測因子,預測因子基於往年資料,確認接下來的銷售計劃。

(2)填寫預算名稱 。

(3)點選“預算編制”選單。

distributeTask: {
    title: "下發預算任務",
    text: "預算編制",
    iconClass: "distribute-icon",
    bigButton: true,
    commandName: "distributeTask",
    execute: function (context) {
        confirmDistribute(context,selectedBudget,distributeVisible)
    }
},

const confirmDistribute = (context,selectBudgetType,distributeVisible) => {
    /**預算任務下發時必填資訊校驗 */
    let sheet = context.getWorkbook().getSheet(0)
    let source = sheet.getDataSource().getSource()
    for(let key in source){
        if(!source[key]){
            ElMessage.error("紅色區域必填項資訊缺失")
            return
        }
    }
    // 確認是否下發編制任務
    ElMessageBox.confirm("確認下發預算編制任務嗎?","下發確認",{
        confirmButtonText:'確認',
        cancelButtonText:"取消",
        type:'warning'
    }).then(() => {
        // 確認下發,儲存當前預算模板,下發部門資訊
        saveBudgetRecord(context, selectBudgetType)
        distributeBudgetTask(context,distributeVisible)
    }).catch(() => {
        ElMessage({
            type:'error',
            message:'取消釋出'
        })
    })
}

在上述程式碼confirmDistribute()中,透過getDataSource()獲取資料來源,來判斷紅色區域的必填項是否填寫。當確認下發任務後,執行saveBudgetRecord 、distributeBudgetTask方法。

5)填寫任務

當確定下發任務後,對不同部門生成不同的編制連結。此彈窗可以參考程式碼中的OnlineDesigner.vue檔案。

部門經理獲取連結,開啟連結,顯示內容是自己部門區域預算明細填寫和實際填寫,此時,部門經理可以在左側藍色區域填寫,而其他單元格不能編輯,這個是怎麼做到的呢?具體可以參考這篇文章中第二點對少部分單元格可以編輯。

var defaultStyle = new GC.Spread.Sheets.Style();
defaultStyle.locked = false;
sheet.setDefaultStyle(defaultStyle, GC.Spread.Sheets.SheetArea.viewport);
// 設定第1行不可編輯
var style = new GC.Spread.Sheets.Style();
style.locked = true;
style.backColor = "red";
sheet.setStyle(0, -1, style);
// 設定表單保護
sheet.options.isProtected = true;  

介紹完單元格的許可權後,我們再來看下上圖中還有哪些值得說一說的功能。

(1)新增簽名

當經理設定完預算後,可以在區域總監單元格右鍵,看到多出來兩個選單“新增簽名”和“新增手寫簽名”。

所以接下來介紹如何在右鍵選單中新增選單並定義其事件,程式碼如下:

let signMenu = {
    text:"新增簽名",
    name:"signName",
    command:"signMenuCommand",
    workArea: "viewport"
}
spread.contextMenu.menuData.push(signMenu)

上述程式碼在spread.contextMenu.menuData中push了一條物件,結果就是可以在右鍵選單中看見“新增簽名選單” ,觀察到上述物件定義了command屬性,接下來定義“signMenuCommand”:

let signMenuCommand = {
    canUndo: true,
    execute: function(context,options,isUndo){
        if(isUndo){
            GC.Spread.Sheets.Commands.undoTransaction(context,options)
            return true
        }else{
            GC.Spread.Sheets.Commands.startTransaction(context,options)
            let {activeRow,activeCol,sheetName} = options
            let sheet = context.getSheetFromName(sheetName)
            sheet.getCell(activeRow,activeCol).value(user).backColor('#F7A711').font('bold normal 15px normal')
            GC.Spread.Sheets.Commands.endTransaction(context,options)
            return true
        }
    }
}
commandManager.register("signMenuCommand",signMenuCommand,null, false, false, false, false)

上述程式碼是SpreadJS中註冊命令的方法,並提供了撤銷機制。我們主要看else裡面的內容:首先從上下文context中獲取sheet物件,接著獲取單元格並設定內容、背景色、字型等。上述兩段程式碼就實現了在SpreadJS中在右鍵選單中新增選單,並完整相應的點選邏輯。

(2)新增手寫簽名

接下來,我們看看如何設定“新增手寫簽名”:

// 註冊簽名的右鍵選單
let commandManager = spread.commandManager()
let signMenu = {
    text:"新增手寫簽名",
    name:"handWriteName",
    command:"handWriteCommand",
    workArea: "viewport"
}
spread.contextMenu.menuData.push(signMenu)
let handWriteCommand = {
    canUndo: false,
    execute: function(context,options,isUndo){
        showWriteDialog.value = true
    }
}
commandManager.register("handWriteCommand",handWriteCommand,null, false, false, false, false)

新增選單和選單命令的方式與前文一致,不同的就是execute的執行邏輯。

最後,簽名設定後,就可以點選“提交預算”按鈕。

對了,如果資料不符合預期,可能會有紅色預警,比如

這個是SpreadJS的資料驗證功能,我們可以透過UI方式設定。如下圖所示:

6)編制完成

當所有部門經理填寫完預算後,就可以點選“編制完成”

此時點選“預算稽核”,預算型別設定為“銷售預算”,可以看到有一條待稽核的標籤,點進去看看。

看到了我們熟悉的頁面

此時點選“華東”sheet看看

這個時候就看到了華東部門經理填寫的銷售預測資料,這個時候點選右上角的“匯入年度實際銷售資料”看看。

嗯,表格內容基本上填寫完整了,這時候稽核員(副總經理)如果對銷售資料表示滿意,可以簽上自己的大名,就可以點選“稽核完畢了”

當四個sheet都“稽核完畢”,此時返回首頁,發現標籤變了。

這時候可以進行列印了。

最後

簡單的全面預算編制系統就算介紹完了。大家可以在Demo地址實際體驗下。總結下本文介紹的SpreadJS的幾個知識點:

1、自定義Designer選單

2、匯入模板

3、設定資料來源

4、獲取資料來源

5、自定義右鍵選單

6、單元格許可權

如果您想了解更多的資訊,歡迎點選這篇參考資料檢視。

擴充套件連結:

【乾貨放送】財務報表勾稽分析要點,一文讀盡!

為什麼你的財務報表不出色?推薦你瞭解這四個設計要點和!

純前端類 Excel 表格控制元件在報表勾稽分析領域的應用場景解析

相關文章