自定义报告导出平台:几分钟生成你的汇报ppt
本文字数:6277字
预计阅读时间:16分钟
一、背景
1.1 背景
在房地产 2B 业务场景中,有这样一种情况,每个季度或者每个月,甚至每个周,交付侧需要定期产出一份报告向客户开发商汇报各种产品的使用情况,投用效率,收益比率等等,制作这样一份报告通常会耗费掉大量时间,需要排版布局,需要统计数据制作图表,需要分析总结。我们知道报告样式可以直接套用模板,如果更进一步,有这么一份模板,模板包括了所需的文字,图表,你需要做的只是选择数据源,就可以生成对应的报告,这样的模板可以大大提高工作效率,将一天的工作量缩短到简单的1分钟就能够完成。针对这个诉求,自定义报告导出平台应运而生,在需要定期汇报的情况下都可以快捷简单迅速地生成一份完整的汇报 ppt ,从“苦 ppt 久矣”的工作中解放出来。
1.2 主要功能
报告模板的自定义编辑平台,实现文字编辑、图片插入、图表插入、字段插入、背景更换等;
报告模版的增删改查,实现报告的导出、编辑、预览、删除、复制等功能; 报告导出,实现自定义选择数据源导出真实数据生成的最终项目报告。
二、整体思路
2.1 模板编辑
主要用表单提交的方式完成用户编辑 ppt 这一交互。用户可以自由的选择页面布局模板,制定页面背景,可以填充图片,文字,图表等各种内容。其中,在编辑内容的时候,用户选中需要编辑的块,再通过右侧的表单来进行内容填充,编辑的结果会实时展示在画布上,而选中块的已有内容也会回填到表单中,如下图所示:
图2.1-1:自定义报告编辑器-布局配置
2.2 报告导出
三、数据结构
、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.1 画布
画布,缩略图,预览图三者大小不同,要求用户编辑的内容能够等比例缩放地显示在缩略图和预览图上; 拖动缩放浏览器时,需求实现与 powerpoint 类似的,仅等比例缩放画布,两侧功能区不做自适应维持原本的大小; 因为浏览器能显示最小字号为 12px,缩略图显示所需字号更小; 浏览器文字的字号为离散分布的的,无法依据画布的大小变化而变化,也无法做到等比例缩放。
.shrink {
transform: scale(0.5)
}// 使用行内样式实现动态改变
style={{
transform: `scale(${shrink})`,
}}
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画布尺寸比例
4.2 状态管理
dva 是一个基于 redux[3] 和 redux -saga[4] 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react - router[5] 和 fetch[6],所以也可以理解为一个轻量级的应用框架。
4.3 拖动排序布局
为了实现和 powerpoint 左侧幻灯片列表相似的,例如拖动排序、新增幻灯片、删除幻灯片等功能,采用开源库 react-grid-layout[7] 来进行页面布局。
该库可以实现子元素(幻灯片实例)在规定网格范围内自由拖动,每一个子元素是一个对象,由唯一的key 名和4个位置大小参数组成。
{
i: string, // 唯一key
x: number, // 子元素在网格中水平方向的位置
y: number, // 子元素在网格中垂直方向的位置,在本项目中,决定幻灯片排列的位置,同时也是每张幻灯片的编号
w: number, // 子元素的宽
h: number, // 子元素的高
},
<ResponsiveGridLayout
className={styles.layout} // 需单独引入该样式
layout={reportLayouts}
onLayoutChange={onLayoutChange} // 拖动改变布局时的回调函数
cols={1} // 网格规定为1列,实现垂直方向的拖动排序
rowHeight={112}
width={220}
>
{reportLayouts.map((item, index) => {
return (
<div
key={item.i}
data-grid={item}
>
</div>
);
})}
</ResponsiveGridLayout>
4.4 报告导出
以上3个部分都是模板编辑相关的内容,接下来介绍报告导出的部分实现。报告导出主要分为两个阶段,第一步是将模板中插入的字段和图表都嵌入对应的数据,第二步是线上编辑的报告模板导出为 pptx 格式文件,和嵌入的统计数据一同导出。
4.4.1 报告生成
const contentItem = {
type: 'text', // 根据不同的类型执行不同的add函数
details: '', // 具体内容
style: {
fontSizeNum: '16px',
fontWeightNum: 400,
colorNum: '#000000',
}, // 文本样式
picSize: {
picWidth: 0,
picHeight: 0,
}, // 图片尺寸,用于缩放
chartTitle: '',
chartPortName: '',
};const contentModules = [contentItem]
4.4.2 嵌入数据
// 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
}
// 图表接口转换
const chartPortNameTans = {
天气晴雨趋势表: 'getDailyWeather',
每日工作时长趋势表: 'getDailyWorkTime',
每日工作内容分布: 'getDailyWorkType',采购成本排行: 'getCostRank',
销售业绩排行: 'getSaleRank',
};//图表变量转换
const chartPortNameTans = {
天气晴雨趋势表: ['date', 'weather'],
每日工作时长趋势表: ['date', 'workTime'],
每日工作内容分布: [], // 空为分布饼图,特殊处理采购成本排行: ['name', 'cost'],
销售业绩排行: ['name', 'sale'],
};
图表接口转换字典:将图表转换为所对应的接口,拼入 graphql 请求语句。 图表所需数据变量名字典:待数据返回后,通过图表接口转换字典找到对应的返回值,再通过图表变量转换字典从返回结果中找到图表统计需要的字段,再对这些数据进行处理。
效果展示
总结
【附录】