1、什么是UReport2?

UReport2是一款高性能的架构在Spring之上纯Java报表引擎,通过迭代单元格可以实现任意复杂的中国式报表。该报表由上海锐道信息技术有限公司开发的一款基于Apache-2.0协议开源的中式报表引擎(点击进入gitee源码页面)。当前报表引擎不知是什么原因没有被作者继续维护,但是前期文档还是相对比较详细(点击进入文档页面)。

2、为啥要整合UReport2?

笔者也是处于对报表引擎的实现原理感兴趣,所以download了源码并对部分细节进行调整和优化。建议对报表引擎感兴趣的同学可以下载源码进行研究的个性化调整优化。同时使用springboot+mysql+jfinal+iview+jquery实现了一个简单的报表demo,在此做一个简单的学习记录,并同各位同学分享,希望能同各位一起不断进步和提升个人技术能力。

3、实现结果

先上部分截图展示一下实现结果

3.1、报表列表

在这里插入图片描述

3.2、报表设计器

在这里插入图片描述

3.3、学生平均分报表预览

在这里插入图片描述

3.4、综合成绩报表预览

在这里插入图片描述

3.5、学生成绩单报表预览

在这里插入图片描述

4、实现过程

先放一张系统文件结构图
在这里插入图片描述

4.1、创建一个springboot项目

将项目命名为ureport,并在pom.xml文件中添加需要的maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.crwl</groupId>
    <artifactId>ureport</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring-cloud.version>2.1.1.RELEASE</spring-cloud.version>
        <flowable.version>6.5.0</flowable.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>2.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.16.20</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.10</version>
        </dependency>
        <dependency>
            <groupId>com.jfinal</groupId>
            <artifactId>activerecord</artifactId>
            <version>4.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.bstek.ureport</groupId>
            <artifactId>ureport2-console</artifactId>
            <version>2.3.0-pro</version>
        </dependency>
    </dependencies>
</project>

此处ureport2的jar包依赖为笔者重新修改并编译源码后生成,如果直接采用UReport2官方下载的jar包,针对本demo有可能无法运行,如由用户需要当前jar包可联系笔者。

4.2、添加yml配置信息

applicaiton.yaml

server:
  port: 9090
  servlet:
    context-path: /pro
spring:
  http:
    encoding:
      force: true
      enabled: true
      charset: UTF-8
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ureport?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&allowMultiQueries=true&nullNamePatternMatchesAll=true
    username: root
    password:
    type: com.alibaba.druid.pool.DruidDataSource
  resources:
    static-locations: classpath:/,classpath:/static/

4.3、添加引用UReport2的Spring配置文件context.xml

笔者是放在resources/config目录下面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <import resource="classpath:ureport-console-context.xml" />
    <bean id="propertyConfigurer" parent="ureport.props">
        <property name="locations">
            <list>
                <value>classpath:config/config.properties</value>
            </list>
        </property>
    </bean>
    <bean id="ureport.fielToDataBaseProvider" class="com.crwl.provider.FileToDatabaserProvider">
        <property name="fileStoreDir" value="${ureport.fileToDbStoreDir}"></property>
        <property name="disabled" value="${ureport.disableFileDbProvider}"></property>
    </bean>
</beans>

4.4、添加property文件

config.properties

#配置文件系统对应的报表文件地址
#ureport.fileStoreDir=D:/myfile/ureportfiles
# 是否禁用
ureport.disableFileProvider=true
#配置同数据库关联文件系统对应的报表文件地址
ureport.fileToDbStoreDir=D:/myfile/ureportDbfiles
# 是否禁用
ureport.disableFileDbProvider=false
# 配置ureport根路径,对应ureport-console/src/main/resources/ureport-console-context.xml中的ureport.designerServletAction
ureport.contextPath=/pro
4.4.1、ureport.fileStoreDir

UReport2报表引擎默认采用文件系统,其中ureport.fileStoreDir是配置生成的报表文件的物理路径,ureport.disableFileProvider是定义是否禁用用当前报表的文件系统,true为禁用,false为启用。笔者当前的思路是将文件系统同mysql数据库结合起来进行管理,所以配置为禁用。

4.4.2、ureport.fileToDbStoreDir

ureport.fileToDbStoreDir为笔者扩展出来的mysql+文件系统结合的一种方式,此参数同样是配置路径,ureport.disableFileDbProvider配置是否禁用

4.4.3、ureport.contextPath

ureport.contextPath参数为笔者添加的参数,此参数可帮助用户实现业务后台系统同前端分离会出现的报表默认资源路径地址不匹配的情况。当前demo未采用前后端分离,所以此参数可以配置,也可以不配置,不配置默认为application.yaml文件对应的context-path。

4.5、java源码

4.5.1、DataSourceConfig.java
package com.crwl.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.crwl.model._MappingKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

import javax.sql.DataSource;
/**
 * @author teamo
 * @Package com.crwl.config
 * @Description:
 * @date 2022-8-17
 */
@Configuration
public class DataSourceConfig {
    @Primary
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    /**
     * 设置数据源代理
     */
    @Bean
    public TransactionAwareDataSourceProxy transactionAwareDataSourceProxy() {
        TransactionAwareDataSourceProxy transactionAwareDataSourceProxy = new TransactionAwareDataSourceProxy();
        transactionAwareDataSourceProxy.setTargetDataSource(druidDataSource());
        return transactionAwareDataSourceProxy;
    }

    /**
     * 设置ActiveRecord
     */
    @Bean
    public ActiveRecordPlugin activeRecordPlugin() {
        ActiveRecordPlugin arp = new ActiveRecordPlugin(transactionAwareDataSourceProxy());
        arp.setDialect(new MysqlDialect());
        arp.setContainerFactory(new CaseInsensitiveContainerFactory(true));//忽略大小写
        arp.setShowSql(true);
        arp.getEngine().setToClassPathSourceFactory();
        //arp.addSqlTemplate("sql/all.sql");
        _MappingKit.mapping(arp);
        arp.start();
        System.out.println("调用Jfinal ActiveRecordPlugin 成功");
        return arp;
    }
}

此类主要用于根据配置文件对数据库链接以及Jfinal的进行初始化工作。
如用户对JFinal不太了解,可以自行百度学习。JFinal 是基于Java 语言的极速 web 开发框架,其核心设计目标是开发迅速、代码量少、学习简单、功能强大、轻量级、易扩展、Restful。当前demo主要使用Jfinal的数据库部分的功能。

4.5.2、model类 Report.java, BaseReport.java,_MappingKit.java

model为jfinal的实体对象类,主要用于同数据表进行orm映射关系。

package com.crwl.model;

import com.crwl.model.base.BaseReport;
/**
 * Generated by JFinal.
 */
@SuppressWarnings("serial")
public class Report extends BaseReport<Report> {
	public static final Report dao = new Report().dao();
}

BaseReport.java

package com.crwl.model.base;

import com.jfinal.plugin.activerecord.IBean;
import com.jfinal.plugin.activerecord.Model;

import java.util.Date;

/**
 * Generated by JFinal, do not modify this file.
 */
@SuppressWarnings({"serial", "unchecked"})
public abstract class BaseReport<M extends BaseReport<M>> extends Model<M> implements IBean {
	public M setId(Integer id) {
		set("id", id);
		return (M)this;
	}
	public Integer getId() {
		return getInt("id");
	}
	public M setRptCode(String rptCode) {
		set("rpt_code", rptCode);
		return (M)this;
	}
	public String getRptCode() {
		return getStr("rpt_code");
	}
	public M setRptName(String rptName) {
		set("rpt_name", rptName);
		return (M)this;
	}
	public String getRptName() {
		return getStr("rpt_name");
	}
	public M setRptType(Integer rptType) {
		set("rpt_type", rptType);
		return (M)this;
	}
	public Integer getRptType() {
		return getInt("rpt_type");
	}
	public M setUreportName(String ureportName) {
		set("ureport_name", ureportName);
		return (M)this;
	}
	public String getUreportName() {
		return getStr("ureport_name");
	}
	public M setRptUrl(String rptUrl) {
		set("rpt_url", rptUrl);
		return (M)this;
	}
	public String getRptUrl() {
		return getStr("rpt_url");
	}
	public M setRemark(String remark) {
		set("remark", remark);
		return (M)this;
	}
	public String getRemark() {
		return getStr("remark");
	}
	public M setSort(Integer sort) {
		set("sort", sort);
		return (M)this;
	}
	public Integer getSort() {
		return getInt("sort");
	}
	public M setStatus(Integer status) {
		set("status", status);
		return (M)this;
	}
	public Integer getStatus() {
		return getInt("status");
	}
	public M setCreateUser(String createUser) {
		set("create_user", createUser);
		return (M)this;
	}
	public String getCreateUser() {
		return getStr("create_user");
	}
	public M setCreateDate(Date createDate) {
		set("create_date", createDate);
		return (M)this;
	}
	public Date getCreateDate() {
		return getDate("create_date");
	}
	public M setUpdateUser(String updateUser) {
		set("update_user", updateUser);
		return (M)this;
	}
	public String getUpdateUser() {
		return getStr("update_user");
	}
	public M setUpdateDate(Date updateDate) {
		set("update_date", updateDate);
		return (M)this;
	}
	public Date getUpdateDate() {
		return getDate("update_date");
	}
}

_MappingKit.java

package com.crwl.model;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
public class _MappingKit {
	public static void mapping(ActiveRecordPlugin arp) {
		arp.addMapping("ur_report", "id", Report.class);
	}
}
4.5.3、Controller类

ReportController.java,报表Controller类

package com.crwl;

import com.bstek.ureport.console.UReportServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * 入口类
 */
@SpringBootApplication()
@EnableTransactionManagement
@ImportResource("classpath:config/context.xml")
@ComponentScan(basePackages = {"com.crwl.*"})
public class UreportApplication {
    public static void main(String[] args) {
        SpringApplication.run(UreportApplication.class, args);
    }
    //ureport报表
    @Bean
    public ServletRegistrationBean buildUReprtServlet(){
        return new ServletRegistrationBean(new UReportServlet(),"/ureport/*");
    }
}
4.5.4、Service类 ReportService.java, ReportServiceImpl.java

ReportService.java

package com.crwl.service;
import com.crwl.model.Report;
import com.jfinal.plugin.activerecord.Page;

public interface ReportService {
    /***
     * 获取表格数据
     * @param currentPage
     * @param pageSize
     * @param rptName
     * @param rptType
     * @return
     */
    Page<Report> getPageList(Integer currentPage, Integer pageSize, String rptName, String rptType);
}

ReportServiceImpl.java

package com.crwl.service.impl;

import com.crwl.model.Report;
import com.crwl.service.ReportService;
import com.jfinal.plugin.activerecord.Page;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

@Service
public class ReportServiceImpl implements ReportService {
    private final String table = "ur_report";

    @Override
    public Page<Report> getPageList(Integer currentPage, Integer pageSize, String rptName, String rptType) {
        StringBuilder sql = new StringBuilder();
        sql.append(" from "+ table + " t where 1=1 ");
        if(StringUtils.isNotEmpty(rptName)){
            sql.append(" and instr(t.rpt_name,'"+rptName +"')>0 ");
        }
        if(StringUtils.isNotEmpty(rptType)){
            sql.append(" and t.rpt_type="+rptType);
        }
        sql.append(" order by t.sort desc ");
        Page<Report> pageList = Report.dao.paginate(currentPage,pageSize,"select t.* ",sql.toString());
        return pageList;
    }
}
4.5.4、Povider类

Provider类是实现UReport开放出来的供业务系统实现的接口类,用户可以同时实现不同的Provider类从代码层面去实现用户的业务需求。
DsProvider.java:通过事项BuildingDatasource接口,用户可以为报表设计界面提供接口级别的数据源。
实现当前接口后,在报表设计界面可直接使用当前数据库链接。
在这里插入图片描述在这里插入图片描述

package com.crwl.provider;

import com.bstek.ureport.definition.datasource.BuildinDatasource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

//提供ureport 内置数据源连接
@Component("dsScoreSys")
public class DsProvider implements BuildinDatasource {
    @Autowired
    private DataSource dataSource;

    @Override
    public String name() {
        return "scoreSys";
    }
    @Override
    public Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }
}

FileToDatabaserProvider.java: 通过实现ReportProvider接口,用户可以增加符合用户自身需求的报表储存方式(UReport2自身已经实现了纯文件系统储存报表的存储方式)。笔者通过实现当前接口实现了使用数据库存储报表的基本信息,使用文件系统存储报表文件,数据库记录报表的寻址地址。即可以使用mysql数据表管理报表文件。

package com.crwl.provider;

import com.bstek.ureport.exception.ReportException;
import com.bstek.ureport.provider.report.ReportFile;
import com.bstek.ureport.provider.report.ReportProvider;
import com.crwl.model.Report;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import java.io.*;
import java.util.*;

public class FileToDatabaserProvider implements ReportProvider {
    private String prefix="fileToDb:";
    private String fileStoreDir;
    private String disabled;

    @Override
    public InputStream loadReport(String file) {
        if(StringUtils.isNotEmpty(file)){
            String[] arr = file.split("@");
            Report report = null;
            if(null != arr && arr.length==2){
                report = Report.dao.findById(Integer.parseInt(arr[1]));
                file = report.getUreportName();
            }else{
                report = Report.dao.findFirst(" select * from ur_report t where t.ureport_name=?", file);
            }
            if(null != report) {
                if (file.startsWith(prefix)) {
                    file = file.substring(prefix.length(), file.length());
                }
                String fullPath = fileStoreDir + "/" + file;
                try {
                    return new FileInputStream(fullPath);
                } catch (FileNotFoundException e) {
                    throw new ReportException(e);
                }
            }else{
                throw new ReportException("报表文件不存在");
            }
        }else{
            throw new ReportException("报表文件不存在");
        }
    }

    @Override
    public void deleteReport(String file) {
        Report report = Report.dao.findFirst(" select * from ur_report t where t.ureport_name=?",file);
        if(null != report){
            if(file.startsWith(prefix)){
                file=file.substring(prefix.length(),file.length());
            }
            String fullPath=fileStoreDir+"/"+file;
            File f=new File(fullPath);
            if(f.exists()){
                f.delete();
            }
            report.delete();
        }
    }
    @Override
    public List<ReportFile> getReportFiles() {
        List<Report> reportList = Report.dao.find("select * from ur_report t where t.rpt_type=2 ");
        File file=new File(fileStoreDir);
        List<ReportFile> list=new ArrayList<ReportFile>();
        for(File f:file.listFiles()){
            Calendar calendar=Calendar.getInstance();
            calendar.setTimeInMillis(f.lastModified());
            Report report = null;
            for(int i=0; i<reportList.size();i++){
                Report r = reportList.get(i);
                String reportName = r.getUreportName();
                if(StringUtils.isNotEmpty(reportName)){
                    reportName = reportName.substring(prefix.length(), reportName.length());
                    if(f.getName().equals(reportName)){
                        report = r;
                    }
                }
            }
            if(null != report){
                list.add(new ReportFile(report.getId(),f.getName(),calendar.getTime()));
            }
        }
        Collections.sort(list, new Comparator<ReportFile>(){
            @Override
            public int compare(ReportFile f1, ReportFile f2) {
                return f2.getUpdateDate().compareTo(f1.getUpdateDate());
            }
        });
        return list;
    }

    @Override
    public void saveReport(String file, String content) {
        if(StringUtils.isNotEmpty(file)) {
            String[] arr = file.split("@");
            Report report = null;
            if(null != arr && arr.length==2){
                report = Report.dao.findById(Integer.parseInt(arr[0]));
                file = arr[1];
            }else{
                report = Report.dao.findFirst(" select * from ur_report t where t.ureport_name=?", file);
            }
            if (null != report) {
                report.setUreportName(file);
                report.setRptUrl("ureport/preview?_u=" + file);
                if (file.startsWith(prefix)) {
                    file = file.substring(prefix.length(), file.length());
                }
                String fullPath = fileStoreDir + "/" + file;
                FileOutputStream outStream = null;
                try {
                    outStream = new FileOutputStream(new File(fullPath));
                    IOUtils.write(content, outStream, "utf-8");
                } catch (Exception ex) {
                    throw new ReportException(ex);
                } finally {
                    if (outStream != null) {
                        try {
                            outStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                report.setUpdateDate(new Date());
                report.update();
            } else {
                throw new ReportException("报表文件不存在");
            }
        }else{
            throw new ReportException("报表文件不存在");
        }
    }
    @Override
    public String getName() {
        return "数据库文件系统";
    }
    @Override
    public boolean disabled() {
        return false;
    }
    @Override
    public String getPrefix() {
        return prefix;
    }
    public void setFileStoreDir(String fileStoreDir) {
        this.fileStoreDir = fileStoreDir;
    }
    public void setDisabled(String disabled) {
        this.disabled = disabled;
    }
}

loadReport方法:通过给定的参数从数据库中找到报表文件,根据寻址地址生成一个InputStream对象,报表设计器根据返回的InputStream对象渲染出正在设计中的报表文件

deleteReport方法:提供过给定的参数删除报表文件,此方法的操作是先删除对应数据库中的记录,在删除对应的物理报表文件

getReportFiles方法:加载当前所有的数据报表记录。

saveReport方法:通过给定的参数找到报表记录,更新报表预览地址并保存物理报表文件。

UreportContextProvider.java是笔者扩展的更改报表contextPath参数的接口,它的作用同config.properties文件中的ureport.contextPath参数一致,此处未使用,故不做介绍了。

4.5.5、springboot启动类

UreportApplication.java
@ImportResource(“classpath:config/context.xml”)不能漏了,它是引入ureport2的spring配置文件。
buildUReprtServlet方法是将UReport注册为一个Servlet,是使用UReport的入口文件,不能缺少。

package com.crwl;

import com.bstek.ureport.console.UReportServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * 入口类
 */
@SpringBootApplication()
@EnableTransactionManagement
@ImportResource("classpath:config/context.xml")
@ComponentScan(basePackages = {"com.crwl.*"})
public class UreportApplication {

    public static void main(String[] args) {
        SpringApplication.run(UreportApplication.class, args);
    }

    //ureport报表
    @Bean
    public ServletRegistrationBean buildUReprtServlet(){
        return new ServletRegistrationBean(new UReportServlet(),"/ureport/*");
    }
}
4.5.5、其它类

Result.java: 返回前端Dto类

package com.crwl.dto;
import com.crwl.enums.ResultEnum;
import java.io.Serializable;
/**
 * 返回的对象(统一返回)
 *
 * @author SmallStrong
 */
public class Result implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 3337439376898084639L;
    /**
     * 处理状态
     */
    private Integer code;
    /**
     * 处理信息
     */
    private String msg;
    private String serverID;
    /**
     * 返回值
     */
    private Object data;
    private int total;
    private Object rows;
    /**
     * 成功,传入data(使用最多)
     *
     * @param data
     * @return
     */
    public static Result success(Object data) {
        return Result.success(data,"请求成功!");
    }
    /**
     * 成功,传入data(使用最多)
     * @param msg
     * @return
     */
    public static Result success(String msg) {
        Result result = new Result();
        result.setCode(ResultEnum.SUCCESS.getCode());
        result.setMsg(msg);
        return result;
    }
    /**
     * 成功,传入rows和total
     * @param rows
     * @param total
     * @return
     */
    public static Result success(Object rows,int total) {
        Result result = new Result();
        result.setCode(ResultEnum.SUCCESS.getCode());
        result.setMsg("请求成功!");
        result.setRows(rows);
        result.setTotal(total);
        return result;
    }

    /**
     * 成功,传入data 和 msg
     * @param data
     * @param msg
     * @return
     */
    public static Result success(Object data, String msg) {
        Result result = new Result();
        result.setCode(ResultEnum.SUCCESS.getCode());
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
    /**
     * 失败
     * @return
     */
    public static Result error() {
        return Result.error("请求失败!");
    }
    /**
     * 失败 传入 msg
     * @param msg
     * @return
     */
    public static Result error(String msg) {
        return  Result.error(msg,ResultEnum.FAILURE);
    }

    public static Result error(String msg ,ResultEnum resultEnum){
        Result result = new Result();
        result.setCode(resultEnum.getCode());
        result.setMsg(msg);
        return result;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
        if(null != this.data && this.data.getClass().getName().equals("com.crwl.commonserver.dto.CurrUser")){
            this.data = null;
        }
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public String getServerID() {
        return serverID;
    }
    public void setServerID(String serverID) {
        this.serverID = serverID;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public Object getRows() {
        return rows;
    }

    public void setRows(Object rows) {
        this.rows = rows;
    }
    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", serverID='" + serverID + '\'' +
                ", data=" + data +
                ", total=" + total +
                ", rows=" + rows +
                '}';
    }
}

ResultEnum.java:返回前端状态枚举类

package com.crwl.enums;
/**
 * 返回状态
 */
public enum ResultEnum {
    /**
     * 200 OK     //客户端请求成功 
     * 400 Bad Request  //客户端请求有语法错误,不能被服务器所理解 
     * 401 Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
     * 403 Forbidden  //服务器收到请求,但是拒绝提供服务 
     * 404 Not Found  //请求资源不存在,eg:输入了错误的 URL 
     * 500 Internal Server Error //服务器发生不可预期的错误 
     * 503 Server Unavailable  //服务器当前不能处理客户端的请求,一段时间后可能恢复正常 
     */
    SUCCESS(200, "操作成功"),ERROR(500,"操作失败"), FAILURE(404, "请求的网页不存在"),INVALID(503,"服务不可用"),LOGINOVERTIME(1000,"登录超时");
    private ResultEnum(Integer code, String data) {
        this.code = code;
        this.data = data;
    }
    private Integer code;
    private String data;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getData() {
        return data;
    }
    public void setData(String data) {
        this.data = data;
    }
}

Tool.java工具类(未用到),RecordModelGenerator.java自动生成Jfinal实体文件类,为节省篇幅,此处不贴出来了

4.6、前端代码

在这里插入图片描述
前端使用的是传统的在html文件引入的方式js库文件的方式实现。采用的技术是jquery,iview。前端文件包括一个report.html和report.js,其它都是类库文件。
report.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="content-type" content="text/html; charset=UTF-8">

	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>报表管理</title>
	<!-- CSS -->
	<link rel="stylesheet" type = "text/css" href="../static/lib/iview/css/iview.css?v=1" />
	<script src="../static/js/jquery-1.8.2.min.js"></script>
	<script src="../static/lib/iview/vue.min.js"></script>
	<script src="../static/lib/iview/iview.min.js"></script>
	<script src="../static/js/common.js"></script>
	<script src="./js/report.js"></script>
	<style>
		#searchForm .ivu-form-item{
			margin-bottom:0px;
		}
		.clearfix{clear:both;}
		#searchForm .ivu-col{padding:10px 0;}
		.valCss{color:#515a6e!important;}
		.ivu-table-body{overflow-y:auto;overflow-x:hidden;}
		.ivu-input[disabled], fieldset[disabled]{
			color:#515a6e;
		}
		.ivu-form-item-content .ivu-form-item-error-tip{
			top:80%;
		}
		#reportDesignerIframe,#reportPreviewIframe{border:0;}
		#reportDesingerWin .ivu-modal-footer,#reportPreviewWin .ivu-modal-footer{display:none;}
		#reportDesingerWin .ivu-modal-body,#reportPreviewWin .ivu-modal-body{bottom:0}
	</style>
</head>
<body  scroll="no" style="overflow-y:hidden;scrollbar-width: none;">
<Layout id="app" style="overflow-y:hidden;">
	<div style="height:54px;">
		<i-row id="searchForm">
			<i-form :label-width="120">
				<i-col span="4">
					<form-item label="报表名称" prop="rptName">
						<i-input :size="styleSize" v-model="searchForm.rptName"></i-input>
					</form-item>
				</i-col>
				<i-col span="3">
					<i-Button :size="styleSize" type="primary" icon="ios-search" style="margin-left:15px;" @click="searchFunc">查询</i-Button>
				</i-col>
			</i-form>
			<div class="clearfix"></div>
		</i-row>
	</div>
	<i-row style="text-align:left;padding:5px;background:#fff;">
		<i-Button :size="styleSize" type="primary" icon="ios-add" @click="openEdit(1)">新增</i-Button>
		<i-Button :size="styleSize" type="primary" icon="ios-create" @click="openEdit(2)">修改</i-Button>
		<i-Button :size="styleSize" type="primary" icon="ios-remove" @click="deleteFunc()">删除</i-Button>
	</i-row>
	<Content>
		<i-Table :size="styleSize" row-key="id"  border :columns="columns" :data="tableDataList" ref="table" @on-row-click="selectRow" :height="tableHeight" :loading="loading" highlight-row style="overflow-y:auto;"></i-Table>
		<Page :total="dataCount" :page-size="pageSize" :page-size-opts="pageOptions"  show-sizer class="paging" @on-change="changepage" @on-page-size-change="pagesize"></Page>
	</Content>
	<Modal v-model="editModal"
		   :title="editFlag=='add'?'新增':'修改'"
		   width="800px"
	>
		<i-form ref="editValidate" :model="formData" :rules="editRuleValidate" :label-width="120" style="padding:5px 50px;">
			<i-col span="12">
				<form-item label="报表编码" prop="rptCode">
					<i-input :size="styleSize" v-model="formData.rptCode"  ></i-input>
				</form-item>
			</i-col>
			<i-col span="12">
				<form-item label="报表名称" prop="rptName">
					<i-input :size="styleSize" v-model="formData.rptName"  ></i-input>
				</form-item>
			</i-col>
			<i-col span="12">
				<form-item label="排序号" prop="sort">
					<i-input :size="styleSize" type="number" v-model="formData.sort"  ></i-input>
				</form-item>
			</i-col>
			<i-col span="12">
				<form-item label="状态" prop="status">
					<i-switch :size="styleSize" v-model="formData.status" true-value="1" false-value="0" :width="100" size="large">
						<span slot="open">启用</span>
						<span slot="close">禁用</span>
					</i-switch>
				</form-item>
			</i-col>
			<i-col span="24">
				<form-item label="备注" prop="remark">
					<i-input :size="styleSize"  type="textarea":rows="2" v-model="formData.remark" placeholder="请输入备注信息" style="width:100%"></i-input>
				</form-item>
			</i-col>
		</i-form>
		<div class="clearfix"></div>
		<div slot="footer">
			<i-Button :size="styleSize" type="primary" @click="submitFunc">保存</i-Button>
			<i-Button :size="styleSize" type="warning" @click="cancelFunc">取消</i-Button>
		</div>
	</Modal>

	<Modal id="reportDesingerWin" v-model="reportDesignerModal" title="报表设计" fullscreen="true">
		<iframe id="reportDesignerIframe" style="width:100%;height:100%;"></iframe>
	</Modal>
	<Modal id="reportPreviewWin" v-model="reportPreviewModal" title="报表预览"  fullscreen="true">
		<iframe id="reportPreviewIframe" style="width:100%;height:100%;"></iframe>
	</Modal>
</Layout>
</body>
</html>

report.js

$(function() {
    var height = $(window).height()-160;
    var vue = new Vue({
        el: '#app',
        data: {
            styleSize:constants.styleSize,
            editModal:false,
            editFlag:'add',
            tableHeight:height,
            loading:true,
            reportDesignerModal:false,
            reportPreviewModal:false,
            ureportPre:'fileToDb:',
            ureportSuffix:'.ureport.xml',
            columns :[
                {type:'selection',key: '',width:'60'},
                {title: '报表代码',width:'100',key: 'rptCode'},
                {title: '报表名称',width:'200',key: 'rptName'},
                //{title: '报表文件名',width:'200',key: 'ureportName'},
                {title: '预览地址',width:'280',key: 'rptUrl'},
                {title: '备注',width:'220',key: 'remark'},
                {title: '排序号',width:'80',key: 'sort'},
                {title: '状态',width:'80',key: 'status',render(h,column){
                    if(column.row.status==0){
                        return h('div', {
                            style: {
                                color: 'red'
                            },
                        }, '禁用');
                    }else {
                        return h('div',{style:{color:'green'}},'启用');
                    }
                }},
                {title: '创建日期',width:'160',key: 'createDate',render(h,column){
                    if(column.row.createDate != null && column.row.createDate != ""){
                        return commonFunc.formatDate(column.row.createDate,'date','date');
                    }
                }},
                {title: '操作',width:'200',render(h,column) {
                    var designBtn = h('Button',
                    {
                        props: {
                            type: 'primary',
                            size: 'small'
                        },
                        style: {
                            marginRight: '5px'
                        },
                        on: {
                            click: () => {
                                vue.openDesigner(column.row);
                            }
                        }
                    }, '报表设计');
                    var previewBtn = h('Button',
                    {
                        props: {
                            type: 'info',
                            size: 'small'
                        },
                        style: {
                            marginRight: '5px'
                        },
                        on: {
                            click: () => {
                                vue.openPreview(column.row);
                            }
                        }
                    }, '预览');
                    return h('div',[designBtn,previewBtn]);
                }

                }
            ],
            searchForm:{
                rptName:'',
                rptType:''
            },
            formData: {
                rptCode:'',
                rptName:'',
                //rptType:1,
                rptUrl:'',
                sort:1,
                status:'1',
                remark:''
            },
            editValidate: {

            },
            editRuleValidate: {
                rptCode:{required:true,message:'请输入报表编码',trigger: 'blur'},
                rptName:{required:true,message:'请输入报表名称',trigger: 'blur'},
                //rptUrl:{required:true,message:'请输入报表地址',trigger: 'blur'},
                //ureportName:{required:true,message:'请输入报表文件名称',trigger: 'blur'},
                sort:{required:true,message:'请输入排序号',trigger: 'blur'}
            },
            tableDataList:[],
            dataCount:0,
            // 每页显示多少条
            pageSize:20,
            // 当前页码
            currentPage:1,
            pageOptions:[20,40,60,80,100]
        },
        created:function(){
            this.initPage();
            var _this = this;
        },
        mounted :function(){
            //this.tableHeight = window.innerHeight - this.$refs.table.$el.offsetTop - 160
            var _this =this;
            window.onresize = () => {
                return (() => {
                    var height = $(window).height()-160;
                    _this.tableHeight = height;
                })()
            }
            //关闭弹窗
            $(".ivu-icon-ios-close").on("click",function(){
                _this.queryFunc();
            });
        },
        methods: {
            queryFunc(params){
                var _this = this ;
                _this.loading = true;
                var searchParam = this.searchForm;
                searchParam.currentPage = this.currentPage;
                searchParam.pageSize = this.pageSize;
                if(null != params){
                    for(var i in params){
                        searchParam[i] = params[i];
                    }
                }
                commonFunc.submit("/report/getTableList","post",searchParam,function(data){
                    _this.tableDataList = data.rows;
                    _this.dataCount = data.total;
                    _this.loading = false;
                },function(){});
            },
            initPage(){
                this.currentPage = 1;
                this.queryFunc();
            },
            changepage(index){
                this.pageFunc(index);
            },
            pagesize(index){
                this.pageFunc(index);
            },
            pageFunc(index){
                this.currentPage = index;
                this.queryFunc();
            },
            searchFunc(){
                this.currentPage = 1;
                this.queryFunc();
            },
            selectRow(data, index) {
                this.$refs.table.toggleSelect(index);
            },
            openEdit(flag){
                var _this = this;
                if(flag=="1"){
                    this.formData= {
                        rptCode:'',
                        rptName:'',
                        //ureportName:'',
                        sort:0,
                        status:'1',
                        remark:''
                    };
                    this.editFlag="add";
                    if(null != this.$refs.editValidate) {
                        this.$refs.editValidate.resetFields();
                    }
                }else{
                    var selRecList = this.$refs.table.getSelection();
                    if(selRecList.length==0 || selRecList.length>1){
                        this.$Message.error('请选择一条报表记录');
                        return;
                    }
                    if(null != this.$refs.editValidate) {
                        this.$refs.editValidate.resetFields();
                    }
                    var rec = selRecList[0];
                    this.formData=commonFunc.deepClone(rec);
                    this.formData.sort = rec.sort+'';
                    //this.formData.ureportName = this.formData.ureportName.replace(this.ureportPre,"");
                    //this.formData.ureportName = this.formData.ureportName.replace(this.ureportSuffix,"");
                    this.editFlag="update";

                }
                this.editModal = true;
            },
            submitFunc(){
                var _this = this;
                this.$refs['editValidate'].validate((valid) => {
                    if(valid) {
                        var submitUrl = "/report/save";
                        var param = commonFunc.deepClone(_this.formData);
                        //param.ureportName = _this.ureportPre+param.ureportName+_this.ureportSuffix;
                        commonFunc.submit(submitUrl,"post",param,function(data){
                            _this.$Message.success(data.msg);
                            _this.editModal = false;
                            _this.searchFunc();
                        },function(data){
                            _this.$Message.error(data.msg);
                        },'obj');
                    }
                });
            },
            deleteFunc(){
                var _this = this;
                var selRecList = this.$refs.table.getSelection();
                if(selRecList.length==0 ){
                    this.$Message.error('请选择报表记录');
                    return;
                }
                _this.$Modal.confirm({
                    title: '你确定删除选中的报表记录吗?',
                    okText: '确定',
                    cancelText: '取消',
                    onOk: function () {
                        commonFunc.submit("/report/delete","post",selRecList,function(data){
                            _this.$Message.success(data.msg);
                            _this.searchFunc();
                        },function(data){
                            _this.$Message.error(data.msg);
                        },'obj');
                    }
                });
            },
            cancelFunc(){
                this.editModal = false;
            },
            changeReportType(){

            },
            openDesigner(row){
                var src = "";
                if(null != row.ureportName && '' != row.ureportName){
                    src = "/pro/ureport/designer?_u="+row.ureportName+"&_rId="+row.id;
                }else{
                    src = "/pro/ureport/designer?_rId="+row.id;
                }
                var parent = $("#reportDesignerIframe").parent();
                var iframeHtml = '<iframe id="reportDesignerIframe" src="'+ src +'" style="width:100%;height:100%;"></iframe>';
                parent.html(iframeHtml);
                this.reportDesignerModal = true;
            },
            openPreview(row){
                var src = null;
                if(null != row.rptUrl && "" != row.rptUrl){
                    src = "/pro/"+row.rptUrl+"&reportId="+row.id;
                }else{
                    this.$Message.error("请先设计报表");
                    return;
                }
                var parent = $("#reportPreviewIframe").parent();
                var iframeHtml = '<iframe id="reportPreviewIframe" src="'+ src +'" style="width:100%;height:100%;"></iframe>';
                parent.html(iframeHtml);
                this.reportPreviewModal=true;
            }
        }
    });
});

5、程序的运行

右键点击启动文件(UreportApplication.java),在弹出的菜单中点击Run UreportApplication.java菜单启动系统。
在这里插入图片描述启动成功后,打开浏览器,输入http://localhost:9090/pro/views/report.html即可进入报表管理页面
在这里插入图片描述点击【新增】按钮,弹出新增报表表单,完善表单基本信息,点击【保存】按钮提交新增报表记录。
在这里插入图片描述在报表列表中选中新增的报表记录,点击【报表设计】按钮,进入报表设计器
在这里插入图片描述在这里插入图片描述
到此就可以使用报表设计器设计器根据自己的业务需要进行报表设计了。设计完成后,可以点击预览图标按钮进行报表预览。

如需学习如何设计报表,可以进入这里进行学习,该学习文档可帮助用户设计出更多符合用户业务需求的报表。

到此,Springboot整合UReport2报表分享结束。

如有需要源码进行学习的同学,请点击【这里】

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐