Merge pull request #2090 from dataease/pr@dev@feat_panel-view-export

feat: 视图明细导出支持附带明细截图
This commit is contained in:
王嘉豪 2022-04-10 16:49:45 +08:00 committed by GitHub
commit 21f5fb286b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 30 deletions

View File

@ -10,6 +10,7 @@ import io.dataease.commons.constants.DePermissionType;
import io.dataease.commons.constants.ResourceAuthLevel; import io.dataease.commons.constants.ResourceAuthLevel;
import io.dataease.controller.handler.annotation.I18n; import io.dataease.controller.handler.annotation.I18n;
import io.dataease.controller.request.panel.PanelGroupRequest; import io.dataease.controller.request.panel.PanelGroupRequest;
import io.dataease.controller.request.panel.PanelViewDetailsRequest;
import io.dataease.dto.PermissionProxy; import io.dataease.dto.PermissionProxy;
import io.dataease.dto.authModel.VAuthModelDTO; import io.dataease.dto.authModel.VAuthModelDTO;
import io.dataease.dto.panel.PanelGroupDTO; import io.dataease.dto.panel.PanelGroupDTO;
@ -22,6 +23,8 @@ import org.apache.shiro.authz.annotation.Logical;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -100,4 +103,12 @@ public class PanelGroupController {
return panelGroupService.queryPanelComponents(id); return panelGroupService.queryPanelComponents(id);
} }
@ApiOperation("导出仪表板视图明细")
@PostMapping("/exportDetails")
@I18n
public void exportDetails(@RequestBody PanelViewDetailsRequest request, HttpServletResponse response) throws IOException {
panelGroupService.exportPanelViewDetails(request,response);
}
} }

View File

@ -0,0 +1,28 @@
package io.dataease.controller.request.panel;
import lombok.Data;
import java.util.List;
/**
* Author: wangjiahao
* Date: 2022/4/8
* Description:
*/
@Data
public class PanelViewDetailsRequest {
private String viewName;
private String[] header;
private List<String[]> details;
private String snapshot;
private int snapshotWidth;
private int snapshotHeight;
}

View File

@ -13,6 +13,7 @@ import io.dataease.commons.utils.TreeUtils;
import io.dataease.controller.request.authModel.VAuthModelRequest; import io.dataease.controller.request.authModel.VAuthModelRequest;
import io.dataease.controller.request.dataset.DataSetTableRequest; import io.dataease.controller.request.dataset.DataSetTableRequest;
import io.dataease.controller.request.panel.PanelGroupRequest; import io.dataease.controller.request.panel.PanelGroupRequest;
import io.dataease.controller.request.panel.PanelViewDetailsRequest;
import io.dataease.dto.PanelGroupExtendDataDTO; import io.dataease.dto.PanelGroupExtendDataDTO;
import io.dataease.dto.authModel.VAuthModelDTO; import io.dataease.dto.authModel.VAuthModelDTO;
import io.dataease.dto.chart.ChartViewDTO; import io.dataease.dto.chart.ChartViewDTO;
@ -20,6 +21,7 @@ import io.dataease.dto.dataset.DataSetTableDTO;
import io.dataease.dto.panel.PanelGroupDTO; import io.dataease.dto.panel.PanelGroupDTO;
import io.dataease.dto.panel.linkJump.PanelLinkJumpBaseRequest; import io.dataease.dto.panel.linkJump.PanelLinkJumpBaseRequest;
import io.dataease.dto.panel.po.PanelViewInsertDTO; import io.dataease.dto.panel.po.PanelViewInsertDTO;
import io.dataease.excel.utils.EasyExcelExporter;
import io.dataease.exception.DataEaseException; import io.dataease.exception.DataEaseException;
import io.dataease.i18n.Translator; import io.dataease.i18n.Translator;
import io.dataease.listener.util.CacheUtils; import io.dataease.listener.util.CacheUtils;
@ -30,14 +32,24 @@ import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.pentaho.di.core.util.UUIDUtil; import org.pentaho.di.core.util.UUIDUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.Base64Utils;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -51,6 +63,7 @@ public class PanelGroupService {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
private final static String DATA_URL_TITLE = "data:image/jpeg;base64,";
@Resource @Resource
private PanelGroupMapper panelGroupMapper; private PanelGroupMapper panelGroupMapper;
@Resource @Resource
@ -110,7 +123,7 @@ public class PanelGroupService {
public PanelGroup saveOrUpdate(PanelGroupRequest request) { public PanelGroup saveOrUpdate(PanelGroupRequest request) {
String userName = AuthUtils.getUser().getUsername(); String userName = AuthUtils.getUser().getUsername();
String panelId = request.getId(); String panelId = request.getId();
if(StringUtils.isNotEmpty(panelId)){ if (StringUtils.isNotEmpty(panelId)) {
panelViewService.syncPanelViews(request); panelViewService.syncPanelViews(request);
} }
if (StringUtils.isEmpty(panelId)) { // 新建 if (StringUtils.isEmpty(panelId)) { // 新建
@ -310,46 +323,46 @@ public class PanelGroupService {
return newPanelId; return newPanelId;
} }
public String newPanel(PanelGroupRequest request){ public String newPanel(PanelGroupRequest request) {
String newPanelId = UUIDUtil.getUUIDAsString(); String newPanelId = UUIDUtil.getUUIDAsString();
String newFrom = request.getNewFrom(); String newFrom = request.getNewFrom();
String templateStyle = null; String templateStyle = null;
String templateData = null; String templateData = null;
String dynamicData = null; String dynamicData = null;
if(PanelConstants.NEW_PANEL_FROM.NEW.equals(newFrom)){ if (PanelConstants.NEW_PANEL_FROM.NEW.equals(newFrom)) {
}else{ } else {
//内部模板新建 //内部模板新建
if(PanelConstants.NEW_PANEL_FROM.NEW_INNER_TEMPLATE.equals(newFrom)){ if (PanelConstants.NEW_PANEL_FROM.NEW_INNER_TEMPLATE.equals(newFrom)) {
PanelTemplateWithBLOBs panelTemplate = templateMapper.selectByPrimaryKey(request.getTemplateId()); PanelTemplateWithBLOBs panelTemplate = templateMapper.selectByPrimaryKey(request.getTemplateId());
templateStyle = panelTemplate.getTemplateStyle(); templateStyle = panelTemplate.getTemplateStyle();
templateData = panelTemplate.getTemplateData(); templateData = panelTemplate.getTemplateData();
dynamicData = panelTemplate.getDynamicData(); dynamicData = panelTemplate.getDynamicData();
}else if(PanelConstants.NEW_PANEL_FROM.NEW_OUTER_TEMPLATE.equals(newFrom)){ } else if (PanelConstants.NEW_PANEL_FROM.NEW_OUTER_TEMPLATE.equals(newFrom)) {
templateStyle = request.getPanelStyle(); templateStyle = request.getPanelStyle();
templateData = request.getPanelData(); templateData = request.getPanelData();
dynamicData = request.getDynamicData(); dynamicData = request.getDynamicData();
} }
Map<String,String> dynamicDataMap = JSON.parseObject(dynamicData,Map.class); Map<String, String> dynamicDataMap = JSON.parseObject(dynamicData, Map.class);
List<PanelViewInsertDTO> panelViews = new ArrayList<>(); List<PanelViewInsertDTO> panelViews = new ArrayList<>();
List<PanelGroupExtendDataDTO> viewsData = new ArrayList<>(); List<PanelGroupExtendDataDTO> viewsData = new ArrayList<>();
for(Map.Entry<String, String> entry : dynamicDataMap.entrySet()){ for (Map.Entry<String, String> entry : dynamicDataMap.entrySet()) {
String originViewId = entry.getKey(); String originViewId = entry.getKey();
String originViewData = entry.getValue(); String originViewData = entry.getValue();
ChartViewDTO chartView = JSON.parseObject(originViewData,ChartViewDTO.class); ChartViewDTO chartView = JSON.parseObject(originViewData, ChartViewDTO.class);
String position = chartView.getPosition(); String position = chartView.getPosition();
String newViewId = UUIDUtil.getUUIDAsString(); String newViewId = UUIDUtil.getUUIDAsString();
chartView.setId(newViewId); chartView.setId(newViewId);
chartView.setSceneId(newPanelId); chartView.setSceneId(newPanelId);
chartView.setDataFrom(CommonConstants.VIEW_DATA_FROM.TEMPLATE); chartView.setDataFrom(CommonConstants.VIEW_DATA_FROM.TEMPLATE);
//TODO 数据处理 1.替换viewId 2.加入panelView 数据(数据来源为template) 3.加入模板view data数据 //TODO 数据处理 1.替换viewId 2.加入panelView 数据(数据来源为template) 3.加入模板view data数据
templateData = templateData.replaceAll(originViewId,newViewId); templateData = templateData.replaceAll(originViewId, newViewId);
panelViews.add(new PanelViewInsertDTO(newViewId,newPanelId,position)); panelViews.add(new PanelViewInsertDTO(newViewId, newPanelId, position));
viewsData.add(new PanelGroupExtendDataDTO(newPanelId,newViewId,originViewData)); viewsData.add(new PanelGroupExtendDataDTO(newPanelId, newViewId, originViewData));
chartViewMapper.insertSelective(chartView); chartViewMapper.insertSelective(chartView);
extChartViewMapper.copyToCache(newViewId); extChartViewMapper.copyToCache(newViewId);
} }
if(CollectionUtils.isNotEmpty(panelViews)){ if (CollectionUtils.isNotEmpty(panelViews)) {
extPanelViewMapper.savePanelView(panelViews); extPanelViewMapper.savePanelView(panelViews);
extPanelGroupExtendDataMapper.savePanelExtendData(viewsData); extPanelGroupExtendDataMapper.savePanelExtendData(viewsData);
} }
@ -439,10 +452,77 @@ public class PanelGroupService {
} }
return null; return null;
} }
private void clearPermissionCache(){
private void clearPermissionCache() {
CacheUtils.removeAll(AuthConstants.USER_PANEL_NAME); CacheUtils.removeAll(AuthConstants.USER_PANEL_NAME);
CacheUtils.removeAll(AuthConstants.ROLE_PANEL_NAME); CacheUtils.removeAll(AuthConstants.ROLE_PANEL_NAME);
CacheUtils.removeAll(AuthConstants.DEPT_PANEL_NAME); CacheUtils.removeAll(AuthConstants.DEPT_PANEL_NAME);
} }
public void exportPanelViewDetails(PanelViewDetailsRequest request, HttpServletResponse response) throws IOException {
OutputStream outputStream = response.getOutputStream();
try {
String snapshot = request.getSnapshot();
List<String[]> details = request.getDetails();
details.add(0,request.getHeader());
HSSFWorkbook wb = new HSSFWorkbook();
//明细sheet
HSSFSheet detailsSheet = wb.createSheet("视图明细");
//给单元格设置样式
CellStyle cellStyle = wb.createCellStyle();
Font font = wb.createFont();
//设置字体大小
font.setFontHeightInPoints((short) 12);
//设置字体加粗
font.setBold(true);
//给字体设置样式
cellStyle.setFont(font);
//设置单元格背景颜色
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
//设置单元格填充样式(使用纯色背景颜色填充)
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
if (CollectionUtils.isNotEmpty(details)) {
for (int i = 0; i < details.size(); i++) {
HSSFRow row = detailsSheet.createRow(i);
String[] rowData = details.get(i);
if (rowData != null) {
for (int j = 0; j < rowData.length; j++) {
HSSFCell cell = row.createCell(j);
cell.setCellValue(rowData[j]);
if(i==0){// 头部
cell.setCellStyle(cellStyle);
//设置列的宽度
detailsSheet.setColumnWidth(j, 255*20);
}
}
}
}
}
if(StringUtils.isNotEmpty(snapshot)){
//截图sheet 1px 2.33dx 0.48 dy 8*24 个单元格
HSSFSheet snapshotSheet = wb.createSheet("截图");
short reDefaultRowHeight = (short)Math.round(request.getSnapshotHeight()*3.5/8);
int reDefaultColumnWidth = (int)Math.round(request.getSnapshotWidth()*0.25/24);
snapshotSheet.setDefaultColumnWidth(reDefaultColumnWidth);
snapshotSheet.setDefaultRowHeight(reDefaultRowHeight);
//画图的顶级管理器一个sheet只能获取一个一定要注意这点i
HSSFPatriarch patriarch = snapshotSheet.createDrawingPatriarch();
HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, reDefaultColumnWidth, reDefaultColumnWidth,(short) 0, 0, (short)8, 24);
anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_DO_RESIZE);
patriarch.createPicture(anchor, wb.addPicture(Base64Utils.decodeFromString(snapshot.replace(DATA_URL_TITLE,"")), HSSFWorkbook.PICTURE_TYPE_JPEG));
}
response.setContentType("application/vnd.ms-excel");
//文件名称
response.setHeader("Content-disposition", "attachment;filename=" + request.getViewName() + ".xlsx");
wb.write(outputStream);
outputStream.flush();
outputStream.close();
} catch (Exception e) {
DataEaseException.throwException(e);
}
}
} }

View File

@ -188,4 +188,14 @@ export function initViewCache(panelId) {
loading: false loading: false
}) })
} }
export function exportDetails(data) {
// 初始化仪表板视图缓存
return request({
url: 'panel/group/exportDetails',
method: 'post',
data: data,
loading: true,
responseType: 'blob'
})
}

View File

@ -1,18 +1,19 @@
<template> <template>
<de-container> <de-container v-loading="$store.getters.loadingMap[$store.getters.currentPath]">
<de-aside-container v-if="chart.type !== 'table-normal' && chart.type !== 'table-info'" :style="customStyle"> <de-aside-container v-if="showChartCanvas">
<plugin-com <div id="chartCanvas" :style="customStyle">
v-if="chart.isPlugin" <plugin-com
v-if="chart.isPlugin"
:component-name="chart.type + '-view'" :component-name="chart.type + '-view'"
:obj="{chart}" :obj="{chart}"
class="chart-class" class="chart-class"
/> />
<chart-component v-else-if="!chart.type.includes('text') && chart.type !== 'label' && !chart.type.includes('table') && renderComponent() === 'echarts'" class="chart-class" :chart="chart" /> <chart-component v-else-if="!chart.type.includes('text') && chart.type !== 'label' && !chart.type.includes('table') && renderComponent() === 'echarts'" class="chart-class" :chart="chart" />
<chart-component-g2 v-else-if="!chart.type.includes('text') && chart.type !== 'label' && !chart.type.includes('table') && renderComponent() === 'antv'" class="chart-class" :chart="chart" /> <chart-component-g2 v-else-if="!chart.type.includes('text') && chart.type !== 'label' && !chart.type.includes('table') && renderComponent() === 'antv'" class="chart-class" :chart="chart" />
<chart-component-s2 v-else-if="chart.type === 'table-pivot' && renderComponent() === 'antv'" class="chart-class" :chart="chart" /> <chart-component-s2 v-else-if="chart.type === 'table-pivot' && renderComponent() === 'antv'" class="chart-class" :chart="chart" />
<label-normal v-else-if="chart.type.includes('text')" :chart="chart" class="table-class" /> <label-normal v-else-if="chart.type.includes('text')" :chart="chart" class="table-class" />
<label-normal-text v-else-if="chart.type === 'label'" :chart="chart" class="table-class" /> <label-normal-text v-else-if="chart.type === 'label'" :chart="chart" class="table-class" />
</div>
</de-aside-container> </de-aside-container>
<de-main-container> <de-main-container>
<table-normal :chart="chartTable" :show-summary="false" class="table-class" /> <table-normal :chart="chartTable" :show-summary="false" class="table-class" />
@ -28,12 +29,14 @@ import LabelNormal from '@/views/chart/components/normal/LabelNormal'
import DeMainContainer from '@/components/dataease/DeMainContainer' import DeMainContainer from '@/components/dataease/DeMainContainer'
import DeContainer from '@/components/dataease/DeContainer' import DeContainer from '@/components/dataease/DeContainer'
import DeAsideContainer from '@/components/dataease/DeAsideContainer' import DeAsideContainer from '@/components/dataease/DeAsideContainer'
import { export_json_to_excel } from '@/plugins/Export2Excel' // import { export_json_to_excel } from '@/plugins/Export2Excel'
import { mapState } from 'vuex' import { mapState } from 'vuex'
import ChartComponentG2 from '@/views/chart/components/ChartComponentG2' import ChartComponentG2 from '@/views/chart/components/ChartComponentG2'
import PluginCom from '@/views/system/plugin/PluginCom' import PluginCom from '@/views/system/plugin/PluginCom'
import ChartComponentS2 from '@/views/chart/components/ChartComponentS2' import ChartComponentS2 from '@/views/chart/components/ChartComponentS2'
import LabelNormalText from '@/views/chart/components/normal/LabelNormalText' import LabelNormalText from '@/views/chart/components/normal/LabelNormalText'
import { exportDetails } from '@/api/panel/panel'
import html2canvas from 'html2canvasde'
export default { export default {
name: 'UserView', name: 'UserView',
components: { LabelNormalText, ChartComponentS2, ChartComponentG2, DeMainContainer, DeContainer, DeAsideContainer, ChartComponent, TableNormal, LabelNormal, PluginCom }, components: { LabelNormalText, ChartComponentS2, ChartComponentG2, DeMainContainer, DeContainer, DeAsideContainer, ChartComponent, TableNormal, LabelNormal, PluginCom },
@ -53,8 +56,12 @@ export default {
} }
}, },
computed: { computed: {
showChartCanvas() {
return this.chart.type !== 'table-normal' && this.chart.type !== 'table-info'
},
customStyle() { customStyle() {
let style = { let style = {
height: '100%'
} }
if (this.canvasStyleData.openCommonStyle) { if (this.canvasStyleData.openCommonStyle) {
if (this.canvasStyleData.panel.backgroundType === 'image' && this.canvasStyleData.panel.imageUrl) { if (this.canvasStyleData.panel.backgroundType === 'image' && this.canvasStyleData.panel.imageUrl) {
@ -83,11 +90,39 @@ export default {
}, },
methods: { methods: {
exportExcel() { exportExcel() {
const _this = this
if (this.showChartCanvas) {
html2canvas(document.getElementById('chartCanvas')).then(canvas => {
const snapshot = canvas.toDataURL('image/jpeg', 1) //
_this.exportExcelDownload(snapshot, canvas.width, canvas.height)
})
} else {
_this.exportExcelDownload()
}
},
exportExcelDownload(snapshot, width, height) {
const excelHeader = JSON.parse(JSON.stringify(this.chart.data.fields)).map(item => item.name) const excelHeader = JSON.parse(JSON.stringify(this.chart.data.fields)).map(item => item.name)
const excelHeaderKeys = JSON.parse(JSON.stringify(this.chart.data.fields)).map(item => item.dataeaseName) const excelHeaderKeys = JSON.parse(JSON.stringify(this.chart.data.fields)).map(item => item.dataeaseName)
const excelData = JSON.parse(JSON.stringify(this.chart.data.tableRow)).map(item => excelHeaderKeys.map(i => item[i])) const excelData = JSON.parse(JSON.stringify(this.chart.data.tableRow)).map(item => excelHeaderKeys.map(i => item[i]))
const excelName = this.chart.name const excelName = this.chart.name
export_json_to_excel(excelHeader, excelData, excelName) const request = {
viewName: excelName,
header: excelHeader,
details: excelData,
snapshot: snapshot,
snapshotWidth: width,
snapshotHeight: height
}
exportDetails(request).then((res) => {
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const link = document.createElement('a')
link.style.display = 'none'
link.href = URL.createObjectURL(blob)
link.download = excelName //
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
})
}, },
renderComponent() { renderComponent() {