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

前言

在現代商業環境中,預測銷售數據和實際成本是每個公司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 表格控件在報表勾稽分析領域的應用場景解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章