突破管理瓶颈:基于前端技术的全面预算编制系统解析

前言

在现代商业环境中,预测销售数据和实际成本是每个公司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 表格控件在报表勾稽分析领域的应用场景解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/701699.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从PCB到芯片的电源控制

随着硅技术的进步&#xff0c;ASIC 密度更高&#xff0c;逻辑电压随之降低。较低的电压与较高的电流要求相结合&#xff0c;要求电源具有更严格的容差。从 PCB 到芯片的电源控制是本研究的主题。使用典型旁路值的频率扫描仿真表明&#xff0c;分立封装电容器并不是降低芯片电源…

Java面试八股之子类可以从父类继承哪些内容

Java子类可以从父类继承哪些内容 Java子类可以从父类继承以下内容&#xff1a; 属性&#xff08;Fields/Variables&#xff09;&#xff1a; public&#xff1a;子类可以继承父类所有的public修饰的属性。 protected&#xff1a;子类可以继承父类所有的protected修饰的属性…

Redisson 实现分布式锁

1、相关配置 Component public class RedissonDistributedLock {Autowiredprivate RedissonClient redissonClient;public boolean tryLock(String lockKey, long expireTime, long waitTime) throws InterruptedException {RLock lock redissonClient.getLock(lockKey);retu…

C++ 引用 - 引用的特点|在优化程序上的作用

引用是C 的一个别名机制&#xff0c;所谓别名&#xff0c;就是同一块内存共用多个名字&#xff0c;每个名字都指的是这片空间&#xff0c;通过这些别名都能访问到同样的一块空间。 就像鲁迅和周树人是同一个人。 ——鲁迅 一、引用的基本用法 int a 10; int& ref a; // …

Python实现base64加密/解密

实现原理&#xff1a;导入base64库 一、加密 import base64# 加密 username "admin" base64_username base64.b64encode(username.encode(utf-8)).decode() print(base64_username) password "123" base64_password base64.b64encode(password.encod…

腾讯云大数据ES Serverless

Elasticsearch&#xff1a;日志和搜索场景首选解决方案。 技术特点&#xff1a;分布式、全文搜索和数据分析引擎&#xff0c;可以对海量数据进行准实时地存储、搜索和统计分析。 ES的技术栈一共包含四个组件&#xff1a; 其中最核心的是Elasticsearch&#xff0c;可用于数据…

halcon算子之prepare_object_model_3d详解

为某一操作准备三维对象模型。 Description 操作符prepare_object_model_3d准备3D对象模型ObjectModel3D,用于下面目的中给出的操作。它计算操作所需的值并将其存储在ObjectModel3D中,从而加快了后续操作。没有必要调用prepare_object_model_3d。但是,如果要多次使用3D对象…

低成本和颜值兼顾的 HomeLab 设备:HPE MicroServer Gen10(二)

本篇文章&#xff0c;继续分享另外一台端午假期折腾的设备&#xff0c;HP MicroServer Gen10 一代。同样分享下我的折腾思路&#xff0c;希望能够帮助到有类似需求的你。 写在前面 Gen10 “标准版”&#xff08;第一代&#xff09;和 Plus 版本设计风格一致&#xff0c;同样颜…

onlyoffice在线预览加载优化

背景&#xff1a; 使用容器部署onlyoffice到linux服务器&#xff0c;使用内网访问速度还可以接受&#xff0c;但是如果放到外网路径访问起来&#xff0c;速度就会很慢&#xff0c;甚至加载失败&#xff1b; 优化方案&#xff1a; 预览的过程排除网络因素&#xff0c;可以发现打…

(1)图像识别yolov5—安装教程

目录 1、安装YOLOv5: 2、下载预训练模型: 3、识别示例图片: 1、安装YOLOv5: 首先,你需要在你的计算机上下载 YOLOv5 的文件包,下载链接:https://github.com/ultralytics/yolov5。下载后对压缩文件进行解压。 通常使用 YOLOv5 识别物体,需要安装必要的 依赖…

海外媒体发稿渠道和方法有哪些?如何选择靠谱的国外媒体发稿服务商?

在选择海外媒体发稿服务商时&#xff0c;以下是一些关键点可以帮助您找到靠谱的服务商&#xff1a; 服务商的经验和口碑&#xff1a;查找该服务商在行业内的声誉和客户评价。拥有丰富经验和良好口碑的服务商通常更可靠。 媒体资源和覆盖范围&#xff1a;了解服务商所能提供的媒…

如何快速学会互联网运营?

答案只有一个&#xff0c;那就是选项目&#xff0c;跟团队&#xff0c;直接实操&#xff0c;比你花几千几万的学费更实在。早上就有好几位伙伴加入&#xff0c;咱们团队几乎每天都有干货分享。 作为千益畅行旅游卡源头&#xff0c;咱们没有套路&#xff0c;唯有靠谱得人心。你…

Python AI 编程助手:Fitten Code插件

一. 简介 今天为大家推荐一款适配了 Viusal Studio&#xff0c;VS Code(本文使用)&#xff0c;JetBrains 系列(本文使用)以及Vim等多种编译器环境的插件 Fitten Code&#xff0c;Fitten Code 是由非十大模型驱动的 AI 编程助手&#xff0c;它可以自动生成代码&#xff0c;提升…

肾合能量不足?揭秘手心热出汗的真相

想象一下&#xff0c;我们的身体如同一座精密的城堡&#xff0c;城堡内的每一个房间都代表着一个器官&#xff0c;而城堡的守卫——气血&#xff0c;则是维系城堡和谐稳定的重要力量。当城堡中的守卫力量不足&#xff0c;或是城堡内的环境出现紊乱时&#xff0c;城堡的某个角落…

React基础教程(07):条件渲染

1 条件渲染 使用条件渲染&#xff0c;结合TodoList案例&#xff0c;进行完善&#xff0c;实现以下功能&#xff1a; 当列表中的数据为空的时候&#xff0c;现实提示信息暂无待办事项当列表中存在数据的时候&#xff0c;提示信息消失 这里介绍三种实现方式。 注意这里的Empty是…

中霖教育怎么样?中霖教育靠谱吗?

中霖教育作为一家正经的教育机构&#xff0c;从初始到现在&#xff0c;学员的高通过率反映了我们的教学质量。 我们的目的是帮助学员通过考试&#xff0c;所以完全是根据考试来授课教学。在备考过程中掌握答题技巧&#xff0c;结合押题提分&#xff0c;只要学生严格按照老师的…

NET 使用UDP协议

1.简单的使用UDP对接示例&#xff1a; /// <summary>/// 定时器&#xff0c;每秒定时获取是否有udp数据/// </summary>public DispatcherTimer Timer1 new DispatcherTimer() { Interval new TimeSpan(0, 0, 0, 1) }; public UdpClient SocketUDP { get; set; }/…

Golang发送邮件性能如何优化?有哪些方法?

Golang发送邮件的认证流程&#xff1f;怎么设置smtp服务器发信&#xff1f; Golang作为一种高效的编程语言&#xff0c;自然也被广泛应用于发送邮件的场景。然而&#xff0c;如何优化Golang发送邮件的性能成为了一个关键问题。AokSend将探讨一些优化方法&#xff0c;以提高Gol…

高压电工作业历年试题分享(含答案)

单项选择题 1、(B)主要用于接通或断开隔离开关&#xff0c;跌落保险&#xff0c;装卸携带型接地线以及带电测量和试验等工作。A.验电器 B.绝缘杆 C.绝缘夹钳 D.绝缘手套 2、(C)的作用是用于作拉线的连接、紧固和调节。 A.支持金具 B.连接金具 C.拉线金具 D.保护金具 3、(C)是利…

python 魔术方法备忘录

python 魔术方法备忘录 网上收集了一些&#xff0c;列出了比较常用的&#xff0c;特别是第一张。 Python中的魔术方法&#xff08;Magic Methods&#xff09;&#xff0c;也被称为特殊方法&#xff08;Special Methods&#xff09;或双下划线方法&#xff08;Dunder Methods&a…