当前位置: 首页 > news >正文

基于前端技术实现的全面预算编制系统

前言

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

相关文章:

  • 利用RWKV-Runner初步感受一下ai的世界
  • Linux的学习之路:3、基础指令(2)
  • SpringBoot集成WebSocket(实时消息推送)
  • PL/SQL的词法单元
  • ida调试技巧-通过修改zf寄存器的值绕过简单反调试
  • Linux manim安装
  • 幻兽帕鲁服务器价格太卷了,4核16G游戏联机服务器价格24元
  • String类相关oj练习
  • amazon中sns的使用
  • Android ViewBinding 使用
  • 【QT入门】 Qt自定义信号后跨线程发送信号
  • 基于大语言模型的云故障根因分析|顶会EuroSys24论文
  • 操作系统系列学习——多级页表与快表
  • k8s入门到实战(十四)—— Helm详细介绍及使用
  • Java实现猜数字游戏:编程入门之旅
  • 实现windows 窗体的自己画,网上摘抄的,学习了
  • [case10]使用RSQL实现端到端的动态查询
  • ➹使用webpack配置多页面应用(MPA)
  • express.js的介绍及使用
  • httpie使用详解
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • mockjs让前端开发独立于后端
  • Rancher-k8s加速安装文档
  • React+TypeScript入门
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • vue-loader 源码解析系列之 selector
  • windows下如何用phpstorm同步测试服务器
  • 包装类对象
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 微信开源mars源码分析1—上层samples分析
  • 《码出高效》学习笔记与书中错误记录
  • C# - 为值类型重定义相等性
  • Linux权限管理(week1_day5)--技术流ken
  • 大数据全解:定义、价值及挑战
  • 扩展资源服务器解决oauth2 性能瓶颈
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #mysql 8.0 踩坑日记
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附源码)springboot太原学院贫困生申请管理系统 毕业设计 101517
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (译)计算距离、方位和更多经纬度之间的点
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .Net8 Blazor 尝鲜
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .net通用权限框架B/S (三)--MODEL层(2)
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • /etc/skel 目录作用