若依前后端分离版集成积木报表和大屏

1.前言

本文将以若依框架前后端分离版为基础,详细介绍如何集成积木报表和大屏,并且配置相应权限。我以前的文章都是从如何下载和配置若依框架开始介绍,从这以后的文章,我会以其他文章配置好的若依框架为基础,完成操作。不过,大家不用担心,我会明确指出会以哪个文章配置好的若依框架为基础完成的操作。

为了更好得集成积木报表和大屏功能,会介绍如何从后端获取数据进行显示。如果跳过测试类的完成过程,有些小伙伴可能跟不上了,但是我又不想浪费时间去写测试类和对应的接口,所以我这次会以集成Mybatis-Plus代码生成器的若依框架基础,完成此文章。如果想将若依框架前后端分离版集成Mybatis-Plus代码生成器,可以查看我的文章《若依前后端分离版创建Mybatis-Plus代码生成器》。我会以这个文章生成的代码为基础完成集成,可以下载我上传的资源《若依前后端分离版集成Mybatis-Plus代码生成器》。当然,如果怕麻烦,也可以看我的文章《从零开始学若依框架-下载和环境初始化》,完成若依框架基础设置,只是最后测试类生成,需要自己创建或者使用若依自带的代码生成器生成,不会影响集成积木报表和大屏功能。

如果不想麻烦,可以去下载已经集成积木报表和大屏的文件,文件地址为若依前后端分离版集成积木报表和大屏。文件中包含所有所有代码,只需要执行sql文件夹中sql文件(会自动创建数据库ry_vue_new),配置数据库信息和redis基本信息后,直接可以使用,从第6步积木报表使用开始看就行。

2.下载和启动程序

1.打开若依前后端分离版集成Mybatis-Plus代码生成器,下载代码。

2.下载完成后,解压到文件夹,然后用IDEA打开解压后的项目。ruoyi-admin/src/main/java/com.ruoyi.data包下的course相关代码,不需要删除,后面可以用来测试功能。这里我会删除下,后面用Mybatis-Plus代码生成器重新生成。(不会使用代码生成器的,不要删除,最后只需要在数据库中执行sql就行了)

3.配置项目本地maven,如果不会配置本地maven,可以查看我的文章《maven项目实现SpringBoot的HelloWorld》,里面包含maven本地配置。创建本地数据库,在项目中设置本地数据库和密码,并且设置redis密码,如果不会操作,可以查看我的文章《从零开始学若依框架-下载和环境初始化》。配置上述信息,成功启动前后端代码。

3.后端集成积木报表和大屏

1.下载依赖

1.打开ruoyi-common模块下的pom.xml文件,添加积木报表和大屏的依赖。

<!-- 积木报表 -->
<dependency>
  <groupId>org.jeecgframework.jimureport</groupId>
  <artifactId>jimureport-spring-boot-starter</artifactId>
  <version>1.9.2</version>
</dependency>

<!--积木BI大屏-->
<dependency>
  <groupId>org.jeecgframework.jimureport</groupId>
  <artifactId>jimubi-spring-boot-starter</artifactId>
  <version>1.9.1</version>
</dependency>

2.点击“加载maven改变”按钮,等待依赖下载完成。

2.yml配置

打开ruoyi-admin模块下application.yml文件,在文件最后增加积木报表和大屏的配置。

jeecg :
  # 权限配置
  permission:
    # 报表权限配置
    report:
      # 查询权限符号,配置此权限代表只能查看报表
      query: jeecg:report:query
      # 修改权限符号,配置此权限代表拥有报表所有权限
      edit: jeecg:report:edit
    # 大屏权限配置
    drag:
      # 查询权限符号,配置此权限代表只能查看大屏
      query: jeecg:drag:query
      # 修改权限符号,配置此权限代表拥有大屏所有权限
      edit: jeecg:drag:edit
  jmreport:
    #自定义项目前缀
    # 现在是开发环境下的前端访问前缀,部署项目时,需要切换到生成环境访问前缀
    customPrePath: /dev-api

注意:

1.目前报表和大屏分别设置两个类型的权限,其中query权限表示只能查看,edit权限表示用于一切权限。

2.customPrePath是用来配置前端访问后端的接口的前缀,当前是开发环境配置为/dev-api没问题。项目发布时,一定将这个配置改为生产环境下访问前缀,如:/prod-api。

3.允许匿名访问

打开ruoyi-framework模块下src/main/java/com.ruoyi.framework.config包中的SecurityConfig.java文件(集成积木报表和大屏操作都在ruoyi-framework模块下,下面配置如果不表明模块就是在此模块下,不表明包路径就是com.ruoyi.framework包下),增加积木报表和大屏匿名访问权限。

, "/jmreport/**", "/drag/**"

3.新建配置类

在com.ruoyi.framework包下增加包report(此包包含报表和大屏配置过程中,所有的新增包或类),在report包下新增config包,在cofig包下新增ReportConfig.java类,并在此类中添加以下代码。

package com.ruoyi.framework.report.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ReportConfig {

    // 报表查询权限
    @Value("${jeecg.permission.report.query}")
    private String reportQueryPermission;

    // 报表修改权限
    @Value("${jeecg.permission.report.edit}")
    private String reportEditPermission;

    // 大屏查看权限
    @Value("${jeecg.permission.drag.query}")
    private String dragQueryPermission;

    //大屏修改权限
    @Value("${jeecg.permission.drag.edit}")
    private String dragEditPermission;

    public String getReportQueryPermission() {
        return reportQueryPermission;
    }

    public void setReportQueryPermission(String reportQueryPermission) {
        this.reportQueryPermission = reportQueryPermission;
    }

    public String getReportEditPermission() {
        return reportEditPermission;
    }

    public void setReportEditPermission(String reportEditPermission) {
        this.reportEditPermission = reportEditPermission;
    }

    public String getDragQueryPermission() {
        return dragQueryPermission;
    }

    public void setDragQueryPermission(String dragQueryPermission) {
        this.dragQueryPermission = dragQueryPermission;
    }

    public String getDragEditPermission() {
        return dragEditPermission;
    }

    public void setDragEditPermission(String dragEditPermission) {
        this.dragEditPermission = dragEditPermission;
    }
}

4.实现token接口

1.打开web.service包下的TokenService.java文件,将自带的getLoginUser方法注释掉。

新增如下代码。

 // 修改
    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        return getLoginUser(token);
    }

    // 新增
    public LoginUser getLoginUser(String token)
    {

        if (StringUtils.isNotEmpty(token))
        {
            try
            {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
            {
            }
        }
        return null;
    }

2.在report包下新增service包下,在service包下新增ReportTokenService.java类,并且在此类中添加以下代码。

package com.ruoyi.framework.report.service;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;

import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.report.config.ReportConfig;
import com.ruoyi.framework.web.service.TokenService;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;


import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;


@Component
public class ReportTokenService implements JmReportTokenServiceI {

    // 若依框架token
    @Value("${token.header}")
    private String ryHeader;

    // 积木报表token
    private String jmHeader = "X-Access-Token";

    @Autowired
    private ReportConfig reportConfig;

    @Autowired
    private TokenService tokenService;


    @Override
    public String getUsername(String s) {
        LoginUser loginUser = tokenService.getLoginUser(s);
        return loginUser.getUsername();
    }

    @Override
    public String[] getRoles(String s) {
        LoginUser loginUser = tokenService.getLoginUser(s);
        SysUser user = loginUser.getUser();
        List<SysRole> roles = user.getRoles();
        String[] roleNameArray = roles.stream().map(SysRole::getRoleName).toArray(String[]::new);
        return roleNameArray;
    }

    @Override
    public Boolean verifyToken(String s) {
        LoginUser loginUser = tokenService.getLoginUser(s);
        if (StringUtils.isNotNull(loginUser)){
            tokenService.refreshToken(loginUser);
            SysUser user = loginUser.getUser();

            // 超级管理员放权
            if (StringUtils.isNotNull(user) && user.isAdmin()) {
                return true;
            } else {
                Set<String> permissions = loginUser.getPermissions();
                if (StringUtils.isNotNull(permissions) && (permissions.contains(reportConfig.getReportQueryPermission()) || permissions.contains(reportConfig.getReportEditPermission()) || permissions.contains(reportConfig.getDragQueryPermission()) || permissions.contains(reportConfig.getDragEditPermission()))) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public String getToken(HttpServletRequest request) {
        String token = request.getParameter("token");
        if (StringUtils.isNull(token)) {
            token = request.getHeader(jmHeader);
        }
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    @Override
    public Map<String, Object> getUserInfo(String token) {
        token = token.replace(Constants.TOKEN_PREFIX, "");
        LoginUser loginUser = tokenService.getLoginUser(token);
        Map<String, Object> map = new HashMap<>();
        map.put(SYS_USER_CODE, loginUser.getUserId());
        map.put(SYS_ORG_CODE, loginUser.getDeptId());
        return map;
    }

    @Override
    public HttpHeaders customApiHeader() {
        HttpHeaders headers = new HttpHeaders();
        headers.add(ryHeader, Constants.TOKEN_PREFIX + getToken());
        headers.add(jmHeader, getToken());
        return headers;
    }
}

注意:

private String jmHeader= "X-Access-Token";这行代码中X-Access-Token有下波浪线,放在波浪线上,选择“Add custom HTTP header”,不影响功能。

5.配置拦截器

1.在report包下新增interceptor包下,在interceptor包下新增ReportInterceptor.java类,并且在此类中添加以下代码。

package com.ruoyi.framework.report.interceptor;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.report.config.ReportConfig;
import com.ruoyi.framework.report.service.ReportTokenService;
import com.ruoyi.framework.web.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

@Component
public class ReportInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private ReportConfig reportConfig;

    @Autowired
    private ReportTokenService reportTokenService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = reportTokenService.getToken(request);
        LoginUser loginUser = tokenService.getLoginUser(token);
//        String uri = request.getRequestURI();
//        System.out.println(uri);

        if (StringUtils.isNotNull(loginUser)) {
            SysUser user = loginUser.getUser();

            // 超级管理员放权
            if (StringUtils.isNotNull(user) && user.isAdmin()) {
                return true;
            } else {
                //获取权限集合
                Set<String> permissions = loginUser.getPermissions();
                //如果拥有设计器的权限,则无需view权限,也可以通过校验
                if (StringUtils.isNotNull(permissions)) {
                    String uri = request.getRequestURI();

                    // 如果访问报表
                    if (uri.contains("/jmreport/")) {
                        // 如果有操作权限,直接放行
                        if (permissions.contains(reportConfig.getReportEditPermission())) {
                            return true;
                        } else {
                            // 设置查询报表的路径,没有带报表编码的路径
                            Set<String> queryReportSet = new HashSet<>();
                            queryReportSet.add("/jmreport/getQueryInfo");
                            queryReportSet.add("/jmreport/show");

                            // 设置查询报表的路径,带报表编码的路径
                            ArrayList<String> queryReportList = new ArrayList<>();
                            queryReportList.add("/jmreport/view/");
                            queryReportList.add("/jmreport/addViewCount/");
                            queryReportList.add("/jmreport/checkParam/");

                            // 如果有查询权限
                            if (permissions.contains(reportConfig.getReportQueryPermission())) {

                                // 如果是没有报表编码的路径,放行
                                if (queryReportSet.contains(uri)) {
                                    return true;
                                } else {

                                    // 如果是带报表编码的路径,放行
                                    for (int i = 0; i < queryReportList.size(); i++) {
                                        String s = queryReportList.get(i);
                                        if (uri.contains(s)) {
                                            return true;
                                        }
                                    }

                                }

                            }
                        }

                        //如果访问大屏
                    } else if (uri.contains("/drag/")) {

                        // 如果有操作权限,直接放行
                        if (permissions.contains(reportConfig.getDragEditPermission())) {
                            return true;
                        } else {
                            // 设置查询大屏的路径,完全路径
                            Set<String> queryDragSet = new HashSet<>();
                            queryDragSet.add("/drag/page/queryById");
                            queryDragSet.add("/drag/page/addVisitsNumber");


                            // 设置查询大屏的路径,带有包含部分路径
                            ArrayList<String> queryDragList = new ArrayList<>();
                            queryDragList.add("/drag/share/view/");
                            queryDragList.add("/drag/mock/json/");

                            // 如果有查询权限,并且访问的查询接口,放行
                            if (permissions.contains(reportConfig.getDragQueryPermission()) && queryDragSet.contains(uri)) {
                                return true;
                            }
                        }
                        // 拦截非报表和大屏路径处理,正确配置,不会出现此情况。
                    } else {
                        return true;
                    }

                }
            }
        }
        AjaxResult ajaxResult = AjaxResult.error(403, "当前操作没有权限");
        ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
        return false;
    }
}

注意:

拦截器拦截路径规则是通过前端控制台查看路径情况和后端上图标注部分路径打印情况实现的。对于edit权限配置,分别对应报表和大屏的全部权限。对于query权限配置,我只设置了根据编码查看报表和大屏。如果想更加细致区分权限情况或者增加query权限可访问路径,需要自己根据前端控制台调试和后端调试重新设置符合要求的拦截器。

2.打开config包下的ResourceConfig.java文件,注入ReportInterceptor拦截器,并且注册拦截器和设置拦截规则。我通过调试设置了一些静态资源不拦截,如果出现我未设置的静态资源,需要自己进行设置。

@Autowired
private ReportInterceptor reportInterceptor;

registry.addInterceptor(reportInterceptor).addPathPatterns("/jmreport/**", "/drag/**")
                // 不拦截静态资源
                .excludePathPatterns("/*.**", "/**/*.html", "/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.woff", "/**/*.woff2", "/**/*.ttf",  "/**/*.svg", "/**/*.ico", "/**/*.map");

6.实现数据格式转换器

1.在report包下新增adapter包,在adapter包下,新增ReportDataConvertAdapter.java文件,并且在此类中添加以下代码。

package com.ruoyi.framework.report.adapter;

import com.alibaba.fastjson.JSONObject;
import org.jeecg.modules.jmreport.desreport.render.handler.convert.ApiDataConvertAdapter;
import org.springframework.stereotype.Component;

@Component("reportDataConvertAdapter")
public class ReportDataConvertAdapter implements ApiDataConvertAdapter {
    @Override
    public String getData(JSONObject jsonObject) {

        if(jsonObject.containsKey("data")){
            String data = jsonObject.getString("data");
            return data;
        }else if(jsonObject.containsKey("rows")){
            return jsonObject.getString("rows");
        }else {
            return jsonObject.toJSONString();
        }
    }
}

7.创建数据库表

可以积木github下载sql,或者通过我上传的资源《积木报表和大屏sql文件》进行下载。执行sql文件,生成对应的表。

8.问题处理

当前使用的若依版本是3.8.9,实现第6步数据格式转换器没有问题。但是我使用若依版本3.8.5,实现数据格式转换器报错,导致后台无法启动。出现这个原因是若依的fastjson2和积木报表的fastjson版本冲突导致的。如果出现此报错,请将ruoyi-admin模块下pom.xml的spring-boot-devtools依赖注释掉,并且将application.yml中热部署开关关闭。

9.启动后端

成功启动后端程序。

4.前端集成积木报表和大屏

1.新建api

1.在src/api/py文件夹下,新建jeecg文件夹,并且jeecg文件夹下,新增request.js文件,并且在此文件中添加以下代码。虽然若依自带了request.js文件访问后端接口,但是有时候不适用于访问积木后端接口。因此,用新增request.js文件专门访问积木后端接口。

import axios from 'axios'
import { Notification, MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams } from "@/utils/ruoyi";
import cache from '@/plugins/cache'

// 是否显示重新登录
export let isRelogin = { show: false };

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})

// request拦截器
service.interceptors.request.use(config => {
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  // 是否需要防止数据重复提交
  const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
  if (getToken() && !isToken) {
    config.headers['X-Access-Token'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
    let url = config.url + '?' + tansParams(config.params);
    url = url.slice(0, -1);
    config.params = {};
    config.url = url;
  }
  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
    const requestObj = {
      url: config.url,
      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
      time: new Date().getTime()
    }
    const sessionObj = cache.session.getJSON('sessionObj')
    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
      cache.session.setJSON('sessionObj', requestObj)
    } else {
      const s_url = sessionObj.url;                  // 请求地址
      const s_data = sessionObj.data;                // 请求数据
      const s_time = sessionObj.time;                // 请求时间
      const interval = 1000;                         // 间隔时间(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
        const message = '数据正在处理,请勿重复提交';
        console.warn(`[${s_url}]: ` + message)
        return Promise.reject(new Error(message))
      } else {
        cache.session.setJSON('sessionObj', requestObj)
      }
    }
  }
  return config
}, error => {
  console.log(error)
  Promise.reject(error)
})

// 响应拦截器
service.interceptors.response.use(res => {
  // 未设置状态码则默认成功状态
  const code = res.data.code || 200;
  // 获取错误信息
  const msg = errorCode[code] || res.data.msg || res.data.message || errorCode['default']

  // 二进制数据则直接返回
  if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
    return res
  }
  if (code === 401) {
    if (!isRelogin.show) {
      isRelogin.show = true;
      MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
        isRelogin.show = false;
        store.dispatch('LogOut').then(() => {
          location.href = '/index';
        })
      }).catch(() => {
        isRelogin.show = false;
      });
    }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    } else if (code === 500) {
      Message({ message: msg, type: 'error' })
      return Promise.reject(new Error(msg))
    } else if (code === 601) {
      Message({ message: msg, type: 'warning' })
      return Promise.reject('error')
    } else if (code !== 200) {
      Notification.error({ title: msg })
      return Promise.reject('error')
    } else {
      return res.data
    }
  },
  error => {
    console.log('err' + error)
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    Message({ message: message, type: 'error', duration: 5 * 1000 })
    return Promise.reject(error)
  }
)


export default service

2.在jeecg文件夹下,新增report.js文件,并且在此文件中添加以下代码。此文件主要用于怎么直接访问积木报表或大屏的后端数据,如果有二次开发需求,可以根据我这个文件方式访问后端接口。

import request from "@/api/py/jeecg/request";
let jmreportUrl = "/jmreport";
import { getToken } from "@/utils/auth";
let paramObj = {
    token: "Bearer " + getToken()
}
// 获取填报报表
export function listReport(queryParams) {
    let params = {
      ...paramObj,
      ...queryParams
    }
    return request({
      url: jmreportUrl + "/excelQuery",
      method: 'get',
      params: params
    })
}

// 获取填报报表列表
export function listFillReport(queryParams) {
  paramObj["reportType"] = "1011126161407836160";
  let params = {
    ...paramObj,
    ...queryParams
  }
  return request({
    url: jmreportUrl + "/excelQuery",
    method: 'get',
    params: params
  })
}

2.页面集成

1.在src/view文件夹下新增py文件夹,在py文件夹下新增jeecg文件夹,在jeecg文件夹下新增report文件夹,在report文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template>
  <div>
    <i-frame :src="reportUrl"></i-frame>
  </div>
</template>
<script>
  import { getToken } from "@/utils/auth";
  import iFrame from "@/components/iFrame/index";
  export default {
    name: "PyJeecgReport",
    components: { iFrame },
    data() {
      return {
        reportUrl: process.env.VUE_APP_BASE_API + "/jmreport/list?token=Bearer " + getToken()
      }
    }
  }
</script>

2.在report文件夹下新增view文件夹,在view文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template>
    <div>
        <i-frame :src="reportUrl"></i-frame>
    </div>
</template>
<script>
    import { getToken } from "@/utils/auth";
    import iFrame from "@/components/iFrame/index";
    export default {
        name: "PyJeecgReportView",
        components: { iFrame },
        data() {
            return {
                reportUrl: ""
            }
        },
        created() {
            let query = this.$route.query;
            let code = undefined;
            let paramsString = "";
            if (query) {
                for (const key in query) {
                    if (Object.hasOwnProperty.call(query, key)) {
                        const param = query[key];
                        if (key == "code") {
                            code = param;
                        } else {
                            if (paramsString != "") {
                                paramsString += paramsString + "&" + key + "=" + param;
                            } else {
                                paramsString = "&" + key + "=" + param;
                            }
                        }

                    }
                }
            }

            if (code) {
                this.reportUrl = process.env.VUE_APP_BASE_API + "/jmreport/view/" + code + "?token=Bearer " + getToken() + paramsString;

            } else {
                this.$modal.msgError("报表编码(code)为空,无法打开报表");
            }
        }
    }
</script>

3.在jeecg文件夹下新增drag文件夹,在drag文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template>
    <div>
        <i-frame :src="dragUrl"></i-frame>
    </div>
</template>
<script>
    import { getToken } from "@/utils/auth";
    import iFrame from "@/components/iFrame/index";
    export default {
        name: "PyJeecgDrag",
        components: { iFrame },
        data() {
            return {
                dragUrl: process.env.VUE_APP_BASE_API + "/drag/list?token=Bearer " + getToken()
            }
        }
    }
</script>

4.在drag文件夹下新增view文件夹,在view文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template>
    <div>
        <i-frame :src="dragUrl"></i-frame>
    </div>
</template>
<script>
    import { getToken } from "@/utils/auth";
    import iFrame from "@/components/iFrame/index";
    export default {
        name: "PyJeecgDragView",
        components: { iFrame },
        data() {
            return {
                dragUrl: ""
            }
        },
        created() {
            let query = this.$route.query;
            let code = undefined;
            let paramsString = "";
            if (query) {
                for (const key in query) {
                    if (Object.hasOwnProperty.call(query, key)) {
                        const param = query[key];
                        if (key == "code") {
                            code = param;
                        } else {
                            if (paramsString != "") {
                                paramsString += paramsString + "&" + key + "=" + param;
                            } else {
                                paramsString = "&" + key + "=" + param;
                            }
                        }

                    }
                }
            }

            if (code) {
                this.dragUrl = process.env.VUE_APP_BASE_API + "/drag/share/view/" + code + "?token=Bearer " + getToken() + paramsString;

            } else {
                this.$modal.msgError("大屏编码(code)为空,无法打开大屏");
            }
        }
    }
</script>

5.在jeecg文件夹下新增test文件夹,在test文件夹下新增index.vue文件,并且在此文件中添加以下代码。

<template>
    <div class="app-container">
        <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
            <el-form-item label="报表名称" prop="name">
                <el-input v-model="queryParams.name" placeholder="请输入报表名称" clearable style="width: 240px"
                    @keyup.enter.native="handleQuery" />
            </el-form-item>
            <el-form-item>
                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
            </el-form-item>
        </el-form>

        <el-row :gutter="10" class="mb8">

            <el-col :span="1.5">
                <el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleOpenReport">打开报表页面</el-button>
            </el-col>

            <el-col :span="1.5">
                <el-button type="success" plain icon="el-icon-download" size="mini" @click="handleOpenDrag">打开大屏页面</el-button>
            </el-col>
            
            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>

        <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange"
            :header-cell-style="{textAlign: 'center'}" :cell-style="{textAlign: 'center'}">
            <el-table-column type="selection" width="55" align="center" />
            <el-table-column label="名称" prop="name" />
        </el-table>

        <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
            @pagination="getList" />



    </div>
</template>

<script>
    import {
        listReport
    } from "@/api/py/jeecg/report";
    export default {
        name: "PyJeecgTest",
        data() {
            return {
                // 遮罩层
                loading: true,
                // 选中数组
                ids: [],
                // 非单个禁用
                single: true,
                // 非多个禁用
                multiple: true,
                // 显示搜索条件
                showSearch: true,
                // 总条数
                total: 0,
                // 角色表格数据
                list: [],
                // 弹出层标题
                title: "",
                // 是否显示弹出层
                open: false,

                // 查询参数
                queryParams: {
                    pageNo: 1,
                    pageSize: 10,
                    name: undefined
                },
                // 表单参数
                form: {},

                // 表单校验
                rules: {
                    name: [
                        { required: true, message: "名称不能为空", trigger: "change" }
                    ],
                }




            };
        },
        created() {
            this.getList();
        },
        methods: {
            // 获取信息列表
            getList() {
                this.loading = true;
                listReport(this.queryParams).then(response => {
                    let result = response.result;
                    this.list = result.records;
                    this.total = result.total;
                    this.loading = false;
                });
            },
            /** 搜索按钮操作 */
            handleQuery() {
                this.queryParams.pageNo = 1;
                this.getList();
            },
            /** 重置按钮操作 */
            resetQuery() {
                this.resetForm("queryForm");
                this.handleQuery();
            },
            // 多选框选中数据
            handleSelectionChange(selection) {
                this.ids = selection.map(item => item.id);
                this.single = selection.length != 1;
                this.multiple = !selection.length;
            },

            // 打开报表测试
            handleOpenReport() {
                let query = {
                    code: "1036502085143068672",
                    id: "3f02599004e0224bb10b7618454faa55",
                    name: "测试"
                }
                this.$router.push({ path: "/jeecg/report/view", query });
            },

            // 打开大屏测试
            handleOpenDrag(){
                let query = {
                    code: "1036879140347150336",
                }
                this.$router.push({ path: "/jeecg/drag/view", query });
            }
        }
    };
</script>

3.新增菜单

1.打开菜单“系统管理->菜单管理”,点击“新增”按钮。

2.依次如下图填写配置信息,然后点击“确定”按钮。

3.强制刷新页面,显示新增菜单信息。

5.生成测试类

1.打开菜单“系统工具->代码生成”,点击“创建”按钮。

2.复制下面sql到填写框,点击“确定”按钮。

CREATE TABLE `py_course` (
  `id` varchar(32) NOT NULL COMMENT '主键ID',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `name` varchar(128) DEFAULT NULL COMMENT '课程名称',
  `teacher_name` varchar(128) DEFAULT NULL COMMENT '老师姓名',
  `course_class` varchar(128) DEFAULT NULL COMMENT '课程班级',
  `introduce` varchar(500) DEFAULT NULL COMMENT '课程介绍',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='课程信息';

3.数据库中执行下面sql代码,添加测试数据。

insert  into `py_course`(`id`,`user_id`,`name`,`teacher_name`,`course_class`,`introduce`,`del_flag`,`create_by`,`create_time`,`update_by`,`update_time`) values ('1fa120950a9113a65107b8382b6a17eb',2,'新增测试','新增测试',NULL,NULL,'0','ry','2023-04-24 10:45:08','admin','2023-05-11 10:37:07'),('3661935371eaf9c8dc374c4440ca29a7',2,'新增测试4','新增测试4',NULL,NULL,'0','ry','2023-04-24 10:45:34','',NULL),('3f02599004e0224bb10b7618454faa55',2,'测试001','李四','计科2002','无','0','ry','2023-04-17 13:38:46','',NULL),('48f28b8bd65808e1850696e2215d5b75',2,'测试','张三','计科2001','好的','0','ry','2023-04-17 13:33:10','ry','2023-04-17 13:36:23'),('5749ecbd9291a96e6cb471621d1ce4b3',2,'测试002','测试002',NULL,NULL,'0','ry','2023-04-24 10:44:57','',NULL),('5d4b77ca3c1192b24517c6e4d5db932c',2,'测试','测试',NULL,NULL,'0','ry','2023-04-24 10:44:45','',NULL),('64b1d449844ce5c582555a049c1ad156',2,'新增测试2','新增测试2',NULL,NULL,'2','ry','2023-04-24 10:45:16','',NULL),('b44657d47341dbf550075148374a1671',2,'测试1','测试','1280','无','2','ry','2023-04-24 10:44:21','ry','2023-04-24 14:05:18'),('b8c6d244b3c59756c3d1eb45043a54bb',2,'新增测试3','新增测试3',NULL,NULL,'2','ry','2023-04-24 10:45:25','',NULL),('c698517d6cd5159a80f6c94fbb390487',2,'数据库','李四','计科2003','好的','0','ry','2023-04-17 13:49:01','ry','2023-04-17 13:49:19'),('cc1ba825be2341a437dbe0bf1af3f273',2,'新增测试5','新增测试5',NULL,NULL,'0','ry','2023-04-24 10:45:51','',NULL);

4.使用Mybatis-Plus代码生成器,生成测试代码,具体怎么生成,可以查看我的文章《若依前后端分离版创建Mybatis-Plus代码生成器》。重启后端程序。(如果和我使用的是一样的代码,不需要重新生成,因为代码中自带course相关代码)

6.积木报表使用

1.打开本地数据库,执行下面代码,新增测试报表。

insert  into `jimu_report`(`id`,`code`,`name`,`note`,`status`,`type`,`json_str`,`api_url`,`thumb`,`create_by`,`create_time`,`update_by`,`update_time`,`del_flag`,`api_method`,`api_code`,`template`,`view_count`,`css_str`,`js_str`,`py_str`,`tenant_id`,`update_count`,`submit_form`)
values
('1036531539282198528','20250107152017__2659','课程信息表api数据集测试',NULL,NULL,'1011126161407836160','{\"loopBlockList\":[],\"querySetting\":{\"izOpenQueryBar\":false,\"izDefaultQuery\":true},\"printConfig\":{\"paper\":\"A4\",\"width\":210,\"height\":297,\"definition\":1,\"isBackend\":false,\"marginX\":10,\"marginY\":10,\"layout\":\"portrait\",\"printCallBackUrl\":\"\"},\"hidden\":{\"rows\":[],\"cols\":[]},\"dbexps\":[],\"dicts\":[],\"freeze\":\"A1\",\"dataRectWidth\":1470,\"autofilter\":{},\"validations\":[],\"cols\":{\"0\":{\"width\":150},\"1\":{\"width\":150},\"2\":{\"width\":100},\"3\":{\"width\":100},\"4\":{\"width\":100},\"5\":{\"width\":100},\"6\":{\"width\":120},\"7\":{\"width\":100},\"8\":{\"width\":200},\"9\":{\"width\":150},\"10\":{\"width\":200},\"11\":{\"width\":100},\"12\":{\"width\":200},\"13\":{\"width\":100},\"14\":{\"width\":200},\"15\":{\"width\":100},\"16\":{\"width\":200},\"17\":{\"width\":100},\"18\":{\"width\":200},\"19\":{\"width\":100},\"20\":{\"width\":200},\"21\":{\"width\":100},\"22\":{\"width\":200},\"23\":{\"width\":100},\"24\":{\"width\":200},\"25\":{\"width\":100},\"26\":{\"width\":200},\"27\":{\"width\":100},\"28\":{\"width\":200},\"29\":{\"width\":100},\"30\":{\"width\":200},\"31\":{\"width\":100},\"32\":{\"width\":200},\"33\":{\"width\":100},\"34\":{\"width\":200},\"35\":{\"width\":100},\"36\":{\"width\":200},\"37\":{\"width\":100},\"38\":{\"width\":200},\"39\":{\"width\":100},\"40\":{\"width\":200},\"41\":{\"width\":100},\"42\":{\"width\":200},\"43\":{\"width\":100},\"44\":{\"width\":200},\"45\":{\"width\":100},\"46\":{\"width\":200},\"47\":{\"width\":100},\"48\":{\"width\":200},\"49\":{\"width\":100},\"len\":50},\"area\":{\"sri\":2,\"sci\":8,\"eri\":2,\"eci\":8,\"width\":200,\"height\":45},\"pyGroupEngine\":false,\"submitHandlers\":[],\"excel_config_id\":\"1036531539282198528\",\"hiddenCells\":[],\"zonedEditionList\":[],\"rows\":{\"0\":{\"cells\":{\"0\":{\"style\":8,\"merge\":[0,10],\"height\":45,\"text\":\"课程信息\"}},\"height\":45},\"1\":{\"cells\":{\"0\":{\"style\":10,\"text\":\"用户唯一标识\",\"rendered\":\"\",\"config\":\"\"},\"1\":{\"text\":\"课程唯一标识\",\"style\":10,\"rendered\":\"\",\"config\":\"\"},\"2\":{\"text\":\"课程名称\",\"style\":10},\"3\":{\"text\":\"老师姓名\",\"style\":10},\"4\":{\"text\":\"课程班级\",\"style\":10},\"5\":{\"text\":\"课程介绍\",\"style\":10},\"6\":{\"text\":\"是否删除\",\"style\":10},\"7\":{\"text\":\"创建者\",\"style\":10},\"8\":{\"text\":\"创建时间\",\"style\":10,\"rendered\":\"\",\"config\":\"\"},\"9\":{\"text\":\"更新者\",\"style\":10},\"10\":{\"text\":\"更新时间\",\"style\":10}},\"height\":45},\"2\":{\"cells\":{\"0\":{\"text\":\"#{courseGetList.userId}\",\"rendered\":\"\",\"config\":\"\",\"display\":\"normal\",\"style\":1},\"1\":{\"text\":\"#{courseGetList.id}\",\"rendered\":\"\",\"config\":\"\",\"style\":1},\"2\":{\"text\":\"#{courseGetList.name}\",\"style\":1,\"rendered\":\"\",\"config\":\"\"},\"3\":{\"text\":\"#{courseGetList.teacherName}\",\"style\":1},\"4\":{\"text\":\"#{courseGetList.courseClass}\",\"style\":1},\"5\":{\"text\":\"#{courseGetList.introduce}\",\"style\":1},\"6\":{\"text\":\"#{courseGetList.delFlag}\",\"style\":1},\"7\":{\"text\":\"#{courseGetList.createBy}\",\"style\":1,\"rendered\":\"\",\"config\":\"\"},\"8\":{\"rendered\":\"\",\"config\":\"\",\"style\":12,\"text\":\"#{courseGetList.createTime}\"},\"9\":{\"text\":\"#{courseGetList.updateBy}\",\"style\":1,\"rendered\":\"\",\"config\":\"\"},\"10\":{\"text\":\"#{courseGetList.updateTime}\",\"style\":11,\"rendered\":\"\",\"config\":\"\"}},\"height\":45},\"3\":{\"cells\":{\"0\":{\"text\":\"\",\"rendered\":\"\",\"config\":\"\"},\"1\":{},\"2\":{},\"8\":{\"text\":\"\",\"rendered\":\"\",\"config\":\"\"}},\"height\":45},\"4\":{\"cells\":{\"0\":{\"rendered\":\"\",\"config\":\"\"},\"1\":{},\"2\":{},\"8\":{\"text\":\"\",\"rendered\":\"\",\"config\":\"\"}},\"height\":45},\"5\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{\"rendered\":\"\",\"config\":\"\"}},\"height\":45},\"6\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{},\"5\":{}},\"height\":45},\"7\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"8\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"9\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{},\"11\":{\"text\":\"\",\"rendered\":\"\",\"config\":\"\"}},\"height\":45},\"10\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"11\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"12\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"13\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"14\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"15\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"16\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"17\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"18\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"19\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"20\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"21\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"22\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"23\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"24\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"25\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"26\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"27\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"28\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"29\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"30\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"31\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"32\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"33\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"34\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"35\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"36\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"37\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"38\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"39\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"40\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"41\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"42\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"43\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"44\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"45\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"46\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"47\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"48\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"49\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"50\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"51\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"52\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"53\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"54\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"55\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"56\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"57\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"58\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"59\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"60\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"61\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"62\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"63\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"64\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"65\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"66\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"67\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"68\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"69\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"70\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"71\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"72\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"73\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"74\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"75\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"76\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"77\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"78\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"79\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"80\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"81\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"82\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"83\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"84\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"85\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"86\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"87\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"88\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"89\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"90\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"91\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"92\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"93\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"94\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"95\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"96\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"97\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"98\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"99\":{\"cells\":{\"0\":{},\"1\":{},\"2\":{}},\"height\":45},\"len\":100,\"\":{\"cells\":{\"\":{\"text\":\"\",\"rendered\":\"\",\"config\":\"\",\"display\":\"normal\"}}}},\"rpbar\":{\"show\":true,\"pageSize\":\"\",\"btnList\":[]},\"fixedPrintHeadRows\":[],\"fixedPrintTailRows\":[],\"displayConfig\":{},\"fillFormInfo\":{\"layout\":{\"direction\":\"horizontal\",\"width\":200,\"height\":45}},\"background\":false,\"name\":\"sheet1\",\"styles\":[{\"align\":\"center\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"bottom\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"bottom\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]}},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"bgcolor\":\"#e7e5e6\"},{\"bgcolor\":\"#e7e5e6\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"bgcolor\":\"#f2f2f2\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"format\":\"datetime\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"format\":\"date\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"format\":\"date2\"},{\"align\":\"center\",\"border\":{\"top\":[\"thin\",\"#000\"],\"bottom\":[\"thin\",\"#000\"],\"left\":[\"thin\",\"#000\"],\"right\":[\"thin\",\"#000\"]},\"format\":\"normal\"}],\"freezeLineColor\":\"rgb(185, 185, 185)\",\"merges\":[\"A1:K1\"]}',NULL,NULL,'admin','2025-01-07 16:53:07','admin','2025-01-08 14:19:46',0,NULL,NULL,0,38,'.viewApp .btnArea {\n        display: none;\n}',NULL,'',NULL,18,1)

2.打开菜单“积木管理->报表管理”,选择“填报报表”,选择第一个报表,点击“设计”按钮。

3.点击“查询”按钮,然后点击“新增”按钮,选择“API数据集”。其他数据集使用方式,请去官方文档查看快速入门相关文档

4.按照下面信息填写配置,点击“Api解析”按钮,获取下面字段信息。

注意:

1.类转换器就是在report.adater包下ReportDataConvertAdapter类注入容器的名称。

2.Api地址配置过程,“#{domainURL}”代表访问地址前缀,等同于customPrePath配置的/dev-api,访问后端数据都要加上。

3.Api地址配置过程,上面填写的地址,是根据id获取当前课程信息的接口。但是为了获取字段信息,先要填写一个已有数据的id。特别注意的是,获取的数据保证数据库中所有字段都有值,填写空值的字段,不会被解析出来。如果数据库中没有所有字段都有值的数据,可以增加一个测试数据,配置好功能后,可以删除,这个功能只用来获取字段信息。

5.根据实际情况,选择字段类型。因为内容太多,其他配置功能,这里就不介绍了,如果有需要,可以去官方文档查看报表查询配置

6.选择“报表参数”,点击“新增”按钮。

7.新增变量id,将原来固定值id,替换为变量id。

8.点击“确定”按钮,保存数据源设置。

9.点击“数据集管理”下拉,显示刚才配置好的数据源。

10.拖拽左侧字段替换现有动态数据(这是我以前测试功能的数据)。

11.点击“保存”按钮,保存当前配置。

注意:

通过拖拽方式,会默认为#{}取动态值,需要手动将“#”替换为“$”符号。虽然官网说“#”和“$”都能取出值来,但是实际上“#”是取不出值来的。

12.根据实际情况复制地址上的报表编码,打开test包下index.vue文件,替换handleOpenReport函数中的code,id替换成需要查询的id。name参数可以删除,也可以保留,因为没有设置name参数的取值。保存文件。

13.选择菜单“积木管理->权限测试”,点击“打开报表页面”按钮,会弹出“报表查看”页面。

14.如果自己新增填报报表,会发现也和我一样配置页面,但是显示时,第一行会显示很多按钮,有些按钮又不能用,不知道如何删除。

打开要隐藏按钮的报表,点击右侧页面中的“增强设置”按钮。

在css配置项,和我一样设置,隐藏按钮。这里的CSS、JS和Python等配置信息是直接操作报表页面的,可以通过前端控制调试,然后在这里进行设置。具体配置方式可以查看官方文档JS增强和CSS增强

.viewApp .btnArea {
  display: none;
}

15.报表显示的时间格式不利于查看,通过官方文档日期函数中的函数转换格式也没有用。这个是因为后端没有将日期进行格式转换,在需要转换的类属性上加上@JsonFormat标签进行格式化即可。重新启动后端,再次打开报表页面格式没有了问题,并且官方日期函数也能使用了。

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

16.如果表中数据使用了字典,如果更好的显示,需要在积木报表中也配置相应的字典。以“是否删除”这一列为例,是能显示2或0。打开需要设置的报表,点击右下角的“数据字典”按钮。

17.为了方便,我这里不新增了,将原有的删除标志进行修改。输入“删除”,然后点击“查询”按钮,最后点击删除状态后面的“字典配置”按钮。

注意:

如果点击“字典配置”按钮没有反应,请强制刷新页面,再去点击就没有问题了,我也不清楚这是什么原因导致的。

18.点击已删除后面的“编辑”按钮,将值1改为2。

19.选择“查询”,点击数据集后面的“修改”按钮。找到“delFlag”字段,在字典编码这一列配置上刚才设置字典的字典编号,点击“确定”按钮保存。

20.点击“保存”按钮,重新进入报表页面,字典配置完成。

7.积木大屏使用

1.选择菜单“积木管理->大屏管理”,点击第一大屏下的“预览”按钮。

2.会打开页面,复制地址上的大屏编码。

3.打开test包下index.vue文件,替换handleOpenDrag函数中的code。

注意:

1.只替换code编码,保存后,vue不会替换当前页面,哪怕强制刷新,也不会更新页面。可以多添加一个控制台打印代码,再保存代码,就不会出现页面不更新的情况(除非停了前端,重新启动)。

2.如果没有增加控制台打印代码,导致页面更新不及时,访问编码为“1036879140347150336”的大屏。可能有小伙伴有疑问了,为什么本地我没有编码为“1036879140347150336”的大屏,为什么还能访问成功了。因为大屏数据应该保存了两个地方,包括本地数据库和线上数据库,要是通过系统自带的删除功能,需要删除一次大屏(逻辑删除),然后再去回收站再删除一次(物理删除),才能彻底删除。我为了方便,直接将本地数据库编码为“1036879140347150336”的大屏数据删除了,通过搜索找不到大屏数据,但是直接访问那个大屏编码还是能访问到。我不知道是操作问题才出这情况,还是大家也都能访问到上面编码的大屏。报表功能我也是通过本地数据库直接删除的页面,但是没有出现上面问题,报表数据应该只保存在本地。因此,大屏删除时,一定要使用系统自带的删除功能,删除两次。

4.选择菜单“积木管理->权限测试”,点击“打开大屏页面”按钮,会弹出“大屏查看”页面。

5.大屏功能和仪表盘功能都通过大屏管理这个功能进行操作,操作都是类似的,这里就不一一介绍了。大屏和仪表盘功能也是可以通过Api配置数据源的,配置方式比积木报表还简单,这里不介绍。更多大屏操作,可以去官方文档查看大屏设计器。仪表盘操作可以去官方文档查看仪表盘设计器

8.总结

上面简单介绍了下若依框架前后端分离版集成积木报表和积木大功能和简单的操作,由于功能内容太多了,我无法全部介绍,具体可以去官方文档查看JimuReport 积木报表文档。本人能力有限,花费时间也不是很长,对积木相关内容研究的不是透彻,同时拦截器也是写的一言难尽,我也就是能起到抛砖引玉的作用。由于这是文字加截图来说明操作,肯定没有视频那么生动,我会尽量写的详细一点,尽量满足所有认的需求,技术大佬可以选择性查看内容。我再多一下嘴,大屏功能有时候在不登录账号的时候进行使用。如果有此功能需求,请将前端访问路径设置在白名单中(permission.js文件中进行配置),后端spring security允许与大屏数据源有关的api允许匿名访问(后端ruoyi-framework模块的config包下的SecurityConfig文件中配置),最后设计拦截器条件时将与大屏有关的接口不进行拦截(和spring security同包,将拦截器中需要设置大屏查询权限检查的相关代码删除,并且将里面包含的接口在ResourcesConfig文件设置为拦截器不拦截)。

虽然我从2022年9月份开始写csdn文章,但是一直看不到收益,当中放弃了。2024年10月份发了文件预览内容,考虑到收费问题,都是打赏后,加我qq拉入群中获取资源。又发了若依Flowable流程管理二次开发这个收费内容。后续又发布一些免费与若依框架相关的内容,比如:包名修改器、集成MyBatis-Plus代码生成器等内容。当中,只有文件预览和流程管理这两部分代码获取是收费的,虽然两部分内容加起来还没有收入100块,但是我也是很开心的。

后续我会继续写一些与若依框架有关的内容,和自己学习过程中感觉能够分享的内容。我会根据工作量大小判断是否收费的,不过大家可以放心,大部分内容都是免费的。我时间有限的,更新不会特别及时,请大家见谅。但是,我会尽量分享一些有用的内容,而不是直接复制别的成果来凑文章数量。当然,如果大家有觉得哪些内容可以集成到若依框架中,可以私信给我,我会根据实际情况判断是否去学习。

如果大家条件允许的话,可以打赏下我的文章。不打赏也没关系,也可以给我文章点个赞,虽然我目前也不知道赞有啥用,但是就觉得挺好。如果你也在用或者想学习若依框架相关内容,可以查看我的主页,并且关注我,查看最新的内容。最后,谢谢大家!!!

### 若依前后端分离架构下积木报表集成解决方案 在若依前后端分离架构中,为了实现积木报表集成,需考虑微服务架构的优势以及具体的构建工具框架支持。微服务架构允许细粒度的服务划分,使得不同前端能够共享不同的后端资源并独立演进[^1]。 对于具体的技术栈而言,在Ant Design Jeecg Vue项目中,完成环境准备与基础设置涉及一系列命令操作,如安装Yarn、下载依赖项、启动应用服务编译等步骤[^2]。然而这些指令主要用于特定项目的初始化,并不直接适用于所有基于Vue.js或其他前端技术的项目。 针对JeecgBoot平台提供的低代码功能模块,特别是其强的在线开发能力丰富的可视化设计选项,表明该平台具备高度灵活性支持快速迭代的能力[^3]。这暗示着如果要在另一个类似的系统——比如采用相同或相似技术路径的若依平台上实施类似的功能,则可能需要借鉴JeecgBoot的设计理念技术实践。 考虑到JeecgBoot已经发布了专门面向报表功能增强的新更新说明,其中提到的手工集成方法或许能为其他系统的开发者提供有价值的指导[^4]。因此,要将在JeecgBoot上成功的经验移植到若依系统里,建议: - **研究官方文档**:深入理解目标平台(即若依)及其现有报表处理机制。 - **分析差异点**:对比两者之间的API接口定义、数据交互模式等方面的不同之处。 - **定制化适配层**:根据上述分析结果编写必要的转换逻辑或者中间件来桥接两个平台间的差距。 - **测试验证**:确保新加入的功能不会破坏原有业务流程的同时满足预期性能指标。 ```bash # 这些命令用于演示目的;实际部署时应参照各自产品的最新指南执行相应操作 npm install -g yarn # 安装全局yarn包管理器 cd your-project-directory # 切换至项目根目录 yarn # 下载所需库文件 ```
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飘逸飘逸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值