Merge pull request !3 from Raod/dev
V0.9.2
Raod 3 years ago committed by Gitee
commit b31bd6fe6c

@ -110,14 +110,18 @@ bin/start.bat Windows修改第4行的JAVA_HOME后双击启动
启动后访问 启动后访问
http://serverip:9095 http://serverip:9095
``` ```
源码编译部署和发行版部署可参考在线文档 <br>
https://report.anji-plus.com/report-doc/guide/quicklyDevelop.html
## 谁在使用 ## 谁在使用
希望你们的logo出现在此[请点此Issue进行登记](https://gitee.com/anji-plus/report/issues/I3ZXT4) <br> 希望你们的logo出现在此[请点此Issue进行登记](https://gitee.com/anji-plus/report/issues/I3ZXT4) ,我们将优先进行技术支持 <br>
<a href='http://www.anji-plus.com/'> <img src="https://report.anji-plus.com/file/download/d287d4d3-d30b-4850-9bac-a6c991409251" width = "130" height = "50" align=left/> </a> <a href='http://www.anji-plus.com/'> <img src="https://report.anji-plus.com/file/download/d287d4d3-d30b-4850-9bac-a6c991409251" width = "130" height = "50" align=left/> </a>
<a href='https://www.yunstech.cn/'><img src="https://www.yunstech.cn/images/logo.png" width = "130" height = "50" /> </a> <a href='https://www.yunstech.cn/'><img src="https://www.yunstech.cn/images/logo.png" width = "130" height = "50" /> </a>
<a href='http://www.fgkb.net/'><img src="https://report.anji-plus.com/file/download/9ee5b709-5033-4cd5-a784-ebd2877fd373" width = "130" height = "50" /> </a> <a href='http://www.fgkb.net/'><img src="https://report.anji-plus.com/file/download/9ee5b709-5033-4cd5-a784-ebd2877fd373" width = "130" height = "50" /> </a>
<a href='https://www.gykjweb.com/'><img src="https://report.anji-plus.com/file/download/d13b03f5-0c20-4878-9a79-f3c76b44bfd9" width = "130" height = "130" /> </a>
## 近期计划 ## 近期计划
@ -128,6 +132,15 @@ http://serverip:9095
- 基础图表增加 - 基础图表增加
- 现有图表功能修复/增加 - 现有图表功能修复/增加
## 版本问题
已知以下版本存在兼容性问题
- Node.js V16
- Jdk 11
- Mysql 8.0
- Windows 11
AJ-Report 使用Druid版本为1.2.6如果你觉得你配置都是正常但是数据源测试不过请尝试降低Druid版本
## 技术支持 ## 技术支持
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** **如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**

@ -35,40 +35,57 @@
**存在问题,还在开发中**<br> **存在问题,还在开发中**<br>
### 柱状图 ### 柱状图
目前**柱状图-渐变色**图形右侧功能区还在开发调整中 <br> 柱状图数据集对应字典值需要选择一个“X轴”、“柱状”只需要2个字段 <br>
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 柱状堆叠图 ### 柱状堆叠图
开发中 <br> 动态的数据集对应字典值需要选择一个“X轴”、“Y轴”、“柱状”也就是说需要3个字段不明白可以看看静态数据 <br>
![img.17](../picture/dashboard/img_17.png)
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 折线图 ### 折线堆叠图
动态的数据集对应字典值需要选择一个“X轴”、“Y轴”、“折线”也就是说需要3个字段不明白可以看看静态数据 <br>
![img.17](../picture/dashboard/img_17.png)
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** **如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 折线图
折线图数据集对应字典值需要选择一个“X轴”、“折线”只需要2个字段 <br>
![img_8.png](../picture/dashboard/img_8.png) ![img_8.png](../picture/dashboard/img_8.png)
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 柱线图 ### 柱线图
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** 柱线图数据集对应字典值需要选择一个“X轴”、“柱状”、“折线”需要3个字段 <br>
![img9](../picture/dashboard/img_9.png) ![img9](../picture/dashboard/img_9.png)
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 饼图 ### 饼图
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** 饼图的数据集选择的时候只能选择饼图对应的字典即“Name”、“Value”不明白可以参考静态数据 <br>
![img10](../picture/dashboard/img_10.png) <br> ![img10](../picture/dashboard/img_10.png) <br>
![img11](../picture/dashboard/img_11.png) <br> ![img11](../picture/dashboard/img_11.png) <br>
![img12](../picture/dashboard/img_12.png) <br> ![img12](../picture/dashboard/img_12.png) <br>
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 漏斗图 ### 漏斗图
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** 和饼图一样 <br>
![img13](../picture/dashboard/img_13.png) <br> ![img13](../picture/dashboard/img_13.png) <br>
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 仪表盘 ### 仪表盘
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** 数据集只能有一个字段,且字典选择“文本数字” <br>
![img14](../picture/dashboard/img_14.png) <br> ![img14](../picture/dashboard/img_14.png) <br>
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 中国地图 ### 中国地图
**注**:气泡地图已支持动态数据,中国地图(迁徙图)暂不支持动态数据 **注**:气泡地图已支持动态数据,中国地图(迁徙图)暂不支持动态数据
气泡地图动态数据集和饼图一样对应字典值需要选择一个“Name”、“Value”且name的字段值要和echarts图表里面的值能对应上可参考静态数据 <br>
![img15](../picture/dashboard/img_15.png) <br> ![img15](../picture/dashboard/img_15.png) <br>
### 百分百图 ### 百分百图
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>** 数据集只能有一个字段,且字典选择“文本数字” <br>
![img16](../picture/dashboard/img_16.png) <br> ![img16](../picture/dashboard/img_16.png) <br>
**如有问题,请提交 [Issue](https://gitee.com/anji-plus/report/issues) <br>**
### 热力图 ### 热力图
**开发中** <br> **开发中** <br>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -132,4 +132,6 @@ public interface ResponseCode {
String SOURCE_CODE_ISEXIST = "4009"; String SOURCE_CODE_ISEXIST = "4009";
String CLASS_NOT_FOUND = "4010"; String CLASS_NOT_FOUND = "4010";
String REPORT_SHARE_LINK_INVALID = "report.share.link.invalid";
} }

@ -0,0 +1,47 @@
package com.anjiplus.template.gaea.business.enums;
public enum DeleteFlagEnum {
DELETED(1,"已删除"),
UNDELETED(0,"未删除"),
;
private int codeValue;
private String codeDesc;
private DeleteFlagEnum(int codeValue, String codeDesc) {
this.codeValue = codeValue;
this.codeDesc = codeDesc;
}
public int getCodeValue(){ return this.codeValue;}
public String getCodeDesc(){ return this.codeDesc;}
//根据codeValue获取枚举
public static DeleteFlagEnum parseFromCodeValue(int codeValue){
for (DeleteFlagEnum e : DeleteFlagEnum.values()){
if(e.codeValue == codeValue){ return e;}
}
return null;
}
//根据codeValue获取描述
public static String getCodeDescByCodeValue(int codeValue){
DeleteFlagEnum enumItem = parseFromCodeValue(codeValue);
return enumItem == null ? "" : enumItem.getCodeDesc();
}
//验证codeValue是否有效
public static boolean validateCodeValue(int codeValue){ return parseFromCodeValue(codeValue)!=null;}
//列出所有值字符串
public static String getString(){
StringBuffer buffer = new StringBuffer();
for (DeleteFlagEnum e : DeleteFlagEnum.values()){
buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
}
buffer.deleteCharAt(buffer.lastIndexOf(","));
return buffer.toString().trim();
}
}

@ -0,0 +1,47 @@
package com.anjiplus.template.gaea.business.enums;
public enum EnableFlagEnum {
ENABLE(1,"启用"),
DISABLE(0,"禁用"),
;
private int codeValue;
private String codeDesc;
private EnableFlagEnum(int codeValue, String codeDesc) {
this.codeValue = codeValue;
this.codeDesc = codeDesc;
}
public int getCodeValue(){ return this.codeValue;}
public String getCodeDesc(){ return this.codeDesc;}
//根据codeValue获取枚举
public static EnableFlagEnum parseFromCodeValue(int codeValue){
for (EnableFlagEnum e : EnableFlagEnum.values()){
if(e.codeValue == codeValue){ return e;}
}
return null;
}
//根据codeValue获取描述
public static String getCodeDescByCodeBalue(int codeValue){
EnableFlagEnum enumItem = parseFromCodeValue(codeValue);
return enumItem == null ? "" : enumItem.getCodeDesc();
}
//验证codeValue是否有效
public static boolean validateCodeValue(int codeValue){ return parseFromCodeValue(codeValue)!=null;}
//列出所有值字符串
public static String getString(){
StringBuffer buffer = new StringBuffer();
for (EnableFlagEnum e : EnableFlagEnum.values()){
buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
}
buffer.deleteCharAt(buffer.lastIndexOf(","));
return buffer.toString().trim();
}
}

@ -6,6 +6,7 @@ import com.anji.plus.gaea.bean.ResponseBean;
import com.anji.plus.gaea.cache.CacheHelper; import com.anji.plus.gaea.cache.CacheHelper;
import com.anji.plus.gaea.utils.JwtBean; import com.anji.plus.gaea.utils.JwtBean;
import com.anjiplus.template.gaea.business.constant.BusinessConstant; import com.anjiplus.template.gaea.business.constant.BusinessConstant;
import com.anjiplus.template.gaea.business.util.JwtUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -68,6 +69,24 @@ public class TokenFilter implements Filter {
return; return;
} }
//针对大屏分享,优先处理
String shareToken = request.getHeader("Share-Token");
if (StringUtils.isNotBlank(shareToken)) {
//两个接口需要处理
// /reportDashboard/getData
// /reportDashboard/{reportCode}
String reportCode = JwtUtil.getReportCode(shareToken);
if (!uri.endsWith("/getData") && !uri.contains(reportCode)) {
ResponseBean responseBean = ResponseBean.builder().code("50014")
.message("分享链接已过期").build();
response.getWriter().print(JSONObject.toJSONString(responseBean));
return;
}
filterChain.doFilter(request, response);
return;
}
//获取token //获取token
String token = request.getHeader("Authorization"); String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) { if (StringUtils.isBlank(token)) {

@ -13,7 +13,7 @@ import com.anjiplus.template.gaea.business.modules.dashboard.controller.dto.Repo
import com.anjiplus.template.gaea.business.modules.dashboard.dao.ReportDashboardMapper; import com.anjiplus.template.gaea.business.modules.dashboard.dao.ReportDashboardMapper;
import com.anjiplus.template.gaea.business.modules.dashboard.service.ChartStrategy; import com.anjiplus.template.gaea.business.modules.dashboard.service.ChartStrategy;
import com.anjiplus.template.gaea.business.modules.dashboard.service.ReportDashboardService; import com.anjiplus.template.gaea.business.modules.dashboard.service.ReportDashboardService;
import com.anjiplus.template.gaea.business.modules.dashboard.util.DateUtil; import com.anjiplus.template.gaea.business.util.DateUtil;
import com.anjiplus.template.gaea.business.modules.dashboardwidget.controller.dto.ReportDashboardWidgetDto; import com.anjiplus.template.gaea.business.modules.dashboardwidget.controller.dto.ReportDashboardWidgetDto;
import com.anjiplus.template.gaea.business.modules.dashboardwidget.controller.dto.ReportDashboardWidgetValueDto; import com.anjiplus.template.gaea.business.modules.dashboardwidget.controller.dto.ReportDashboardWidgetValueDto;
import com.anjiplus.template.gaea.business.modules.dashboard.dao.entity.ReportDashboard; import com.anjiplus.template.gaea.business.modules.dashboard.dao.entity.ReportDashboard;

@ -54,9 +54,7 @@ public class JsTransformServiceImpl implements TransformStrategy {
engine.eval(js); engine.eval(js);
if(engine instanceof Invocable){ if(engine instanceof Invocable){
Invocable invocable = (Invocable) engine; Invocable invocable = (Invocable) engine;
Object exec = invocable.invokeFunction("dataTransform", data); return (List<JSONObject>) invocable.invokeFunction("dataTransform", data);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(exec, List.class);
} }
} catch (Exception ex) { } catch (Exception ex) {

@ -9,12 +9,12 @@ import com.anjiplus.template.gaea.business.modules.report.controller.dto.ReportD
import com.anjiplus.template.gaea.business.modules.report.controller.param.ReportParam; import com.anjiplus.template.gaea.business.modules.report.controller.param.ReportParam;
import com.anjiplus.template.gaea.business.modules.report.dao.entity.Report; import com.anjiplus.template.gaea.business.modules.report.dao.entity.Report;
import com.anjiplus.template.gaea.business.modules.report.service.ReportService; import com.anjiplus.template.gaea.business.modules.report.service.ReportService;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
import com.anjiplus.template.gaea.business.modules.reportshare.service.ReportShareService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** /**
* TODO * TODO
@ -28,6 +28,9 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/report") @RequestMapping("/report")
public class ReportController extends GaeaBaseController<ReportParam, Report, ReportDto> { public class ReportController extends GaeaBaseController<ReportParam, Report, ReportDto> {
@Autowired
private ReportShareService reportShareService;
@Autowired @Autowired
private ReportService reportService; private ReportService reportService;
@ -47,15 +50,17 @@ public class ReportController extends GaeaBaseController<ReportParam, Report, Re
} }
@DeleteMapping("/delReport") @DeleteMapping("/delReport")
@Permission( @Permission(code = "delete", name = "删除")
code = "delete", @GaeaAuditLog(pageTitle = "删除")
name = "删除"
)
@GaeaAuditLog(
pageTitle = "删除"
)
public ResponseBean delReport(@RequestBody ReportDto reportDto) { public ResponseBean delReport(@RequestBody ReportDto reportDto) {
reportService.delReport(reportDto); reportService.delReport(reportDto);
return ResponseBean.builder().build(); return ResponseBean.builder().build();
} }
@PostMapping("/share")
@Permission(code = "share", name = "分享")
@GaeaAuditLog(pageTitle = "分享")
public ResponseBean share(@Validated @RequestBody ReportShareDto dto) {
return ResponseBean.builder().data(reportShareService.insertShare(dto)).build();
}
} }

@ -0,0 +1,69 @@
package com.anjiplus.template.gaea.business.modules.reportshare.controller;
import com.anji.plus.gaea.annotation.AccessKey;
import com.anji.plus.gaea.annotation.Permission;
import com.anji.plus.gaea.bean.ResponseBean;
import com.anji.plus.gaea.curd.controller.GaeaBaseController;
import com.anji.plus.gaea.curd.service.GaeaBaseService;
import com.anji.plus.gaea.utils.GaeaBeanUtils;
import com.anji.plus.gaea.utils.GaeaUtils;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.param.ReportShareParam;
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
import com.anjiplus.template.gaea.business.modules.reportshare.service.ReportShareService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @author Raod
* @desc controller
* @date 2021-08-18 13:37:26.663
**/
@RestController
@Api(tags = "报表分享管理")
@RequestMapping("/reportShare")
@Permission(code = "reportShareManage", name = "报表分享管理")
public class ReportShareController extends GaeaBaseController<ReportShareParam, ReportShare, ReportShareDto> {
@Autowired
private ReportShareService reportShareService;
@Override
public GaeaBaseService<ReportShareParam, ReportShare> getService() {
return reportShareService;
}
@Override
public ReportShare getEntity() {
return new ReportShare();
}
@Override
public ReportShareDto getDTO() {
return new ReportShareDto();
}
@GetMapping({"/{id}"})
@AccessKey
@Override
@Permission(code = "detail", name = "明细")
public ResponseBean detail(@PathVariable("id") Long id) {
this.logger.info("{}根据ID查询服务开始id为{}", this.getClass().getSimpleName(), id);
ReportShare result = reportShareService.getDetail(id);
ReportShareDto dto = this.getDTO();
GaeaBeanUtils.copyAndFormatter(result, dto);
ResponseBean responseBean = this.responseSuccessWithData(this.resultDtoHandle(dto));
this.logger.info("{}根据ID查询结束结果{}", this.getClass().getSimpleName(), GaeaUtils.toJSONString(responseBean));
return responseBean;
}
@GetMapping({"/detailByCode"})
@Permission(code = "detail", name = "明细")
public ResponseBean detailByCode(@RequestParam("shareCode") String shareCode) {
return ResponseBean.builder().data(reportShareService.detailByCode(shareCode)).build();
}
}

@ -0,0 +1,56 @@
package com.anjiplus.template.gaea.business.modules.reportshare.controller.dto;
import java.io.Serializable;
import com.anji.plus.gaea.curd.dto.GaeaBaseDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
*
* @description dto
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
@Data
public class ReportShareDto extends GaeaBaseDTO implements Serializable {
/** 分享编码系统生成默认UUID */
@ApiModelProperty(value = "分享编码系统生成默认UUID")
private String shareCode;
/** 分享有效期类型DIC_NAME=SHARE_VAILD */
@ApiModelProperty(value = "分享有效期类型DIC_NAME=SHARE_VAILD")
@NotNull(message = "6002")
private Integer shareValidType;
/** 分享有效期 */
@ApiModelProperty(value = "分享有效期")
private Date shareValidTime;
/** 分享token */
@ApiModelProperty(value = "分享token")
private String shareToken;
/** 分享url */
@ApiModelProperty(value = "分享url")
@NotEmpty(message = "6002")
private String shareUrl;
/** 报表编码 */
@ApiModelProperty(value = "报表编码")
@NotEmpty(message = "6002")
private String reportCode;
/** 0--已禁用 1--已启用 DIC_NAME=ENABLE_FLAG */
@ApiModelProperty(value = "0--已禁用 1--已启用 DIC_NAME=ENABLE_FLAG")
private Integer enableFlag;
/** 0--未删除 1--已删除 DIC_NAME=DELETE_FLAG */
@ApiModelProperty(value = "0--未删除 1--已删除 DIC_NAME=DELETE_FLAG")
private Integer deleteFlag;
}

@ -0,0 +1,16 @@
/**/
package com.anjiplus.template.gaea.business.modules.reportshare.controller.param;
import com.anji.plus.gaea.curd.params.PageParam;
import lombok.Data;
import java.io.Serializable;
/**
* @desc ReportShare
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
@Data
public class ReportShareParam extends PageParam implements Serializable{
}

@ -0,0 +1,15 @@
package com.anjiplus.template.gaea.business.modules.reportshare.dao;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
import org.apache.ibatis.annotations.Mapper;
/**
* ReportShare Mapper
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
@Mapper
public interface ReportShareMapper extends GaeaBaseMapper<ReportShare> {
}

@ -0,0 +1,43 @@
package com.anjiplus.template.gaea.business.modules.reportshare.dao.entity;
import lombok.Data;
import io.swagger.annotations.ApiModelProperty;
import com.anji.plus.gaea.curd.entity.GaeaBaseEntity;
import com.baomidou.mybatisplus.annotation.TableName;
import javax.validation.constraints.*;
import java.util.Date;
/**
* @description entity
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
@TableName(value="gaea_report_share")
@Data
public class ReportShare extends GaeaBaseEntity {
/** 分享编码系统生成默认UUID */
private String shareCode;
/** 分享有效期类型DIC_NAME=SHARE_VAILD */
private Integer shareValidType;
/** 分享有效期 */
private Date shareValidTime;
/** 分享token */
private String shareToken;
/** 分享url */
private String shareUrl;
/** 报表编码 */
private String reportCode;
/** 0--已禁用 1--已启用 DIC_NAME=ENABLE_FLAG */
private Integer enableFlag;
/** 0--未删除 1--已删除 DIC_NAME=DELETE_FLAG */
private Integer deleteFlag;
}

@ -0,0 +1,27 @@
package com.anjiplus.template.gaea.business.modules.reportshare.service;
import com.anji.plus.gaea.curd.service.GaeaBaseService;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.param.ReportShareParam;
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
/**
* @desc ReportShare
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
public interface ReportShareService extends GaeaBaseService<ReportShareParam, ReportShare> {
/***
*
*
* @param id
* @return
*/
ReportShare getDetail(Long id);
ReportShareDto insertShare(ReportShareDto dto);
ReportShare detailByCode(String shareCode);
}

@ -0,0 +1,107 @@
package com.anjiplus.template.gaea.business.modules.reportshare.service.impl;
import com.anji.plus.gaea.constant.BaseOperationEnum;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.exception.BusinessException;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import com.anjiplus.template.gaea.business.code.ResponseCode;
import com.anjiplus.template.gaea.business.enums.EnableFlagEnum;
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
import com.anjiplus.template.gaea.business.modules.reportshare.dao.ReportShareMapper;
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
import com.anjiplus.template.gaea.business.modules.reportshare.service.ReportShareService;
import com.anjiplus.template.gaea.business.util.DateUtil;
import com.anjiplus.template.gaea.business.util.JwtUtil;
import com.anjiplus.template.gaea.business.util.UuidUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @desc ReportShare
* @author Raod
* @date 2021-08-18 13:37:26.663
**/
@Service
public class ReportShareServiceImpl implements ReportShareService {
/**
* aj
*/
private static final String SHARE_FLAG = "#/aj/";
private static final String SHARE_URL = "#";
@Autowired
private ReportShareMapper reportShareMapper;
@Override
public GaeaBaseMapper<ReportShare> getMapper() {
return reportShareMapper;
}
@Override
public ReportShare getDetail(Long id) {
ReportShare reportShare = this.selectOne(id);
return reportShare;
}
@Override
public ReportShareDto insertShare(ReportShareDto dto) {
ReportShareDto reportShareDto = new ReportShareDto();
ReportShare entity = new ReportShare();
BeanUtils.copyProperties(dto, entity);
insert(entity);
//将分享链接返回
reportShareDto.setShareUrl(entity.getShareUrl());
return reportShareDto;
}
@Override
public ReportShare detailByCode(String shareCode) {
LambdaQueryWrapper<ReportShare> wrapper = Wrappers.lambdaQuery();
wrapper.eq(ReportShare::getShareCode, shareCode);
wrapper.eq(ReportShare::getEnableFlag, EnableFlagEnum.ENABLE.getCodeValue());
ReportShare reportShare = selectOne(wrapper);
if (null == reportShare) {
throw BusinessExceptionBuilder.build(ResponseCode.REPORT_SHARE_LINK_INVALID);
}
return reportShare;
}
@Override
public void processBeforeOperation(ReportShare entity, BaseOperationEnum operationEnum) throws BusinessException {
switch (operationEnum) {
case INSERT:
init(entity);
break;
default:
break;
}
}
/**
*
* @param entity
*/
private void init(ReportShare entity) {
//前端地址 window.location.href https://report.anji-plus.com/index.html#/report/bigscreen
//截取#之前的内容
//http://localhost:9528/#/bigscreen/viewer?reportCode=bigScreen2
//http://127.0.0.1:9095/reportDashboard/getData
String shareCode = UuidUtil.generateShortUuid();
entity.setShareCode(shareCode);
if (entity.getShareUrl().contains(SHARE_URL)) {
String prefix = entity.getShareUrl().substring(0, entity.getShareUrl().indexOf("#"));
entity.setShareUrl(prefix + SHARE_FLAG + shareCode);
} else {
entity.setShareUrl(entity.getShareUrl() + SHARE_FLAG + shareCode);
}
entity.setShareValidTime(DateUtil.getFutureDateTmdHms(entity.getShareValidType()));
entity.setShareToken(JwtUtil.createToken(entity.getReportCode(), shareCode, entity.getShareValidTime()));
}
}

@ -0,0 +1,152 @@
/*
*Copyright © 2018 anji-plus
*
*http://www.anji-plus.com
*All rights reserved.
*/
package com.anjiplus.template.gaea.business.util;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.Base64;
public class AESUtil {
//算法
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
private static final String AES_KEY = "AnjiPLUSAjReport";
/**
* key
*
* @return
*/
public static String getKey() {
return AES_KEY;
}
/**
* byte[]
*
* @param bytes byte[]
* @param radix Character.MIN_RADIXCharacter.MAX_RADIX10
* @return
*/
public static String binary(byte[] bytes, int radix) {
return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
}
/**
* base 64 encode
*
* @param bytes byte[]
* @return base 64 code
*/
public static String base64Encode(byte[] bytes) {
//return Base64.encodeBase64String(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
/**
* base 64 decode
*
* @param base64Code base 64 code
* @return byte[]
* @throws Exception
*/
public static byte[] base64Decode(String base64Code) throws Exception {
Base64.Decoder decoder = Base64.getDecoder();
return StringUtils.isEmpty(base64Code) ? null : decoder.decode(base64Code);
}
/**
* AES
*
* @param content
* @param encryptKey
* @return byte[]
* @throws Exception
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
return cipher.doFinal(content.getBytes("utf-8"));
}
/**
* AESbase 64 code
*
* @param content
* @param encryptKey
* @return base 64 code
* @throws Exception
*/
public static String aesEncrypt(String content, String encryptKey) throws Exception {
if (StringUtils.isBlank(encryptKey)) {
return content;
}
return base64Encode(aesEncryptToBytes(content, encryptKey));
}
/**
* AES
*
* @param encryptBytes byte[]
* @param decryptKey
* @return String
* @throws Exception
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
byte[] decryptBytes = cipher.doFinal(encryptBytes);
return new String(decryptBytes);
}
/**
* base 64 code AES
*
* @param encryptStr base 64 code
* @param decryptKey
* @return string
* @throws Exception
*/
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
if (StringUtils.isBlank(decryptKey)) {
return encryptStr;
}
return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
}
/**
*
*/
public static void main(String[] args) throws Exception {
String randomString = getKey();
String content = "report";
System.out.println("加密前:" + content);
System.out.println("加密密钥和解密密钥:" + randomString);
String encrypt = aesEncrypt(content, randomString);
System.out.println("加密后:" + encrypt);
String decrypt = aesDecrypt(encrypt, randomString);
System.out.println("解密后:" + decrypt);
}
}

@ -1,7 +1,8 @@
package com.anjiplus.template.gaea.business.modules.dashboard.util; package com.anjiplus.template.gaea.business.util;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
/** /**
@ -44,4 +45,25 @@ public class DateUtil {
return null; return null;
} }
} }
/**
*
*
* @param day
* @return
*/
public static Date getFutureDateTmdHms(int day) {
if (day <= 0) {
//默认2099年
return parse("2099-01-01", defaultDatePattern);
}
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR) + day);
return calendar.getTime();
}
public static void main(String[] args) {
Date futureDateTmdHms = getFutureDateTmdHms(7);
System.out.println(futureDateTmdHms);
}
} }

@ -0,0 +1,58 @@
package com.anjiplus.template.gaea.business.util;
import com.anji.plus.gaea.exception.BusinessExceptionBuilder;
import com.anjiplus.template.gaea.business.code.ResponseCode;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.Map;
/**
* Created by raodeming on 2021/8/18.
*/
public class JwtUtil {
private static final String JWT_SECRET = "aj-report";
public static String createToken(String reportCode, String shareCode, Date expires) {
String token = JWT.create()
.withIssuedAt(new Date())
.withExpiresAt(expires)
.withClaim("reportCode", reportCode)
.withClaim("shareCode", shareCode)
.sign(Algorithm.HMAC256(JWT_SECRET));
return token;
}
public static Map<String, Claim> getClaim(String token) {
try {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(JWT_SECRET)).build();
DecodedJWT decodedJwt = jwtVerifier.verify(token);
return decodedJwt.getClaims();
} catch (Exception e) {
throw BusinessExceptionBuilder.build(ResponseCode.REPORT_SHARE_LINK_INVALID, e.getMessage());
}
}
public static String getReportCode(String token) {
Claim claim = getClaim(token).get("reportCode");
if (null == claim) {
throw BusinessExceptionBuilder.build(ResponseCode.REPORT_SHARE_LINK_INVALID);
}
return claim.asString();
}
public static String getShareCode(String token) {
Claim claim = getClaim(token).get("shareCode");
if (null == claim) {
throw BusinessExceptionBuilder.build(ResponseCode.REPORT_SHARE_LINK_INVALID);
}
return claim.asString();
}
}

@ -0,0 +1,44 @@
package com.anjiplus.template.gaea.business.util;
import java.util.UUID;
/**
* Created by raodeming on 2021/8/19.
*/
public class UuidUtil {
public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z" };
/**
* 8id
* @return
*/
public static String generateShortUuid() {
StringBuilder builder = new StringBuilder();
String uuid = UUID.randomUUID().toString().replace("-", "");
for (int i = 0; i < 8; i++) {
String str = uuid.substring(i * 4, i * 4 + 4);
int x = Integer.parseInt(str, 16);
builder.append(chars[x % 0x3E]);
}
return builder.toString();
}
public static String generateUuid() {
return UUID.randomUUID().toString().replace("-", "");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(generateShortUuid());
}
}
}

@ -73,7 +73,7 @@ customer:
downloadPath: http://10.108.26.197:9095/file/download downloadPath: http://10.108.26.197:9095/file/download
# 跳过token验证和权限验证的url清单 # 跳过token验证和权限验证的url清单
skip-authenticate-urls: /gaeaDict/all, /login, /static, /file/download/, /index.html, /favicon.ico skip-authenticate-urls: /gaeaDict/all, /login, /static, /file/download/, /index.html, /favicon.ico, /reportShare/detailByCode
user: user:
##新增用户默认密码 ##新增用户默认密码
default: default:

@ -0,0 +1,86 @@
use
aj_report;
CREATE TABLE `gaea_report_share`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`share_code` varchar(50) DEFAULT NULL COMMENT '分享编码系统生成默认UUID',
`share_valid_type` int(2) DEFAULT NULL COMMENT '分享有效期类型DIC_NAME=SHARE_VAILD',
`share_valid_time` datetime DEFAULT NULL COMMENT '分享有效期',
`share_token` varchar(255) DEFAULT NULL COMMENT '分享token',
`share_url` varchar(100) DEFAULT NULL COMMENT '分享url',
`report_code` varchar(50) DEFAULT NULL COMMENT '报表编码',
`enable_flag` int(1) DEFAULT '1' COMMENT '0--已禁用 1--已启用 DIC_NAME=ENABLE_FLAG',
`delete_flag` int(1) DEFAULT '0' COMMENT '0--未删除 1--已删除 DIC_NAME=DELETE_FLAG',
`create_by` varchar(255) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(255) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`version` int(8) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `UNIQUE_SHARE_CODE` (`share_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='报表分享';
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '分享有效期', 'SHARE_VAILD', '报表分享', 'admin', '2021-08-18 13:29:19', 'admin', '2021-08-18 13:29:24', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '柱状图属性', 'BAR_PROPERTIES', '柱状图属性', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '折线图属性', 'LINE_PROPERTIES', '折线图属性', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '柱线图属性', 'BAR_LINE_PROPERTIES', '柱线图属性', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '饼图属性', 'PIE_PROPERTIES', '饼图、漏斗图', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '单文本图属性', 'TEXT_PROPERTIES', '仪表盘、百分比、文本框、滚动文本', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '堆叠图属性', 'STACK_PROPERTIES', '堆叠图属性', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict`(`id`, `dict_name`, `dict_code`, `remark`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , '地图属性', 'MAP_PROPERTIES', '地图属性', 'admin', '2021-04-29 10:28:15', 'admin', '2021-06-23 10:47:20', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'SHARE_VAILD', '永久有效', '0', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-18 13:30:21', 'admin', '2021-08-18 13:30:21', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default, 'SHARE_VAILD', '1天', '1', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-18 13:30:39', 'admin', '2021-08-18 13:30:39', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default, 'SHARE_VAILD', '7天', '7', NULL, 1, 'zh', NULL, 3, 'admin', '2021-08-18 13:30:51', 'admin', '2021-08-18 13:30:56', 2);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default, 'SHARE_VAILD', '30天', '30', NULL, 1, 'zh', NULL, 4, 'admin', '2021-08-18 13:31:11', 'admin', '2021-08-18 13:31:11', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_PROPERTIES', 'x轴字段', 'xAxis', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:19:35', 'admin', '2021-08-20 10:19:35', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_PROPERTIES', '柱状', 'bar', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:19:56', 'admin', '2021-08-20 10:19:56', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'LINE_PROPERTIES', 'x轴字段', 'xAxis', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:27:39', 'admin', '2021-08-20 10:27:39', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'LINE_PROPERTIES', '折线', 'line', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:27:49', 'admin', '2021-08-20 10:27:49', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', 'x轴字段', 'xAxis', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:31:51', 'admin', '2021-08-20 10:31:51', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', 'x轴时间轴-时', 'xAxis-hour', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:32:11', 'admin', '2021-08-20 10:32:11', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', 'x轴时间轴-天', 'xAxis-day', NULL, 1, 'zh', NULL, 3, 'admin', '2021-08-20 10:32:25', 'admin', '2021-08-20 10:32:25', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', 'x轴时间轴-月', 'xAxis-month', NULL, 1, 'zh', NULL, 4, 'admin', '2021-08-20 10:32:38', 'admin', '2021-08-20 10:32:38', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', '时间轴-年', 'xAxis-year', NULL, 1, 'zh', NULL, 5, 'admin', '2021-08-20 10:32:52', 'admin', '2021-08-20 10:32:52', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', '柱状', 'bar', NULL, 1, 'zh', NULL, 6, 'admin', '2021-08-20 10:33:02', 'admin', '2021-08-20 10:33:02', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'BAR_LINE_PROPERTIES', '折线', 'line', NULL, 1, 'zh', NULL, 7, 'admin', '2021-08-20 10:33:11', 'admin', '2021-08-20 10:33:11', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'PIE_PROPERTIES', '名称name', 'name', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:35:27', 'admin', '2021-08-20 10:35:27', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'PIE_PROPERTIES', '数值value', 'value', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:35:38', 'admin', '2021-08-20 10:35:38', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'TEXT_PROPERTIES', '文本数字', 'text', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:36:04', 'admin', '2021-08-20 10:36:04', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', 'x轴字段', 'xAxis', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:31:51', 'admin', '2021-08-20 10:31:51', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', 'x轴时间轴-时', 'xAxis-hour', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:32:11', 'admin', '2021-08-20 10:32:11', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', 'x轴时间轴-天', 'xAxis-day', NULL, 1, 'zh', NULL, 3, 'admin', '2021-08-20 10:32:25', 'admin', '2021-08-20 10:32:25', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', 'x轴时间轴-月', 'xAxis-month', NULL, 1, 'zh', NULL, 4, 'admin', '2021-08-20 10:32:38', 'admin', '2021-08-20 10:32:38', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', '时间轴-年', 'xAxis-year', NULL, 1, 'zh', NULL, 5, 'admin', '2021-08-20 10:32:52', 'admin', '2021-08-20 10:32:52', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', 'y轴字段', 'yAxis', NULL, 1, 'zh', NULL, 6, 'admin', '2021-08-20 10:32:52', 'admin', '2021-08-20 10:32:52', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', '柱状', 'bar', NULL, 1, 'zh', NULL, 7, 'admin', '2021-08-20 10:33:02', 'admin', '2021-08-20 10:33:02', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'STACK_PROPERTIES', '折线', 'line', NULL, 1, 'zh', NULL, 8, 'admin', '2021-08-20 10:33:11', 'admin', '2021-08-20 10:33:11', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'MAP_PROPERTIES', '名称name', 'name', NULL, 1, 'zh', NULL, 1, 'admin', '2021-08-20 10:41:00', 'admin', '2021-08-20 10:41:00', 1);
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (default , 'MAP_PROPERTIES', '数值value', 'value', NULL, 1, 'zh', NULL, 2, 'admin', '2021-08-20 10:41:11', 'admin', '2021-08-20 10:41:11', 1);
INSERT INTO `aj_report`.`gaea_report_data_set`(`set_code`, `set_name`, `set_desc`, `source_code`, `dyn_sentence`, `case_result`, `enable_flag`, `delete_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES ('barstack_ajreport', '柱状堆叠数据', '', 'mysql_ajreport', 'select time,type,nums from aj_report_barstack', '[{\"time\":\"2021-07-27\",\"type\":\"A\",\"nums\":12},{\"time\":\"2021-07-27\",\"type\":\"B\",\"nums\":20},{\"time\":\"2021-07-27\",\"type\":\"C\",\"nums\":11},{\"time\":\"2021-07-26\",\"type\":\"A\",\"nums\":11},{\"time\":\"2021-07-26\",\"type\":\"B\",\"nums\":30},{\"time\":\"2021-07-25\",\"type\":\"B\",\"nums\":20},{\"time\":\"2021-07-25\",\"type\":\"C\",\"nums\":15}]', 1, 0, 'admin', '2021-07-27 19:50:52', 'admin', '2021-08-16 14:08:51', 7);
use
aj_report_init;
CREATE TABLE `aj_report_barstack`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` date DEFAULT NULL,
`type` varchar(255) DEFAULT NULL,
`nums` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*堆叠图数据*/
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (1, '2021-07-27', 'A', 12);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (2, '2021-07-27', 'B', 20);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (3, '2021-07-27', 'C', 11);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (4, '2021-07-26', 'A', 11);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (5, '2021-07-26', 'B', 30);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (6, '2021-07-25', 'B', 20);
INSERT INTO `aj_report_init`.`aj_report_barstack`(`id`, `time`, `type`, `nums`) VALUES (7, '2021-07-25', 'C', 15);

@ -0,0 +1,3 @@
UPDATE `aj_report`.`gaea_dict_item` SET `dict_code` = 'CHART_PROPERTIES', `item_name` = 'x轴字段', `item_value` = 'xAxis', `item_extend` = NULL, `enabled` = 1, `locale` = 'zh', `remark` = NULL, `sort` = 1, `create_by` = 'admin', `create_time` = now(), `update_by` = 'admin', `update_time` = now(), `version` = 1 WHERE `id` = 260;
INSERT INTO `aj_report`.`gaea_dict_item`(`id`, `dict_code`, `item_name`, `item_value`, `item_extend`, `enabled`, `locale`, `remark`, `sort`, `create_by`, `create_time`, `update_by`, `update_time`, `version`) VALUES (288, 'CHART_PROPERTIES', 'y轴字段', 'yAxis', NULL, 1, 'zh', NULL, 11, 'admin', '2021-07-05 15:33:59', 'admin', '2021-07-05 15:33:59', 1);

@ -47,3 +47,5 @@ Component.load.check.error={0} Component not load
4008=The set code does not allow duplication 4008=The set code does not allow duplication
4009=The source code does not allow duplication 4009=The source code does not allow duplication
4010=Can't auto find match driver class 4010=Can't auto find match driver class
report.share.link.invalid=report share link invalid

@ -52,3 +52,4 @@ Component.load.check.error={0}\u7EC4\u4EF6\u672A\u52A0\u8F7D
7001=\u89E3\u6790\u5931\u8D25 7001=\u89E3\u6790\u5931\u8D25
report.share.link.invalid=\u5206\u4EAB\u94FE\u63A5\u5DF2\u5931\u6548

@ -0,0 +1,26 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.anjiplus.template.gaea.business.modules.reportshare.dao.ReportShareMapper">
<resultMap type="com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare" id="ReportShareMap">
<!--jdbcType="{column.columnType}"-->
<result property="id" column="id" />
<result property="shareCode" column="share_code" />
<result property="shareValidType" column="share_valid_type" />
<result property="shareValidTime" column="share_valid_time" />
<result property="shareUrl" column="share_url" />
<result property="reportCode" column="report_code" />
<result property="enableFlag" column="enable_flag" />
<result property="deleteFlag" column="delete_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="version" column="version" />
</resultMap>
<sql id="Base_Column_List">
id,share_code,share_valid_type,share_valid_time,share_url,report_code,enable_flag,delete_flag,create_by,create_time,update_by,update_time,version
</sql>
</mapper>

@ -0,0 +1,48 @@
package com.anjiplus.template.gaea.business.modules.reportshare.service.impl;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;
import java.util.Date;
/**
* Created by raodeming on 2021/8/18.
*/
public class ReportShareServiceImplTest {
@Test
public void jwtTest() throws InterruptedException {
long l = System.currentTimeMillis();
String sign = JWT.create()
.withIssuedAt(new Date())
.withExpiresAt(new Date(l + 5000))
.withClaim("reportCode", "report")
.withClaim("shareCode", "1234567")
.sign(Algorithm.HMAC256("111"));
System.out.println(sign);
Thread.sleep(8000L);
DecodedJWT verify = JWT.require(Algorithm.HMAC256("111")).build().verify(sign);
Date expiresAt = verify.getExpiresAt();
String reportCode = verify.getClaim("reportCode").asString();
String shareCode = verify.getClaim("shareCode").asString();
System.out.println(expiresAt);
System.out.println(reportCode);
System.out.println(shareCode);
}
}

@ -1,4 +1,5 @@
import request from '@/utils/request' import request from '@/utils/request'
import { getShareToken, getToken } from "@/utils/auth";
// 保存大屏设计 // 保存大屏设计
export function insertDashboard(data) { export function insertDashboard(data) {
@ -13,6 +14,7 @@ export function insertDashboard(data) {
export function detailDashboard(data) { export function detailDashboard(data) {
return request({ return request({
url: 'reportDashboard/' + data, url: 'reportDashboard/' + data,
headers: { 'Share-Token': getShareToken(), 'Authorization': getToken() },
method: 'get', method: 'get',
}) })
} }
@ -22,6 +24,7 @@ export function queryAllDataSet(data) {
return request({ return request({
url: 'dataSet/queryAllDataSet', url: 'dataSet/queryAllDataSet',
method: 'get', method: 'get',
}) })
} }
@ -38,6 +41,7 @@ export function getData(data) {
return request({ return request({
url: 'reportDashboard/getData', url: 'reportDashboard/getData',
method: 'post', method: 'post',
headers: { 'Share-Token': getShareToken(), 'Authorization': getToken() },
data, data,
}) })
} }

@ -0,0 +1,50 @@
import request from '@/utils/request'
export function reportShareList(params) {
return request({
url: 'reportShare/pageList',
method: 'GET',
params,
})
}
export function reportShareAdd(data) {
return request({
url: 'report/share',
method: 'post',
data
})
}
export function reportShareDeleteBatch(data) {
return request({
url: 'reportShare/delete/batch',
method: 'post',
data
})
}
export function reportShareUpdate(data) {
return request({
url: 'reportShare',
method: 'put', data,
})
}
export function reportShareDetail(data) {
return request({
url: 'reportShare/' + data.id,
method: 'get',
params: { accessKey: data.accessKey }
})
}
export function reportShareDetailByCode(data) {
return request({
url: 'reportShare/detailByCode',
method: 'get',
params: { shareCode: data }
})
}
export default { reportShareList, reportShareAdd, reportShareDeleteBatch, reportShareUpdate, reportShareDetail }

@ -54,6 +54,18 @@
<div class="content unicode" style="display: block;"> <div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe621;</span>
<div class="name">堆叠图</div>
<div class="code-name">&amp;#xe621;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61f;</span>
<div class="name">堆叠图</div>
<div class="code-name">&amp;#xe61f;</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe645;</span> <span class="icon iconfont">&#xe645;</span>
<div class="name">文件</div> <div class="name">文件</div>
@ -750,9 +762,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1628064452460') format('woff2'), src: url('iconfont.woff2?t=1629425895962') format('woff2'),
url('iconfont.woff?t=1628064452460') format('woff'), url('iconfont.woff?t=1629425895962') format('woff'),
url('iconfont.ttf?t=1628064452460') format('truetype'); url('iconfont.ttf?t=1629425895962') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@ -778,6 +790,24 @@
<div class="content font-class"> <div class="content font-class">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont iconbianzu23"></span>
<div class="name">
堆叠图
</div>
<div class="code-name">.iconbianzu23
</div>
</li>
<li class="dib">
<span class="icon iconfont iconduidietu"></span>
<div class="name">
堆叠图
</div>
<div class="code-name">.iconduidietu
</div>
</li>
<li class="dib"> <li class="dib">
<span class="icon iconfont iconfill_folder"></span> <span class="icon iconfont iconfill_folder"></span>
<div class="name"> <div class="name">
@ -1822,6 +1852,22 @@
<div class="content symbol"> <div class="content symbol">
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconbianzu23"></use>
</svg>
<div class="name">堆叠图</div>
<div class="code-name">#iconbianzu23</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconduidietu"></use>
</svg>
<div class="name">堆叠图</div>
<div class="code-name">#iconduidietu</div>
</li>
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconfill_folder"></use> <use xlink:href="#iconfill_folder"></use>

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 1513211 */ font-family: "iconfont"; /* Project id 1513211 */
src: url('iconfont.woff2?t=1628064452460') format('woff2'), src: url('iconfont.woff2?t=1629425895962') format('woff2'),
url('iconfont.woff?t=1628064452460') format('woff'), url('iconfont.woff?t=1629425895962') format('woff'),
url('iconfont.ttf?t=1628064452460') format('truetype'); url('iconfont.ttf?t=1629425895962') format('truetype');
} }
.iconfont { .iconfont {
@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.iconbianzu23:before {
content: "\e621";
}
.iconduidietu:before {
content: "\e61f";
}
.iconfill_folder:before { .iconfill_folder:before {
content: "\e645"; content: "\e645";
} }

File diff suppressed because one or more lines are too long

@ -5,6 +5,20 @@
"css_prefix_text": "icon", "css_prefix_text": "icon",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "18888301",
"name": "堆叠图",
"font_class": "bianzu23",
"unicode": "e621",
"unicode_decimal": 58913
},
{
"icon_id": "23457042",
"name": "堆叠图",
"font_class": "duidietu",
"unicode": "e61f",
"unicode_decimal": 58911
},
{ {
"icon_id": "5472881", "icon_id": "5472881",
"name": "文件", "name": "文件",

@ -109,6 +109,7 @@ export default {
// widget-gauge 仪表盘 // widget-gauge 仪表盘
// widget-text 文本框 // widget-text 文本框
// widge-table 表格(数据不要转) // widge-table 表格(数据不要转)
// widget-stackchart 堆叠图
const chartType = params.chartType const chartType = params.chartType
if ( if (
chartType == "widget-barchart" || chartType == "widget-barchart" ||
@ -125,6 +126,8 @@ export default {
return this.gaugeFn(params.chartProperties, data); return this.gaugeFn(params.chartProperties, data);
} else if (chartType == "widget-text") { } else if (chartType == "widget-text") {
return this.widgettext(params.chartProperties, data) return this.widgettext(params.chartProperties, data)
} else if (chartType == "widget-stackchart") {
return this.stackChartFn(params.chartProperties, data)
} else { } else {
return data return data
} }
@ -157,6 +160,39 @@ export default {
ananysicData["series"] = series; ananysicData["series"] = series;
return ananysicData; return ananysicData;
}, },
//堆叠图
stackChartFn(chartProperties, data) {
const ananysicData = {};
const series = [];
//全部字段字典值
const types = Object.values(chartProperties)
//x轴字段、y轴字段名
const xAxisField = Object.keys(chartProperties)[types.indexOf('xAxis')]
const yAxisField = Object.keys(chartProperties)[types.indexOf('yAxis')]
//x轴数值去重y轴去重
const xAxisList = this.setUnique(data.map(item => item[xAxisField]))
const yAxisList = this.setUnique(data.map(item => item[yAxisField]))
const dataGroup = this.setGroupBy(data, yAxisField)
for (const key in chartProperties) {
if (chartProperties[key] !== 'yAxis' && !chartProperties[key].startsWith('xAxis')) {
Object.keys(dataGroup).forEach(item => {
const data = new Array(yAxisList.length).fill(0)
dataGroup[item].forEach(res => {
data[xAxisList.indexOf(res[xAxisField])]= res[key]
})
series.push({
name: yAxisList[item],
type: chartProperties[key],
data,
})
})
}
}
ananysicData["xAxis"] = xAxisList;
ananysicData["series"] = series;
return ananysicData;
},
// 饼图、漏斗图 // 饼图、漏斗图
piechartFn(chartProperties, data) { piechartFn(chartProperties, data) {
const ananysicData = []; const ananysicData = [];
@ -208,6 +244,24 @@ export default {
} }
return ananysicData; return ananysicData;
}, },
setUnique(arr) {
let newArr = [];
arr.forEach(item => {
return newArr.includes(item) ? '' : newArr.push(item);
});
return newArr;
},
setGroupBy(array, name) {
const groups = {}
array.forEach(function (o) {
const group = JSON.stringify(o[name])
groups[group] = groups[group] || []
groups[group].push(o)
})
return Object.keys(groups).map(function (group) {
return groups[group]
})
},
}, },
watch: { watch: {
'selectInput.keyname'(newVal, oldVal) { 'selectInput.keyname'(newVal, oldVal) {

@ -4,7 +4,7 @@ import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式 import 'nprogress/nprogress.css'// Progress 进度条样式
import { getToken, getAccessUser } from "@/utils/auth"; import { getToken, getAccessUser } from "@/utils/auth";
export default router export default router
var whiteList = ['/login'] var whiteList = ['/login', '/aj/**', '/bigscreen/viewer']
// 判断是否需要登录权限 以及是否登录 // 判断是否需要登录权限 以及是否登录
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
@ -19,14 +19,20 @@ router.beforeEach((to, from, next) => {
}else{ }else{
next() next()
} }
}else {
console.log(to.path)
if (whiteList.includes(to.path)) {
next()
}else { }else {
// 如果没有token // 如果没有token
if ((token == null || token == '' || token ==undefined || gaeaUser == {}) && (to.meta != null && to.meta.requireAuth == true)) {// 在免登录白名单,直接进入 if ((token == null || token == '' || token ==undefined || gaeaUser == {}) && (to.meta != null && to.meta.requireAuth == true)) {// 在免登录白名单,直接进入
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 next(`/login?redirect=${to.path}`); // 否则全部重定向到登录页
NProgress.done() NProgress.done();
} else { } else {
next() next();
}
} }
} }
}) })

@ -27,6 +27,7 @@ import Layout from '../views/layout/Layout'
**/ **/
export const constantRouterMap = [ export const constantRouterMap = [
{ path: '/login', component: () => import('@/views/login'), hidden: true }, { path: '/login', component: () => import('@/views/login'), hidden: true },
{ path: '/aj/**', component: () => import('@/views/report/aj'), hidden: true },
{ {
path: '/index', component: Layout, name: 'index', meta: { title: '首页', icon: 'iconhome2' }, path: '/index', component: Layout, name: 'index', meta: { title: '首页', icon: 'iconhome2' },
children: [ children: [
@ -56,7 +57,7 @@ export const constantRouterMap = [
children: [ children: [
{ path: 'file', name: 'file', component: () => import('@/views/file-management/index'), meta: { title: '文件管理', icon: 'iconfill_folder', keepAlive: true, requireAuth: true, permission: 'fileManage'} }, { path: 'file', name: 'file', component: () => import('@/views/file-management/index'), meta: { title: '文件管理', icon: 'iconfill_folder', keepAlive: true, requireAuth: true, permission: 'fileManage'} },
{ path: 'dict', name: 'dict', component: () => import('@/views/dict/index'), meta: { title: '数据字典', icon: 'iconzidian', keepAlive: true, requireAuth: true, permission: 'dictManage'} }, { path: 'dict', name: 'dict', component: () => import('@/views/dict/index'), meta: { title: '数据字典', icon: 'iconzidian', keepAlive: true, requireAuth: true, permission: 'dictManage'} },
{ path: 'dictItem', name: 'dictItem', component: () => import('@/views/dict/dict-item'), meta: { title: '字典项', icon: 'iconzidianxiang', keepAlive: true, requireAuth: true, permission: 'dictItemManage'} }, { path: 'dictItem', name: 'dictItem', component: () => import('@/views/dict/dict-item'), hidden: true, meta: { title: '字典项', icon: 'iconzidianxiang', keepAlive: true, requireAuth: true, permission: 'dictItemManage'} },
] ]
}, },
{ path: '/bigscreen/viewer', component: () => import('@/views/report/bigscreen/viewer'), hidden: true, meta: { requireAuth: true }}, { path: '/bigscreen/viewer', component: () => import('@/views/report/bigscreen/viewer'), hidden: true, meta: { requireAuth: true }},

@ -1,5 +1,5 @@
import { login, logout } from '@/api/login' import { login, logout } from '@/api/login'
import { setToken, delToken, setAccessUser, delAccessUser } from '@/utils/auth' import {setToken, delToken, setAccessUser, delAccessUser, delShareToken} from '@/utils/auth'
const user = { const user = {
state: { state: {
@ -51,6 +51,7 @@ const user = {
commit('SET_TOKEN', '') commit('SET_TOKEN', '')
commit('SET_ACCESSUSER', {}) commit('SET_ACCESSUSER', {})
delToken() delToken()
delShareToken()
delAccessUser() delAccessUser()
resolve() resolve()
}).catch(error => { }).catch(error => {
@ -65,6 +66,7 @@ const user = {
commit('SET_TOKEN', '') commit('SET_TOKEN', '')
commit('SET_ACCESSUSER', {}) commit('SET_ACCESSUSER', {})
delToken() delToken()
delShareToken()
delAccessUser() delAccessUser()
resolve() resolve()
}) })

@ -1,17 +1,27 @@
import { getStorageItem, setStorageItem, delStorageItem } from '@/utils/storage' import { getStorageItem, setStorageItem, delStorageItem } from '@/utils/storage'
const TokenKey = 'token' const TokenKey = 'token'
const ShareTokenKey = 'shareToken'
const AccessUserKey = 'gaeaUser' const AccessUserKey = 'gaeaUser'
export function getToken() { export function getToken() {
return getStorageItem(TokenKey) return getStorageItem(TokenKey)
} }
export function getShareToken() {
return getStorageItem(ShareTokenKey) == null ? '' : getStorageItem(ShareTokenKey);
}
export function setToken(token) { export function setToken(token) {
return setStorageItem(TokenKey, token) return setStorageItem(TokenKey, token)
} }
export function setShareToken(shareToken) {
return setStorageItem(ShareTokenKey, shareToken)
}
export function delToken() { export function delToken() {
return delStorageItem(TokenKey) return delStorageItem(TokenKey)
} }
export function delShareToken() {
return delStorageItem(ShareTokenKey)
}
export function getAccessUser() { export function getAccessUser() {
return getStorageItem(AccessUserKey) return getStorageItem(AccessUserKey)

@ -49,7 +49,7 @@ service.interceptors.response.use(
type: 'error', type: 'error',
duration: 5 * 1000 duration: 5 * 1000
}) })
return Promise.reject('error') return response.data
} else { } else {
return response.data return response.data
} }

@ -177,7 +177,8 @@ export default {
rules: [ rules: [
{ required: true, message: "状态不能为空", trigger: "blur" } { required: true, message: "状态不能为空", trigger: "blur" }
], ],
disabled: false disabled: false,
defaultValue: 1
}, },
{ {
label: "排序", // label: "排序", //

@ -1,12 +1,14 @@
<template> <template>
<anji-crud ref="listPage" :option="crudOption"> <anji-crud ref="listPage" :option="crudOption">
<template v-slot:buttonLeftOnTable> <template v-slot:buttonLeftOnTable>
<el-upload class="el-upload" :action="uploadUrl" :headers="headers" :on-success="handleUpload" :on-error="handleUpload" :show-file-list="false" :limit="1"> <el-upload class="el-upload" ref="upload" :action="uploadUrl" :headers="headers" :on-success="handleUpload"
:on-error="handleError" :show-file-list="false" :limit="1">
<el-button type="primary" icon="el-icon" v-permission="'fileManage:upload'"></el-button> <el-button type="primary" icon="el-icon" v-permission="'fileManage:upload'"></el-button>
</el-upload> </el-upload>
</template> </template>
<template slot="rowButton" slot-scope="props"> <template slot="rowButton" slot-scope="props">
<el-button type="text" @click="copyUrlPath(props)">url</el-button>
<el-button type="text" @click="customButtom(props)"></el-button> <el-button type="text" @click="customButtom(props)"></el-button>
</template> </template>
</anji-crud> </anji-crud>
@ -14,6 +16,7 @@
<script> <script>
import {fileList, fileAdd, fileDel, fileUpdate, fileDetail} from '@/api/file' import {fileList, fileAdd, fileDel, fileUpdate, fileDetail} from '@/api/file'
import {getToken} from '@/utils/auth' import {getToken} from '@/utils/auth'
export default { export default {
name: 'File', name: 'File',
components: { components: {
@ -162,21 +165,45 @@ export default {
}, },
}, },
created() {}, created() {
},
methods: { methods: {
// //
handleUpload(response) { handleUpload(response, file, fileList) {
console.log(this) console.log(this)
// //
this.$refs.listPage.handleQueryForm() this.$refs.listPage.handleQueryForm()
//el-upload
this.$refs.upload.clearFiles()
},
handleError() {
this.$message({
message: '上传失败!',
type: 'error',
})
}, },
handleError() {},
async downloadFile(row) { async downloadFile(row) {
window.open(row.urlPath) window.open(row.urlPath)
}, },
customButtom(val) { customButtom(val) {
this.downloadFile(val.msg) this.downloadFile(val.msg)
}, },
copyUrlPath(val) {
this.copyToClip(val.msg.urlPath)
this.$message({
message: '已将url路径复制至剪切板',
type: 'success',
})
},
copyToClip(content, message) {
var aux = document.createElement("input");
aux.setAttribute("value", content);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
},
}, },
} }
</script> </script>

@ -8,20 +8,19 @@
<div class="login_container"> <div class="login_container">
<!-- 顶部logo --> <!-- 顶部logo -->
<div class="login_title"> <div class="login_title">
<img src="@/assets/images/home-logo.png" <img src="@/assets/images/home-logo.png" alt="logo" />
alt="logo" />
</div> </div>
<div class="login_contant"> <div class="login_contant">
<img src="@/assets/images/login.png" <img src="@/assets/images/login.png" alt="image" class="login_img" />
alt="image" <el-form
class="login_img" /> ref="loginForm"
<el-form ref="loginForm"
:model="loginForm" :model="loginForm"
:rules="loginRules" :rules="loginRules"
class="login_form" class="login_form"
autocomplete="on" autocomplete="on"
label-position="left" label-position="left"
@keyup.enter.native="handleLogin"> @keyup.enter.native="handleLogin"
>
<div class="title_container"> <div class="title_container">
<h3 class="title"> <h3 class="title">
HELLO, HELLO,
@ -37,7 +36,8 @@
<div> <div>
<p>用户名</p> <p>用户名</p>
<el-form-item prop="loginName"> <el-form-item prop="loginName">
<el-input ref="loginName" <el-input
ref="loginName"
v-model="loginForm.loginName" v-model="loginForm.loginName"
placeholder="用户名" placeholder="用户名"
name="loginName" name="loginName"
@ -45,21 +45,27 @@
tabindex="1" tabindex="1"
autocomplete="on" autocomplete="on"
@focus="setTop('0')" @focus="setTop('0')"
@change="getPsw" /> @change="getPsw"
/>
</el-form-item> </el-form-item>
</div> </div>
<div> <div>
<p>密码</p> <p>密码</p>
<input name="password" <input
name="password"
type="password" type="password"
autocomplete="off" autocomplete="off"
class="take" /> class="take"
<el-tooltip v-model="capsTooltip" />
<el-tooltip
v-model="capsTooltip"
content="Caps lock is On" content="Caps lock is On"
placement="right" placement="right"
manual> manual
>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input :key="passwordType" <el-input
:key="passwordType"
ref="password" ref="password"
v-model="loginForm.password" v-model="loginForm.password"
:type="passwordType" :type="passwordType"
@ -69,9 +75,9 @@
autocomplete="on" autocomplete="on"
@blur="capsTooltip = false" @blur="capsTooltip = false"
@focus="setTop('50')" @focus="setTop('50')"
@keyup.native="checkCapslock" /> @keyup.native="checkCapslock"
<span class="show_pwd" />
@click="showPwd"> <span class="show_pwd" @click="showPwd">
<i class="el-icon-view" /> <i class="el-icon-view" />
</span> </span>
</el-form-item> </el-form-item>
@ -80,180 +86,181 @@
</div> </div>
<div class="control"> <div class="control">
<div class="remember"> <div class="remember">
<input v-model="rememberPsw" <input v-model="rememberPsw" type="checkbox" />
type="checkbox" />
<p>记住密码</p> <p>记住密码</p>
</div> </div>
</div> </div>
<el-button :loading="loading" <el-button
:loading="loading"
type="primary" type="primary"
class="login_btn" class="login_btn"
@click.native.prevent="handleLogin">登录</el-button> @click.native.prevent="handleLogin"
>登录</el-button
>
</el-form> </el-form>
</div> </div>
<!-- 验证码 --> <!-- 验证码 -->
<Verify v-if="needCaptcha" <Verify
v-if="needCaptcha"
ref="verify" ref="verify"
:captcha-type="'blockPuzzle'" :captcha-type="'blockPuzzle'"
:img-size="{ width: '400px', height: '200px' }" :img-size="{ width: '400px', height: '200px' }"
@success="verifylogin" /> @success="verifylogin"
/>
</div> </div>
</template> </template>
<script> <script>
import Verify from '@/components/verifition/Verify' import Verify from "@/components/verifition/Verify";
import cookies from 'js-cookie' import cookies from "js-cookie";
import { Decrypt, Encrypt } from '@/utils/index' import { Decrypt, Encrypt } from "@/utils/index";
import { login } from '@/api/login' import { login } from "@/api/login";
import { transPsw } from '@/utils/encrypted' import { transPsw } from "@/utils/encrypted";
import { setToken, setAccessUser } from "@/utils/auth"; import { setToken, setAccessUser } from "@/utils/auth";
export default { export default {
name: 'Login', name: "Login",
components: { components: {
Verify, Verify
}, },
data() { data() {
return { return {
activeTop: '-50%', // activeTop: "-50%", //
rememberPsw: false, // rememberPsw: false, //
loginForm: { loginForm: {
loginName: '', loginName: "",
password: '', password: "",
verifyCode: '', verifyCode: ""
}, // }, //
loginRules: { loginRules: {
loginName: [{ required: true, message: '用户名必填', trigger: 'blur' }], loginName: [{ required: true, message: "用户名必填", trigger: "blur" }],
password: [ password: [{ required: true, message: "用户密码必填", trigger: "blur" }]
{ required: true, message: '用户密码必填', trigger: 'blur' },
],
}, // }, //
passwordType: 'password', // passwordType: "password", //
capsTooltip: false, // capsTooltip: false, //
loading: false, // loding loading: false, // loding
redirect: undefined, // redirect: undefined, //
otherQuery: {}, // otherQuery: {}, //
needCaptcha: false, needCaptcha: false
} };
}, },
watch: { watch: {
$route: { $route: {
// from // from
handler: function(route) { handler: function(route) {
const query = route.query const query = route.query;
if (query) { if (query) {
this.redirect = query.redirect this.redirect = query.redirect;
this.otherQuery = this.getOtherQuery(query) this.otherQuery = this.getOtherQuery(query);
} }
}, },
immediate: true, immediate: true
}, }
}, },
mounted() { mounted() {
// //
if (this.loginForm.loginName === '') { if (this.loginForm.loginName === "") {
this.$refs.loginName.focus() this.$refs.loginName.focus();
} else if (this.loginForm.password === '') { } else if (this.loginForm.password === "") {
this.$refs.password.focus() this.$refs.password.focus();
} }
}, },
methods: { methods: {
// //
getPsw() { getPsw() {
const cookVal = cookies.get(`u_${this.loginForm.loginName}`) const cookVal = cookies.get(`u_${this.loginForm.loginName}`);
this.loginForm.password = cookVal && Decrypt(cookVal) this.loginForm.password = cookVal && Decrypt(cookVal);
}, },
// top // top
setTop(val) { setTop(val) {
this.activeTop = val this.activeTop = val;
}, },
// //
checkCapslock(e) { checkCapslock(e) {
const { key } = e const { key } = e;
this.capsTooltip = key && key.length === 1 && key >= 'A' && key <= 'Z' this.capsTooltip = key && key.length === 1 && key >= "A" && key <= "Z";
}, },
// //
showPwd() { showPwd() {
if (this.passwordType === 'password') { if (this.passwordType === "password") {
this.passwordType = '' this.passwordType = "";
} else { } else {
this.passwordType = 'password' this.passwordType = "password";
} }
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.password.focus() this.$refs.password.focus();
}) });
}, },
// //
useVerify() { useVerify() {
this.$refs.loginForm.validate((valid) => { this.$refs.loginForm.validate(valid => {
if (valid) { if (valid) {
this.$refs.verify.show() this.$refs.verify.show();
} else { } else {
return false return false;
} }
}) });
}, },
// //
verifylogin(params) { verifylogin(params) {
this.loginForm.verifyCode = params.captchaVerification this.loginForm.verifyCode = params.captchaVerification;
if (this.loginForm.verifyCode) { if (this.loginForm.verifyCode) {
this.loginApi() this.loginApi();
} }
}, },
// //
handleLogin() { handleLogin() {
this.$refs.loginForm.validate((valid) => { this.$refs.loginForm.validate(valid => {
if (valid) { if (valid) {
this.loading = true this.loading = true;
// //
if (this.needCaptcha) { if (this.needCaptcha) {
this.useVerify() this.useVerify();
return return;
} }
this.loginApi() this.loginApi();
} else { } else {
return false return false;
} }
}) });
}, },
async loginApi() { async loginApi() {
let obj = { let obj = {
loginName: this.loginForm.loginName, loginName: this.loginForm.loginName,
password: transPsw(this.loginForm.password), password: transPsw(this.loginForm.password),
verifyCode: '', verifyCode: ""
} };
const { code, data } = await login(obj) const { code, data } = await login(obj);
console.log(data) this.loading = false;
this.loading = false if (code != "200") return;
if (code != '200') return setToken(data.token);
setToken(data.token) setAccessUser(data);
setAccessUser(data)
// cookie,15 // cookie,15
this.rememberPsw && this.rememberPsw &&
cookies.set( cookies.set(
`u_${this.loginForm.loginName}`, `u_${this.loginForm.loginName}`,
Encrypt(this.loginForm.password), Encrypt(this.loginForm.password),
{ expires: 15 } { expires: 15 }
) );
if (data && data.captcha) { if (data && data.captcha) {
this.needCaptcha = true this.needCaptcha = true;
} else { } else {
this.needCaptcha = false this.needCaptcha = false;
this.$router.push({ this.$router.push({
path: this.redirect || '/index', path: this.redirect || "/index",
query: this.otherQuery, query: this.otherQuery
}) });
} }
}, },
getOtherQuery(query) { getOtherQuery(query) {
return Object.keys(query).reduce((acc, cur) => { return Object.keys(query).reduce((acc, cur) => {
if (cur !== 'redirect') { if (cur !== "redirect") {
acc[cur] = query[cur] acc[cur] = query[cur];
}
return acc;
}, {});
} }
return acc
}, {})
},
},
} }
};
</script> </script>
<style lang="scss"> <style lang="scss">

@ -0,0 +1,50 @@
<!--
* @Author: lide1202@hotmail.com
* @Date: 2021-3-13 11:04:24
* @Last Modified by: lide1202@hotmail.com
* @Last Modified time: 2021-3-13 11:04:24
!-->
<template>
<div>
</div>
</template>
<script>
import { reportShareDetailByCode} from '@/api/reportShare'
import { setShareToken } from '@/utils/auth'
export default {
name: "Report",
components: {
},
data() {
return {
};
},
created() {
this.handleOpen()
},
methods: {
async handleOpen() {
const url = window.location.href
const shareCode = url.substring(url.lastIndexOf("/") + 1);
console.log(shareCode)
const {code, data} = await reportShareDetailByCode(shareCode)
if (code != '200') return
setShareToken(data.shareToken)
//shareToken
//
this.$router.push({
path: '/bigscreen/viewer',
query: {
reportCode: data.reportCode
},
})
},
}
};
</script>

@ -5,7 +5,7 @@
<el-select <el-select
size="mini" size="mini"
v-model="dataSetValue" v-model="dataSetValue"
clearable filterable
placeholder="请选择" placeholder="请选择"
@change="selectDataSet" @change="selectDataSet"
> >
@ -27,7 +27,7 @@
<el-form-item v-for="item in setParamList" :key="item" :label="item"> <el-form-item v-for="item in setParamList" :key="item" :label="item">
<Dictionary <Dictionary
v-model="params" v-model="params"
:dict-key="'CHART_PROPERTIES'" :dict-key="getDictKey()"
@input="selectParams($event, item)" @input="selectParams($event, item)"
/> />
</el-form-item> </el-form-item>
@ -57,6 +57,7 @@ export default {
}, },
props: { props: {
chartType: String, chartType: String,
dictKey: String,
props: ["formData"] props: ["formData"]
}, },
data() { data() {
@ -66,7 +67,7 @@ export default {
userNameList: [], // userNameList: [], //
setParamList: [], // setParamList: [], //
params: {}, params: {},
chartProperties: {} chartProperties: {},
}; };
}, },
computed: { computed: {
@ -113,6 +114,9 @@ export default {
}, },
selectParams(val, key) { selectParams(val, key) {
this.chartProperties[key] = val; this.chartProperties[key] = val;
},
getDictKey(){
return this.dictKey == null ? 'CHART_PROPERTIES' : this.dictKey
} }
} }
}; };

@ -137,6 +137,7 @@
v-if="item.type == 'dycustComponents' && inputShow[item.name]" v-if="item.type == 'dycustComponents' && inputShow[item.name]"
v-model="formData[item.name]" v-model="formData[item.name]"
:chart-type="item.chartType" :chart-type="item.chartType"
:dict-key='item.dictKey'
@change="changed($event, item.name)" @change="changed($event, item.name)"
/> />
<dynamic-add-table <dynamic-add-table

@ -87,7 +87,7 @@
<i class="iconfont iconyulan" @click="viewScreen"></i> <i class="iconfont iconyulan" @click="viewScreen"></i>
</el-tooltip> </el-tooltip>
</span> </span>
<span class="btn border-left"> <!-- <span class="btn border-left">
<ul class="nav"> <ul class="nav">
<li> <li>
<i class="el-icon-brush"></i><i class="el-icon-arrow-down"></i> <i class="el-icon-brush"></i><i class="el-icon-arrow-down"></i>
@ -116,7 +116,7 @@
</ul> </ul>
</li> </li>
</ul> </ul>
</span> </span> -->
</div> </div>
<div <div
class="workbench-container" class="workbench-container"
@ -170,8 +170,6 @@
</div> </div>
</vue-ruler-tool> </vue-ruler-tool>
</div> </div>
<!-- 如果是二次开发商业使用必须带上这个技术支持 -->
<div class="bottom-text">技术支持 安吉加加信息技术有限公司</div>
</div> </div>
<div class="layout-right" :style="{ width: widthLeftForOptions + 'px' }"> <div class="layout-right" :style="{ width: widthLeftForOptions + 'px' }">

File diff suppressed because it is too large Load Diff

@ -0,0 +1,411 @@
<template>
<div :style="styleObj">
<v-chart :options="options" autoresize/>
</div>
</template>
<script>
export default {
name: "WidgetBarStackchart",
components: {},
props: {
value: Object,
ispreview: Boolean
},
data() {
return {
options: {
grid: {},
legend: {
textStyle: {
color: "#fff"
}
},
xAxis: {
type: "category",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
yAxis: {
type: "value",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
series: [
{
data: [],
name: '',
type: "bar",
barGap: "0%",
itemStyle: {
barBorderRadius: null
}
}
]
},
optionsStyle: {}, //
optionsData: {}, //
optionsSetup: {},
flagInter: null
};
},
computed: {
styleObj() {
return {
position: this.ispreview ? "absolute" : "static",
width: this.optionsStyle.width + "px",
height: this.optionsStyle.height + "px",
left: this.optionsStyle.left + "px",
top: this.optionsStyle.top + "px",
background: this.optionsSetup.background
};
}
},
watch: {
value: {
handler(val) {
this.optionsStyle = val.position;
this.optionsData = val.data;
this.optionsCollapse = val.setup;
this.optionsSetup = val.setup;
this.editorOptions();
},
deep: true
}
},
mounted() {
this.optionsStyle = this.value.position;
this.optionsData = this.value.data;
this.optionsCollapse = this.value.setup;
this.optionsSetup = this.value.setup;
this.editorOptions();
},
methods: {
// options
editorOptions() {
this.setOptionsTitle();
this.setOptionsX();
this.setOptionsY();
this.setOptionsTooltip();
this.setOptionsMargin();
this.setOptionsLegend();
this.setOptionsData();
},
//
setOptionsTitle() {
const optionsSetup = this.optionsSetup;
const title = {};
title.text = optionsSetup.titleText;
title.show = optionsSetup.isNoTitle;
title.left = optionsSetup.textAlign;
title.textStyle = {
color: optionsSetup.textColor,
fontSize: optionsSetup.textFontSize,
fontWeight: optionsSetup.textFontWeight
};
title.subtext = optionsSetup.subText;
title.subtextStyle = {
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.subTextFontWeight,
fontSize: optionsSetup.subTextFontSize
};
this.options.title = title;
},
// X
setOptionsX() {
const optionsSetup = this.optionsSetup;
const xAxis = {
type: "category",
show: optionsSetup.hideX, //
name: optionsSetup.xName, //
nameTextStyle: {
color: optionsSetup.xNameColor,
fontSize: optionsSetup.xNameFontSize
},
nameRotate: optionsSetup.textAngleX, //
inverse: optionsSetup.reversalX, //
axisLabel: {
show: true,
interval: optionsSetup.textInterval, //
rotate: optionsSetup.textAngle, //
textStyle: {
color: optionsSetup.Xcolor, // x
fontSize: optionsSetup.fontSizeX
}
},
axisLine: {
show: true,
lineStyle: {
color: optionsSetup.lineColorX
}
},
splitLine: {
show: optionsSetup.isShowSplitLineX,
lineStyle: {
color: optionsSetup.splitLineColorX
}
}
};
this.options.xAxis = xAxis;
},
// Y
setOptionsY() {
const optionsSetup = this.optionsSetup;
const yAxis = {
type: "value",
show: optionsSetup.isShowY, //
name: optionsSetup.textNameY, //
nameTextStyle: {
color: optionsSetup.NameColorY,
fontSize: optionsSetup.NameFontSizeY
},
inverse: optionsSetup.reversalY, // y
axisLabel: {
show: true,
rotate: optionsSetup.textAngleY,//
textStyle: {
color: optionsSetup.colorY, // y
fontSize: optionsSetup.fontSizeY
}
},
axisLine: {
show: true,
lineStyle: {
color: optionsSetup.lineColorY
}
},
splitLine: {
show: optionsSetup.isShowSplitLineY,
lineStyle: {
color: optionsSetup.splitLineColorY
}
}
};
this.options.yAxis = yAxis;
},
// tooltip
setOptionsTooltip() {
const optionsSetup = this.optionsSetup;
const tooltip = {
trigger: "item",
show: true,
textStyle: {
color: optionsSetup.lineColor,
fontSize: optionsSetup.tipsFontSize
}
};
this.options.tooltip = tooltip;
},
//
setOptionsMargin() {
const optionsSetup = this.optionsSetup;
const grid = {
left: optionsSetup.marginLeft,
right: optionsSetup.marginRight,
bottom: optionsSetup.marginBottom,
top: optionsSetup.marginTop,
containLabel: true
};
this.options.grid = grid;
},
// legend
setOptionsLegend() {
const optionsSetup = this.optionsSetup;
const legend = this.options.legend;
legend.show = optionsSetup.isShowLegend;
legend.left = optionsSetup.lateralPosition;
legend.top = optionsSetup.longitudinalPosition == "top" ? 0 : "auto";
legend.bottom =
optionsSetup.longitudinalPosition == "bottom" ? 0 : "auto";
legend.orient = optionsSetup.layoutFront;
legend.textStyle = {
color: optionsSetup.lengedColor,
fontSize: optionsSetup.lengedFontSize
};
legend.itemWidth = optionsSetup.lengedWidth;
},
//
setOptionsData() {
const optionsSetup = this.optionsSetup;
const optionsData = this.optionsData; // or
optionsData.dataType == "staticData"
? this.staticDataFn(optionsData.staticData, optionsSetup)
: this.dynamicDataFn(
optionsData.dynamicData,
optionsData.refreshTime,
optionsSetup
);
},
//
setUnique(arr) {
let newArr = [];
arr.forEach(item => {
return newArr.includes(item) ? '' : newArr.push(item);
});
return newArr;
},
//
getStackStyle() {
const optionsSetup = this.optionsSetup;
let style = ""
if (optionsSetup.stackStyle == "upDown") {
style = "total"
}
return style
},
//
staticDataFn(val) {
const optionsSetup = this.optionsSetup;
//
const customColor = optionsSetup.customColor;
const arrColor = [];
for (let i = 0; i < customColor.length; i++) {
arrColor.push(customColor[i].color);
}
//
const series = [];
let xAxisList = []
let yAxisList = []
for (const i in val) {
xAxisList[i] = val[i].axis
yAxisList[i] = val[i].name
}
xAxisList = this.setUnique(xAxisList) // x 0725 0726 0727
yAxisList = this.setUnique(yAxisList) // y A B C
for (const i in yAxisList) {
const data = new Array(yAxisList.length).fill(0)
for (const j in xAxisList) {
for (const k in val) {
if (val[k].name == yAxisList[i]) { // a = a
if (val[k].axis == xAxisList[j]) { // 0725
data[j] = val[k].data
}
}
}
}
series.push({
name: yAxisList[i],
type: "bar",
data: data,
barGap: "0%",
stack: this.getStackStyle(),
barWidth: optionsSetup.maxWidth,
label: {
show: optionsSetup.isShow,
position: "top",
distance: 10,
fontSize: optionsSetup.fontSize,
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.fontWeight
},
//
itemStyle: {
normal: {
color: arrColor[i],
barBorderRadius: optionsSetup.radius,
}
}
})
}
this.options.series = series
if (optionsSetup.verticalShow) {
this.options.xAxis.data = [];
this.options.yAxis.data = xAxisList;
this.options.xAxis.type = "value";
this.options.yAxis.type = "category";
} else {
this.options.xAxis.data = xAxisList;
this.options.yAxis.data = [];
this.options.xAxis.type = "category";
this.options.yAxis.type = "value";
}
},
//
dynamicDataFn(val, refreshTime, optionsSetup) {
if (!val) return;
if (this.ispreview) {
this.getEchartData(val, optionsSetup);
this.flagInter = setInterval(() => {
this.getEchartData(val, optionsSetup);
}, refreshTime);
} else {
this.getEchartData(val, optionsSetup);
}
},
getEchartData(val, optionsSetup) {
const data = this.queryEchartsData(val);
data.then(res => {
this.renderingFn(optionsSetup, res);
});
},
renderingFn(optionsSetup, val) {
//
const customColor = optionsSetup.customColor;
const arrColor = [];
for (let i = 0; i < customColor.length; i++) {
arrColor.push(customColor[i].color);
}
// x
if (optionsSetup.verticalShow) {
this.options.xAxis.data = [];
this.options.yAxis.data = val.xAxis;
this.options.xAxis.type = "value";
this.options.yAxis.type = "category";
} else {
this.options.xAxis.data = val.xAxis;
this.options.yAxis.data = [];
this.options.xAxis.type = "category";
this.options.yAxis.type = "value";
}
const series = [];
for (const i in val.series) {
if (val.series[i].type == "bar") {
series.push({
name: val.series[i].name,
type: "bar",
data: val.series[i].data,
barGap: "0%",
stack: this.getStackStyle(),
barWidth: optionsSetup.maxWidth,
label: {
show: optionsSetup.isShow,
position: "top",
distance: 10,
fontSize: optionsSetup.fontSize,
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.fontWeight
},
//
itemStyle: {
normal: {
color: arrColor[i],
barBorderRadius: optionsSetup.radius,
}
}
})
}
}
this.options.series = series
}
}
};
</script>
<style scoped lang="scss">
.echarts {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>

@ -283,12 +283,11 @@ export default {
fontWeight: optionsCollapse.fontWeight fontWeight: optionsCollapse.fontWeight
}; };
series[key].barWidth = optionsCollapse.maxWidth; series[key].barWidth = optionsCollapse.maxWidth;
//series[key].barMinHeight = optionsCollapse.minHeight;
} }
} }
this.options.series = series; this.options.series = series;
}, },
// tooltip // tooltip
setOptionsTooltip() { setOptionsTooltip() {
const optionsCollapse = this.optionsSetup; const optionsCollapse = this.optionsSetup;
const tooltip = { const tooltip = {

@ -0,0 +1,443 @@
<template>
<div :style="styleObj">
<v-chart :options="options" autoresize/>
</div>
</template>
<script>
export default {
name: "WidgetBarStackchart",
components: {},
props: {
value: Object,
ispreview: Boolean
},
data() {
return {
options: {
grid: {},
legend: {
textStyle: {
color: "#fff"
}
},
xAxis: {
type: "category",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
yAxis: {
type: "value",
data: [],
axisLabel: {
show: true,
textStyle: {
color: "#fff"
}
}
},
series: [
{
data: [],
name: '',
type: "line",
barGap: "0%",
itemStyle: {
barBorderRadius: null
}
}
]
},
optionsStyle: {}, //
optionsData: {}, //
optionsSetup: {},
flagInter: null
};
},
computed: {
styleObj() {
return {
position: this.ispreview ? "absolute" : "static",
width: this.optionsStyle.width + "px",
height: this.optionsStyle.height + "px",
left: this.optionsStyle.left + "px",
top: this.optionsStyle.top + "px",
background: this.optionsSetup.background
};
}
},
watch: {
value: {
handler(val) {
this.optionsStyle = val.position;
this.optionsData = val.data;
this.optionsCollapse = val.setup;
this.optionsSetup = val.setup;
this.editorOptions();
},
deep: true
}
},
mounted() {
this.optionsStyle = this.value.position;
this.optionsData = this.value.data;
this.optionsCollapse = this.value.setup;
this.optionsSetup = this.value.setup;
this.editorOptions();
},
methods: {
// options
editorOptions() {
this.setOptionsTitle();
this.setOptionsX();
this.setOptionsY();
this.setOptionsTooltip();
this.setOptionsMargin();
this.setOptionsLegend();
this.setOptionsData();
},
//
setOptionsTitle() {
const optionsSetup = this.optionsSetup;
const title = {};
title.text = optionsSetup.titleText;
title.show = optionsSetup.isNoTitle;
title.left = optionsSetup.textAlign;
title.textStyle = {
color: optionsSetup.textColor,
fontSize: optionsSetup.textFontSize,
fontWeight: optionsSetup.textFontWeight
};
title.subtext = optionsSetup.subText;
title.subtextStyle = {
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.subTextFontWeight,
fontSize: optionsSetup.subTextFontSize
};
this.options.title = title;
},
// X
setOptionsX() {
const optionsSetup = this.optionsSetup;
const xAxis = {
type: "category",
show: optionsSetup.hideX, //
name: optionsSetup.xName, //
nameTextStyle: {
color: optionsSetup.xNameColor,
fontSize: optionsSetup.xNameFontSize
},
nameRotate: optionsSetup.textAngleX, //
inverse: optionsSetup.reversalX, //
axisLabel: {
show: true,
interval: optionsSetup.textInterval, //
rotate: optionsSetup.textAngle, //
textStyle: {
color: optionsSetup.Xcolor, // x
fontSize: optionsSetup.fontSizeX
}
},
axisLine: {
show: true,
lineStyle: {
color: optionsSetup.lineColorX
}
},
splitLine: {
show: optionsSetup.isShowSplitLineX,
lineStyle: {
color: optionsSetup.splitLineColorX
}
}
};
this.options.xAxis = xAxis;
},
// Y
setOptionsY() {
const optionsSetup = this.optionsSetup;
const yAxis = {
type: "value",
show: optionsSetup.isShowY, //
name: optionsSetup.textNameY, //
nameTextStyle: {
color: optionsSetup.NameColorY,
fontSize: optionsSetup.NameFontSizeY
},
inverse: optionsSetup.reversalY, // y
axisLabel: {
show: true,
rotate: optionsSetup.textAngleY,//
textStyle: {
color: optionsSetup.colorY, // y
fontSize: optionsSetup.fontSizeY
}
},
axisLine: {
show: true,
lineStyle: {
color: optionsSetup.lineColorY
}
},
splitLine: {
show: optionsSetup.isShowSplitLineY,
lineStyle: {
color: optionsSetup.splitLineColorY
}
}
};
this.options.yAxis = yAxis;
},
//
getOptionArea() {
const optionsSetup = this.optionsSetup;
let areaStyle = [];
if (optionsSetup.area) {
areaStyle = {
opacity: optionsSetup.areaThickness / 100
}
} else {
areaStyle = {
opacity: 0
}
}
return areaStyle
},
// tooltip
setOptionsTooltip() {
const optionsSetup = this.optionsSetup;
const tooltip = {
trigger: "item",
show: true,
textStyle: {
color: optionsSetup.lineColor,
fontSize: optionsSetup.tipsFontSize
}
};
this.options.tooltip = tooltip;
},
//
setOptionsMargin() {
const optionsSetup = this.optionsSetup;
const grid = {
left: optionsSetup.marginLeft,
right: optionsSetup.marginRight,
bottom: optionsSetup.marginBottom,
top: optionsSetup.marginTop,
containLabel: true
};
this.options.grid = grid;
},
// legend
setOptionsLegend() {
const optionsSetup = this.optionsSetup;
const legend = this.options.legend;
legend.show = optionsSetup.isShowLegend;
legend.left = optionsSetup.lateralPosition;
legend.top = optionsSetup.longitudinalPosition == "top" ? 0 : "auto";
legend.bottom =
optionsSetup.longitudinalPosition == "bottom" ? 0 : "auto";
legend.orient = optionsSetup.layoutFront;
legend.textStyle = {
color: optionsSetup.lengedColor,
fontSize: optionsSetup.lengedFontSize
};
legend.itemWidth = optionsSetup.lengedWidth;
},
//
setOptionsColor() {
const optionsCollapse = this.optionsSetup;
const customColor = optionsCollapse.customColor;
if (!customColor) return;
const arrColor = [];
for (let i = 0; i < customColor.length; i++) {
arrColor.push(customColor[i].color);
}
this.options.color = arrColor;
this.options = Object.assign({}, this.options);
},
//
setOptionsData() {
const optionsSetup = this.optionsSetup;
const optionsData = this.optionsData; // or
optionsData.dataType == "staticData"
? this.staticDataFn(optionsData.staticData, optionsSetup)
: this.dynamicDataFn(
optionsData.dynamicData,
optionsData.refreshTime,
optionsSetup
);
},
//
setUnique(arr) {
let newArr = [];
arr.forEach(item => {
return newArr.includes(item) ? '' : newArr.push(item);
});
return newArr;
},
//
staticDataFn(val) {
const optionsSetup = this.optionsSetup;
//
const customColor = optionsSetup.customColor;
const arrColor = [];
for (let i = 0; i < customColor.length; i++) {
arrColor.push(customColor[i].color);
}
//
const series = [];
let xAxisList = []
let yAxisList = []
for (const i in val) {
xAxisList[i] = val[i].axis
yAxisList[i] = val[i].name
}
xAxisList = this.setUnique(xAxisList) // x 0725 0726 0727
yAxisList = this.setUnique(yAxisList) // y A B C
for (const i in yAxisList) {
const data = new Array(yAxisList.length).fill(0)
for (const j in xAxisList) {
for (const k in val) {
if (val[k].name == yAxisList[i]) { // a = a
if (val[k].axis == xAxisList[j]) { // 0725
data[j] = val[k].data
}
}
}
}
series.push({
name: yAxisList[i],
type: "line",
data: data,
width: optionsSetup.lineWidth,
symbol: 'circle',
showSymbol: optionsSetup.markPoint,
symbolSize: optionsSetup.pointSize,
symbolColor: arrColor[i],
smooth: optionsSetup.smoothCurve,
// 线
lineStyle: {
color: arrColor[i],
width: optionsSetup.lineWidth
},
//
itemStyle: {
color: arrColor[i],
},
areaStyle: this.getOptionArea(),
//
label: {
show: optionsSetup.isShow,
position: "top",
distance: 10,
fontSize: optionsSetup.fontSize,
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.fontWeight
},
})
}
this.options.series = series
if (optionsSetup.verticalShow) {
this.options.xAxis.data = [];
this.options.yAxis.data = xAxisList;
this.options.xAxis.type = "value";
this.options.yAxis.type = "category";
} else {
this.options.xAxis.data = xAxisList;
this.options.yAxis.data = [];
this.options.xAxis.type = "category";
this.options.yAxis.type = "value";
}
},
//
dynamicDataFn(val, refreshTime, optionsSetup) {
if (!val) return;
if (this.ispreview) {
this.getEchartData(val, optionsSetup);
this.flagInter = setInterval(() => {
this.getEchartData(val, optionsSetup);
}, refreshTime);
} else {
this.getEchartData(val, optionsSetup);
}
},
getEchartData(val, optionsSetup) {
const data = this.queryEchartsData(val);
data.then(res => {
this.renderingFn(optionsSetup, res);
});
},
renderingFn(optionsSetup, val) {
//
const customColor = optionsSetup.customColor;
const arrColor = [];
for (let i = 0; i < customColor.length; i++) {
arrColor.push(customColor[i].color);
}
// x
if (optionsSetup.verticalShow) {
this.options.xAxis.data = [];
this.options.yAxis.data = val.xAxis;
this.options.xAxis.type = "value";
this.options.yAxis.type = "category";
} else {
this.options.xAxis.data = val.xAxis;
this.options.yAxis.data = [];
this.options.xAxis.type = "category";
this.options.yAxis.type = "value";
}
const series = [];
for (const i in val.series) {
if (val.series[i].type == "line") {
series.push({
name: val.series[i].name,
type: "line",
data: val.series[i].data,
width: optionsSetup.lineWidth,
symbol: 'circle',
showSymbol: optionsSetup.markPoint,
symbolSize: optionsSetup.pointSize,
symbolColor: arrColor[i],
smooth: optionsSetup.smoothCurve,
// 线
lineStyle: {
color: arrColor[i],
width: optionsSetup.lineWidth
},
//
itemStyle: {
color: arrColor[i],
},
areaStyle: this.getOptionArea(),
//
label: {
show: optionsSetup.isShow,
position: "top",
distance: 10,
fontSize: optionsSetup.fontSize,
color: optionsSetup.subTextColor,
fontWeight: optionsSetup.fontWeight
},
})
}
}
this.options.series = series
}
}
};
</script>
<style scoped lang="scss">
.echarts {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>

@ -31,6 +31,9 @@ import widgetTable from "./widgetTable.vue";
import widgetMap from "./widgetMap.vue"; import widgetMap from "./widgetMap.vue";
import widgetPiePercentageChart from "./pie/widgetPiePercentageChart"; import widgetPiePercentageChart from "./pie/widgetPiePercentageChart";
import widgetAirBubbleMap from "./map/widgetAirBubbleMap"; import widgetAirBubbleMap from "./map/widgetAirBubbleMap";
import widgetBarStackChart from "./bar/widgetBarStackChart";
import widgetLineStackChart from "./line/widgetLineStackChart";
export default { export default {
name: "WidgetTemp", name: "WidgetTemp",
components: { components: {
@ -53,7 +56,9 @@ export default {
widgetTable, widgetTable,
widgetMap, widgetMap,
widgetPiePercentageChart, widgetPiePercentageChart,
widgetAirBubbleMap widgetAirBubbleMap,
widgetBarStackChart,
widgetLineStackChart
}, },
model: { model: {
prop: "value", prop: "value",

@ -41,6 +41,9 @@ import widgetTable from "./widgetTable.vue";
import widgetMap from "./widgetMap.vue"; import widgetMap from "./widgetMap.vue";
import widgetPiePercentageChart from "./pie/widgetPiePercentageChart"; import widgetPiePercentageChart from "./pie/widgetPiePercentageChart";
import widgetAirBubbleMap from "./map/widgetAirBubbleMap"; import widgetAirBubbleMap from "./map/widgetAirBubbleMap";
import widgetBarStackChart from "./bar/widgetBarStackChart";
import widgetLineStackChart from "./line/widgetLineStackChart";
export default { export default {
name: "Widget", name: "Widget",
components: { components: {
@ -63,7 +66,9 @@ export default {
widgetTable, widgetTable,
widgetMap, widgetMap,
widgetPiePercentageChart, widgetPiePercentageChart,
widgetAirBubbleMap widgetAirBubbleMap,
widgetBarStackChart,
widgetLineStackChart
}, },
model: { model: {
prop: "value", prop: "value",

@ -99,6 +99,7 @@
<Share <Share
:visib="visibleForShareDialog" :visib="visibleForShareDialog"
:reportCode="reportCodeForShareDialog" :reportCode="reportCodeForShareDialog"
:reportName="reportNameForShareDialog"
@handleClose="visibleForShareDialog = false" @handleClose="visibleForShareDialog = false"
/> />
</div> </div>
@ -127,7 +128,8 @@ export default {
}, },
// //
visibleForShareDialog: false, visibleForShareDialog: false,
reportCodeForShareDialog: "" reportCodeForShareDialog: "",
reportNameForShareDialog: ""
}; };
}, },
mounted() {}, mounted() {},
@ -172,6 +174,7 @@ export default {
// //
share(val) { share(val) {
this.reportCodeForShareDialog = val.reportCode; this.reportCodeForShareDialog = val.reportCode;
this.reportNameForShareDialog = val.reportName;
this.visibleForShareDialog = true; this.visibleForShareDialog = true;
}, },
openDesign(val) { openDesign(val) {

@ -14,8 +14,6 @@
:type="widget.type" :type="widget.type"
/> />
</div> </div>
<!-- 如果是二次开发商业使用必须带上这个技术支持 -->
<div class="bottom-text">技术支持 安吉加加信息技术有限公司</div>
</div> </div>
</template> </template>

@ -1,14 +1,45 @@
<template> <template>
<el-dialog class="tree_dialog" title="报表分享" width="60%" :close-on-click-modal="false" center :visible.sync="visib" :before-close="closeDialog"> <el-dialog class="tree_dialog" :title="titleBuild()" width="60%" :close-on-click-modal="false" center :visible.sync="visib" :before-close="closeDialog">
<div v-if="shareLinkFlag1">
<el-form ref="userForm" :model="dialogForm" :rules="rules" size="small" label-width="100px">
<el-row :gutter="10">
<el-col :xs="24" :sm="20" :md="6" :lg="6" :xl="6">
<el-form-item label="有效期" prop="shareValidType">
<el-select v-model.trim="dialogForm.shareValidType" placeholder="请选择" clearable @change="selectChange">
<el-option v-for="item in shareValidTypeOptions" :key="item.id" :label="item.text" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-button type="primary" plain @click="createShare"></el-button>
</div>
<div v-else>
<el-row :gutter="10">
<el-col :xs="24" :sm="20" :md="20" :lg="20" :xl="16">
<el-input v-model="reportShareUrl" :disabled="true"/>
</el-col>
</el-row>
<el-row :gutter="10">
<el-button type="primary" plain @click="copyShare"></el-button>
</el-row>
</div>
<div slot="footer" style="text-align: center"> <div slot="footer" style="text-align: center">
{{reportCode}}
<el-button type="primary" plain @click="saveReportShare"></el-button> <!-- <el-button type="primary" plain @click="saveReportShare"></el-button>-->
<el-button type="danger" plain @click="closeDialog"></el-button> <el-button type="danger" plain @click="closeDialog"></el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import { reportShareAdd } from '@/api/reportShare'
import { getDictList } from '@/api/dict-data' //
import Dictionary from '@/components/Dictionary/index'
export default { export default {
components: { Dictionary },
props: { props: {
visib: { visib: {
required: true, required: true,
@ -22,20 +53,87 @@ export default {
return '' return ''
}, },
}, },
reportName: {
required: true,
type: String,
default: () => {
return ''
},
},
}, },
data() { data() {
return { return {
} title: '报表分享',
reportShareUrl:'',
shareValidTypeOptions: [], //
dialogForm: {
shareValidType: 0,
reportCode: '',
shareUrl: '',
shareCode: '',
},
shareLinkFlag1: true,
rules: {
shareValidType: [
{required: true, message: '有效期必选', trigger: 'change'},
],
},
};
}, },
watch: { watch: {
visib(val) { visib(val) {
if (val) { if (val) {
// //
this.getSystem()
} }
}, },
}, },
created() {}, created() {},
methods: { methods: {
titleBuild(){
return '【' +this.reportName + '】' + '报表分享-' + this.reportCode
},
selectChange(val) {
this.dialogForm.shareValidType = val
},
//
async getSystem() {
this.shareLinkFlag1 = true
const { code, data } = await getDictList('SHARE_VAILD')
if (code != '200') return
this.shareValidTypeOptions = data
this.dialogForm.shareValidType = this.shareValidTypeOptions[0].id
},
async createShare() {
this.dialogForm.reportCode = this.reportCode
this.dialogForm.shareUrl = window.location.href
console.log(this.dialogForm)
const {code, data} = await reportShareAdd(this.dialogForm)
if (code != '200') return
console.log(data)
this.shareLinkFlag1 = false
this.$message({
message: '创建链接成功!',
type: 'success',
})
this.reportShareUrl = data.shareUrl
},
copyShare(){
this.copyToClip(this.reportShareUrl)
this.$message({
message: '复制链接成功!',
type: 'success',
})
},
copyToClip(content, message) {
var aux = document.createElement("input");
aux.setAttribute("value", content);
document.body.appendChild(aux);
aux.select();
document.execCommand("copy");
document.body.removeChild(aux);
},
async saveReportShare() { async saveReportShare() {
var params = { var params = {

@ -19,6 +19,21 @@
v-permission="'bigScreenManage:design'" v-permission="'bigScreenManage:design'"
>设计</el-button >设计</el-button
> >
<el-button
type="text"
@click="shareReport(props.msg)"
v-permission="'bigScreenManage:share'"
>分享</el-button
>
</template>
<template v-slot:pageSection>
<Share
:visib="visibleForShareDialog"
:reportCode="reportCodeForShareDialog"
:reportName="reportNameForShareDialog"
@handleClose="visibleForShareDialog = false"
/>
</template> </template>
</anji-crud> </anji-crud>
</template> </template>
@ -30,13 +45,19 @@ import {
reportUpdate, reportUpdate,
reportDetail reportDetail
} from "@/api/reportmanage"; } from "@/api/reportmanage";
import Share from "@/views/report/report/components/share";
export default { export default {
name: "Report", name: "Report",
components: { components: {
anjiCrud: require("@/components/AnjiPlus/anji-crud/anji-crud").default anjiCrud: require("@/components/AnjiPlus/anji-crud/anji-crud").default,
Share
}, },
data() { data() {
return { return {
//
visibleForShareDialog: false,
reportCodeForShareDialog: "",
reportNameForShareDialog: "",
crudOption: { crudOption: {
// 使 // 使
title: "报表管理", title: "报表管理",
@ -233,6 +254,12 @@ export default {
} }
}); });
window.open(routeUrl.href, "_blank"); window.open(routeUrl.href, "_blank");
},
//
shareReport(val){
this.reportCodeForShareDialog = val.reportCode;
this.reportNameForShareDialog = val.reportName;
this.visibleForShareDialog = true;
} }
} }
}; };

@ -161,11 +161,11 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-checkbox <!-- <el-checkbox-->
v-model="isShowPagination" <!-- v-model="isShowPagination"-->
@change="changePagination" <!-- @change="changePagination"-->
>加入分页参数 <!-- >加入分页参数-->
</el-checkbox> <!-- </el-checkbox>-->
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="数据转换" name="second"> <el-tab-pane label="数据转换" name="second">
<template> <template>

Loading…
Cancel
Save