本文字数:6277字
预计阅读时间:16分钟
报告模版的增删改查,实现报告的导出、编辑、预览、删除、复制等功能;
报告导出,实现自定义选择数据源导出真实数据生成的最终项目报告。
可以说,自定义报告导出平台是一个简易的线上 ppt 编辑器和大数据嵌入两个功能的结合,所以主要分为两个大的功能块去实现:模板编辑和报告导出。
图2.1-1:自定义报告编辑器-布局配置
图2.1-2:自定义报告编辑器-内容配置
最终的报告根据用户选择的项目和数据统计区间,嵌入模板中所插入字段和图表的地方,最后再导出填充数据后的pptx格式的报告。该功能主要通过开源库 PptxGenJS[1] 来完成。该库可以根据大量的配置项来生成与 html 页面内容对应的 pptx 文件,所生成的 pptx 文件所有内容都可以自行调整编辑。图表生成的使用方法类似 echarts ,不同的是 echarts 是在页面上进行渲染, PptxGenJS[1] 是导出一个文件。
图2.2:PptxGenJs库
报告的导出需要每个元素的具体定位,元素内容,元素样式等多种数据,为了方便导出一比一的报告,根据 pptxGen 库导出时所需数据以及页面展示所需内容,每一张幻灯片的数据包含4个部分:layout、
content、slideTitle、background。
layout:slide 模块栅格布局,包括布局宽高以及导出所需位置数据;
content:slide 包含的内容和样式数据;
slideTitle:slide 标题和 logo 区域的内容,样式数据;
background:背景。
每新增一张幻灯片,便会在这个对象里面新生成一套数据——
// 第一张幻灯片
'0': {
layout: frameLayouts[0],
content: contentModules[0],
slideTitle: titleItem,
background: bgStart,
},
// 第二张幻灯片
'1': {
layout: frameLayouts[1],
content: contentModules[1],
slideTitle: titleItem,
background: bgMenu,
},
除此之外,由于需要维护一个包含整个 ppt 文件的幻灯片位置数据的变量layouts,整个报告的数据结构如下所示:
const newReportTemplate = {
layouts: Layout[],
'0': {
layout: frameLayouts,
content: contentModules
slideTitle: titleItem,
background: string,
},
'1': {
layout: frameLayouts,
content: contentModules,
slideTitle: titleItem,
background: string,
},
};
这一部分,主要介绍自定义报告导出平台的4个重点功能。
画布主要实现,自定义编辑内容的展示,以及可选定需要编辑的模块,这部分为简单的前端展示就不在此赘述。除此之外,实现一个简易的在线报告编辑器,还需要解决的问题有:
画布,缩略图,预览图三者大小不同,要求用户编辑的内容能够等比例缩放地显示在缩略图和预览图上;
拖动缩放浏览器时,需求实现与 powerpoint 类似的,仅等比例缩放画布,两侧功能区不做自适应维持原本的大小;
因为浏览器能显示最小字号为 12px,缩略图显示所需字号更小;
浏览器文字的字号为离散分布的的,无法依据画布的大小变化而变化,也无法做到等比例缩放。
可以使用 transform 这一简单的 css 属性解决上述所有问题。transform 属性可以用于对元素进行旋转,缩放,移动,倾斜,很多时候会用来实现前端的动画效果。而 transform 的取值之一 scale 是用于对元素进行 2d 层面的缩放,这一特性正好适用于实现画布,简单的代码如下所示:
.shrink {
transform: scale(0.5)
}
// 使用行内样式实现动态改变
style={{
transform: `scale(${shrink})`,
}}
在实现时,画布所有样式统一使用1920*1080作为基础尺寸的设计稿。对于画布的缩放,主要通过 document.body.clientWidth 和 document.body.clientHeight 获得浏览器视窗宽高,从而计算浏览器视窗宽高和固定基础尺寸之间的比例来得到应该缩放的比例,使用 transform 属性来进行缩放,这样避免写多套样式也能解决缩放自适应等问题。对于缩略图和预览图的缩放,缩略图和预览图的缩放比例是根据设计图数据的一个定值来进行缩放,将整个画布内容等比例缩小,获取可视区域大小部分代码如下:
function getWindowWidth() {
setWindowWidth(document.body.clientWidth - EDIT_AREA_WIDTH);
setWindowHeight(document.body.clientHeight);
setHeight(((document.body.clientWidth - EDIT_AREA_WIDTH) / 16) * 9);
}// 16/9 为传统ppt画布尺寸比例
模板编辑平台存在大量且频繁的组件间通信,且需要满足编辑内容在画布和缩略图以及预览图上都实现实时更新,也为了最后导出报告时能直接获取整体数据,所以采用 dvajs[2] 来进行状态管理。
如图所示,用户在填写表单进行内容编辑后,需要将表单数据传递到画布和缩略图两个大组件进行实时更新展示。而当选中画布某个模块儿和选中一张幻灯片时,需要在表单区域展示选中区域已有的内容,即表单回填。再者,选中的幻灯片也需要在画布中实时展示。由此可见,模板编辑器部分存在大量的组件间通信,为了方便状态管理,采用 dvajs[2] 这一轻量框架。
图4.2-1:自定义报告组件间通信
dva 是一个基于 redux[3] 和 redux -saga[4] 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react - router[5] 和 fetch[6],所以也可以理解为一个轻量级的应用框架。
dva 基于 redux ,配合 umi ,很适合在 react 项目中完成大量的组件间通信,并进行状态管理。使用 dva ,可以将编辑区表单的内容存在 store 中,方便画布和缩略图进行视图更新。而选中幻灯片,或是选中幻灯片的某个模块儿这一事件也可以传递到编辑区,完成表单的回填。最重要的,是可以轻松的管理每个幻灯片和每个模块儿编辑后的内容,便于使用 pptxGen 进行报告的导出。
图4.2-2:使用dva进行状态管理并完成组件间通信
为了实现和 powerpoint 左侧幻灯片列表相似的,例如拖动排序、新增幻灯片、删除幻灯片等功能,采用开源库 react-grid-layout[7] 来进行页面布局。
该库可以实现子元素(幻灯片实例)在规定网格范围内自由拖动,每一个子元素是一个对象,由唯一的 key 名和4个位置大小参数组成。
{
i: string, // 唯一key
x: number, // 子元素在网格中水平方向的位置
y: number, // 子元素在网格中垂直方向的位置,在本项目中,决定幻灯片排列的位置,同时也是每张幻灯片的编号
w: number, // 子元素的宽
h: number, // 子元素的高
},
为了实现幻灯片列表的效果,cols参数设置为1,表示该网格只有一列,这样就只能上下拖动,不回出现两张幻灯片并列的情况。当网格只有1列的时候,子元素参数中的 y 就决定了该元素在这一列中的顺位,也决定了该幻灯片的编号,也就是说,幻灯片的顺序和编号都是依赖 y 来决定的。所以,多个幻灯片就是包含多个这样的对象的一个数组,数组元素的 index 和幻灯片的生成时间有关, i 是该幻灯片的唯一标识符,决定幻灯片的内容,y 决定幻灯片在列表中的顺序和位置,当移动某一张幻灯片时,触发回调函数修改 y,从而达到拖动并排序的效果。部分代码如下所示:
{reportLayouts.map((item, index) => {
return (
);
})}
4.4.1 报告生成
报告生成主要依赖于 PptxGenJS[1] 。该库的基本使用方法如图所示:
在本项目,主要使用思路是,遍历 slideContents 的每一项来生成每一个 slide对象,再遍历每一个幻灯片中的每一项(每一个模块),通过判断模块的 type,来执行不同的 add... 函数。例如,若 type 为 text,就执行 addText 函数来向幻灯片中插入编辑的文本。生成幻灯片中的对应内容,从而生成整个报告,如下图所示。
图4.4-2:报告导出基本逻辑
除此之外,在类型为图表时,需要配置不同种类的图表样式参数,类型为图片时,需要根据所在模块大小对图片进行一定的缩放处理。
slideContent 为多张幻灯片所组成的对象,每一张幻灯片的内容数据结构如下所示,主要包括幻灯片某一模块的具体内容,样式数据,图片尺寸(以便进行缩放)三部分内容:
const contentItem = {
type: 'text', // 根据不同的类型执行不同的add函数
details: '', // 具体内容
style: {
fontSizeNum: '16px',
fontWeightNum: 400,
colorNum: '#000000',
}, // 文本样式
picSize: {
picWidth: 0,
picHeight: 0,
}, // 图片尺寸,用于缩放
chartTitle: '',
chartPortName: '',
};
const contentModules = [contentItem]
在模块类型为文字或图表的时候,需要进行数据嵌入。在导出的前一步,用户选择对应项目和统计时间后,会请求接口获取模板中所插入字段和图表的对应数据,生成数据所对应的图表,并以返回数据来替换文本中的插入字段,最后嵌入嵌入数据后的报告。
在嵌入数据功能的实现上,存在两个问题:
模板会插入大量的图表和字段,每个图表和字段都需要请求不同的接口,这就意味着会一次性请求几十个接口,由于浏览器会限制一次性请求接口的数量,故导致导出效率低。
插入的图表和字段需要在接口返回数据后,嵌入报告中对应的位置,需要做到一一对应。
Graphql[8]
解决请求过多的问题,采用 graphql 来发起请求。graphql 可以采用字符串拼接的形式将多个请求合并为一个请求发送。其返回结果,按照前端所需变量一次性返回一个对象,接口的返回值作为以接口名为 key 的 value 。
// const client = ...
client
.query({
query: gql`
query GetLocations(baseQueryReq: {
projectId: 1,
startDate: "2023-2-24",
endDate: "2023-2-25",
}) {
id
name
description
photo
}
`,
})
.then((result) => console.log(result));
返回结果示例如下所示:
data = {
getData: [],
getTime: '2023-12-25',
getAge: 18
}
字典
既要在接口请求时,找到插入图表和字段的对应的接口拼入 graphql 里面,也要在接口数据返回后,能将对应的数据用于生成图表或替代字段,考虑维护多个对象当作字典,以便一一对应,例如如下图表和接口转换字典和图表变量转换字典。
// 图表接口转换
const chartPortNameTans = {
天气晴雨趋势表: 'getDailyWeather',
每日工作时长趋势表: 'getDailyWorkTime',
每日工作内容分布: 'getDailyWorkType',
采购成本排行: 'getCostRank',
销售业绩排行: 'getSaleRank',
};
//图表变量转换
const chartPortNameTans = {
天气晴雨趋势表: ['date', 'weather'],
每日工作时长趋势表: ['date', 'workTime'],
每日工作内容分布: [], // 空为分布饼图,特殊处理
采购成本排行: ['name', 'cost'],
销售业绩排行: ['name', 'sale'],
};
例如,图表的生成总共维护2个对象:
图表接口转换字典:将图表转换为所对应的接口,拼入 graphql 请求语句。
图表所需数据变量名字典:待数据返回后,通过图表接口转换字典找到对应的返回值,再通过图表变量转换字典从返回结果中找到图表统计需要的字段,再对这些数据进行处理。
字段的嵌入替换和图表基本相同,不同的是,图表所需要的变量多数情况下都是2个以上的变量,而字段是一对一进行替换的。
自定义报告导出平台,实现了幻灯片编辑的基础功能,如图片编辑、文字编辑、图表插入等。
能够完成在线ppt模板的编辑,并且在选择对应统计对象和统计区间后,可以直接导出模板嵌入数据后生成的完整报告。
针对不同的项目,不同的产品,不同的统计区间,只需要1分钟就能生成一份完美的 ppt ,将人从重复的写报告和繁杂的数据统计中解放出来。
【附录】
[1] https://github.com/gitbrent/PptxGenJS
[2] https://dvajs.com/
[3] https://link.zhihu.com/?target=https%3A//github.com/reduxjs/redux
[4] https://link.zhihu.com/?target=https%3A//github.com/redux-saga/redux-saga
[5] https://link.zhihu.com/?target=https%3A//github.com/ReactTraining/react-router
[6] https://link.zhihu.com/?target=https%3A//github.com/github/fetch
[7] https://github.com/react-grid-layout
[8] https://www.apollographql.com/docs/react/