找回密码
 立即注册

QQ登录

只需一步,快速开始

葡萄城花卷
超级版主   /  发表于:2022-11-17 13:57  /   查看:3385  /  回复:1
本帖最后由 葡萄城花卷 于 2022-11-17 14:01 编辑

一、项目背景
企业日常工作中需要制作大量的报表,比如商品的销量、销售额、库存详情、员工打卡信息、保险报销、办公用品采购、差旅报销、项目进度等等,都需要制作统计图表以更直观地查阅。但是报表的制作往往需要耗费大量的时间,即使复用制作好的报表模版,一次次周期性对数据的复制粘贴操作也很耗人,同时模版在此过程中也会逐渐变得面目全非。
基于此,我们需要挖掘数据背后隐藏的关联信息,将人工的常规性操作抽离出来,使用工具和代码去实现,这个过程就称之为报表自动化。文内附前后端demo项目源码,家人们自行下载即可。
二、报表自动化的优势
报表自动化带来的价值有哪些呢?
1、节省时间,提高效率
身处信息**的时代,任何一家企业都有体量庞大、结构复杂、各种各样的数据,多类数据交互融合,对其进行分析经常伴随着大量人力资源的消耗。自动化报表通过合理的设计,独立出各个业务功能模块,后续重复引用该模块,实现重复操作的代码复用。
对于固定流程或逻辑的一些操作,计算机的执行速度是人力不可及的,为我们节省了时间,可以投入更有意义的工作。
2、降低出错率
人工操作总是受太多不可控因素影响,存在各种出错的潜在可能。与之相比,自动化意味着通过编码手段实现了持久化的逻辑、流程,经过重复的测试验证之后,便可完全信任该程序。在重复性的工作场景下机器产出的稳定性远高于人工操作。
3、时效性高
日报、周报、月报这种周期性的报表,人为操作很难控制时间的准确性,但是通过代码控制可以最大程度的保证其定点触发操作。
三、系统功能点
  • 任务配置灵活:支持根据业务需求,通过界面操作控制任务的启停状态,任务对应生成的文件类型等。
  • 报表模版设计自由:业务人员可根据对应任务配置的预览数据自定义报表模版,然后将其保存生效。
  • 前端预览:支持从前端预览报表详情。
  • 定时发送:定时生成报表文件并将其同步到微信群。
  • 支持多种类型:支持Excel、PDF、图表等文件类型的报表格式。
  • 数据自动抽取:动态读取数据库中的数据生成报表。
  • 模版和数据独立存储:使用在线表格设计器编辑模版,存储时只保存模版,数据从数据库加载。
四、方案设计1、整体流程
报表自动化的起点是能对接数据源,期间能自动化的生成事先设计好格式的报表,最终通过企业微信自动推送消息。具体流程可以分为3个步骤:
1.报表模版设计
2.对接数据源:从数据库中读取数据,动态适配数据模版。
3.自动化过程实现:利用定时任务,定时捞取数据,借助GcExcel生成对应类型的文件,通过对接企业微信的API,将文件同步到微信群。
2、技术栈3、具体实现3.1 数据库表设计
note:此为测试demo,故没有创建主键索引之外的索引。
3.1.1 任务配置列表 task_config
依赖Quartz组件实现定时任务。读取任务配置表中启动状态的任务配置,按照任务类型读取对应数据源的增量更新数据。





  1. SQL
  2. CREATE TABLE `order` (
  3.     `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  4.     `order_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单ID',
  5.     `order_amount` DECIMAL DEFAULT NULL DEFAULT 0 COMMENT '订单金额',
  6.     `order_discount` DECIMAL DEFAULT NULL DEFAULT 0 COMMENT '订单折扣金额',
  7.     `shipping_fee` DECIMAL DEFAULT NULL DEFAULT 0 COMMENT '运费',
  8.     `receiver_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名',
  9.     `receiver_state` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '省',
  10.     `receiver_city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '市',
  11.     `receiver_district` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '区',
  12.     `receiver_address` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '详细地址',
  13.     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  14.     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  15.     PRIMARY KEY (`id`) USING BTREE
  16. )  ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COMMENT='订单表';
复制代码


3.1.2 订单信息表 order
订单相关数据源信息,对应订单类的报表任务。





  1. SQL
  2. CREATE TABLE `waybill` (
  3.     `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  4.     `order_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单ID',
  5.     `waybill_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发货单ID',
  6.     `express_sn` VARCHAR(128) DEFAULT NULL DEFAULT '' COMMENT '物流单号',
  7.     `express_name` VARCHAR(64) DEFAULT NULL DEFAULT '' COMMENT '物流公司名称',
  8.     `receiver_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名',
  9.     `receiver_state` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '省',
  10.     `receiver_city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '市',
  11.     `receiver_district` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '区',
  12.     `receiver_address` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '详细地址',
  13.     `out_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发货时间',
  14.     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  15.     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  16.     PRIMARY KEY (`id`) USING BTREE
  17. )  ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COMMENT='发货单表';
复制代码


3.1.3 发货单表 waybill
发货单相关数据源信息,对应发货单类的报表任务。



  1. SQL
  2. CREATE TABLE `waybill` (
  3.     `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  4.     `order_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '订单ID',
  5.     `waybill_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '发货单ID',
  6.     `express_sn` VARCHAR(128) DEFAULT NULL DEFAULT '' COMMENT '物流单号',
  7.     `express_name` VARCHAR(64) DEFAULT NULL DEFAULT '' COMMENT '物流公司名称',
  8.     `receiver_name` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '收件人姓名',
  9.     `receiver_state` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '省',
  10.     `receiver_city` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '市',
  11.     `receiver_district` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '区',
  12.     `receiver_address` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '详细地址',
  13.     `out_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发货时间',
  14.     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  15.     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  16.     PRIMARY KEY (`id`) USING BTREE
  17. )  ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COMMENT='发货单表';
复制代码



3.2 功能拆解
1、前端实现
前端使用React框架,嵌入了SpreadJS组件,初始化时从后端读取任务配置列表数据并展示。可从前端配置任务规则,主要是配置模版信息。
2、后端
后端是一个SpringBoot项目,嵌入GcExcel组件对编辑、导出等操作,同时借助Quartz定时任务调度框架实现定时任务的管理,并接入了企业微信暴露的群机器人消息对接API,发送消息到企业微信群。
定时任务调度框架quartz
Quartz的三个核心概念:调度器、任务、触发器,三者之间的关系是:
一个作业,比较重要的三个要素就是Scheduler,JobDetail,Trigger;而Trigger对于Job而言就好比一个驱动器,没有触发器来定时驱动作业,作业就无法运行;对于Job而言,一个Job可以对应多个Trigger,但对于Trigger而言,一个Trigger只能对应一个Job,所以一个Trigger只能被指派给一个Job;如果你需要一个更复杂的触发计划,可以创建多个Trigger并指派它们给同一个Job。

image.png736638488.png

调度器的主要API
  1. Java
  2. //绑定jobDetail与trigger
  3. scheduler.scheduleJob(jobDetail, trigger);
  4. //检查JobDetail是否存在
  5. scheduler.checkExists(JobKey.jobKey(name, group))  
  6. //检查Trigger是否存在      
  7. scheduler.checkExists(TriggerKey.triggerKey(name, group))
  8. //删除jobDetail      
  9. scheduler.deleteJob(JobKey.jobKey(name, group))
  10. //立即执行一次指定的任务               
  11. scheduler.triggerJob(JobKey.jobKey(name, group), dataMap)
  12. //启动任务调度      
  13. scheduler.start();      
  14. //暂停指定的job         
  15. scheduler.pauseJob(jobKey);   
  16. //任务调度挂起,即暂停操作     
  17. scheduler.standby();   
  18. //关闭任务调度,同shutdown(false)     
  19. scheduler.shutdown();   
  20. //表示等待所有正在执行的Job执行完毕之后,再关闭Scheduler   
  21. scheduler.shutdown(true);   
  22. // 表示直接关闭Scheduler   
  23. scheduler.shutdown(false);
复制代码



定时任务出发规则:
1、使用cron表达式 定时发送
  1. Java
  2. Trigger trigger = TriggerBuilder.newTrigger()
  3.         .withIdentity("trigger1", "group1")
  4.         .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))  // 日历
  5.         .build();
复制代码

2、使用simpleTrigger触发器
为那种需要在特定的日期/时间启动,且以一个可能的间隔时间重复执行 n 次的 Job 所设计的。
  1. <span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">JavaScript
  2. </span><span class="hljs-operator" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(171, 86, 86); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">/</span><span class="hljs-operator" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(171, 86, 86); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">/</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">立即开始执行,</span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">2</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">秒执行一次,重复</span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">3</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">次,</span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">3</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">秒后结束执行(当重复次数或者结束时间有一个先达到时,就会停止执行)
  3. </span><span class="hljs-keyword" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(0, 0, 255); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">Trigger</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> </span><span class="hljs-keyword" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(0, 0, 255); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">trigger</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> </span><span class="hljs-operator" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(171, 86, 86); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">=</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> TriggerBuilder.newTrigger()
  4.                 .withIdentity("trigger1", "triggerGroup1")
  5.                 .startNow()
  6.                 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(</span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">2</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">).withRepeatCount(</span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">3</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">))
  7.                 .endAt(</span><span class="hljs-keyword" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(0, 0, 255); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">new</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> </span><span class="hljs-type" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(163, 21, 21); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">Date</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">(</span><span class="hljs-keyword" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(0, 0, 255); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">new</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> </span><span class="hljs-type" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(163, 21, 21); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">Date</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">().getTime() </span><span class="hljs-operator" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(171, 86, 86); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">+</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);"> </span><span class="hljs-number" style="transition-duration: 0.2s; transition-property: background-color, border-color, border-radius, padding-top, padding-bottom, margin-top, margin-bottom, color, opacity; color: rgb(136, 0, 0); line-height: 1.8; font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap;">3000</span><span style="color: rgb(68, 68, 68); font-family: &quot;Courier New&quot;, sans-serif; font-size: 12px; white-space: pre-wrap; background-color: rgb(245, 245, 245);">L))
  8.                 .build();
  9. </span>
复制代码




五、效果演示使用步骤说明:
整个页面布局可以分为两大部分,上半部分为从数据库中读取的任务配置列表,下半部分为SpreadJS的Designer模块。在前端配置任务规则后,后端服务会读取具体的任务配置信息,调度任务进行生产。整个操作可以分为以下几个步骤:
1)、读取任务配置数据到React表格中。
2)、选中特定的任务配置项,读取对应数据源的数据到Worksheet中展示。
3)、编辑报表任务模版并保存。
由于json文件是存储在mysql数据库表中的一个字段中,若字段太大会导致溢出且影响性能,故仅保存样式,后端进行数据源动态查询去适配生成报表。
一个完整的json对象示例如下所示:
  1. JSON
  2. {"version":"15.0.2","sheetCount":1,"customList":[],"sheets":{"order":{"name":"order","isSelected":true,"rowCount":20,"columnCount":13,"frozenTrailingRowStickToEdge":true,"frozenTrailingColumnStickToEdge":true,"theme":"Office","data":{"defaultDataNode":{"style":{"themeFont":"Body"}}},"rowHeaderData":{"defaultDataNode":{"style":{"themeFont":"Body"}}},"colHeaderData":{"defaultDataNode":{"style":{"themeFont":"Body"}}},"columns":[{"name":"receiverName","displayName":"姓名","size":60,"visible":true},{"name":"orderAmount","displayName":"订单金额","size":80,"visible":true},{"name":"orderDiscount","displayName":"订单折扣","size":60,"visible":true}],"leftCellIndex":0,"topCellIndex":0,"selections":{"activeSelectedRangeIndex":-1,"length":0},"autoGenerateColumns":false,"rowOutlines":{"items":[]},"columnOutlines":{"items":[]},"cellStates":{},"states":{},"outlineColumnOptions":{},"autoMergeRangeInfos":[],"charts":[{"name":"Chart 1","x":4,"y":4,"width":480,"height":300,"startRow":0,"startRowOffset":4,"startColumn":0,"startColumnOffset":4,"endRow":15,"endRowOffset":4,"endColumn":7,"endColumnOffset":36,"isSelected":true,"typeName":"2","chartSpace":{"typeName":"chartSpace","roundedCorners":false,"chart":{"title":{"tx":{"rich":{"p":[{"elements":[{"elementType":0,"t":"Amount","rPr":{"latin":{"typeface":"+mn-lt"},"sz":18.67,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}}],"pPr":{"defRPr":{"latin":{"typeface":"+mn-lt"},"sz":18.67,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}},"endParaRPr":{}}],"bodyPr":{},"lstStyle":{}}},"overlay":false,"spPr":{"noFill":true,"ln":{"noFill":true},"effectLst":{}}},"autoTitleDeleted":false,"plotArea":{"axes":[{"axisType":0,"axId":59604390,"delete":false,"majorTickMark":2,"minorTickMark":2,"tickLblPos":2,"title":null,"axPos":0,"scaling":{"orientation":1},"spPr":{"ln":{"solidFill":{"schemeClr":{"val":1,"lumMod":[15000],"lumOff":[85000]}}}},"numFmt":{"formatCode":"General"},"txPr":{"p":[{"elements":[{"elementType":0,"t":"","rPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}}],"pPr":{"defRPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}},"endParaRPr":{}}]},"auto":true,"lblOffset":0,"tickMarkSkip":1,"noMultiLvlLbl":true,"AxisGroup":0,"AxisType":0,"crosses":1,"crossAx":36407267},{"axisType":3,"axId":36407267,"delete":false,"majorTickMark":2,"minorTickMark":2,"tickLblPos":2,"title":null,"axPos":1,"scaling":{"orientation":1},"spPr":{"ln":{"solidFill":{"schemeClr":{"val":1,"lumMod":[15000],"lumOff":[85000]}}}},"numFmt":{"formatCode":"General"},"txPr":{"p":[{"elements":[{"elementType":0,"t":"","rPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}}],"pPr":{"defRPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}},"endParaRPr":{}}]},"majorGridlines":{"spPr":{"ln":{"solidFill":{"srgbClr":{"val":[217,217,217]}},"w":1},"effectLst":{}}},"AxisGroup":0,"AxisType":1,"crosses":1,"crossBetween":0,"crossAx":59604390}],"chartGroups":[{"chartType":6,"ser":[{"seriesType":0,"idx":0,"order":0,"cat":{"strRef":{"f":"order!$A$1:$A$2"}},"val":{"numRef":{"f":"order!$B$1:$B$2","numCache":{"formatCode":"General"}}},"shape":2,"invertIfNegative":false},{"seriesType":0,"idx":1,"order":1,"cat":{"strRef":{"f":"order!$A$1:$A$2"}},"val":{"numRef":{"f":"order!$C$1:$C$2","numCache":{"formatCode":"General"}}},"shape":2,"invertIfNegative":false}],"axId":[59604390,36407267],"barDir":1,"grouping":1,"gapWidth":150,"varyColors":false,"overlap":-27}],"spPr":{"noFill":true,"ln":{"noFill":true}}},"legend":{"legendPos":4,"spPr":{"noFill":true,"ln":{"noFill":true}},"txPr":{"p":[{"elements":[{"elementType":0,"t":"","rPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}}],"pPr":{"defRPr":{"latin":{"typeface":"+mn-lt"},"sz":12,"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}},"endParaRPr":{}}]}},"plotVisOnly":true,"dispBlanksAs":1,"dispNaAsBlank":false},"spPr":{"solidFill":{"schemeClr":{"val":0}},"ln":{"solidFill":{"schemeClr":{"val":1,"lumMod":[15000],"lumOff":[85000]}},"w":1}},"txPr":{"p":[{"elements":[{"elementType":0,"t":"","rPr":{"latin":{"typeface":"+mn-lt"},"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}}],"pPr":{"defRPr":{"latin":{"typeface":"+mn-lt"},"b":false,"solidFill":{"schemeClr":{"val":1,"lumMod":[65000],"lumOff":[35000]}}}},"endParaRPr":{}}]}},"useAnimation":false}],"preserveUnsupportedChartFlag":false,"printInfo":{"paperSize":{"width":850,"height":1100,"kind":1}},"shapeCollectionOption":{"snapMode":0},"index":0}},"sheetTabCount":0,"pivotCaches":{},"i0c":0}
复制代码

1 个回复

倒序浏览
妄想社成员活字格认证
银牌会员   /  发表于:2023-1-5 17:10:09
沙发
6
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 立即注册
返回顶部